Files
home-network-k3s-impl/platform/vpn-egress/gluetun-gateway.yaml
T
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

127 lines
5.2 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
# 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. `vpn`-class
# pods are steered through this gateway (see README.md "Egress steering").
#
# CUSTOM-PROVIDER mode (DECISIONS-LOG "Phase 3 VPN — custom .ovpn"): gluetun's PIA-provider server
# selection random-picks server IPs, many of which don't answer this cluster's flows (intermittent
# TLS timeouts — verified by packet capture: the pod CAN complete a full handshake, only against
# responsive servers). So we feed gluetun the operator's own PIA `.ovpn`, which pins a known-healthy
# Stockholm server IP (gluetun's custom parser requires an IP `remote`, not a hostname). Trade-off:
# custom mode loses gluetun's PIA-native port forwarding (Phase-6/qBittorrent concern) — revisit then.
#
# Credentials: `pia-credentials` SOPS Secret (username/password) → OPENVPN_USER/OPENVPN_PASSWORD (the
# .ovpn uses auth-user-pass). Config: `pia-custom-ovpn` SOPS Secret (key `pia.ovpn`), mounted at
# /config/pia.ovpn. Both operator-provisioned.
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:
containers:
- name: gluetun
image: qmcgaw/gluetun:v3.41.3 # pinned; Renovate bumps.
securityContext:
capabilities:
add: ["NET_ADMIN"] # required for the tunnel + firewall
env:
- name: VPN_SERVICE_PROVIDER
value: "custom" # custom .ovpn — see header + DECISIONS-LOG
- name: VPN_TYPE
value: "openvpn"
- name: OPENVPN_CUSTOM_CONFIG
value: "/config/pia.ovpn" # the operator's PIA .ovpn (pia-custom-ovpn Secret, IP remote)
# PIA username/password for the .ovpn's auth-user-pass (pia-credentials SOPS Secret):
- name: OPENVPN_USER
valueFrom:
secretKeyRef:
name: pia-credentials
key: username
- name: OPENVPN_PASSWORD
valueFrom:
secretKeyRef:
name: pia-credentials
key: password
# Pod MTU is 1450 (Cilium VXLAN: 1500 − 50 encap). Shrink OpenVPN's tun-mtu so data-channel
# packets fit (the .ovpn likely defaults to 1500). Hardcoded strings (int-coercion gotcha).
- name: OPENVPN_MTU
value: "1300"
- name: OPENVPN_MSSFIX
value: "1300"
- 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 + tunnel-state lookup)
livenessProbe:
# EXEC (localhost) not httpGet: once the tunnel redirects the default route via tun0, the pod's
# response to a kubelet HTTP probe would egress the tunnel and never return → probe times out →
# needless restarts. A local wget stays on lo, unaffected by the VPN routing.
exec:
command: ["/bin/sh", "-c", "wget -qO- http://127.0.0.1:8000/v1/vpn/status | grep -q running"]
initialDelaySeconds: 30
periodSeconds: 30
timeoutSeconds: 5
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
memory: 160Mi
volumeMounts:
- name: tun
mountPath: /dev/net/tun
- name: gluetun-data
mountPath: /gluetun # gluetun runtime state
- name: custom-ovpn
mountPath: /config # the .ovpn (read-only)
readOnly: true
volumes:
- name: tun
hostPath:
path: /dev/net/tun
type: CharDevice
- name: gluetun-data
emptyDir: {}
- name: custom-ovpn
secret:
secretName: pia-custom-ovpn
items:
# operator's secret keys the .ovpn by filename; map it to /config/pia.ovpn
- key: sweden-aes-128-cbc-udp-ip.ovpn
path: pia.ovpn
---
# Control API Service — 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