Adds platform/cloudflared: a locally-managed tunnel whose ingress rules live in git. LAN/VPN-only hosts (plex, plex-vip, jackett, pihole, pihole-dns, semaphore, qbittorrent, radarr, sonarr, headlamp, unifi, crafty-mc) map to 404. The apex and *.home go to cilium-gateway-shared.gateway over TLS with matchSNItoHost. Last comes a catch-all 404. Nothing routes to the Kubernetes API. - 2-replica Deployment, hard node spread, non-root, pinned digest, PDB - default-deny plus an egress CNP: Cloudflare edge 7844/443, the Gateway, and Gateway backends by gateway-access=shared minus the LAN-only namespaces (needed because of the CVE-2025-30162 L7LB egress re-check) - PodMonitor plus two alerts that fire on absent series, with promtool tests - its own Flux Kustomization (dependsOn platform-config, wait:false, nothing depends on it), plus SOPS decryption patches in both overlays - placeholder credentials (SOPS-encrypted) so it can merge before enrolment - L0 cloudflared-ingress-rules, and a cloudflared_ scrape owner - runbooks/cloudflare-tunnel.md and decisions/2026-11-08-cloudflare-tunnel.md Co-Authored-By: Claude Opus 5.5 <[email protected]>
62 lines
3.5 KiB
YAML
62 lines
3.5 KiB
YAML
---
|
|
# Flux Kustomization: Cloudflare Tunnel connector — reconciles ./platform/cloudflared.
|
|
#
|
|
# ISOLATION IS THE POINT OF THIS FILE. The Deployment it applies CANNOT become Ready until the
|
|
# operator enrols the tunnel (runbooks/cloudflare-tunnel.md): it ships with REPLACE-ME credentials
|
|
# and a placeholder UUID, and a wrong or revoked credential later produces the same state. So:
|
|
#
|
|
# * ITS OWN Kustomization, not a line in platform/kustomization.yaml. `platform` is wait:true and
|
|
# gates platform-config, which gates every apps wave; an unenrolled tunnel there would be a
|
|
# platform-wide stall ON MERGE, with the cause (a missing manual step) invisible from any Flux
|
|
# condition. Same reasoning, and same failure shape, as vpn-ingress's unfilled server key.
|
|
# * wait: false. Its pods are legitimately not-Ready whenever the credentials are absent or the
|
|
# edge is unreachable. Health is reported by the CloudflaredTunnelDown / ReplicasDegraded
|
|
# alerts (platform/cloudflared/alerts.yaml), not by this Kustomization's Ready condition —
|
|
# which reaches Ready on APPLY success.
|
|
# * NOTHING dependsOn THIS, and nothing should. A missing or invalid credentials Secret can then
|
|
# only ever affect the tunnel itself. conformance/tests/l0-static/cloudflared-ingress-rules
|
|
# fails the gate if any Kustomization gains `dependsOn: cloudflared`, or if this one stops
|
|
# being wait:false.
|
|
# * No Certificate, no HelmRelease, no CRD: nothing in the path for Flux's health checks to wait
|
|
# on even if wait were flipped (the Certificate-stall lesson).
|
|
#
|
|
# dependsOn: [platform-config] — A REAL EDGE.
|
|
# 1. CRD ORDERING: PodMonitor and PrometheusRule are monitoring.coreos.com CRs whose CRDs are
|
|
# installed by kube-prometheus-stack in `platform`; platform-config dependsOn platform, so this
|
|
# edge carries that guarantee transitively (Flux aborts a whole pass on an unknown kind).
|
|
# 2. THE FAR SIDE OF THE TUNNEL: the Gateway (and so the cilium-gateway-shared Service the ingress
|
|
# rules point at) is applied by platform-config. A tunnel that comes up before its origin is a
|
|
# support call.
|
|
#
|
|
# ZERO EFFECT ON LIVE TRAFFIC UNTIL DNS MOVES. Cloudflare only sends a hostname through a tunnel
|
|
# when that hostname's DNS record is a CNAME to <uuid>.cfargotunnel.com. This change creates no
|
|
# such record and leaves external-dns, the Gateway annotations, Pi-hole and CoreDNS untouched.
|
|
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
|
kind: Kustomization
|
|
metadata:
|
|
name: cloudflared
|
|
namespace: flux-system
|
|
spec:
|
|
interval: 10m
|
|
retryInterval: 2m
|
|
timeout: 5m
|
|
prune: true
|
|
wait: false
|
|
dependsOn:
|
|
- name: platform-config
|
|
sourceRef:
|
|
kind: GitRepository
|
|
name: flux-system
|
|
path: ./platform/cloudflared
|
|
# REQUIRED, not boilerplate: platform/cloudflared/config.yaml renders `${BASE_DOMAIN}` into every
|
|
# ingress hostname, and alerts.yaml into both runbook_url annotations. Without this block the
|
|
# token becomes the EMPTY STRING and the Kustomization still reports Ready (substitution fails
|
|
# OPEN), leaving ingress rules for `plex.` and `*.` — a tunnel that 404s everything at best.
|
|
postBuild:
|
|
substituteFrom:
|
|
- kind: ConfigMap
|
|
name: cluster-vars
|
|
# SOPS decryption is layered per-environment: clusters/dev and clusters/prod each patch THIS
|
|
# Kustomization by name with a `decryption:` stanza for cloudflared-credentials.sops.yaml.
|
|
# Without it the Secret applies cleanly full of ENC[...] and cloudflared cannot authenticate.
|