Part IX — Capstone and Reference
43. Capstone: assemble and verify the whole system
43.1 Bootstrap order (kind)
# Makefile (excerpt)
cluster: ## 1. cluster + LB
kind create cluster --config kind-orbit.yaml
images: ## 2. build + load images (CI pushes to GHCR in the real flow)
for s in catalog orders worker; do \
docker build --build-arg SERVICE=$$s -t orbit-$$s:dev . && kind load docker-image orbit-$$s:dev --name orbit; \
done
gitops: ## 3. Argo CD + root app: everything else is pulled from Git
helm upgrade --install argocd argo/argo-cd -n argocd --create-namespace --wait
kubectl apply -n argocd -f platform/root.yaml
verify: ## 4. smoke + load + chaos
k6 run -e BASE=https://orbit.localtest.me load/smoke.js
k6 run load/orders.js
Sync waves (from §36.4) give the platform its order: CRDs and operators
(cert-manager, CNPG, KEDA, Rollouts, Kyverno, ESO) → gateways, monitoring
and Collectors → policies → the orbit-* ApplicationSet (DB → migration
hook → services → routes → ScaledObjects).
macOS note: if the Gateway's LoadBalancer address isn't reachable from the host, port-forward the Envoy service instead:
kubectl -n envoy-gateway-system port-forward svc/$(kubectl -n envoy-gateway-system get svc -l gateway.envoyproxy.io/owning-gateway-name=public -o name | cut -d/ -f2) 8443:443and usehttps://orbit.localtest.me:8443.Lab VM note (§0): inside the VM the LoadBalancer IP works directly (
cloud-provider-kindruns as a systemd service). To use the browser on your Mac, run the same port-forward inside the VM: Lima forwards8443to your Mac'slocalhostautomatically (§0.5).
43.2 Acceptance tests: the system is "done" when all of these pass
| # | Test | Proves |
|---|---|---|
| 1 | git push to main → new digest running in dev in < 10 min, no human steps |
CI + GitOps |
| 2 | Unsigned image applied by hand → rejected by admission | supply chain policy |
| 3 | kubectl run with root user in orbit → rejected |
Pod Security |
| 4 | k6 800 RPS: p95 < 300 ms, errors < 1%; orders and worker replicas scale out, then back in (worker to 0) | autoscaling |
| 5 | Rolling restart of all services during k6 → 0 failed requests | probes, preStop, PDB, maxUnavailable: 0 |
| 6 | Kill the Postgres primary → failover < 30 s; orders not-ready, then ready; no pod restarts | liveness/readiness design, CNPG |
| 7 | Bad canary (5% 500s) → automatic abort at ≤ 25% weight | progressive delivery |
| 8 | Burn-rate alert fires for #7 if you force-promote; Slack message has a runbook link | SLO alerting |
| 9 | From that alert: dashboard → exemplar → trace → logs in < 5 min | observability correlation |
| 10 | Drain a node / stop a kind "zone" during k6 → SLO holds | topology spread, tolerations, PDB |
| 11 | Delete the orbit namespace → restore DB (PITR) + Argo CD resync; measure RTO; verify orders exist |
backup & DR |
| 12 | Catalog pod cannot open a connection to Postgres admin port / metadata IP | NetworkPolicy |
| 13 | NetworkChaos 200 ms to DB → p95 degrades but error rate stays < 1%, alerts are tickets, not pages | timeouts, alert design |
| 14 | A poison message (unknown order_id that fails forever) → moved to orders.dlq after 5 attempts; alert on DLQ length > 0 |
queue hygiene |
43.3 The dead-letter queue (test #14)
// in worker.handle, on failure: check delivery count from XPENDING; after maxAttempts, park it
const maxAttempts = 5
func (w *Worker) deadLetterIfExhausted(ctx context.Context, m redis.XMessage, cause error) bool {
p, err := w.RDB.XPendingExt(ctx, &redis.XPendingExtArgs{
Stream: w.Stream, Group: w.Group, Start: m.ID, End: m.ID, Count: 1,
}).Result()
if err != nil || len(p) == 0 || p[0].RetryCount < maxAttempts {
return false
}
values := map[string]any{"original_id": m.ID, "error": cause.Error(), "attempts": p[0].RetryCount}
for k, v := range m.Values {
values[k] = v
}
pipe := w.RDB.TxPipeline() // MULTI: add to DLQ + ACK atomically
pipe.XAdd(ctx, &redis.XAddArgs{Stream: w.Stream + ".dlq", Values: values})
pipe.XAck(ctx, w.Stream, w.Group, m.ID)
_, err = pipe.Exec(ctx)
slog.ErrorContext(ctx, "message dead-lettered", "order_id", m.Values["order_id"], "attempts", p[0].RetryCount)
return err == nil
}
Alert on XLEN orders.created.dlq > 0 (via redis_exporter's
--check-streams). A DLQ nobody watches just loses messages slowly.
⚠️ Caveat (verify): check the
XPendingExtresult field (RetryCount) against your go-redis version, and check whether redis_exporter's--check-streamsflag exists in yours (redis_exporter --help).
43.4 What you built
Git (app + deploy + platform)
└─► CI: test → build multi-arch → scan → sign → SBOM → bump digest
└─► Argo CD: platform add-ons + orbit (dev → staging → prod via PR)
└─► Kubernetes: Envoy Gateway (TLS) → catalog / orders (Rollout canary) → CNPG Postgres, Redis
worker (KEDA 0..N on stream lag), CronJobs, PDBs, spread, NetworkPolicies, PSA restricted
Kyverno (signed images), ESO (secrets), Velero + CNPG PITR (backups)
OTel agent/gateway → Prometheus / Loki / Tempo → Grafana, SLO burn alerts → Alertmanager