openova/platform/cilium/chart/values.yaml
e3mrah 6edb8b4635
fix(cilium): gatewayAPI hostNetwork.nodes.matchLabels (prov #76) (#1480)
Cilium gatewayAPI.hostNetwork.enabled=true was set in values.yaml,
but without nodes.matchLabels Cilium silently DISABLES hostNetwork
mode. The configmap key gateway-api-hostnetwork-nodelabelselector is
rendered EMPTY → eBPF redirect for the gateway NodePorts is never
programmed → envoy listener has empty bind address → incoming
30443/30080 traffic dead-ends at the Hetzner LB target.

Caught on prov #76 (omantel.biz, 2026-05-14): public TLS handshake
to console.omantel.biz returns SSL_ERROR_SYSCALL because envoy
isn't listening on the NodePort. cilium service list shows zero
30443/30080 entries. cilium proxy status shows 0 redirects active.

Set nodes.matchLabels: kubernetes.io/os: linux (every k3s node carries
this label) so the gateway listener is exposed on every CP.

Chart: 1.3.4 → 1.3.5. bootstrap-kit slot 01 version pin bumped to match.

Co-authored-by: e3mrah <catalyst@openova.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 18:17:35 +04:00

324 lines
16 KiB
YAML

# Catalyst Blueprint umbrella metadata — the upstream chart is now resolved
# as a Helm subchart via Chart.yaml `dependencies:`. This values.yaml carries
# both:
# 1. The catalystBlueprint metadata block (provenance + version) so
# observability/audit pipelines can inspect the artifact and report
# which upstream chart + version is bundled.
# 2. The upstream subchart values overlay under the `cilium:` key
# (umbrella-chart convention — the dependency name from Chart.yaml is
# the values namespace).
global:
# When set, ALL image pulls in this chart route through this registry.
# Used post-handover when the Sovereign's own Harbor takes over the
# proxy_cache role from contabo's central Harbor. Empty = no rewrite
# (image references use upstream defaults). The upstream cilium subchart
# does not expose a single `image.registry` knob; per-Sovereign overlays
# should set specific image paths (e.g. `cilium.image.repository`) in
# conjunction with this value. Tracked under #560.
imageRegistry: ""
catalystBlueprint:
# Pinned to match Chart.lock (truth-on-disk). Aligned with Chart.yaml's
# dependencies version. The previous "1.19.3" was provenance drift —
# Chart.lock had been resolving 1.16.5 the whole time (EPIC-0 #1095 audit).
upstream: { chart: cilium, version: "1.16.5", repo: "https://helm.cilium.io" }
# Catalyst-curated Cilium values per platform/cilium/README.md.
#
# Defaults assume a Catalyst Sovereign on bare-metal/VM Kubernetes (k3s on
# Hetzner is the canonical target). Cilium replaces both flannel and
# kube-proxy; k3s must be started with --flannel-backend=none and
# --disable-network-policy.
cilium:
# kube-proxy replacement (faster + simpler than iptables). MUST be true
# because we also set `bpf.masquerade: true` (line 54) — Cilium rejects
# the BPF masquerade datapath without NodePort enabled, and Cilium only
# auto-enables NodePort when kubeProxyReplacement is true:
# "BPF masquerade requires NodePort (--enable-node-port=\"true\")"
# The Sovereign cloud-init pre-Flux Cilium install (infra/hetzner/
# cloudinit-control-plane.tftpl `/var/lib/catalyst/cilium-values.yaml`)
# already uses kubeProxyReplacement: true, so this aligns the Flux HR
# overlay with the pre-Flux bootstrap. Symptom when divergent: CP
# cilium-agent kept its old in-memory state and stayed Ready, but every
# WORKER node that joined AFTER the Flux upgrade saw the new ConfigMap
# with enable-bpf-masquerade=true + enable-node-port=false → fatal
# "BPF masquerade requires NodePort" → cilium-agent CrashLoopBackOff →
# node.cilium.io/agent-not-ready taint never lifts → every keycloak/
# gitea/catalyst-platform/openbao/powerdns/mimir post-install Job pod
# stays Pending → whole bootstrap-kit chain stalls.
# Caught on prov #55 (8d85a64cb8807cdc, 2026-05-12). The DaemonSet
# is started with kube-proxy-replacement off → NodePort off → masq off
# incompat. Both single-node and multi-node Sovereigns hit this once
# cluster-autoscaler scales workers.
kubeProxyReplacement: true
# k8sServiceHost: CP's stable private IP on the Sovereign's 10.0.1.0/24
# subnet (cp1=10.0.1.2 per infra/hetzner/main.tf). Multi-node-aware:
# works on the CP itself (10.0.1.2 IS its own private IP) AND on
# workers (which need the CP's IP because k3s agents do NOT expose the
# apiserver on localhost — only the supervisor on :6444). Previously
# `127.0.0.1` here, which crashloop'd Cilium on every worker for
# multi-node Sovereigns (issue #733). For air-gap / multi-CP / non-
# Hetzner Sovereigns the canonical fix is to point this at the in-
# cluster apiserver Service (kubernetes.default.svc.cluster.local) —
# but that requires DNS up before Cilium, which is the chicken-egg
# this whole config exists to solve. The Sovereign template subnet
# is fixed at 10.0.1.0/24 so 10.0.1.2 is a stable load-bearing value.
k8sServiceHost: 10.0.1.2
k8sServicePort: 6443
# eBPF-host-routing — bypasses iptables stack
bpf:
masquerade: true
# qa-loop iter-12 Fix #54 Workstream 2 — fresh worker pods on
# catalyst-omantel-biz-w2/w3 could not resolve github.com (BPF map
# capacity exhaustion when a node first joins). Pre-allocate the
# NAT/connection BPF map slots at agent startup so DNS lookups from
# newly-scheduled pods succeed on the first eBPF socketLB hit instead
# of falling through to iptables fallback (which on k3s with
# --disable-network-policy is no-op — the lookup just fails).
#
# Cost: ~12 MiB extra RSS per cilium-agent. Benefit: DNS lookups from
# pods on first-join workers don't transient-fail for the 30-90s
# window the lazy-allocate path otherwise takes (see Cilium issue
# cilium/cilium#28456 — "DNS races during node bring-up when BPF maps
# allocate on-demand"). Worth the RSS cost; canonical fix per upstream.
preallocateMaps: true
# qa-loop iter-12 Fix #54 Workstream 2 — socket-level load-balancing
# MUST stay in the host network namespace (default true is correct, but
# we pin the value explicitly so a future upstream default-flip doesn't
# silently re-introduce the per-pod-netns kube-proxy-replacement DNS
# race). When socketLB runs in the pod netns (hostNamespaceOnly: false),
# a brand-new pod's first DNS query can race with cilium-agent's
# service-map sync and miss kube-dns; the lookup then falls back to
# the iptables stack which on a kube-proxy-replacement cluster is
# absent → DNS fails. Keeping socketLB host-only ensures every
# pod-egress lookup goes through the cilium-agent's already-synced
# service map.
socketLB:
hostNamespaceOnly: true
# IPAM — k8s mode uses the k8s pod CIDR allocator (works with k3s defaults)
ipam:
mode: kubernetes
# WireGuard mTLS for transparent encryption between every pod (per
# platform/cilium/README.md "mTLS via WireGuard"). This is the
# CANONICAL east-west mTLS layer for OpenOva Sovereign clusters —
# 100% mesh enforcement at the kernel transport level. SPIFFE-based
# workload identity is intentionally NOT enabled here; bp-spire was
# dropped from the bootstrap-kit (founder direction 2026-05-03)
# because (a) Cilium WireGuard already gives every connection
# transparent encryption + node-level identity, and (b) ESO→OpenBao
# auth uses K8s ServiceAccount auth method (TokenReview), not
# JWT-SVID. Federation across Sovereigns or per-workload-fingerprint
# attestation can re-introduce SPIRE later as an opt-in blueprint.
encryption:
enabled: true
type: wireguard
nodeEncryption: true
# Hubble — network observability (L3/L4/L7 flows, DNS, drop, http metrics).
#
# DEFAULT OFF per docs/BLUEPRINT-AUTHORING.md §11.2 (Observability toggles
# must default false) + INVIOLABLE-PRINCIPLES.md #4 (never hardcode).
#
# Hubble relay/ui depend on the kube-prometheus-stack
# `monitoring.coreos.com/v1` ServiceMonitor CRD AND on Hubble metrics
# being scraped — neither exists on a fresh Sovereign before the
# observability tier (Application Blueprint bp-kube-prometheus-stack)
# is reconciled. Worse: enabling `hubble.metrics.enabled` with values
# like `dns:query;ignoreAAAA` causes the upstream Cilium chart to render
# a ServiceMonitor unconditionally (verified on omantel.omani.works
# 2026-04-29 — install fails with "no matches for kind ServiceMonitor in
# version monitoring.coreos.com/v1").
#
# `metrics.enabled: null` (NOT [] and NOT a populated list) is the
# exact value that makes the upstream Cilium chart skip the metrics
# ServiceMonitor template branch — verified by reading the upstream
# cilium 1.19.3 chart's _hubble.tpl.
#
# Operator opts in via clusters/<sovereign>/bootstrap-kit/01-cilium.yaml
# values overlay once bp-kube-prometheus-stack reconciles (issue #182).
hubble:
enabled: true
relay:
# Default false — every Sovereign in production runs without the relay.
# EPIC-5 (#1100) flips this on per-Sovereign via clusters/<sov>/bootstrap-kit/01-cilium.yaml
# values overlay alongside the catalystOverlay.hubbleUI block below.
enabled: false
ui:
enabled: false
metrics:
# `null` — not a list. Disables the upstream chart's metrics
# ServiceMonitor template entirely.
enabled: null
serviceMonitor:
enabled: false
# Gateway API — replaces traditional ingress controllers
gatewayAPI:
enabled: true
gatewayClass:
# Force GatewayClass creation regardless of CRD presence at Helm
# render time. Upstream default is "auto" which skips creation when
# the gateway.networking.k8s.io CRDs are absent (Capabilities check
# at helm install time). During bootstrap, bp-gateway-api runs AFTER
# bp-cilium, so "auto" silently skips the GatewayClass — leaving
# every HTTPRoute orphaned until the operator is manually restarted.
# Forcing "true" ensures GatewayClass/cilium is always created.
# Permanent fix for the cilium-gateway race (issue #503).
create: "true"
# hostNetwork mode — bind gateway listeners directly to the host's
# 80/443 ports (instead of Service-of-type-LoadBalancer with random
# nodePort allocation). Required for the Hetzner Cloud Load Balancer
# forwarding chain: HCLB listens on public 80/443 and forwards via
# the private network to the CP node's 80/443 (NOT to a random
# nodePort). With nodePort mode, Cilium's BPF L7LB Proxy Port doesn't
# match what cilium-envoy actually binds, breaking TLS termination
# entirely (cilium service list shows the redirect, but envoy isn't
# listening on the proxy port). Caught live on otech45 — TCP connects
# but TLS handshake never completes.
hostNetwork:
enabled: true
# nodes.matchLabels MUST be non-empty for Cilium hostNetwork
# mode to activate. An empty selector becomes the configmap key
# `gateway-api-hostnetwork-nodelabelselector =` (empty value),
# which Cilium docs explicitly call out as DISABLED state:
# https://docs.cilium.io/en/stable/network/servicemesh/gateway-api/host-network/
# Without this, the Gateway listener config has empty bind
# address, eBPF never programs a redirect for the gateway
# NodePorts, and incoming traffic on 30443/30080 never reaches
# envoy. Hetzner LB health checks against those NodePorts
# report `unhealthy` indefinitely.
#
# Caught live on prov #76 (2026-05-14): TLS handshake initiates
# at the Hetzner LB, then SSL_ERROR_SYSCALL because no backend
# answers — envoy is running with the listener config but not
# bound to a socket because hostNetwork mode is silently OFF.
#
# Match labels are conservative — `kubernetes.io/os: linux` is
# universally present on every k3s node we ship, so the gateway
# listener is exposed on every node in single- and multi-CP
# Sovereign topologies. Operators that want gateway-only-on-
# primary-region can tighten this in their per-cluster overlay.
nodes:
matchLabels:
kubernetes.io/os: linux
# L7 proxy via Envoy — for HTTPRoute, gRPCRoute, L7 NetworkPolicy
envoy:
enabled: true
# L2 LoadBalancer announcements (alternative to MetalLB for bare-metal)
l2announcements:
enabled: false # Catalyst uses Hetzner LB on cloud Sovereigns; flip true for bare-metal
# Operator HA — single replica is fine for solo, bump to 2-3 for HA
operator:
replicas: 1
# Pod resource requests/limits — tuned for k3s on cx32 (8 vCPU / 16 GB)
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
memory: 1Gi
# Prometheus scraping + ServiceMonitor — DEFAULT OFF.
#
# Per docs/INVIOLABLE-PRINCIPLES.md #4 (never hardcode) and
# docs/BLUEPRINT-AUTHORING.md §11.2 (Observability toggles must default
# false): observability toggles MUST default `false` because the
# `monitoring.coreos.com/v1` CRDs that back ServiceMonitor / PrometheusRule
# ship with kube-prometheus-stack — a second-tier Application Blueprint
# that itself depends on the bootstrap-kit (cilium first). Defaulting
# `serviceMonitor.enabled: true` here creates a circular CRD dependency:
# bp-cilium wants the CRD that bp-kube-prometheus-stack provides, but
# bp-kube-prometheus-stack cannot install before bp-cilium. The
# `trustCRDsExist=true` mitigation only suppresses Helm's render-time
# gate; at install time the apiserver still rejects ServiceMonitor with
# "no matches for kind ServiceMonitor in version monitoring.coreos.com/v1
# — ensure CRDs are installed first" (verified on omantel.omani.works
# 2026-04-29, issue #182).
#
# Operator opts in once the observability tier is reconciled. The opt-in
# is a per-cluster values overlay at clusters/<sovereign>/bootstrap-kit/
# 01-cilium.yaml `spec.values.cilium.prometheus.serviceMonitor.enabled:
# true` — no rebuild of the Blueprint OCI artifact is required.
prometheus:
enabled: false
serviceMonitor:
enabled: false
# ─── Catalyst overlay templates (chart/templates/) ─────────────────────
#
# Templates that ride alongside the upstream cilium subchart. Every block
# is gated by an explicit `enabled: false` so installing the chart never
# regresses production behaviour — operator opts in via per-Sovereign
# overlay at clusters/<sov>/bootstrap-kit/01-cilium.yaml.
catalystOverlay:
# Hubble UI exposed via Cilium Gateway with Keycloak OIDC. Activated
# by EPIC-5 (#1100) as part of the zero-trust observability roll-out.
#
# Prerequisite to enable:
# 1. cilium.hubble.relay.enabled = true
# 2. cilium.hubble.ui.enabled = true
# 3. Keycloak realm has a `hubble-ui` OIDC client (ConfigMap
# configmap-sovereign-realm.yaml in bp-keycloak — wired by
# slice D1).
# 4. Cilium Gateway exists with the wildcard cert (every Sovereign
# has this from clusters/_template/sovereign-tls/).
hubbleUI:
enabled: false
# Hostname under the Sovereign domain. Per docs/NAMING-CONVENTION.md
# §5.1 the canonical control-plane DNS pattern is
# `{component}.{location-code}.{sovereign-domain}`, but every existing
# bootstrap-kit HTTPRoute (gitea, auth/keycloak, grafana, harbor,
# openbao, powerdns, console/catalyst-platform) uses the flat
# `{component}.{sovereign-domain}` form because the Cilium Gateway's
# wildcard cert + listener is `*.${SOVEREIGN_FQDN}` — a single-level
# wildcard. Multi-level forms like `hubble.console.<fqdn>` would NOT
# match `*.<fqdn>` (TLS wildcards are single-label). For consistency
# this overlay uses the flat form.
#
# Two ways to populate this:
# 1. Set hostname directly (full FQDN, e.g. "hubble.omantel.biz").
# 2. Set sovereignFQDN (e.g. "omantel.biz") and the chart derives
# `hubble.<sovereignFQDN>` automatically — preferred for Flux
# envsubst flows where the Sovereign FQDN is the single source.
# Explicit hostname wins when both are set.
hostname: ""
# Optional: when set AND hostname is empty, the chart auto-derives
# `hubble.<sovereignFQDN>` so per-Sovereign overlays only need to
# forward ${SOVEREIGN_FQDN} once (matches the gitea/auth/grafana
# bootstrap-kit pattern). Empty = no auto-derivation.
sovereignFQDN: ""
# Reference to the Cilium Gateway that fronts this Sovereign's wildcard
# cert. Defaults match clusters/_template/sovereign-tls/cilium-gateway.yaml,
# which installs the Gateway into kube-system (NOT a separate
# cilium-gateway namespace — the previous default was a stale leftover
# from an earlier design that never landed).
gatewayRef:
name: cilium-gateway
namespace: kube-system
# Authentication mode at the gateway boundary. `oidc` requires the
# Keycloak realm to have an `hubble-ui` client (slice D1). `none` is
# only acceptable for fully air-gapped lab Sovereigns; per-Sovereign
# overlays flip to `oidc` once the realm-config wires the client.
auth: oidc
# Hubble UI Service ref. The bp-cilium HelmRelease installs Cilium
# into kube-system (clusters/_template/bootstrap-kit/01-cilium.yaml
# `targetNamespace: kube-system`), so the upstream subchart renders
# the hubble-ui Service in kube-system. The previous default
# `namespace: cilium` would have routed traffic to a non-existent
# Service on every Sovereign.
serviceRef:
name: hubble-ui
namespace: kube-system
port: 80