Kubernetes
Three patterns for pulling ScaiVault secrets into Kubernetes pods, from simplest to most flexible.
Patterns at a glance#
| Pattern | When to use | Trade-offs |
|---|---|---|
| Init container | Most common. Secrets needed at process startup. | Restart-needed for rotation. |
| Sidecar | Long-running services with rotation. | More moving parts; reload signal needed. |
| CSI driver | Multiple secrets, mountpoint semantics, no app changes. | Cluster-wide install; opinionated mount layout. |
For dynamic secrets (per-pod DB credentials), the sidecar pattern is the right answer — it can renew and revoke leases over the pod's lifetime.
Authentication#
Workload Identity is the modern path. Map a Kubernetes service account to a ScaiKey identity via OIDC token exchange:
1 2 3 4 5 6 7 | |
The pod's projected service-account token is exchanged for a ScaiVault bearer token at startup via POST /v1/auth/exchange. No long-lived secret material in cluster.
If your cluster doesn't have OIDC configured for ScaiKey yet, fall back to a static client-credentials secret stored in a Kubernetes Secret. The exchange flow is preferred long-term.
Pattern 1: init container#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | |
emptyDir.medium: Memory keeps the secrets off disk. projected serviceAccountToken gives the init container a freshly-projected ScaiKey-compatible JWT.
The init container does:
- Read its SA token.
- Exchange for a ScaiVault bearer token via
/v1/auth/exchange. - Read each secret listed in
$SECRETS. - Write to the indicated file in the shared
emptyDir. - Exit 0.
App reads from /run/secrets/db.json like any local file. No SDK in the app needed.
Rotation with init containers#
Restart the pods periodically. A CronJob:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
Crude but works. For finer-grained rotation, use the sidecar pattern.
Pattern 2: sidecar#
The sidecar runs alongside the app, fetches secrets, and refreshes them on a schedule or on event. The app reloads when the file changes.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | |
The sidecar polls every REFRESH_INTERVAL, compares the fetched value against the current file, and on change writes the new value + sends SIGHUP to the app process. App needs to handle the signal (or watch the file with inotify).
For event-driven refresh instead of polling, subscribe the sidecar to secret.rotated for the configured paths and refresh on event. Lower latency but adds network dependency.
Sidecar with dynamic credentials#
For Postgres dynamic creds, the sidecar generates the lease and renews it:
1 2 3 4 5 6 7 | |
On startup: generate, write. Periodically: renew. On shutdown: revoke. The pod gets a fresh DB user on each restart and revokes it on exit.
Pattern 3: CSI driver#
The Secret Store CSI driver mounts secrets as volumes. Install the ScaiVault provider:
1 2 3 | |
Then a SecretProviderClass:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
The field selector pulls one field instead of the whole JSON.
Pod consuming it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
CSI handles auth via the service account's projected token automatically. Secrets appear as files at /run/secrets/db.json etc. Rotation: enable the driver's rotation reconciler (--enable-secret-rotation=true) and it polls and updates the files at the configured interval.
Common questions#
"Can I sync ScaiVault into native Kubernetes Secrets?" Yes, via syncSecret on the CSI driver's SecretProviderClass. The driver materializes the values into a Kubernetes Secret object. Useful for things that expect to read from native Secrets (some Helm charts, third-party operators). Less recommended for application code — the indirection adds a refresh problem.
"What about secrets that need to exist before the pod starts?" Use a Job that runs before the deployment rolls out, has access to the right ScaiKey identity, and writes a native Kubernetes Secret. ArgoCD can sequence this via sync waves.
"How do I rotate the kubelet's projected token?" You don't. Kubelet refreshes projected service-account tokens automatically; ScaiVault auth-exchange handles short-lived tokens by re-exchanging on each call. Don't cache exchanged tokens longer than ~50 minutes.
"Should the init container exit cleanly if ScaiVault is down?" Default behavior: fail. The pod doesn't start. Better than starting with stale or missing credentials. Override with FAIL_OPEN=true if you have an explicit fallback path.
What's next#
- Terraform — provisioning ScaiVault resources from Kubernetes manifests' adjacent config.
- Dynamic Postgres tutorial — works well with the sidecar pattern.
- Migrate from .env files — typical entry point.