Architecture
Zeabur shows a Kubernetes cluster as projects and services, and lets people change them, without holding any power of its own. It is a projection: Kubernetes objects are the only state.
Principles
Section titled “Principles”- No power of its own. Every read and write is made with the person's credential: a token
issued by Zeabur Identity in the cluster, or their kubeconfig context on their machine. kube-apiserver
decides. Zeabur does not impersonate, and its service account can do nothing a person cannot.
Member roles are RBAC too: the chart installs three ClusterRoles (
zeabur-member,zeabur-project-developer,zeabur-project-viewer), and an admin giving a member a role makes RoleBindings to them in that project's namespace, as the admin (members/access.rs; Members' rights). A Developer may patch their project's Namespace; the chart's ValidatingAdmissionPolicy keeps that patch to the project's details, since labels decide Pod Security admission and NetworkPolicy namespace selectors, and adopting is a label, left to whoever mayzeabur-managethe namespace. Every member can list namespaces and so read their annotations: details hold no secrets. A project Zeabur creates enforces, audits, and warns at Pod Securitybaseline. New services select Strict or Compatible based on the image user or build method. Switching profiles changes only the service and needs no admin rights; Developers still cannot change Namespace labels (Members' rights). When some namespace carrieszeabur.com/platform: "true", creating a project also writes the NetworkPolicyzeabur-project(zeabur-model'skernel::isolation): ingress only from the project, the labelled platform namespaces andkube-system, egress open. Without the label nothing tells which namespaces the platform runs in, so no policy is written. On an existing cluster, label them yourself:kubectl label namespace <ns> zeabur.com/platform=true.GET /api/v1/cluster/gapsnames the project namespaces without it. The trust boundary is the cluster: everyone who signs in is trusted, and the policy only limits the damage one compromised service can do. A new project'sdefaultServiceAccount hasautomountServiceAccountToken: false; a workload that calls the Kubernetes API names a ServiceAccount of its own. - Kubernetes objects are the only state. No database, no cache of Secret values, and no
custom resources for layers L0 to L2. A project is a namespace; creating one is a plan like
any other change, and adopting one only adds a label. Removing Zeabur leaves the cluster as it
was, but for the member bindings admins made, which carry the label
zeabur.com/member. - Project, not own. Services are inferred from owner references, selectors, env and volume references, backendRefs, operator conventions and the labels of Helm, Argo CD and Flux. For objects a GitOps tool or Helm manages, Zeabur exports a patch or warns, and never fights over fields.
- Suggest, then plan. Zeabur detects missing capabilities and writes the
helmcommand, FluxHelmRelease, Argo CDApplicationor K3s/RKE2HelmChart. People run thehelmcommand themselves; the objects are created by a plan they review, as the signed-in person.
Product layers
Section titled “Product layers”| Layer | What it adds | Runs as |
|---|---|---|
| L0 | read: projects, services, relations, logs, events | zeaburctl ui (local) |
| L1 | write: plans, JSON Patch, create and delete with preconditions, releases and rollback | zeaburctl ui (local) |
| L2 | the in-cluster server, sign-in through Zeabur Identity | zeabur |
| L3, L4 | full sign-in, multi-tenancy | later |
A layer depends only on the layers below it; turning one off leaves those below working.
Binaries
Section titled “Binaries”zeabur: the in-cluster server. It serves the API under/apiand the embedded web interface, and signs people in as a confidential client of Zeabur Identity.zeaburctl: the command-line tool on a person's machine:ui(the interface with the current kubeconfig context, on a random loopback port) anddoctor.
The split follows Zeabur Identity (zeabur-identity/zeabur-identityctl): code only a person's machine runs never ships in
the server image.
Explaining problems
Section titled “Explaining problems”Everything Zeabur says about a failure is read from the cluster at the time of the request, with the person's rights, and names the objects and fields it rests on:
- Diagnoses (
zeabur-gaps) say what fails, why, how to fix it, where the cause lies (the code, the configuration, the cluster, a component, an upstream registry) and who can fix it. - Incidents group what one cluster cause breaks (an ingress controller or cluster DNS down, a node not ready), so the cluster's fault is not shown as each service's own.
- Traces follow each domain and private address hop by hop, from DNS to a ready pod.
- Plan previews say, before an apply, which pods are replaced, whether new ones fit the quotas and the nodes, and which data and fields go.
- Timelines and rollouts read releases,
managedFields, events and container statuses; no history of Zeabur's own is kept, so what the cluster does not keep (who made a write, an event older than its TTL) is not shown. - Evidence bundles put a service's problems, trace, events, previous logs and objects in one Markdown text, with no Secret and no variable value.
What cannot be read is named (unreadable), and a check that cannot be made is unknown, never
ok.
Terminals
Section titled “Terminals”A terminal is pods/exec as the person, relayed over a WebSocket. Two plans prepare one, each
applied like any other plan and alone in it:
- A debug container (
debugContainer), for a container without a shell: an ephemeral container written throughpods/ephemeralcontainers, with a pinned busybox image,stdinandtty,targetContainerNameset so it shares the target's process namespace, and the target's user with every capability dropped. Kubernetes keeps it until the pod is replaced; the preview says so. Zeabur's earlier one is reused, matched by its name prefixzeabur-debug-. - A tools pod (
clusterTools):kubectlandhelmacting as the person, for what Zeabur has no page for.
The tools pod holds the person's Zeabur Identity token, which Zeabur otherwise never lets out of its process. So:
- In the terminal namespace (
zeabur-terminals), after a plan that says what runs, as whom, until when, and that it is deleted on close or at the deadline. Zeabur first asks kube-apiserver whether the person maycreatepods/execin the project (for the cluster scope: whether they are a cluster administrator), then creates, execs into and deletes the pod with its own ServiceAccount. The chart binds that account to a Role with exactly those rights in that one namespace. Members have no rights there, so no one can list or exec into another person's tools pod. kubectl in it starts in the project. - Nothing of Zeabur's in it:
automountServiceAccountToken: false, no service links. - Locked down: non-root, read-only root filesystem, no privilege escalation, every
capability dropped, seccomp
RuntimeDefault; small requests and limits;activeDeadlineSeconds(30 minutes by default); labels naming it Zeabur's and whose it is. - The token never rests in the API: not in the pod spec, an environment variable, a
ConfigMap or a Secret. Each terminal writes a kubeconfig through exec stdin into an
emptyDirwithmedium: Memoryat/home/user/.kube, readable only by the pod's user. The kubeconfig reaches kube-apiserver atkubernetes.default.svcwith the cluster CA fromkube-root-ca.crt. - Bounded by the token: Zeabur Identity's tokens live minutes and cannot be revoked. The
terminal closes when the person's token expires;
kubectlandhelmrefuse with a plain message after that time; reopening writes a fresh token. - Gone after use: when the last terminal closes, the kubeconfig is removed, and the pod is deleted after a grace for reconnects (60 seconds by default). Pods left behind (Zeabur restarted, the token expired before the deletion) stop at their deadline, and the person's next tools request deletes them.
What remains: until the token expires, Zeabur itself, cluster administrators and whoever administers the pod's node can read it. Nobody else should get rights in the terminal namespace.
Use and disk
Section titled “Use and disk”CPU and memory use come from metrics-server (metrics.k8s.io). Everything else measured
comes from the metrics store, a Service serving the Prometheus HTTP API, read through
kube-apiserver's service proxy as the person (get on its services/proxy). With Zeabur Proxy,
history from the default store goes through the proxy instead, held to the project's namespace
(get on metrics.proxy.zeabur.com):
- History: range queries over the series the store keeps.
- Node disk: node-exporter's
node_filesystem_size_bytes,node_filesystem_avail_bytesandnode_filesystem_free_bytesfor the filesystem holding/var/lib/kubelet(the kubelet'snodefs, which disk-pressure eviction watches), else/. A series counts for a node by itsnodelabel, else by itsinstancehost matching the node's name or one of its addresses. - Volume use: the kubelets'
kubelet_volume_stats_*bynamespaceandpersistentvolumeclaim.
Zeabur never reads the kubelets' own API (nodes/proxy): that right also lets someone run
commands in any pod on the node. Where the store is not set up, refuses, does not answer, or
holds no series for a node or claim, the answer says which, and the interface says it in one
line instead of a number; requests and requested sizes are still shown.
Storage classes
Section titled “Storage classes”GET /api/v1/projects/{project}/storage lists the cluster's StorageClasses and, for each of
the project's PersistentVolumeClaims, its class and the node its PersistentVolume is kept on.
Both are cluster-scoped and read as the person; the chart's member role grants list on
StorageClasses and get on PersistentVolumes, and a refusal is named in unreadable instead of
failing the answer.
A class is shown by the kind of disk its provisioner gives:
| Kind | Provisioners | What it means |
|---|---|---|
| local | rancher.io/local-path, openebs.io/local, kubernetes.io/no-provisioner | A directory or disk of one node; the PersistentVolume's node affinity pins its pods there |
| replicated | driver.longhorn.io | Copies on several nodes; a volume follows its pod |
| other | any other | Up to its driver |
A new volume, or a database's storage, goes on the class chosen for it; the default sends no
storageClassName, so the cluster's default applies. Growing a volume is offered only when its
class has allowVolumeExpansion; the plan refuses it otherwise (expansion_not_allowed).
Accounts
Section titled “Accounts”Zeabur holds no credentials and reads none of the issuer's objects. The Account page shows what kube-apiserver says:
- Who you are: the user name and groups of a
SelfSubjectReview. - Your role in each project: one
SelfSubjectRulesReviewper adopted project, summarised as Admin, Developer or Viewer by a few key rights (crates/zeabur-api/src/account.rslists them). A cluster administrator, found with oneSelfSubjectAccessReviewfor every verb on every resource, is Admin everywhere and no project is reviewed. A project whose review fails says its access could not be read. - Sign-in methods: a link to the issuer's own page, from
issuer.accountUrl, else themanageURL of the issuer's/.well-known/passkey-endpoints(W3C Passkey Endpoints), read with a 3-second deadline and kept for 10 minutes, else no link.
Members (/members) is Zeabur Identity's admin view and still reads and changes its objects as
the signed-in person.
The sign-in settings on the Settings page are the ConfigMap zeabur-identity-settings in Zeabur
Identity's namespace, read and written as the person; Zeabur Identity applies them at every
sign-in and refresh (Sign-in settings).
A sign-in session (crates/zeabur-auth/src/session.rs) ends at the shorter of Zeabur's idle
limit (1 hour) and the idle time Zeabur Identity gives the refresh token
(refresh_expires_in), and 12 hours after sign-in at the latest. Requests the browser marks as
background, sent after 60 seconds without input, renew the tokens but do not count as activity,
so a page left open lets the session go idle.