homelab/manifests/multus/20-canary.yaml
Nik Afiq 26894d16ce
Some checks failed
validate / lint (push) Failing after 1s
feat: strengthen VLAN50 canary egress guard and document its lifecycle
Diagnosed the vlan50-canary FailedCreatePodSandBox->net1-timeout failure
as a stale Pod artifact of the Multus DaemonSet rollout race (sandbox
created via a transient daemon/shim state mid-rollout; the current,
fully-settled daemon's own logs show no ADD for that UID, only a DEL).
No defect found in 02-daemonset.yaml or 10-nad-vlan50.yaml; both are
unchanged.

Independent of that diagnosis, harden the shared guard script per
review: print safe interface/address/route diagnostics before every
FATAL exit; validate net1 actually carries the workload's expected
static /24 address rather than just existing; add arping-based
duplicate-address and gateway-reachability checks before installing
the net1 default route (exit-code semantics verified against arping's
own source). Requires EXPECTED_VLAN50_IP and NET_RAW (for arping's raw
ARP sockets) on every consumer - wired into 20-canary.yaml now,
qbittorrent.yaml/jdownloader.yaml need the same when they're migrated.

Document the canary's Pod lifecycle: restartPolicy: Never means a
Failed canary never reruns on its own, and Argo "Synced" only reflects
manifest match, not runtime success - recreate it (new UID) after any
Multus/CNI change before trusting its result. Recommend keeping it a
manually recreated, controller-less Pod rather than a Job/Deployment,
since unattended auto-retry risks a duplicate-address race on its
static .100 IP - the exact class of bug this diagnosis just walked
through.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 22:02:27 +09:00

90 lines
3.3 KiB
YAML

# Apply: kubectl apply -f manifests/multus/20-canary.yaml
# Delete when done: kubectl delete -f manifests/multus/20-canary.yaml
# Description: TEMPORARY canary pod for Phase 5 validation (plan.md) —
# proves eth0/net1 routing, kill-switch failure modes, and the netns
# egress guard before qBittorrent/JDownloader are touched. Not a
# long-lived resource; delete it once the Phase 5 validation matrix has
# run. sync-wave 2 — after Multus (0) and the NAD (1).
#
# *** DO NOT SYNC/APPLY until: ***
# - Phases 0-4 have actually been applied and verified live (this file
# is drafted alongside them, not proof they work).
# - The user has explicitly approved running the canary specifically —
# per this task's own gates, Argo CD sync of this file needs the same
# explicit go-ahead as any other live change, even though the
# multus.yaml Application it lives under is manual-sync-only anyway.
#
# Image: nicolaka/netshoot:v0.11 — a widely-used network-debugging image
# (iproute2 + iptables + curl/dig/tcpdump). Tag not independently
# verified against a live pull; confirm it still resolves before
# applying, same caveat as the Multus image tag in 02-daemonset.yaml.
apiVersion: v1
kind: Pod
metadata:
name: vlan50-canary
namespace: downloads
labels:
app: vlan50-canary
annotations:
argocd.argoproj.io/sync-wave: "2"
k8s.v1.cni.cncf.io/networks: |
[{"name": "vlan50", "namespace": "downloads", "interface": "net1", "ips": ["10.10.50.100/24"]}]
spec:
nodeSelector:
kubernetes.io/hostname: nik-debian
restartPolicy: Never
# Same reasoning as qbittorrent.yaml/jdownloader.yaml: no cluster
# DNS/CoreDNS resolution needed, single Technitium resolver, routed via
# net1 so Technitium sees the real VLAN 50 source.
dnsPolicy: None
dnsConfig:
nameservers:
- "10.10.40.53"
initContainers:
- name: vlan50-egress-guard
image: nicolaka/netshoot:v0.11
command: ["/bin/sh", "/scripts/guard.sh"]
env:
- name: VLAN50_GATEWAY
value: "10.10.50.1"
- name: TECHNITIUM_IP
value: "10.10.40.53"
- name: POD_CIDR
value: "10.42.0.0/16"
- name: SERVICE_CIDR
value: "10.43.0.0/16"
- name: NODE_IP
value: "10.10.40.20"
- name: EXPECTED_VLAN50_IP
value: "10.10.50.100"
securityContext:
capabilities:
drop: ["ALL"]
# NET_RAW alongside NET_ADMIN: the guard script's arping-based
# duplicate-address and gateway-reachability checks build raw
# ARP frames, which the kernel gates on CAP_NET_RAW specifically
# — see vlan50-egress-guard-script.yaml's header comment.
add: ["NET_ADMIN", "NET_RAW"]
volumeMounts:
- name: guard-script
mountPath: /scripts
containers:
- name: netshoot
image: nicolaka/netshoot:v0.11
command: ["sleep", "infinity"]
securityContext:
capabilities:
drop: ["ALL"]
resources:
requests:
cpu: 20m
memory: 32Mi
limits:
cpu: 200m
memory: 128Mi
volumes:
- name: guard-script
configMap:
name: vlan50-egress-guard-script
defaultMode: 365 # octal 0555, r-xr-xr-x — yamllint here forbids octal literals