audit-core/deploy/audit-core.yaml
tegwick 3a7d63e18f
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Prepare railiance01 delivery: dynamic leases, migrate Job, operator runbook
VaultDynamicSecret pulls database/creds/* so a rotating lease is not frozen
into KV. Runtime sets AUDIT_CORE_AUTO_MIGRATE=0; schema is a Job with the
migration lease. Image base is digest-pinned. Namespace and NetworkPolicies
are on the cluster; Deployment waits for the attended OpenBao ESO token.
2026-08-13 00:58:49 +02:00

174 lines
6.4 KiB
YAML

# audit-core receiver — railiance01 (AUDIT-WP-0005-T03).
#
# Conventions match the deployed user-engine workload: digest-pinned image from
# forgejo.coulomb.social, non-root with a read-only root filesystem, and probes
# on a named http port.
#
# Apply order matters: the namespace label railiance.io/postgres-client is what
# platform-pg-consumer-ingress in the databases namespace admits, so without it
# the pod cannot reach the database at all.
---
apiVersion: v1
kind: Namespace
metadata:
name: audit-core
labels:
kubernetes.io/metadata.name: audit-core
railiance.io/workload-class: platform
# Admitted by NetworkPolicy platform-pg-consumer-ingress (rapp-postgres).
railiance.io/postgres-client: platform-pg
---
apiVersion: v1
kind: Service
metadata:
name: audit-core
namespace: audit-core
labels:
app.kubernetes.io/name: audit-core
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: audit-core
ports:
- name: http
port: 8080
targetPort: http
protocol: TCP
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: audit-core
namespace: audit-core
labels:
app.kubernetes.io/name: audit-core
annotations:
# Rollback position. Update both together; `kubectl rollout undo` returns to
# the previous digest, and the schema note records whether that is safe.
audit-core.railiance.io/rollback-note: >-
Migrations 0001-0004 are additive (CREATE TABLE/INDEX/TRIGGER IF NOT
EXISTS) and are not reversed by a rollback. An older image runs against
the newer schema without harm. A future migration that drops or narrows a
column breaks that property and must state its own rollback position
before it is released.
spec:
replicas: 1
revisionHistoryLimit: 5
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0
maxSurge: 1
selector:
matchLabels:
app.kubernetes.io/name: audit-core
template:
metadata:
labels:
app.kubernetes.io/name: audit-core
spec:
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
# In-flight events must not be lost on rollout; the app shuts down
# gracefully on SIGTERM (AUDIT-WP-0004-T06).
terminationGracePeriodSeconds: 30
containers:
- name: audit-core
# REPLACE at release time with the built digest. A mutable tag is not
# an immutable image, and `:latest` must never be the only reference.
image: forgejo.coulomb.social/coulomb/audit-core@sha256:REPLACE_AT_RELEASE
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8080
env:
- name: AUDIT_CORE_HOST
value: "0.0.0.0"
- name: AUDIT_CORE_HTTP_PORT
value: "8080"
# Refuses to start on anything but archive-class custody, so a
# missing database URL fails loudly instead of silently downgrading
# to the development store.
- name: AUDIT_CORE_REQUIRE_CUSTODY_CLASS
value: archive
# Runtime role cannot CREATE TABLE. Schema changes are a Job
# with the migration lease (deploy/migrate-job.yaml).
- name: AUDIT_CORE_AUTO_MIGRATE
value: "0"
- name: AUDIT_CORE_DATABASE_SCHEMA
value: audit_core
- name: AUDIT_CORE_THREADS
value: "8"
- name: AUDIT_CORE_REQUEST_TIMEOUT
value: "30"
- name: AUDIT_CORE_DB_STATEMENT_TIMEOUT_MS
value: "30000"
# The database credential is a mounted directory, not a variable.
# A dynamic lease rotates while the pod runs; an env var is fixed at
# process start, so env delivery would force a restart — and a
# delivery gap — on every rotation. audit-core re-reads this
# directory on each connection attempt (AUDIT-WP-0005-T02).
- name: AUDIT_CORE_CREDENTIAL_DIR
value: /etc/audit-core/db
# The sender registry is read once at startup, so a variable is
# adequate here. Token rotation is overlap-first inside the
# registry itself and needs no restart either.
- name: AUDIT_CORE_SENDERS
valueFrom:
secretKeyRef:
name: audit-core-senders
key: senders.json
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 500m
memory: 256Mi
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
# Custody lives in PostgreSQL. Nothing of value is written to the
# pod filesystem, and a read-only root enforces that rather than
# trusting it — the SQLite fallback cannot silently start
# accumulating audit records on ephemeral storage.
readOnlyRootFilesystem: true
volumeMounts:
- name: tmp
mountPath: /tmp
- name: database-credential
mountPath: /etc/audit-core/db
readOnly: true
startupProbe:
httpGet: {path: /healthz, port: http}
periodSeconds: 3
failureThreshold: 20
readinessProbe:
# /readyz checks the backend is reachable and durable, so the pod
# leaves the Service when custody is unavailable rather than
# accepting events it cannot store.
httpGet: {path: /readyz, port: http}
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 3
livenessProbe:
# Deliberately /healthz, not /readyz: a database outage must not
# restart the pod in a loop. Losing readiness is the correct
# response; restarting solves nothing and loses in-flight work.
httpGet: {path: /healthz, port: http}
periodSeconds: 20
timeoutSeconds: 2
failureThreshold: 3
volumes:
- name: tmp
emptyDir: {}
- name: database-credential
secret:
# Kubernetes updates the projected files in place when the
# ExternalSecret refreshes, which is what makes restart-free
# rotation possible.
secretName: audit-core-database
defaultMode: 0400