Kubernetes
Wharfbase can run in a Kubernetes cluster and find the databases in its pods: PostgreSQL in a StatefulSet, Redis behind a Service, a MongoDB someone started by hand. It reads the cluster through the API, with a read-only service account. It needs no Docker socket, no host access and no privileges, and it never creates, changes or deletes anything in the cluster.
Install
Section titled “Install”Both ways below make the same objects:
- a namespace;
- a service account with read-only roles;
- a StatefulSet with one Wharfbase pod and a volume for its data;
- a ClusterIP Service on port 8480.
With the manifest
Section titled “With the manifest”kubectl create namespace wharfbasekubectl -n wharfbase create secret generic wharfbase-master-key \ --from-literal=master.key="$(head -c 32 /dev/urandom | base64)"kubectl apply -f deploy/kubernetes/wharfbase.yamlkubectl -n wharfbase exec wharfbase-0 -- wb setup-tokenkubectl -n wharfbase port-forward svc/wharfbase 8480Then open http://localhost:8480 and paste the setup token.
The master key encrypts the passwords Wharfbase saves. Keep a copy somewhere safe: without it, the saved passwords in the data volume can’t be read.
With Helm
Section titled “With Helm”helm install wharfbase ./deploy/helm/wharfbase --namespace wharfbase --create-namespaceThe chart prints how to get the setup token and open Wharfbase. It makes the master key itself, and keeps it in a Secret that survives upgrades and helm uninstall.
| Value | Default | What it does |
|---|---|---|
publicUrl |
http://localhost:8480 |
The address people open. Set it to the Ingress’s https:// address |
kubernetes.namespaces |
[] (every namespace) |
Only these namespaces: a Role in each instead of ClusterRoles |
rbac.secrets |
true |
get on Secrets, for “Use the password from the pod” (see below) |
masterKey.existingSecret |
– | A Secret you made, instead of the generated one |
persistence.size, persistence.storageClass |
5Gi, the default class |
Wharfbase’s data volume (with the SQLite store) |
store.url or store.existingSecret (store.key), store.schema |
– (SQLite) | PostgreSQL for Wharfbase’s own data, and standby replicas (below) |
replicaCount |
1 |
More needs the PostgreSQL store |
shared.size, shared.storageClass, shared.accessModes, shared.existingClaim |
5Gi, the default class, ReadWriteMany |
With the PostgreSQL store: the volume every replica shares |
ingress.enabled, ingress.host, ingress.tlsSecret |
off | An Ingress in front |
extraEnv |
[] |
Any other WB_* variable (environment) |
What it reads
Section titled “What it reads”| Kind | Verbs | Why |
|---|---|---|
| Pods | get, list, watch | The containers: image, ports, env names, volume mounts, labels, state |
| Services | get, list, watch | How each database is reached (cluster DNS), and what exposes it outside the cluster |
| StatefulSets, Deployments | get, list, watch | Which workload a pod belongs to, and whether it has replicas |
| PersistentVolumeClaims | get, list, watch | Where the data lives |
| NetworkPolicies | get, list, watch | Whether Wharfbase’s pod is allowed to connect |
| Namespaces | get, list, watch | Their labels, for NetworkPolicies |
| Secrets | get only | One Secret, by the name a database’s pod references, when someone asks for it |
Without a kind it may not read (NetworkPolicies, say), discovery goes on, and wb doctor says what’s missing. Without pods or Services, a scan changes nothing: the databases stay as they were last seen.
What it finds
Section titled “What it finds”Each container of each pod is classified the way a Docker container is (discovery): by its image, its ports, the names of its environment variables and its data directory.
The wharfbase.* labels work as pod labels or annotations. For example, wharfbase.enable: "false" hides a pod, and wharfbase.engine: postgres forces the engine.
-
One database per instance:
- each pod of a StatefulSet (
postgres-0,postgres-1); - a single-replica Deployment (it keeps its identity across rollouts);
- each pod of a scaled Deployment;
- a bare pod.
Job and CronJob pods are skipped: a
pg_dumpJob isn’t a database. - each pod of a StatefulSet (
-
Names come from the workload, with the namespace added when two namespaces use the same one (
postgres · staging). -
Environment: a
wharfbase.environmentlabel, else words in the namespace or workload (prod,staging,dev). Production databases open read-only by default. -
Endpoints, best first:
- a StatefulSet pod’s own name under its headless Service,
postgres-0.postgres-hl.db.svc; - the Services that select the pod,
postgres.db.svcon the Service’s port; - the pod’s IP.
For replicated workloads, the pod’s own address comes before the Services, because a Service spreads connections over every replica.
- a StatefulSet pod’s own name under its headless Service,
-
Consumers are the pods whose environment points at the database. That’s a connection URL or
*_HOSTvariables, by short name in the same namespace or by cluster DNS name from anywhere, on the Service’s port. The replicas of an app count as one consumer. -
Findings speak Kubernetes:
- a NodePort or LoadBalancer Service is “exposed outside the cluster”;
- data on an
emptyDiror the container’s filesystem is “not on a persistent volume”; - a missing
readinessProbeis reported as such.
Passwords from Secrets
Section titled “Passwords from Secrets”A pod usually gets its database password from a Secret:
env: - name: POSTGRES_PASSWORD valueFrom: secretKeyRef: { name: pg-auth, key: password }Discovery records only where the password is (secret:db/pg-auth/password), never its value. These work too:
- a Secret mounted as a file (
POSTGRES_PASSWORD_FILEunder a Secret volume); - a flag that names a variable (
--requirepass $(REDIS_PASSWORD)): Kubernetes expands it, and Wharfbase follows it to the Secret.
When someone connects with Use the password from the pod:
- Wharfbase reads that one key of that one Secret, uses it for the connection, and writes
credential.revealedto the audit log; - the value isn’t stored, unless the person chooses Remember, which encrypts it.
The service account can get a Secret only by name: it can’t list them or watch them.
To never read Secrets, either:
- set
rbac.secrets=false(or delete thewharfbase-secretsrole and binding from the manifest); - set
WB_SECRET_HINTS=off.
Passwords are then typed in.
NetworkPolicies
Section titled “NetworkPolicies”When a database doesn’t answer, the Reachability tab says why. If NetworkPolicies are the reason, it names them and shows a policy that lets Wharfbase in.
The policy is either:
- an ingress rule on the database’s pods, for Wharfbase’s namespace and pod;
- an egress rule on Wharfbase’s own pod.
Policies add up, so applying it opens this one way without changing anything else.
Rules by IP block (ipBlock) can’t be judged from the API; then the tab says it can’t tell.
From outside the cluster
Section titled “From outside the cluster”Wharfbase running elsewhere, in Docker on a laptop for example, can read a cluster too:
WB_KUBERNETES_API=https://…:6443;- a token, from
WB_KUBERNETES_TOKENorWB_KUBERNETES_TOKEN_FILE; WB_KUBERNETES_CA_FILE.
It lists the cluster’s databases next to the Docker ones. Cluster DNS names and pod addresses only answer inside the cluster, though, so they show as unreachable, with “Run Wharfbase in the cluster” as the fix.
Every setting is in the environment reference.
What doesn’t work in a pod
Section titled “What doesn’t work in a pod”Wharfbase in Kubernetes has no Docker (WB_DOCKER=false), so the features that run a container next to a database aren’t there:
- backups and restores (Pro);
wb shell;- relays and network joining (Kubernetes needs neither: pods reach each other unless a NetworkPolicy says otherwise);
- SQLite and DuckDB files inside pods. Reading files from a pod would need
exec, which Wharfbase doesn’t ask for.
Databases on other servers can still come in through agents.
Standby replicas (Team)
Section titled “Standby replicas (Team)”By default Wharfbase runs as one replica, with its own data in a SQLite file on its volume. With its data in PostgreSQL it can run as several: one leads and the others stand by, taking over within seconds when it stops, for example when its node goes down or is drained (High availability).
kubectl -n wharfbase create secret generic wharfbase-store \ --from-literal=url='postgres://wharfbase:…@postgres.db:5432/wharfbase'helm install wharfbase ./deploy/helm/wharfbase --namespace wharfbase --create-namespace \ --set store.existingSecret=wharfbase-store --set replicaCount=2- The chart makes a Deployment instead of the StatefulSet. Only the leading replica is ready, so the Service sends traffic to it alone, and replicas spread over nodes where there are several.
- A shared volume (
shared.*, ReadWriteMany) holds backups, exports and the key agents use to trust Wharfbase. On a single node,ReadWriteOncedoes. Each replica’s own data dir is anemptyDir. - Upgrades replace every replica at once, since a standby never becomes ready and a rolling update would wait for it forever. One of the new replicas takes over: a few seconds without Wharfbase.
- Every replica discovers the same cluster, so any of them can lead the discovery. That’s why replicas here may stand by without
WB_ROLE=hub. - Without Team, the other replicas wait but don’t take over.
- The setup token comes from the ready replica: the chart’s notes print the command.
To move an existing installation, run wb store migrate in its pod first (kubectl -n wharfbase exec -it wharfbase-0 -- wb store migrate, with the URL in WB_STORE_URL), then helm upgrade with the store values. The old volume keeps the SQLite file, untouched since the copy.
Checking it
Section titled “Checking it”wb doctor (kubectl -n wharfbase exec wharfbase-0 -- wb doctor) reports:
- the cluster’s version;
- how many pods it read, and in which namespaces;
- the kinds it may not read;
- whether Wharfbase knows its own pod. The manifests set
WB_KUBERNETES_POD_NAMEandWB_KUBERNETES_POD_NAMESPACEfrom the downward API; without them, NetworkPolicies aren’t checked.