fix: move browser-vpn-proxy out of the canary's sync-wave gate
Some checks failed
validate / lint (push) Failing after 1s

sync-wave 3 (after the canary's wave 2) was a documentation convention,
not a real dependency, and it broke syncing entirely: this cluster's
argocd-cm ignores /status updates for reconciliation cluster-wide
(resource.customizations.ignoreResourceUpdates.all), so Argo's wave
orchestrator can never observe a freshly recreated Pod's Pending ->
Running transition happening during an active sync - exactly what the
canary does every test round, since it's deleted and recreated each
time. The sync hung forever on "waiting for healthy state of
/Pod/vlan50-canary" even once the pod was actually Running live.

Move browser-vpn-proxy to wave 1, alongside the NAD - the only thing
it actually needs to exist first. Document the root cause and the
kubectl patch to clear a stuck operation in the README, since this
risk applies to anything synced after the canary in this Application,
not just this one file.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Nik Afiq 2026-08-24 23:40:34 +09:00
parent b333b5e6d9
commit 462a9d9c79
2 changed files with 43 additions and 11 deletions

View File

@ -1,11 +1,26 @@
# sync-wave 3 — after RBAC/DaemonSet (0), the NAD (1), and the canary # sync-wave 1 — same wave as the NAD (10-nad-vlan50.yaml), the only
# (2). Moved here from manifests/multus/reserved/ (2026-08-24) now that # thing this actually needs to exist first. NOT wave 3/after-the-canary
# qBittorrent and JDownloader have both independently passed their # as originally drafted: that was a documentation convention, not a real
# canary, validation, and soak gates — the reservation this file's # dependency, and it turned out to actively break syncing. Root-caused
# earlier header described is satisfied. Still governed only by the # live (2026-08-24): this cluster's argocd-cm sets
# `multus` Argo CD Application's own manual-sync gate (no automated # resource.customizations.ignoreResourceUpdates.all to ignore /status
# sync there at all) — the same gate already used for the DaemonSet, NAD, # changes cluster-wide (a deliberate, existing setting, not something to
# and canary, not a new mechanism. # change just for this file) — Argo's wave orchestrator waits for each
# wave's resources to report Healthy before starting the next wave, but
# a Pod that's deleted and recreated (exactly what the canary is, every
# test round) needs Argo to observe a fresh Pending -> Running /status
# transition *during that sync*, which the ignore-updates config
# prevents it from ever seeing — so the operation hangs forever on
# "waiting for healthy state of /Pod/vlan50-canary" even once the Pod is
# actually Running. Already-stable resources (the DaemonSet, RBAC, NAD)
# never hit this because their cached health from a prior sync is still
# valid when nothing about them changed. Moved here from
# manifests/multus/reserved/ (2026-08-24) now that qBittorrent and
# JDownloader have both independently passed their canary, validation,
# and soak gates — the reservation this file's earlier header described
# is satisfied. Still governed only by the `multus` Argo CD Application's
# own manual-sync gate (no automated sync there at all) — the same gate
# already used for the DaemonSet, NAD, and canary, not a new mechanism.
# #
# Before applying: # Before applying:
# 1. Verify the NodePort below (30889) doesn't collide with anything # 1. Verify the NodePort below (30889) doesn't collide with anything
@ -85,7 +100,7 @@ metadata:
name: browser-vpn-proxy name: browser-vpn-proxy
namespace: downloads namespace: downloads
annotations: annotations:
argocd.argoproj.io/sync-wave: "3" argocd.argoproj.io/sync-wave: "1"
spec: spec:
replicas: 1 replicas: 1
strategy: strategy:

View File

@ -13,9 +13,9 @@ comment. Nothing here applies to the cluster just because it's merged to
| `01-rbac.yaml` | 0 | Multus ServiceAccount/ClusterRole/ClusterRoleBinding | | `01-rbac.yaml` | 0 | Multus ServiceAccount/ClusterRole/ClusterRoleBinding |
| `02-daemonset.yaml` | 0 | Multus thick-plugin DaemonSet — **nodeSelector restricted to `nik-debian` only**, k3s-specific CNI paths | | `02-daemonset.yaml` | 0 | Multus thick-plugin DaemonSet — **nodeSelector restricted to `nik-debian` only**, k3s-specific CNI paths |
| `10-nad-vlan50.yaml` | 1 | VLAN 50 `NetworkAttachmentDefinition`, macvlan bridge on `enp1s0.50`, static IPAM | | `10-nad-vlan50.yaml` | 1 | VLAN 50 `NetworkAttachmentDefinition`, macvlan bridge on `enp1s0.50`, static IPAM |
| `30-browser-vpn-proxy.yaml` | 1 | `.12` — SOCKS5 proxy for browser traffic via PIA, replaces `manifests/media/gluetun-proxy.yaml`. No auth, no app-level IP allowlist (see its own header for why and what actually restricts access instead). Deliberately **not** waved after the canary — see its own header's "sync-wave 1" note for why that would hang the sync entirely — **do not sync without separate explicit approval** |
| `vlan50-egress-guard-script.yaml` | (no wave annotation — apply alongside 10) | Shared init-container script consumed by the canary, qBittorrent, JDownloader, and `browser-vpn-proxy` | | `vlan50-egress-guard-script.yaml` | (no wave annotation — apply alongside 10) | Shared init-container script consumed by the canary, qBittorrent, JDownloader, and `browser-vpn-proxy` |
| `20-canary.yaml` | 2 | Temporary canary pod — **do not sync without separate explicit approval**, see its own header | | `20-canary.yaml` | 2 | Temporary canary pod — **do not sync without separate explicit approval**, see its own header |
| `30-browser-vpn-proxy.yaml` | 3 | `.12` — SOCKS5 proxy for browser traffic via PIA, replaces `manifests/media/gluetun-proxy.yaml`. No auth, no app-level IP allowlist (see its own header for why and what actually restricts access instead) — **do not sync without separate explicit approval** |
## Before applying anything here ## Before applying anything here
@ -30,10 +30,27 @@ comment. Nothing here applies to the cluster just because it's merged to
3. Sync order matters even within this one manual-sync Application: 00 → 3. Sync order matters even within this one manual-sync Application: 00 →
01 → 02, confirm the Multus pod is actually Running on `nik-debian` 01 → 02, confirm the Multus pod is actually Running on `nik-debian`
and `/var/lib/rancher/k3s/agent/etc/cni/net.d` now has a generated and `/var/lib/rancher/k3s/agent/etc/cni/net.d` now has a generated
`00-multus.conf`, *then* 10, *then* — only with separate approval — 20. `00-multus.conf`, *then* 10 (and, independently, 30 — same wave, no
ordering between them), *then* — only with separate approval — 20.
`30-browser-vpn-proxy.yaml` follows the same "separate approval" gate `30-browser-vpn-proxy.yaml` follows the same "separate approval" gate
as 20 — no Secret precondition, it needs no credentials at all (see as 20 — no Secret precondition, it needs no credentials at all (see
its own header for why). its own header for why).
4. **This cluster's Argo CD ignores `/status` updates for reconciliation
purposes on every resource** (`argocd-cm`'s
`resource.customizations.ignoreResourceUpdates.all`, cluster-wide, not
specific to this Application — do not change it just for this
directory). Practical effect: a sync that wave-gates on a resource
whose health depends on a `/status` transition happening *during that
sync* — e.g. a Pod that was just deleted and recreated, going
Pending → Running — can hang forever on "waiting for healthy state of
/X", even once the resource is actually healthy live. This is why
`20-canary.yaml` no longer has anything synced after it in this
Application (see `30-browser-vpn-proxy.yaml`'s header) — don't
sync-wave anything new after the canary for the same reason. If a
sync ever hangs like this: `kubectl -n argocd patch application
multus --type=merge -p '{"operation": null}'` clears the stuck
operation (touches nothing in the actual cluster, just Argo's own
tracking) so a fresh sync can start.
## Canary lifecycle — re-running it after a Multus/CNI change ## Canary lifecycle — re-running it after a Multus/CNI change