--- # Declarative JetStream provisioning: the LOGS stream + durable consumers. # ArgoCD PostSync hook, idempotent create-or-UPDATE, re-runs each sync. # # Stream LOGS: file storage, 3 replicas, retention=limits (NOT workqueue) so the # transform tier AND the archiver each independently see every message — reading # never deletes; only max-age/max-bytes do. S2 compression is on (logs compress # well). Replay window = max-age (3d default). Beyond that, the S3 archive is the # ONLY long-term source — everything else is gone after 3 days (accepted design). # # TUNABLE LIMITS LIVE IN A CONFIGMAP (nats-stream-limits): max_age, max_bytes, # dupe_window. Change the ConfigMap and re-sync — this Job re-runs and applies # the new limits via `nats stream edit` (no manual surgery). The ConfigMap is # generated with a content-hash suffix (kustomize), so editing it changes both # the ConfigMap name AND this Job's env reference → the PostSync hook Job's spec # changes and Argo re-runs it (belt-and-suspenders on top of hooks running each # sync; hook-delete-policy=BeforeHookCreation recreates it every time). # # Consumers (independent offsets = true fan-out): # transform -> whole log stream, feeds the ClickHouse transform tier # archiver -> configurable security-relevant subset, feeds the S3 archiver. # Default filter is Vault audit (logs.k8s.vault.>); ADD subjects # by editing ARCHIVE_SUBJECTS (space-separated -> repeated # --filter). Exact default set is an open decision for Ben. # # Runbook (replay): # (a) reprocess from JetStream (within max-age, 3d): scale the transform tier # to 0, then `nats consumer rm LOGS transform` and re-run this Job # (recreates at DeliverAll), or `nats consumer edit`/`--replay` from a # start seq/time. # (b) long-horizon (beyond JetStream): re-ingest S3 archive objects back # through the transform tier (vector aws_s3 source or a one-shot Job). apiVersion: batch/v1 kind: Job metadata: name: nats-bootstrap namespace: logging annotations: argocd.argoproj.io/hook: PostSync argocd.argoproj.io/hook-delete-policy: BeforeHookCreation labels: app.kubernetes.io/name: nats-bootstrap app.kubernetes.io/component: bootstrap spec: backoffLimit: 20 activeDeadlineSeconds: 1800 ttlSecondsAfterFinished: 3600 template: metadata: labels: app.kubernetes.io/name: nats-bootstrap vector.dev/exclude: "true" spec: restartPolicy: OnFailure securityContext: runAsNonRoot: true runAsUser: 1000 runAsGroup: 1000 containers: - name: nats-bootstrap image: docker.io/natsio/nats-box:0.18.0 # nats CLI stats the working directory when loading its response # schemas; under readOnlyRootFilesystem + runAsUser 1000 the image's # default WORKDIR is not accessible ("stat .: permission denied"), so # run from the writable /tmp emptyDir. workingDir: /tmp securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true capabilities: drop: - ALL env: - name: HOME value: /tmp - name: NATS_URL value: "nats://nats.logging.svc.cluster.local:4222" - name: NATS_ADMIN_PASSWORD valueFrom: secretKeyRef: name: nats-auth key: admin_password # Tunable stream limits — sourced from the ConfigMap. - name: MAX_AGE valueFrom: configMapKeyRef: name: nats-stream-limits key: max_age - name: MAX_BYTES valueFrom: configMapKeyRef: name: nats-stream-limits key: max_bytes - name: DUPE_WINDOW valueFrom: configMapKeyRef: name: nats-stream-limits key: dupe_window # Space-separated subject filters for the archiver consumer. - name: ARCHIVE_SUBJECTS value: "logs.k8s.vault.>" command: - /bin/sh - -ec - | export NATS_USER=log-admin NATS_PASSWORD="$NATS_ADMIN_PASSWORD" echo "Waiting for NATS + JetStream ..." until nats --server "$NATS_URL" account info >/dev/null 2>&1; do echo " not ready, retry in 5s"; sleep 5 done echo "Ensuring stream LOGS (max_age=$MAX_AGE max_bytes=$MAX_BYTES dupe=$DUPE_WINDOW) ..." # Create if absent; otherwise converge the mutable limits from the # ConfigMap. (storage/retention/replicas are immutable, set only on # create.) nats stream add LOGS \ --subjects='logs.>' --storage=file --replicas=3 \ --retention=limits --discard=old --compression=s2 \ --max-age="$MAX_AGE" --max-bytes="$MAX_BYTES" \ --max-msgs=-1 --max-msgs-per-subject=-1 --max-msg-size=-1 \ --max-consumers=-1 --dupe-window="$DUPE_WINDOW" --defaults 2>/dev/null \ && echo " created" \ || nats stream edit -f LOGS \ --subjects='logs.>' --discard=old --compression=s2 \ --max-age="$MAX_AGE" --max-bytes="$MAX_BYTES" \ --max-msgs=-1 --max-msgs-per-subject=-1 --max-msg-size=-1 \ --max-consumers=-1 --dupe-window="$DUPE_WINDOW" echo "Ensuring consumer transform ..." nats consumer add LOGS transform \ --pull --filter='logs.>' --deliver=all --ack=explicit \ --max-deliver=-1 --replay=instant --defaults 2>/dev/null \ || echo " transform already exists" echo "Ensuring consumer archiver (filters: $ARCHIVE_SUBJECTS) ..." filter_args="" for s in $ARCHIVE_SUBJECTS; do filter_args="$filter_args --filter=$s"; done # shellcheck disable=SC2086 nats consumer add LOGS archiver \ --pull $filter_args --deliver=all --ack=explicit \ --max-deliver=-1 --replay=instant --defaults 2>/dev/null \ || echo " archiver already exists" echo "Done." nats stream info LOGS resources: requests: cpu: 50m memory: 64Mi limits: cpu: 500m memory: 256Mi volumeMounts: - name: tmp mountPath: /tmp volumes: - name: tmp emptyDir: {}