Kubernetes Observability¶
The observability subsystem is the in-cluster monitoring, alerting, logging and
metrics-scraping stack that runs on both the prod and staging EKS clusters
(af-south-1). It is built around the community kube-prometheus-stack Helm chart
(Prometheus + Alertmanager + Grafana) with a self-hosted Thanos long-term store,
Loki + Fluent Bit for logs, a fleet of purpose-built exporters, and the
per-camera / per-router blackbox probes that feed the network dashboards.
Everything lives under
kubernetes/infrastructure/services/observability,
plus the small privileged debug DaemonSet
kubernetes/infrastructure/services/netshoot.
It is deployed by Flux and all workloads land in the observability namespace.
Layout: base vs overlays¶
The directory follows a base + overlays (prod / staging) Kustomize pattern, but with
one deliberate twist: base/ has no aggregating kustomization.yaml. Each overlay
fans out to the individual base/<component> directories itself, so prod and staging can
pick a different subset of components. Staging is described as "the prod
kube-prometheus-stack, mirrored via the prod per-component fan-out".
observability/
├── base/ # shared component manifests (no top-level kustomization)
│ ├── kube-prometheus-stack/ (HelmRelease + HelmRepository + namespace)
│ ├── blackbox-exporter/ (deployment, config, service, servicemonitor)
│ ├── cloudwatch-exporter/ (YACE — AWS/VPN metrics)
│ ├── thanos-query/ thanos-store/ thanos-compactor/
│ ├── loki/ fluent-bit/ krr/
│ ├── mongodb-exporter/ mysql-exporter/ postgres-exporter/ redis-exporter/
│ └── grafana/ (dashboards + generators)
└── overlays/
├── prod/ # patched for prod (16Gi Prom, AMs, camera scrapeconfigs, VPN, rules)
│ └── flux/flux-kustomization.yaml
└── staging/ # patched for the small staging cluster
└── flux/flux-kustomization.yaml
How it wires into Flux and the clusters¶
Each overlay ships a Flux Kustomization CR under its flux/ directory that points Flux
at the overlay path:
| Env | Flux Kustomization path | Notes |
|---|---|---|
| prod | ./kubernetes/infrastructure/services/observability/overlays/prod |
interval 5m, prune: true, timeout 2m |
| staging | ./kubernetes/infrastructure/services/observability/overlays/staging |
same, plus dependsOn: postgres (Grafana state lives in CNPG) |
The cluster-level Kustomize overlays wire these in:
kubernetes/overlays/prod-cpt-aws
references observability/overlays/prod/flux, and
staging-cpt-aws
references observability/overlays/staging/flux/flux-kustomization.yaml. netshoot is
referenced directly (not through a Flux Kustomization CR) by both cluster overlays.
Which components run where¶
| Component | prod | staging | Notes |
|---|---|---|---|
| kube-prometheus-stack | ✅ | ✅ | single-replica Prometheus in both (see resources) |
| blackbox-exporter | ✅ | ✅ | staging = base only; prod adds camera probes/scrapeconfigs |
| cloudwatch-exporter | ✅ | ❌ | AWS/VPN metrics, prod only |
| thanos (query/store/compactor) | ✅ | ✅ | different S3 buckets |
| loki + fluent-bit | ✅ | ✅ | RF 2 (prod) / RF 1 (staging) |
| krr | ✅ | ✅ | different Slack channel + Prometheus URL |
| mongodb-exporter | ✅ | ✅ | |
| redis-exporter | ✅ | ✅ | prod → cache.internal, staging → Valkey |
| mysql-exporter | ✅ | ❌ | needs staging RDS wiring (follow-up) |
| postgres-exporter | ✅ | ❌ | needs staging CNPG credential wiring (follow-up) |
| PrometheusRules (vpn / router) | ✅ | ❌ | defined only in the prod overlay |
| Alertmanager routing config | ✅ | ❌ | full Slack/PagerDuty config is prod-only |
kube-prometheus-stack¶
Deployed as a Flux HelmRelease
(base)
from the prometheus-community HelmRepository
(https://prometheus-community.github.io/helm-charts).
| Field | Value |
|---|---|
| Chart | kube-prometheus-stack v68.2.2 |
| Release namespace | observability |
fullnameOverride |
prometheus (so the Prometheus Service is prometheus) |
| Reconcile interval | 1h (timeout 15m) |
| CRDs | CreateReplace on install + upgrade |
Prometheus¶
Base prometheusSpec:
replicas: 2(overridden to 1 in both overlays — see resources below)- Retention 7d / 49GiB (Thanos holds long-term); storage 50Gi
gp3PVC - Thanos sidecar
quay.io/thanos/thanos:v0.37.2,objStorageConfigFile /etc/thanos/objstore.yml, mounting thethanos-objstore-configConfigMap thanosService.enabled: true(creates theprometheus-thanos-discoverygRPC service that thanos-query fans out to)thanosRuler.enabled: true
Prod (overlay):
prometheus:
serviceAccount:
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::777788889999:role/prod-thanos-role
prometheusSpec:
enableRemoteWriteReceiver: true # accepts remote-write (used by amp-migrate)
tsdb:
outOfOrderTimeWindow: 180d # allows back-filling historical samples
externalLabels:
cluster: prod
replica: $(POD_NAME)
Prod IRSA/Thanos-sidecar trust quirk
Prod annotates the chart-created prometheus ServiceAccount with
prod-thanos-role, but that SA is not in the role's trust scope
(system:serviceaccount:observability:thanos). The sidecar's S3 upload only works
via an out-of-band trust-policy edit. Staging does this "correctly" by running the
Prometheus pod as the thanos SA (serviceAccount.create: false, name: thanos) so
the IRSA subject matches staging-thanos-role. See the comments in the staging
helmrelease.yaml.
Staging externalLabels: cluster: staging, replica: $(POD_NAME).
Resource sizing (resources.yaml PatchTransformer)¶
Both overlays apply a builtin PatchTransformer
(prod,
staging)
that injects requests/limits on every workload (the base manifests declare none, so
without this they schedule as BestEffort and are first-evicted). Highlights:
| Setting | prod | staging |
|---|---|---|
| Prometheus replicas | 1 | 1 |
| Prometheus mem request / limit | 2Gi / 16Gi | 2Gi / 3Gi |
| Prometheus CPU request / limit | 250m / 2000m | 250m / 2000m |
| Alertmanager mem | 64Mi | 64Mi |
| Most exporters mem limit | 512Mi (until KRR runs) | 512Mi |
Prod Prometheus is single-replica by incident, not by design
The prod patch drops Prometheus to a single replica (incident 2026-06-10): the
memory-heavy Prometheus must stay off the operator-flagged sensitive single-node
AZ, and pod-1's EBS volume is AZ-locked there. The mem limit was raised 4Gi→16Gi
after the head ballooned to ~22Gi and OOM-crashlooped WAL replay for ~3 months.
Restore replicas: 2 only once dedicated capacity exists in that AZ and pod-1's
stale WAL has been flushed. Staging is single-replica simply because the small nodes
cannot fit a second 2Gi replica.
Alertmanager (prod only)¶
The prod overlay carries the full Alertmanager config. It mounts two secrets
(slack-credentials — the shared platform-bot bot token, also used by KRR — and
pagerduty-credentials) and routes by label:
| Route matcher | Receiver | Destination |
|---|---|---|
alertname = "Watchdog" |
null |
dropped (dead-man's switch) |
alertname = "ClientRouterDown" |
networking-critical |
#networking-alerts (C000000AAA5) + PagerDuty |
team = networking, severity = warning |
networking-warnings |
#networking-warnings (C000000AAA6) |
team = networking |
networking-alerts |
#networking-alerts (Slack only) |
| everything else | null |
swallowed |
Slack is posted via chat.postMessage with a Bearer bot token (not incoming webhooks),
so the token is reusable across channels. PagerDuty is reserved for ClientRouterDown
only — VPN paging already comes from the CloudWatch alarms in the vpn-alerting
Terraform module, so the Prometheus VPN rules are deliberately Slack-only to avoid
double-paging.
Grafana¶
| Setting | Value |
|---|---|
| Admin creds | grafana-credentials (ExternalSecret from SM secret grafana) |
| Default timezone | Africa/Johannesburg |
| Datasources | Prometheus (prometheus.observability...:9090, POST) + Loki (loki-gateway) |
defaultDashboardsEnabled |
false in both overlays (ship only the platform dashboards) |
| Ingress | prod: none — served via the cloudflared tunnel, gated by Cloudflare Access. staging: ALB, internet-facing, Google OIDC auth (google-oidc-secret) |
Grafana stores its state in Postgres (not the sqlite PVC):
- prod: external
postgres.internal.prod.cpt.aws.example.net:5432, DBgrafana,ssl_mode: require; hostgrafana.example.com. - staging: shared in-cluster CNPG at
postgres-rw.database.svc.cluster.local:5432; hostgrafana.staging.cpt.aws.example.net. Thegrafanarole/password come fromgrafana-postgres-credentials(ESO), and the CNPG Database CR (owner: grafana,databaseReclaimPolicy: retain) creates it. This is why the staging Flux KustomizationdependsOn: postgres.
Grafana access control (prod)¶
prod Grafana has no Ingress. It is reachable only through the in-cluster
cloudflared
tunnel, which serves grafana.example.com from
kube-prometheus-stack-grafana.observability.svc.cluster.local:80. A Cloudflare Access
application in front of that hostname decides who gets in, so access is managed in
Cloudflare rather than by ALB Google OIDC.
Access forwards the verified identity in Cf-Access-Authenticated-User-Email, and Grafana
consumes it via auth.proxy — people sign in as themselves instead of sharing the admin
credential. New users are auto-provisioned as Viewer (auto_assign_org_role); promote
deliberately in Grafana. Access controls who gets in, Grafana controls what they can do.
auth.proxy depends on Grafana having no public route
The Cf-Access-Authenticated-User-Email header is only trustworthy because the tunnel
is the sole network path to the Service (auth.proxy.whitelist additionally requires
the request to come from inside 10.161.0.0/16). Re-adding an Ingress, a LoadBalancer
Service, or otherwise exposing Grafana makes the header spoofable — anyone could then
set it and sign in as anyone. If you must re-expose it, disable auth.proxy first.
The built-in Grafana login form is deliberately left enabled as break-glass: with no
Ingress it is only reachable via kubectl port-forward, using the admin credential from
grafana-credentials.
The Cloudflare-side config (tunnel public hostname, Access application and its policies) is managed in the Cloudflare dashboard, matching how the infra docs site is gated — it is not in this repo.
The other ALB-OIDC services (defectdojo, minio, dependency-track, sonarqube) still use
Google OIDC on the ALB with google-oidc-secret; prod Grafana is the first migrated.
Thanos (long-term metrics)¶
Three self-hosted Thanos components (all quay.io/thanos/thanos:v0.37.2) plus the
sidecar in the Prometheus pod. Every S3-touching component runs as the thanos
ServiceAccount (IRSA prod-thanos-role / staging-thanos-role).
| Component | Kind | Replicas | Storage | Key args |
|---|---|---|---|---|
| thanos-query | Deployment | 2 | — | --query.replica-label=replica, stores = prometheus-thanos-discovery + thanos-store (dnssrv), --query.timeout=5m, --query.lookback-delta=15m |
| thanos-store | StatefulSet | 2 | 10Gi gp3 | --index-cache-size=512MB, --chunk-pool-size=512MB |
| thanos-compactor | StatefulSet | 1 | 20Gi gp3 | retention raw 30d / 5m 180d / 1h 365d, --delete-delay=48h, --wait --wait-interval=3h |
The object store is an S3 bucket with SSE-S3, wired via the thanos-objstore-config
ConfigMap per overlay:
| Env | Bucket |
|---|---|
| prod | prod-thanos-777788889999 |
| staging | staging-thanos-444455556666 |
Buckets and IRSA roles are created by the terraform/aws/<env>/af-south-1/observability
Terraform. thanos-query is the datasource behind long-range Grafana queries; the
Prometheus sidecar uploads TSDB blocks and thanos-store serves them back.
Blackbox exporter and the camera probes¶
The base
blackbox-exporter
is prom/blackbox-exporter:v0.25.0 (replicas 2) with two modules:
icmp_camera— ICMP, 5s timeout, IPv4, ttl 64http_camera— HTTP GET, 5s, accepts200/401/403, HTTP/1.1 + HTTP/2, no SSL fail
A ServiceMonitor (label release: kube-prometheus-stack, the label the stack's
Prometheus selects on) scrapes the exporter's own /metrics.
Prod probe targets (network + cameras)¶
The prod overlay adds the actual probe targets. Staging deploys only the base exporter (no probe targets).
probe-public.yaml — a Probe CR (blackbox-icmp, module icmp_camera, 30s) with a
hand-maintained staticConfig of ~120 network endpoints: public DNS anchors
(1.1.1.1, 8.8.8.8), platform Azure/AWS CHRs, ISP next-hops, and every platform/Client C WireGuard
public endpoint (*.sn.mynetname.net, client-c-tmc.ddns.net, …). Used for general egress /
path monitoring.
scrapeconfig-camera-http.yaml and scrapeconfig-camera-icmp.yaml — two
ScrapeConfig CRs (monitoring.coreos.com/v1alpha1) driving the blackbox exporter
against the capture camera fleet:
| ScrapeConfig | jobName | Interval / timeout | Module | Targets |
|---|---|---|---|---|
| camera-http | blackbox-camera-http |
120s / 30s | http_camera |
926 camera HTTP endpoints |
| camera-icmp | blackbox-camera-icmp |
30s / 10s | icmp_camera |
926 camera hosts |
Each target carries a rich per-camera label set (camera_id, camera_name, site_name,
is_vpn, camera_make, camera_model, camera_serial_number, camera_firmware,
connection_type, camera_lat, camera_long). Standard blackbox relabelings move
__address__ → __param_target → instance and point __address__ at
blackbox-exporter.observability.svc.cluster.local:9115; the ICMP config also strips a
trailing :port off the address before probing.
These two files are machine-generated (and huge)
scrapeconfig-camera-http.yaml and scrapeconfig-camera-icmp.yaml are ~500KB /
~14k lines each, produced from the camera inventory by the
camera-scrapeconfig-generator (an out-of-repo/utils job), then committed. The
matching router-blackbox-icmp job's targets/labels are produced by
router-fleet-resolver
(kubernetes/apps/utils/base/router-fleet-resolver),
not this directory. Treat the camera scrapeconfigs as generated artifacts — edit the
generator, not the YAML.
cloudwatch-exporter (AWS/VPN metrics) — prod only¶
cloudwatch-exporter
is YACE (ghcr.io/prometheus-community/yet-another-cloudwatch-exporter:v0.62.0),
a single-replica (Recreate) scraper that bridges CloudWatch AWS/VPN metrics into
Prometheus.
- Runs as SA
cloudwatch-exporterwithautomountServiceAccountToken: false; AWS creds come from EKS Pod Identity (assumesvpn-metrics-exporter, which role-chains intoarn:aws:iam::210987654321:role/vpn-metrics-cloudwatch-readerin the network account where the Site-to-Site VPNs live). - Config (prod configmap)
tag-discovers
AWS/VPNresources, exportsCustomer/Nametags onto every series, and collectsTunnelState(Max),TunnelDataIn/TunnelDataOut(Sum) at 300s period / 600s length. - Scraped via a
ServiceMonitorat 60s (and duplicateprometheus.io/scrapeannotations). Series land asaws_vpn_tunnel_state_maximum,aws_vpn_tunnel_data_{in,out}_sumwithdimension_VpnId,dimension_TunnelIpAddress,tag_Customerlabels — consumed by the VPN dashboard and PrometheusRules.
PrometheusRules / alerts (prod only)¶
Three PrometheusRule objects, all labelled release: kube-prometheus-stack (required
for discovery) and routed by the team=networking labels above.
router-icmp — the only paging rule¶
prometheusrule-router-icmp.yaml
defines ClientRouterDown (severity: critical): probe_success == 0 for 5m against
a hand-picked set of three sites only (Client B, Client A, Client C TMC public endpoint). This
is the alert that pages PagerDuty. The rest of the fleet is dashboarded but not paged.
router-health — Slack-only fleet health¶
prometheusrule-router-health.yaml
groups:
- monitoring-integrity (critical → #networking-alerts):
MktxpExporterDown,RouterFleetResolverStalled(no CronJob run in >2h),RouterBlackboxProbesMissing. - connection-health (warning → #networking-warnings):
RouterReachabilityDegraded(10–50% loss),RouterHighLatency(>500ms),RouterInterfaceErrors. - device-health (warning, mktxp subset):
RouterUnexpectedReboot,RouterHighTemperature(>70°C),RouterHighCPU(>90%),RouterHighMemory(>90%),RouterDiskFull(>90%),RouterLowVoltage(<20V).
vpn-tunnels¶
prometheusrule-vpn-tunnels.yaml
alerts on the cloudwatch-exporter series (Slack-only, team: networking):
| Alert | Condition | Severity |
|---|---|---|
VpnTunnelDown |
one tunnel state == 0 for 15m |
warning |
VpnConnectionDown |
all tunnels of a connection == 0 for 10m |
critical |
VpnTunnelRekeyChurn |
changes(...[1h]) > 6 for 10m (flapping) |
warning |
VpnMetricsExporterDown |
up{job="cloudwatch-exporter"} == 0 for 15m |
warning |
Grafana dashboards + generators¶
Dashboards live in
base/grafana/dashboards
and are provisioned via a configMapGenerator (label grafana_dashboard: "1",
disableNameSuffixHash: true) that the Grafana sidecar picks up. Both overlays reference
base/grafana unchanged (staging additionally adds the CNPG Database).
| Dashboard | uid | Source | Notes |
|---|---|---|---|
cameras.json |
example-cameras |
generated by gen_cameras.py |
capture camera fleet health |
routers.json |
example-routers |
generated by gen_routers.py |
MikroTik fleet view |
mktxp.json |
example-mktxp |
upstream Grafana #13679, datasource-adapted | per-device MikroTik deep-dive |
vpn-tunnels.json |
example-vpn-tunnels |
hand-written | AWS Site-to-Site VPN tunnel health |
Bump version on every dashboard change
Grafana's file provisioner only re-imports a provisioned dashboard when its integer
version increases. A content change with the same version is silently ignored
and the old dashboard stays live. The generators embed the current versions
(cameras v15, routers v6, vpn-tunnels v1) — bump them when regenerating.
gen_cameras.py¶
gen_cameras.py
builds the single Cameras dashboard (python gen_cameras.py writes cameras.json
next to it). Key design:
- HTTP is the primary reachability test (a dead HTTP endpoint = unusable camera);
ICMP is kept for RTT / packet-loss. "Sending" = ≥1 capture event in the last 1h
(
increase(capture_events_total[1h]) > 0). - Per-camera tri-state health glyph (0 = not sending/red, 1 = sending but HTTP-down/yellow,
2 = OK/green) computed as
sending * (1 + http_up). - Template variables from the scrapeconfig labels (site, make, model, link type, VPN,
/24subnet, camera name) plusrouter/peer(from the camera-probe-propagator peer labels) and two custom cascade filters (Reachability, Sending) that inject a verbatim PromQL clause into every panel. - Panels: overview stat rows, an over-time timeseries, a geomap of camera health, a
merged status table, and collapsed ICMP / HTTP / capture / WireGuard peers sections.
The peers section joins
mikrotik_wireguard_peer_*(from mikrotik-wireguard-exporter) onto the camerapeer_pubkey.
The lumpy capture counter
capture_events_total is incremented once per ~60s exporter cycle, so rate() over a
narrow window aliases into a sawtooth. The dashboard deliberately uses a 15m rate
window (a query-level fix) because the ripple is baked into stored samples that no
scrape/exporter change can retro-smooth.
gen_routers.py¶
gen_routers.py
builds the Routers fleet dashboard. Two tiers produced by router-fleet-resolver:
- blackbox (ICMP) reachability for every router — the reliable spine
(
job="router-blackbox-icmp", labelssite_name,router_endpoint,tier,client). - mktxp (RouterOS API) device metrics for the authenticated subset, joined onto the
filtered spine on
routerboard_address == router_endpoint.
It renders a fleet table (reachability + auth status + CPU/mem/disk/temp/volts/uptime),
an interfaces table, connection-health and device-health timeseries, and firewall traffic
panels. Per-device drill-down is deferred to mktxp.json. Template vars: client / site /
tier / router.
vpn-tunnels.json¶
Hand-written dashboard (example-vpn-tunnels) reading the cloudwatch-exporter AWS/VPN
series: per-tunnel state (stepped 0/1), and tunnel data in/out (bps).
Loki + Fluent Bit (logs)¶
Loki (statefulset):
grafana/loki:3.6.7, StatefulSet replicas 2, 10Gi gp3, runs as SA loki
(IRSA prod-loki-role / staging-loki-role). Services: loki (3100/9096),
loki-memberlist (7946 headless), loki-gateway (:80 → 3100, the write/read entry point
and the Grafana datasource URL).
Config differs per env
(prod /
staging):
auth_enabled: false, TSDB shipper on S3, schema v13, 31-day retention (744h),
ingestion 16MB/s (burst 32MB), compactor with retention deletes.
| Env | S3 bucket | replication_factor | memberlist |
|---|---|---|---|
| prod | prod-loki-777788889999 |
2 | yes (join loki-memberlist) |
| staging | staging-loki-444455556666 |
1 | not configured |
Staging Loki replica/RF mismatch
Staging sets replication_factor: 1 and omits the memberlist ring config, but does
not override the base StatefulSet replicas: 2. Two staging Loki replicas run
without a configured memberlist join — a minor inconsistency worth being aware of when
debugging staging log ingestion.
Fluent Bit (daemonset):
fluent/fluent-bit:3.2.2 DaemonSet (tolerates all taints), SA fluent-bit with a
ClusterRole granting get/list/watch on namespaces + pods. It tails
/var/log/containers/*.log, enriches via the kubernetes filter, strips
pod_id/docker_id/container_hash, and ships to loki-gateway:80 stamping
cluster=prod / cluster=staging and namespace/pod/container labels. No ServiceMonitor
(exposes only the HTTP health server on 2020).
KRR (right-sizing)¶
krr
(robustadev/krr:v1.28.0) is a CronJob that runs Mondays 09:00 (0 9 * * 1),
queries Prometheus for resource recommendations, and posts a CSV to Slack via the shared
slack-credentials bot token. Its ClusterRole grants cluster-wide get/list.
| Env | PROMETHEUS_URL |
Slack channel |
|---|---|---|
| prod | http://prometheus.observability.svc.cluster.local:80 |
C000000AAA2 (#engineering-info) |
| staging | http://prometheus-prometheus.observability.svc.cluster.local:9090 |
C000000AAA4 (#staging-engineering-info) |
The many # higher mem limit until KRR runs for this resource comments in
resources.yaml are placeholders awaiting these KRR recommendations.
Prod vs staging Prometheus URL differ
Prod points KRR at the prometheus service on port 80; staging explicitly points at
prometheus-prometheus:9090 with a comment that the plain prometheus service "only
the now-removed orphan owned". Worth confirming the prod URL still resolves to the
live kube-prometheus-stack Prometheus.
Metric exporters¶
All four are Deployments scraped either by a ServiceMonitor
(label release: kube-prometheus-stack) or prometheus.io/scrape annotations.
| Exporter | Image | Replicas | Target (prod) | Target (staging) | Credentials |
|---|---|---|---|---|---|
| mongodb-exporter | bitnami/mongodb-exporter (pinned by digest), --collect-all |
2 | mongo-uri SM |
mongo-uri SM |
ExternalSecret mongo-uri (MONGO_URI_READ_ONLY) |
| mysql-exporter | prom/mysqld-exporter:v0.18.0 |
2 | db-proxy.internal.prod...:3306 |
(not deployed) | ExternalSecret mysql-credentials → .my.cnf from prod-db-readonly-credentials |
| postgres-exporter | quay.io/prometheuscommunity/postgres-exporter:v0.19.0 |
2 | postgres.internal.prod...:5432 |
(not deployed) | ExternalSecret postgres-credentials (readonly_* from SM postgres) |
| redis-exporter | quay.io/oliver006/redis_exporter:v1.56.0 |
2 | cache.internal.prod...:6379 |
valkey.database.svc:6379 |
none (no-auth) |
Notes:
- postgres-exporter ships a
queries.yamlConfigMap with custom queries (pg_replicationlag,pg_postmaster,pg_stat_user_tables). The base DSN uses a literal@hostnameplaceholder that the prod overlay patches to the real host. - mysql/postgres are intentionally omitted on staging pending RDS/CNPG credential
wiring (see the staging
kustomization.yamlcomment). - The exporter SAs (e.g.
mongodb-exporter) setautomountServiceAccountToken: false.
netshoot¶
netshoot
is a network-debug DaemonSet in its own netshoot namespace, deployed directly by both
cluster overlays.
image: nicolaka/netshoot:latest # sleeps forever; kubectl exec in to debug
hostNetwork: true
hostPID: true
securityContext:
privileged: true # full node network/PID visibility
resources: { requests: {64Mi,100m}, limits: {128Mi,200m} }
Privileged, host-network, mutable tag
This pod runs privileged with hostNetwork/hostPID and pulls :latest — powerful
for on-node network troubleshooting but a standing high-privilege workload with a
non-pinned image. It exists purely for interactive kubectl exec debugging.
Legacy / migration artifacts (AMP → Prometheus)¶
The overlays/prod directory contains a one-off AMP (Amazon Managed Prometheus) →
in-cluster Prometheus migration toolkit that is not wired into any Kustomization or
Flux — it is standalone tooling run by hand. It is effectively dead once the migration
completed and the AMP workspaces are deleted.
amp_query_migrate.go— Go program that pages metric names from AMP (SigV4-signed, optional cross-account assume-role), queries in 30m chunks and remote-writes into Prometheus with a resumable checkpoint.go.mod/go.sumsit beside it.run_migration.sh— local runner (port-forwards Prometheus, builds + runs the binary against shared account123456789012, workspacews-aaaa1111…).amp-migrate-job/— the in-cluster variant:job.yaml(shared AMP, cross-account assume-role),job-prod-amp.yaml(prod AMP workspacews-bbbb2222…),serviceaccount.yaml(amp-migrator, IRSAprod-amp-migrator-role), andsetup-iam.sh/teardown.shthat create/destroy the cross-account IAM and theamp-migrate-srcConfigMap. Both jobs build the Go source from a ConfigMap at pod start.
The committed 12.9 MB binary was removed
overlays/prod/amp-migrate — a Mach-O 64-bit arm64 executable (~12.9 MB), the
locally-built output of amp_query_migrate.go — was git-tracked and has been deleted in a
cleanup PR (run_migration.sh rebuilds it on demand). The amp-migrate-job/ manifests and
the Go source remain; they are a one-off AMP→Prometheus migration and can be removed once the
migration is confirmed complete.
Other archived / generated weight in-tree:
base/grafana/dashboards/.archive/— 13 archived Grafana dashboards (~704 KB) kept in git after the Grafana clean-up (blackbox, fluent-bit, krr, loki, mongodb, mysql, postgres, redis, ping2, and four Thanos dashboards). Intentionally retained but dead (not provisioned).- The two camera
ScrapeConfigYAMLs (~500 KB each) are committed generated artifacts.