--- # Gluetun — the single shared in-cluster VPN egress gateway (ADR-0013): PIA over OpenVPN-UDP, with # Gluetun's built-in kill-switch/firewall (fail-closed), health checks, auto-reconnect, and native # port forwarding for qBittorrent. `vpn`-class pods are steered through this gateway (see README.md # "Egress steering" — the Cilium-native mechanism, pending live validation with PIA creds). # # Credentials: the `pia-credentials` SOPS Secret (keys: username, password). Gluetun uses PIA's # username/password (via OPENVPN_USER/OPENVPN_PASSWORD) to authenticate to PIA. Gluetun ships PIA's # OpenVPN server configs, so no manual `.ovpn` is needed (ADR-0013 amendment: OpenVPN-UDP — Gluetun # does NOT support PIA over WireGuard). apiVersion: apps/v1 kind: Deployment metadata: name: gluetun namespace: vpn-egress labels: app: gluetun spec: replicas: 1 # ONE shared tunnel (ADR-0013). Active/standby is a later enhancement. strategy: type: Recreate # a tunnel is a singleton — never run two overlapping during a rollout selector: matchLabels: app: gluetun template: metadata: labels: app: gluetun spec: initContainers: # gluetun's BUNDLED PIA server list is STALE (retired 46.246.x IPs → timeouts; current PIA Sweden # is 158.173.x), and UPDATER_PERIOD only refreshes AFTER the tunnel is ready (chicken-and-egg). So # fetch PIA's CURRENT list into the shared /gluetun volume BEFORE the main container starts. - name: update-servers image: qmcgaw/gluetun:v3.41.0 args: ["update", "-enduser", "-providers", "private internet access"] resources: requests: { cpu: 25m, memory: 32Mi } limits: { memory: 128Mi } volumeMounts: - name: gluetun-data mountPath: /gluetun containers: - name: gluetun image: qmcgaw/gluetun:v3.41.0 # pinned; Renovate bumps. PIA is OpenVPN-only in Gluetun # (WireGuard for PIA is unsupported upstream — ADR-0013 # amendment; thrnz/docker-wireguard-pia is the WG fallback). securityContext: capabilities: add: ["NET_ADMIN"] # required for the tunnel + firewall env: - name: VPN_SERVICE_PROVIDER value: "private internet access" - name: VPN_TYPE value: "openvpn" # OpenVPN-UDP — satisfies "protocol != TCP" (R-NET); ADR-0013 amended - name: OPENVPN_ENDPOINT_PORT value: "8080" # NOT 1197: with live servers, PIA's replies FROM src-port 1197 get # no return here — the home OpenVPN server / port-forward on 1197 # appears to swallow them. 8080 avoids that (PIA serves it too). # Fresh server list comes from the update-servers initContainer (see above). UPDATER_PERIOD then # keeps it fresh for long-running pods (gluetun docs: >=360h to be nice to the provider API): - name: UPDATER_PERIOD value: "720h" # Pod MTU is 1450 (Cilium VXLAN: 1500 − 50 encap; node/host/WAN are 1500). Shrink OpenVPN so # DATA-channel packets fit (the handshake is small so this wasn't the connect blocker, but the # data channel needs it). Hardcoded strings (int-coercion gotcha). See DECISIONS-LOG. - name: OPENVPN_MTU value: "1350" - name: OPENVPN_MSSFIX value: "1350" # PIA credentials from the SOPS secret (Gluetun's PIA OpenVPN creds env): - name: OPENVPN_USER valueFrom: secretKeyRef: name: pia-credentials key: username - name: OPENVPN_PASSWORD valueFrom: secretKeyRef: name: pia-credentials key: password - name: SERVER_REGIONS value: "${PIA_REGION}" # cluster-vars: a PF-CAPABLE PIA region (ADR-0013 PF caveat) - name: VPN_PORT_FORWARDING value: "on" # PIA native port forwarding for qBittorrent - name: FIREWALL value: "on" # KILL-SWITCH: block all egress except the tunnel (fail-closed, R-NET-06) - name: FIREWALL_OUTBOUND_SUBNETS value: "${CLUSTER_POD_CIDR}" # allow steered client pods (pod CIDR) to route out via the tunnel - name: DOT value: "off" # DNS via cluster CoreDNS, not Gluetun's DoT ports: - name: control containerPort: 8000 # Gluetun control API (health + forwarded-port lookup) livenessProbe: httpGet: { path: /v1/openvpn/status, port: 8000 } # Gluetun control API health initialDelaySeconds: 20 periodSeconds: 30 resources: requests: cpu: 50m memory: 64Mi limits: memory: 160Mi volumeMounts: - name: tun mountPath: /dev/net/tun - name: gluetun-data mountPath: /gluetun # shared with update-servers initContainer (fresh servers.json) volumes: - name: tun hostPath: path: /dev/net/tun type: CharDevice - name: gluetun-data emptyDir: {} --- # Control API Service — the port-sync helper (qbittorrent-natmap, README) reads the PIA-assigned # forwarded port from here; also used for tunnel-state health/alerting (ADR-0013 "alert on tunnel state"). apiVersion: v1 kind: Service metadata: name: gluetun-control namespace: vpn-egress labels: app: gluetun spec: selector: app: gluetun ports: - name: control port: 8000 targetPort: 8000