Resolve OQ-1/2/3, refine OQ-4: single ExpressVPN gateway, single WAN, external media
Operator answers folded into the specs: - OQ-3 VPN: ExpressVPN, cluster-controlled SINGLE shared in-cluster tunnel (OpenVPN UDP / non-TCP), rules enforced outside the service. New ADR-0012 supersedes ADR-0008 (Gluetun-sidecar default dropped). Central kill-switch. - OQ-1 WAN/edge: one commercial WAN, no multi-ISP plans; dumb SOHO edge (no VLAN/VPN/services). Multi-WAN out of scope; segmentation + VPN in-cluster; edge = NAT + manual port-forward. Gateway VIP resolved to Cilium L2 announcement. T-3.3 marked won't-do. - OQ-2 storage: bulk media lives outside the cluster (backing TBD, non-blocking) via a 'bulk' external-mount indirection; Plex metadata DB stays on Longhorn. Split out a separate OQ-BKP for the off-cluster DR backup target (Phase 4). - OQ-4 refined: wildcard record covers reachability but NOT ACME DNS-01 (needs TXT-write access) and does not match *.home.* depth. Still OPEN: Cloudflare API token (Option A) vs acme-dns delegation (Option B). T-2.4/T-2.5 blocked. Updated: docs 00,01,03,04,08,09; ADR-0008/0012 + index; task board. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
1 parent
1a67ab5cfa
commit
a2cbdf656d
10 files changed
+222
-87
No files matched your search
+2
-1
@@ -49,7 +49,8 @@ serves. **Do not edit an accepted decision in place** — supersede it with a ne
|
||||
| [0005](adr-0005-vm-workloads-kubevirt.md) | VM workloads: KubeVirt + CDI | Accepted | R-PLAT-03 |
|
||||
| [0006](adr-0006-tls-cert-manager-letsencrypt.md) | TLS: cert-manager + Let's Encrypt (DNS-01) | Accepted | R-TLS-01..03 |
|
||||
| [0007](adr-0007-secrets-sops-age.md) | Secrets: SOPS + age | Accepted | R-SEC-01 |
|
||||
| [0008](adr-0008-vpn-egress-model.md) | VPN egress model: Gluetun sidecar + Cilium Egress Gateway | Accepted | R-NET-05/06 |
|
||||
| [0008](adr-0008-vpn-egress-model.md) | VPN egress model: Gluetun sidecar + Cilium Egress Gateway | ~~Superseded by 0012~~ | R-NET-05/06 |
|
||||
| [0009](adr-0009-automated-image-updates.md) | Automated updates: Flux image automation + Renovate | Accepted | R-UPD-01..03 |
|
||||
| [0010](adr-0010-conformance-testing.md) | Conformance testing: automated, reproducible, behavior-based | Accepted | R-TEST-01..06 |
|
||||
| [0011](adr-0011-test-tooling-kyverno-chainsaw.md) | Conformance test tooling: Kyverno + Chainsaw | Accepted | R-TEST-01..05 |
|
||||
| [0012](adr-0012-vpn-egress-single-expressvpn-gateway.md) | VPN egress: single shared in-cluster ExpressVPN gateway | Accepted | R-NET-05/06/07 |
|
||||
@@ -1,9 +1,13 @@
|
||||
# ADR-0008 — VPN egress model: Gluetun sidecar (default) + Cilium Egress Gateway
|
||||
- Status: Accepted
|
||||
- Status: **Superseded by [ADR-0012](adr-0012-vpn-egress-single-expressvpn-gateway.md)** (2026-08-15)
|
||||
- Date: 2026-08-15
|
||||
- Serves: R-NET-05, R-NET-06, R-NET-07
|
||||
- Supersedes: —
|
||||
|
||||
> **Superseded.** OQ-3 resolved to ExpressVPN + a **single shared in-cluster tunnel** with rules
|
||||
> enforced outside the service. The Gluetun-sidecar-per-pod default below is no longer the model; see
|
||||
> [ADR-0012](adr-0012-vpn-egress-single-expressvpn-gateway.md). Retained for history.
|
||||
|
||||
## Context
|
||||
A subset of services (sonarr, radarr, jackett, qbittorrent) must egress **only** through a commercial
|
||||
VPN, with a **kill-switch** guaranteeing no leak to the plain internet if the tunnel drops. Egress
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
# ADR-0012 — VPN egress: single shared in-cluster ExpressVPN gateway
|
||||
- Status: Accepted
|
||||
- Date: 2026-08-15
|
||||
- Serves: R-NET-05, R-NET-06, R-NET-07
|
||||
- Supersedes: [ADR-0008](adr-0008-vpn-egress-model.md)
|
||||
|
||||
## Context
|
||||
ADR-0008 offered two VPN-egress patterns (Gluetun sidecar per pod as default, central gateway as
|
||||
alternative) and left the provider open (OQ-3). The operator has now specified (OQ-3):
|
||||
- Provider is **ExpressVPN**; the **cluster must provision and control** the connection (nothing on
|
||||
the edge router — the edge is a dumb SOHO device with no VPN, OQ-1).
|
||||
- Protocol **must not be TCP**; UDP or better is required, data must be protected.
|
||||
- A **single shared VPN connection** is preferred over per-service sidecars, **so network rules are
|
||||
enforced outside the service**.
|
||||
|
||||
This changes the decision, so ADR-0008 is superseded.
|
||||
|
||||
## Decision
|
||||
Adopt a **single, shared, in-cluster VPN egress gateway** as the mandated model (ADR-0008's
|
||||
"Pattern B" becomes the only pattern; the per-service Gluetun sidecar default is dropped):
|
||||
|
||||
- **One VPN egress gateway** (HA-capable Deployment, run **active/standby**) holds a **single
|
||||
ExpressVPN tunnel** using a **non-TCP protocol**: **OpenVPN UDP** (via ExpressVPN's manual
|
||||
OpenVPN-UDP config), or **Lightway-UDP** if a containerizable client proves viable. The cluster —
|
||||
not the edge — provisions and controls the tunnel.
|
||||
- **`vpn`-class pods are steered through the gateway** (Cilium Egress Gateway / routing) and have
|
||||
**no independent egress path**. The **kill-switch (fail-closed)** and egress allow rules are
|
||||
enforced at the **gateway + namespace/policy level — outside each service**. A service is merely
|
||||
labeled `egress: vpn`; it cannot leak on its own even if misconfigured.
|
||||
- Credentials (ExpressVPN manual OpenVPN config + manual-config username/password) are stored
|
||||
**SOPS-encrypted** ([ADR-0007](adr-0007-secrets-sops-age.md)).
|
||||
- The `egress: internet | vpn` service contract is unchanged (R-NET-07).
|
||||
|
||||
## Alternatives considered
|
||||
- **ADR-0008 Pattern A (Gluetun sidecar per pod)** — rejected per OQ-3: puts VPN inside each service
|
||||
(rules enforced *in* the service), multiplies tunnels, and isn't the operator's preferred single-
|
||||
connection model.
|
||||
- **VPN on the edge router** — impossible: the SOHO edge has no VPN capability (OQ-1); the cluster
|
||||
must own the tunnel.
|
||||
- **WireGuard** — ExpressVPN does not offer WireGuard; OpenVPN UDP (or Lightway-UDP) is the non-TCP
|
||||
path.
|
||||
- **OpenVPN TCP** — rejected: operator requires non-TCP.
|
||||
|
||||
## Consequences
|
||||
- Rules enforced outside the service (operator's goal); one tunnel to manage; simpler per-service
|
||||
workloads (no sidecars).
|
||||
- **Single shared egress IP** for all `vpn` services and a **single failure domain** → mitigate with
|
||||
**active/standby** gateway and tunnel-state alerting ([10](../docs/10-observability-and-alerting.md)).
|
||||
- **ExpressVPN is not a first-class containerized provider**: expect a **custom OpenVPN-UDP** setup
|
||||
(e.g. gluetun custom-provider mode or a purpose-built OpenVPN client), and the operator must obtain
|
||||
the manual OpenVPN config + manual credentials. Tunnel + kill-switch **must be validated in
|
||||
dev/test** (fault-injection test, R-TEST-05).
|
||||
- Exact pod→gateway steering (Cilium Egress Gateway vs routing/SNAT) is a WS-NET implementation
|
||||
detail; the **kill-switch acceptance test is the gate** ([14](../docs/14-testing-and-conformance.md)).
|
||||
- If ExpressVPN containerization proves impractical, revisit provider choice via a superseding ADR
|
||||
(escalation to the operator — agents may not change this decision themselves).
|
||||
Reference in new issue
Block a user