Files
home-network-k3s-impl/platform/vpn-egress
jakman85andClaude Opus 4.8 4a781ace49 fix(vpn): exec (localhost) liveness probe — VPN routing breaks kubelet HTTP probe
Once the tunnel redirects the pod default route via tun0, the pod's response to the
kubelet's httpGet liveness probe egresses the tunnel and never returns → probe times
out → pod restarts ~every 2min despite the tunnel being up (PIA egress IP confirmed).
Switch to a local wget exec probe (stays on lo).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-23 23:57:31 -05:00
..

platform/vpn-egress — shared VPN egress gateway (Phase 3, ADR-0013)

Single shared Gluetun gateway: PIA over OpenVPN-UDP (ADR-0013 amendment — Gluetun does not support PIA over WireGuard), built-in kill-switch, native port forwarding. vpn-class services (sonarr, radarr, jackett, qbittorrent) egress only through this tunnel; internet-class services go out the normal WAN. Serves R-NET-05/06/07.

STATUS: enabled in platform/kustomization.yaml (Phase 3). pia-credentials SOPS secret is provisioned. Remaining work: validate the tunnel comes up + correct egress IP, then the egress-steering mechanism below (the hard part) and the kill-switch conformance test.

Pieces

  • gluetun-gateway.yaml — the Gluetun Deployment (replicas: 1, Recreate) + the gluetun-control Service (control API :8000 for forwarded-port lookup + tunnel-state health). Kill-switch = FIREWALL=on.
  • pia-credentials (SOPS) — PIA username/password only; Gluetun ships PIA's OpenVPN configs.

Operator hand-off (when deploying Phase 3)

  1. Provision pia-credentials (see bootstrap/sops/secret-templates/pia-credentials.yaml.example, namespace vpn-egress, keys username/password) → sops -e -i → uncomment it in kustomization.yaml.
  2. cluster-vars (already defined in clusters/base/cluster-vars.yaml; a ConfigMap — values are plain strings under data:, injected into the ${...} tokens by Flux postBuild.substituteFrom). Confirm or override:
    • PIA_REGION — a port-forwarding-capable PIA region (default Sweden; e.g. Netherlands, CA Toronto, Switzerland; NOT a US region — those lack PF). Gluetun SERVER_REGIONS.
    • CLUSTER_POD_CIDR — the Cilium cluster-pool pod CIDR (default 10.0.0.0/8; pods get 10.x, confirmed live — NOT the k3s 10.42.0.0/16). Gluetun FIREWALL_OUTBOUND_SUBNETS. Per-env overrides go in clusters/<env>/kustomization.yaml as a cluster-vars JSON patch (- op: replace, path: /data/PIA_REGION, value: "…"). With the self-managing root, a git push auto-applies (no manual kubectl apply -k).
  3. Enable - vpn-egress in platform/kustomization.yaml (Phase 3).

Egress steering — the DESIGN DECISION (needs live validation)

The hard part: routing vpn-class pods through this ONE shared tunnel. Cilium Egress Gateway (CiliumEgressGatewayPolicy) SNATs matched pods out of a gateway node's interface — it does not natively route through a pod's tunnel. Options considered:

  1. Cilium Egress Gateway → the Gluetun node's tunnel interface (recommended). Pin Gluetun to a node; a CiliumEgressGatewayPolicy selects pods labeled homelab.keithjackson.dev/egress-class=vpn and egresses them via that node's tunnel interface (tun0, OpenVPN). If the tunnel drops, tun0 is gone → traffic has no egress path → fail-closed (the kill-switch behavior R-NET-06 needs). This is the most Cilium-native fit but requires Gluetun's tunnel interface to be visible to the node datapath (hostNetwork Gluetun, or a shared netns) — the detail to VALIDATE.
  2. Gateway-pod routing: client pods default-route to the Gluetun pod IP (init-container + NET_ADMIN, or a Multus secondary interface); Gluetun forwards+NATs via the tunnel. Works without Cilium egress gateway but is more moving parts and pod-IP-fragile.
  3. Per-pod Gluetun sidecar — explicitly REJECTED by ADR-0013 (single shared gateway preferred).

Plan: validate option 1 against a live PIA tunnel (confirm tun0 on the Gluetun node + a CiliumEgressGatewayPolicy steering egress-class=vpn pods, then run the egress-class (R-NET-05/07) and vpn-killswitch (R-NET-06) conformance tests). Fall back to option 2 if the interface isn't addressable. The CiliumEgressGatewayPolicy is intentionally NOT committed until validated, to avoid shipping an untested datapath policy.

Port forwarding (qBittorrent) — follow-up

PIA assigns a forwarded port that can change on reconnect. A small port-sync helper (qbittorrent-natmap) reads the current port from the gluetun-control API (:8000 /v1/openvpn/portforwarded) and writes it into qBittorrent, keeping it hands-off (R-UPD). Authored alongside the qBittorrent app (Phase 6 apps / or here when qBittorrent lands).