Kubernetes Deployment
This page explains how to adapt the tracked Kubernetes examples under
examples/kubernetes/. The examples are intentionally conservative. They show
deployment shape, security boundaries, graceful shutdown, resource limits, and
observability separation without embedding real service endpoints, credentials,
wallets, certificates, private subjects, or payload samples.
nats-sinks runtime configuration remains JSON. The Kubernetes API manifests
are YAML because Kubernetes commonly represents resources that way, but the
config.json stored in the ConfigMap is the same JSON configuration used by
the CLI, systemd examples, tests, and Python users.
Example Layout
examples/kubernetes/
README.md
namespace.yaml
service-account.yaml
configmap-file-worker.yaml
secret-template.yaml
persistent-volume-claim.yaml
sink-worker-deployment.yaml
observability-policy-configmap.yaml
prometheus-http-sidecar-deployment.yaml
prometheus-http-service.yaml
network-policy.yaml
The examples are not a Helm chart. They are meant to be readable, reviewable starting points for platform teams. A future chart remains a separate roadmap item because it needs upgrade semantics, values validation, release packaging, and compatibility guarantees.
Deployment Shape
The recommended shape keeps delivery-critical work isolated from optional observability work:
flowchart LR
JS[NATS JetStream] --> Worker[nats-sink worker container]
Worker --> Sink[(Durable destination)]
Worker --> Snapshot[Local metrics snapshot]
Snapshot --> Observe[nats-sink-observe container]
Policy[Observability policy ConfigMap] --> Observe
Observe --> Prom[Prometheus scrape]
subgraph Pod
Worker
Snapshot
Observe
end
The sink worker container runs:
nats-sink run /etc/nats-sinks/config.json
The optional observability container runs:
nats-sink-observe prometheus-http \
/var/lib/nats-sinks/metrics/metrics.json \
/etc/nats-sinks-observability/observability.prometheus.json
The observability container reads only the local metrics snapshot and the policy file. It does not connect to NATS, Oracle, the file sink output directory, DLQ subjects, or future destination backends. If observability fails, delivery must continue to follow commit-then-acknowledge behavior.
What To Customize
Before applying any manifest, customize these values:
| Area | Values to review |
|---|---|
| Image | Replace ghcr.io/projectcuillin/nats-sinks:0.4.0 with the image registry, tag, and digest approved for your environment. Review Production Container Hardening before promotion. |
| NATS | Replace the example NATS URL, stream, consumer, subject, DLQ subject, TLS CA mount, and authentication settings. |
| Sink | Replace the file sink with the sink and destination settings required by your deployment. Oracle deployments should use Secret references and wallet or TLS material mounted from approved secrets. |
| Secrets | Replace the placeholder Secret with a cluster secret manager, sealed-secret workflow, External Secrets operator, or another approved secret-injection mechanism. |
| Storage | Replace the example PersistentVolumeClaim with a storage class, access mode, size, retention policy, and backup policy suitable for the selected sink. |
| Resources | Tune CPU and memory requests and limits based on measured payload size, batch size, compression, encryption, and destination latency. |
| Network | Replace the example NetworkPolicy selectors with the namespaces, pod labels, egress gateway, or CNI-specific controls used in your cluster. |
| Observability | Keep external metric sharing disabled until the allowed metric list, scrape exposure, and service labels have been reviewed. |
Configuration And Secrets
Non-secret runtime configuration belongs in a ConfigMap. Secrets belong in Kubernetes Secrets or, preferably, in the platform secret system that populates Kubernetes Secrets at deploy time.
flowchart LR
ConfigMap[ConfigMap config.json] --> Pod[nats-sink Pod]
RuntimeSecret[Runtime Secret] --> Env[Environment variables]
TrustSecret[Trust material Secret] --> Files[Mounted CA or credentials files]
Env --> Pod
Files --> Pod
The example ConfigMap uses password_env instead of a literal NATS password:
{
"nats": {
"url": "tls://nats.example.svc.cluster.local:4222",
"user": "nats_sink_worker",
"password_env": "NATS_PASSWORD",
"tls_ca_file": "/etc/nats-sinks/tls/ca.crt",
"tls_verify": true
}
}
The Deployment resolves NATS_PASSWORD from a Secret key:
env:
- name: NATS_PASSWORD
valueFrom:
secretKeyRef:
name: nats-sink-runtime-secrets
key: NATS_PASSWORD
Do not place real passwords, tokens, private keys, Oracle wallet contents, NATS credentials files, internal hostnames, production subjects, or sensitive payload examples in tracked manifests or GitHub Issues.
Security Context
The example worker uses a restrictive pod and container security context:
automountServiceAccountToken: false
securityContext:
runAsNonRoot: true
runAsUser: 10001
runAsGroup: 10001
fsGroup: 10001
seccompProfile:
type: RuntimeDefault
containers:
- name: nats-sink
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- "ALL"
This follows least privilege. If your image or sink needs writable paths, mount
explicit emptyDir or persistent volumes only at those paths. Avoid broad
filesystem write access. Avoid mounting the Kubernetes service account token
unless a future feature explicitly needs Kubernetes API access.
The current container image runs as UID/GID 10001 and is designed to work
with readOnlyRootFilesystem: true when /tmp and the selected runtime state
paths are mounted explicitly. The examples mount /tmp, file output, and
metrics snapshot paths. Oracle-only deployments may not need file-output
storage, while spool deployments need a protected persistent mount for
encrypted spool custody. See Production Container Hardening
for the full writable-path and supply-chain evidence checklist.
Graceful Shutdown
The Deployment sets terminationGracePeriodSeconds and a short preStop
delay. The goal is to stop receiving new work, give the process time to handle
the active batch, close clients, and avoid turning pod termination into
unnecessary redelivery pressure.
sequenceDiagram
participant K as Kubernetes
participant Pod as nats-sink Pod
participant R as Runner
participant S as Sink
participant N as JetStream
K->>Pod: send termination signal
Pod->>R: stop fetching new batches
R->>S: finish active write_batch
S-->>R: durable success
R->>N: ACK completed messages
R->>Pod: close sink and NATS clients
Pod-->>K: exit before grace period expires
Choose a grace period that is longer than a normal batch write plus destination commit time. If the period is too short, Kubernetes may kill the container while the worker is still handling the active batch. That should still prefer redelivery over silent loss, but it may increase duplicate processing.
Readiness And Liveness
The example uses nats-sink validate /etc/nats-sinks/config.json as a simple
exec probe. This verifies that the mounted JSON configuration is syntactically
and structurally valid. It does not prove that NATS or the destination backend
is reachable.
For production, consider layering:
- config validation probes for early configuration mistakes;
- platform monitoring for NATS availability, Oracle or destination health, and pod restart count;
- metrics alerts for stale
last_sink_success_epoch_seconds,sink_write_errors_total,messages_failed_total, and DLQ activity; - a future readiness integration only if it can be implemented without weakening commit-then-ACK behavior.
Observability
The observability policy example is disabled by default:
{
"enabled": false,
"prometheus": {
"enabled": false,
"http_endpoint": {
"enabled": false
}
}
}
Before enabling the sidecar, review the allow list and decide which metrics may leave the pod. Avoid high-cardinality labels and avoid exporting raw subjects, classification values, labels, route names, table names, file paths, or any payload-derived information.
The optional sidecar exposes a ClusterIP service. Keep the service private to Prometheus or your monitoring path. Use NetworkPolicy, service mesh policy, or cluster firewall rules to restrict access.
Applying The Examples
Review locally first:
kubectl apply --dry-run=client -f examples/kubernetes/
Apply the base worker only after customization:
kubectl apply -f examples/kubernetes/namespace.yaml
kubectl apply -f examples/kubernetes/service-account.yaml
kubectl apply -f examples/kubernetes/configmap-file-worker.yaml
kubectl apply -f examples/kubernetes/secret-template.yaml
kubectl apply -f examples/kubernetes/persistent-volume-claim.yaml
kubectl apply -f examples/kubernetes/sink-worker-deployment.yaml
Apply observability only after enabling and reviewing the policy:
kubectl apply -f examples/kubernetes/observability-policy-configmap.yaml
kubectl apply -f examples/kubernetes/prometheus-http-sidecar-deployment.yaml
kubectl apply -f examples/kubernetes/prometheus-http-service.yaml
Testing Without A Cluster
The repository includes deterministic tests that inspect the examples without connecting to a Kubernetes cluster:
pytest tests/unit/test_kubernetes_examples.py
These tests check for expected resource kinds, JSON ConfigMap content, Secret references, security context controls, resource limits, graceful shutdown settings, observability separation, and absence of obvious private operational values.
Production Checklist
Before production use:
- pre-create or strictly scope the NATS stream and durable consumer;
- verify NATS account permissions with the least-privilege templates;
- verify destination identity and permissions;
- use idempotent sink modes;
- keep payload logging disabled unless explicitly approved;
- use TLS verification and approved CA or credentials-file material;
- keep Secret values out of git, logs, tickets, screenshots, and issue comments;
- size resource requests and limits from measured workload behavior;
- validate graceful shutdown with realistic batch sizes and destination latency;
- review observability allow lists before enabling any external sharing;
- run the relevant unit, smoke, synthetic, and live non-production tests before release.