Files
home-network-k3s-impl/platform/vpn-egress/gluetun-gateway.yaml
T
jakman85andClaude Opus 4.8 b859e52697 tune(vpn): shrink OpenVPN control-channel MTU + widen handshake window
RSA-4096 strong-preset handshake is marginal over the pod's 1450 MTU (Cilium VXLAN).
Lower tun-mtu to 1300 and add --tls-mtu 1200 (smaller control packets) + --hand-window
120 (more time to complete the handshake over a lossy path). Empirical test of the
MTU-tuning path (operator's choice over Cilium native routing).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-22 23:46:36 -05:00

139 lines
6.6 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, 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: PIA_ENCRYPTION_PRESET
value: "strong" # operator's working .ovpn (sweden.privacy.network:1197) uses PIA's
# STRONG preset: RSA-4096 + AES-256-CBC + SHA256. gluetun defaults
# to "normal" (AES-128-GCM) → cipher mismatch, so the 1197 endpoint
# never completes the handshake. "strong" also selects port 1197.
# 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: "1300" # tun-mtu: extra margin under the 1450 pod MTU (data channel)
- name: OPENVPN_MSSFIX
value: "1300" # clamp TCP-in-tunnel to match
- name: OPENVPN_FLAGS
value: "--tls-mtu 1200 --hand-window 120" # shrink the CONTROL-channel packets (RSA-4096 strong
# preset handshake) so they fit reliably under 1450, and give the
# handshake longer than the default 60s to complete over a lossy path.
# 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