--- # clusters/prod — DESIGN-ONLY overlay. NOT a build target until roadmap Phase 7 opens prod # (spec roadmap/phases.md, AGENTS.md §6: "Never build directly on production"). # # When Phase 7 is opened, `flux bootstrap ... --path=clusters/prod` points the prod cluster's Flux # here. Like dev, this overlay adds NO resources — it ONLY patches cluster-vars with prod-scoped # values (R-ENV-02). Promotion to prod is a change to THESE variables, never a base edit. apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - ../base patches: - target: kind: ConfigMap name: cluster-vars patch: |- - op: replace path: /data/CLUSTER_ENV value: "prod" # trusted, rate-limited real certs in prod — ADR-0006. - op: replace path: /data/ACME_CLUSTER_ISSUER value: "letsencrypt-prod" - op: replace path: /data/LONGHORN_DEFAULT_REPLICA_COUNT value: "3" # longer retention / larger volumes on prod NVMe. - op: replace path: /data/MONITORING_PROMETHEUS_RETENTION value: "30d" - op: replace path: /data/MONITORING_PROMETHEUS_PVC_SIZE value: "50Gi" # Size-cap below the 50Gi PVC (headroom for WAL + head + compaction scratch): prod may hit the # size cap before 30d if series volume is high, keeping the disk from filling either way. - op: replace path: /data/MONITORING_PROMETHEUS_RETENTION_SIZE value: "45GiB" - op: replace path: /data/MONITORING_LOKI_PVC_SIZE value: "50Gi" # HA control-plane addons in prod. - op: replace path: /data/KEYCLOAK_REPLICAS value: "2" - op: replace path: /data/OAUTH2_PROXY_REPLICAS value: "2" - op: replace path: /data/VELERO_RETENTION value: "720h" # ========================================================================================== # PROD LAN ADDRESSING (192.168.2.0/24). Nodes: k3s-prod-1/2/3 = .10 / .11 / .12. # ========================================================================================== # These knobs were HARDCODED to the dev LAN inside base manifests until 2026-08-31; prod would # have come up ANNOUNCING DEV ADDRESSES (192.168.1.x) on the prod LAN. They are knobs now, and # this overlay supplies every one of them. Anything the operator has NOT yet decided stays an # INVALID `TBD-PROD-*` placeholder on purpose: base defaults are the DEV values, so an OMITTED # op means prod silently inherits a dev address — far worse than a failed apply. `TBD-PROD-*` # is not a valid IP/URL, so Cilium CRD validation / LB-IPAM / CNPG / Velero reject it LOUDLY. # # ------------------------------------------------------------------------------------------ # ADDRESS PLAN (operator, 2026-08-31) — see infra/PROD-PROVISIONING.md §4 for the full table. # ------------------------------------------------------------------------------------------ # nodes 192.168.2.10 / .11 / .12 (Ansible inventory) # control-plane VIP 192.168.2.9 (Ansible group_vars/k3s_prod — NOT here) # ingress Gateway VIP 192.168.2.20 (GATEWAY_VIP, below) # Cilium LB pool 192.168.2.20 - .30 (CILIUM_LB_POOL_*, below) # # CONTAINMENT INVARIANT SATISFIED: GATEWAY_VIP (.20) IS CILIUM_LB_POOL_START, mirroring dev # (.53 of .53-.60). This matters because the `shared` Gateway (platform/gateway/gateway.yaml) # pins NOTHING — no `lbipam.cilium.io/ips`, no spec.addresses (unlike platform/crafty and # apps/vms/unifi, which DO pin) — so Cilium LB-IPAM assigns it the FIRST FREE address of a # matching pool block. Had GATEWAY_VIP stayed outside every pool (the original .21-.30 # proposal), the Gateway would have come up on .21 while split-horizon coredns-custom kept # answering *.home.keithjackson.dev with .20 — an address nothing announces — blackholing # in-cluster hairpin traffic (OIDC clients -> Keycloak/Grafana) while looking healthy. # KEEP GATEWAY_VIP == CILIUM_LB_POOL_START when changing either. # The control-plane VIP (.9) is deliberately OUTSIDE this pool: kube-vip floats it, and # LB-IPAM must never be able to hand it to a Service. - op: replace path: /data/GATEWAY_VIP value: "192.168.2.20" # Cilium LB IP pool, operator-supplied: 192.168.2.20-.30 (START == GATEWAY_VIP, see above). # Mirror this range into the prod tofu reservation (cilium_l2_vip_range) if prod ever gets one. - op: replace path: /data/CILIUM_LB_POOL_START value: "192.168.2.20" - op: replace path: /data/CILIUM_LB_POOL_STOP value: "192.168.2.30" # --- still UNDECIDED -> loud placeholders (must be real before `flux bootstrap`) ------------ # k3s API endpoint Cilium's kube-proxy replacement dials. This is the prod CONTROL-PLANE VIP # (kube-vip), NOT .20 (the ingress VIP) and NOT the single node address .10. It is owned # Ansible-side (prod inventory k3s_api_host) and MUST equal that value — [parity] contract in # platform/cilium/helmrelease.yaml. Not yet decided. - op: replace path: /data/K3S_API_HOST value: "192.168.2.10" # Pinned L4 VIPs, both carved out of the pool by lbipam.cilium.io/ips. Not yet assigned for # prod. Leaving the dev literals in place would hand a prod LoadBalancer a dev-LAN address, so # they stay placeholders; whatever is chosen MUST fall inside [POOL_START, POOL_STOP] and not # collide with the Gateway VIP or the node IPs (.10/.11/.12). - op: replace path: /data/UNIFI_L4_VIP value: "192.168.2.21" - op: replace path: /data/CRAFTY_L4_VIP value: "192.168.2.22" # Off-cluster DR target. Dev's versitygw/MinIO (192.168.1.9:9000) sits on the DEV side of the # network — a prod backup written there is not off-cluster DR and would mix two environments' # PITR streams in one bucket. Operator supplies the prod endpoint (full URL incl. scheme+port). - op: replace path: /data/S3_BACKUP_ENDPOINT value: "TBD-PROD-S3-BACKUP-ENDPOINT" # TODO(WS-INFRA, Phase 7): pin prod node placement once the bare-metal role labels exist.