gluetun's custom parser rejects the .ovpn's 'remote sweden.privacy.network' (needs
an IP). sweden.privacy.network LB pool = 158.173.241.{22,34,55,112,239}; pin .22
(verified to handshake) via VPN_ENDPOINT_IP/PORT while the .ovpn supplies cipher/CA.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
130 lines
5.3 KiB
YAML
130 lines
5.3 KiB
YAML
---
|
||
# 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, but only against
|
||
# responsive servers). The operator's downloaded PIA `.ovpn` targets `sweden.privacy.network` — a
|
||
# load-balanced hostname that always lands on a healthy server (reliable in GNOME on the host). So we
|
||
# feed gluetun that exact `.ovpn`. Trade-off: custom mode loses gluetun's PIA-native port forwarding
|
||
# (needed only at Phase 6 for qBittorrent) — 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 (the .ovpn file), 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.0 # pinned; Renovate bumps.
|
||
securityContext:
|
||
capabilities:
|
||
add: ["NET_ADMIN"] # required for the tunnel + firewall
|
||
env:
|
||
- name: VPN_SERVICE_PROVIDER
|
||
value: "custom" # custom .ovpn (sweden.privacy.network) — 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)
|
||
# gluetun's custom parser needs an IP (not the .ovpn's `remote sweden.privacy.network`
|
||
# hostname). sweden.privacy.network's load-balanced pool is 158.173.241.{22,34,55,112,239};
|
||
# pin a currently-healthy one. NOTE: if PIA rotates it out, re-resolve + update.
|
||
- name: VPN_ENDPOINT_IP
|
||
value: "158.173.241.22"
|
||
- name: VPN_ENDPOINT_PORT
|
||
value: "1198"
|
||
# 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:
|
||
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 # 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:
|
||
# the operator's secret keyed the .ovpn by its filename; map it to /config/pia.ovpn
|
||
- key: sweden-aes-128-cbc-udp-dns.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
|