Git, CI/CD & GitOpsCXVIII · Final Reference ArchitectureObservability
The observability and audit fleet — the operational visibility
What you'll learn
- Identify the four primitives a production observability and audit fleet depends on: metrics, logs, traces, audit trail
- Distinguish an observation (what the system is doing) from an attestation (what the system is and who decided it should be)
- Recognise why the audit trail is derived, not pushed, by joining receipts to git history at query time
- Configure cross-store query so an incident responder can ask "which commit reached which cluster at which minute" and get one answer
Prerequisites
Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x
The observability and audit fleet is the lens through which the other four fleets become queryable. Observations tell the team what the system is doing; attestations tell the team why it is running that way. A team that has metrics, logs, and traces but no audit trail sees the symptom but not the cause. A team that has the audit trail but no metrics has the history of decisions but no view of the runtime. The reference architecture requires both; the unification is the trail.
The four primitives
A production observability and audit fleet depends on four primitives. Forgetting any one of them turns the operations plane into a place where data exists but answers do not.
flowchart LR
CLUS["Cluster"] --> MET["Metrics"]
CLUS --> LOG["Logs"]
CLUS --> TRC["Traces"]
ACD["Argo CD"] --> RCPT["Deployment receipt"]
WF["Actions run"] --> BLD["Build receipt"]
MAN["Manifest repository"] --> GIT["Git history"]
RCPT --> STORE["Receipt store"]
BLD --> STORE
MET --> PROM["Prometheus"]
LOG --> LOKI["Loki"]
TRC --> TEMPO["Tempo"]
STORE --> AUD["Audit trail — derived"]
GIT --> AUD
PROM --> AUD
LOKI --> AUD
TEMPO --> AUD
- Metrics. Quantitative signals with a label dimension. Prometheus is the default; the metric is queryable by labels and time. The fleet ships metrics from every component: the runner’s queue depth, the Argo CD controller’s reconcile duration, the cluster’s node-state gauge.
- Logs. Discrete events with a timestamp and a structured payload. Loki is the default; logs join to metrics by label and to traces by trace ID. The fleet ships logs from every component: the workflow step, the controller reconcile, the kubelet’s event.
- Traces. Distributed requests across components. Tempo (or an OTel-compatible backend) is the default. The trace connects a request across the workflow, the registry call, the controller pull, and the cluster apply.
- Audit trail. The derivation that joins receipts to git history to runtime signals. Not a separate primitive in the runtime sense - it is a query over the other three plus the receipt store - but the primitive the operations team depends on when an incident asks “why is this running here?”.
The audit trail is derived, not pushed
The audit trail is not a fourth data store with its own ingestion path. It is a query that joins three data stores: the receipt store, git history, and the runtime signals. Three reasons this matters:
- Push-based audit trails lose data at the boundary. A separate audit-pipeline that copies receipts into an audit database must be kept in sync; gaps in that pipeline are gaps in the trail.
- Derived trails survive schema evolution. A new field on the receipt is a schema change in the receipt store and a backwards-compatible change in the query. A push-based trail would require a migration in the audit pipeline.
- The query defines the trail. Different incidents ask different questions, and a derived trail lets each question have its own query without a separate ingestion path per question.
The downside is that the query must be fast. A trail
that takes ten minutes to answer “which commit reached
which cluster” is a trail nobody uses during incidents.
That is why the receipt store is indexed on the fields
the question needs (cluster, application, applied_at)
and the git history is queried with a stable diff hash.
Cross-store query
The query an incident responder actually runs looks like this:
"Which commit reached the production cluster in the last
24 hours, and which deploy stamp did it produce on each
node?"
The answer joins:
- The receipt store by
(cluster=prod, application in [...])onapplied_atin the window. - The git history by
(commit in [...])joined to the manifest at that commit. - The runtime signals by
(node, label)joined toapplied_at.
A single SQL or LogQL query against a federated backend produces the answer; the same query against four independent stores does not. A federated query layer - Tempo, Loki, Prometheus, with a Grafana panel or an MCP server - is what makes the trail queryable.
Operating the fleet
A production observability and audit fleet is operated as a product with three recurring concerns:
- Retention. Observations (metrics, logs, traces) follow the standard observability retention curves: high resolution for a week, lower resolution for a quarter, archived for a year. Receipts follow the audit retention window: keep receipts for the longer of the compliance window or the engineering review window.
- Cost. Logs and traces are the big spenders. A fleet that ships every kubelet log line to a year of retention is a fleet whose bill will eventually reshape itself out of existence. Sampling strategies belong at the application level: log only the events the audit trail needs, not every event the runtime emits.
- Cross-store query latency. The query that joins receipts to git to runtime must answer in seconds, not minutes. A federated query layer that fans out to four stores with synchronous joins is too slow for incident use; a layer with cached indices on the hot path is the production discipline.
echo "Inspect a recent Argo CD receipt on the production cluster:"
argocd app history "${APP}" --server "${PROD_SERVER}" \
| jq '.[] | select(.deployedAt > (now - 86400 | todate))'
The command above is the engineer’s hands-on path during incidents: list the deploys in the last 24 hours, get the digests and the source commits, and walk them through the git history.
Production discipline
- The four primitives are mandatory. A team without traces during an incident is a team that walks the trail by hand.
- The audit trail is derived, not pushed. Keep ingestion simple; invest in the query layer.
- Receipts are indexed for the hot query. Cluster, application, applied_at - the fields the question needs are the fields the index has.
- Retention matches the audit window. A receipt that is dropped before the audit window is a receipt the audit team does not have.
Cross-course references
- This course, Part LXIV (Auditability) - the six links the trail reconstructs.
- This course, Part LXXI (Observability stack) - the runtime signals the trail joins to.
- Kubernetes for Production Sysadmins - Parts XXXVIII-XLI cover the cluster-side observability this lesson assumes.
Quiz
Knowledge check · 4 questions
Q1. A team has metrics, logs, and traces but no receipt store. Argo CD reconciles successfully but does not stamp receipts. What is the audit capability?
Q2. The audit trail should be derived at query time by joining the receipt store to git history and to runtime signals, rather than pushed into a separate audit database at ingest time.
Q3. Name the four primitives a production observability and audit fleet depends on, and explain the difference between an observation and an attestation.
Q4. Diagnose the audit gap and prescribe the corrections.
A team runs Prometheus, Loki, and Tempo. They do not stamp Argo CD receipts. They do not log build identifiers from the runner. An incident requires reconstructing which commit reached which cluster at which minute. The metrics show controller reconcile events. The logs show 'image pulled: app@sha256:8a3f...' lines. The traces show the registry pull but not the manifest commit. The team can answer some sub-questions but not the headline one.
Passing score: 75%. Answers are checked in this browser.