Skip to content
Wharfbase

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.

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.
Terminal window
kubectl create namespace wharfbase
kubectl -n wharfbase create secret generic wharfbase-master-key \
--from-literal=master.key="$(head -c 32 /dev/urandom | base64)"
kubectl apply -f deploy/kubernetes/wharfbase.yaml
kubectl -n wharfbase exec wharfbase-0 -- wb setup-token
kubectl -n wharfbase port-forward svc/wharfbase 8480

Then 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.

Terminal window
helm install wharfbase ./deploy/helm/wharfbase --namespace wharfbase --create-namespace

The 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)
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.

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_dump Job isn’t a database.

  • Names come from the workload, with the namespace added when two namespaces use the same one (postgres · staging).

  • Environment: a wharfbase.environment label, else words in the namespace or workload (prod, staging, dev). Production databases open read-only by default.

  • Endpoints, best first:

    1. a StatefulSet pod’s own name under its headless Service, postgres-0.postgres-hl.db.svc;
    2. the Services that select the pod, postgres.db.svc on the Service’s port;
    3. the pod’s IP.

    For replicated workloads, the pod’s own address comes before the Services, because a Service spreads connections over every replica.

  • Consumers are the pods whose environment points at the database. That’s a connection URL or *_HOST variables, 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 emptyDir or the container’s filesystem is “not on a persistent volume”;
    • a missing readinessProbe is reported as such.

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_FILE under 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.revealed to 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 the wharfbase-secrets role and binding from the manifest);
  • set WB_SECRET_HINTS=off.

Passwords are then typed in.

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.

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_TOKEN or WB_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.

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.

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).

Terminal window
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, ReadWriteOnce does. Each replica’s own data dir is an emptyDir.
  • 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.

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_NAME and WB_KUBERNETES_POD_NAMESPACE from the downward API; without them, NetworkPolicies aren’t checked.