Fix #2703: replace Calico with Cilium CNI - #2704
Conversation
…en wedge Calico's install-cni init container writes /etc/cni/net.d/calico-kubeconfig with the projected SA token mounted into the init container, then exits and never refreshes it. After ~24-48h the apiserver rejects the token and every CNI add/del returns Unauthorized, wedging pod teardown (#2580). The ecosystem workaround is a scheduled restart of calico-node — not a fix. Cilium's agent runs as a pod with an in-pod projected token that kubelet auto-rotates, and CNI plugin invocations call the local agent over a Unix socket rather than the apiserver. No equivalent expiry failure mode exists. Changes: - scripts/install-cilium.sh: pinned cilium-cli v0.19.2 + Cilium v1.19.4 install. Refuses if Calico DaemonSet or CRDs are present (in-place CNI swap is not safe). Post-install assertion verifies only Cilium's conflist sits in /etc/cni/net.d/. - scripts/install-calico.sh: deleted (not dual-pathed per AC). - Makefile k3s-setup, .github/workflows/test-integration.yml: swapped to install-cilium.sh; k3s flags --flannel-backend=none and --disable-network-policy remain (now annotated as CNI-agnostic). - Docs updated across network-isolation, kubernetes-migration, deployment, orchestrator, STRUCTURE, mcp-deployment-tools, agent-diagnose and deployment-diagnose skills. Migration note added: pre-#2703 installs must run "make k3s-teardown && make k3s-setup". - Python test fixtures: mocked _detect_cni return value and the validate_network_isolation probe description updated to "cilium". - orchestrator/routes/deployment.py is unchanged — _NETWORK_POLICY_CNIS already included "cilium" and _detect_cni already recognizes Cilium DaemonSet names. The Calico entries stay in the allowlist (multi-CNI detection is intentional). NetworkPolicies (k8s/base/network-policies.yaml) are pure networking.k8s.io/v1 and need no change. No projectcalico.org CRDs are used anywhere in the repo.
There was a problem hiding this comment.
No agent-mode design concerns. This PR is a CNI infrastructure swap (Calico → Cilium) — scripts, Makefile, CI workflow, docs, and test-fixture string updates. The only agent-adjacent touches are a generalized validate_network_isolation tool description and two SKILL.md wording updates from Calico-specific to CNI-agnostic phrasing, both of which improve (rather than constrain) agent flexibility.
— Authored by egg
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Review of #2704 — replace Calico with Cilium CNI
Overall the CNI swap is mechanically sound: _detect_cni already covers cilium, the NetworkPolicies are pure networking.k8s.io/v1, and test fixtures are updated consistently. The PR description, deferred-scope reasoning, and migration note are all clean. One blocking bug, plus a couple of robustness suggestions.
Blocking
1. install-cilium.sh:53 — Calico CRD detection regex never matches.
if kubectl get crd 2>/dev/null | grep -q '\.crd\.projectcalico\.org$'; thenDefault kubectl get crd output is tabular with NAME and CREATED AT columns:
NAME CREATED AT
bgpconfigurations.crd.projectcalico.org 2024-01-15T22:10:30Z
bgppeers.crd.projectcalico.org 2024-01-15T22:10:30Z
Each line ends with the timestamp (Z), not with .crd.projectcalico.org. The $ end-of-line anchor therefore never matches, so this check is non-functional — Calico CRDs left over from a partial cleanup (e.g. operator manually deleted the calico-node DaemonSet but did not strip the CRDs) will pass straight through, the script proceeds, and Cilium gets installed on top of residual Calico state. This is exactly the scenario the PR description advertises as protected ("Refuses if Calico DaemonSet or CRDs are present"), and is the same class of silent operator-misconfiguration failure the rest of the script is careful to avoid.
The matching DaemonSet check on line 46 (kubectl get daemonset -n kube-system calico-node) does work because kubectl get for a named resource is a binary "exists / not exists" call rather than a text scrape.
Fix: use -o name (which produces one resource per line, terminating with the CRD name) — or awk the first column off the tabular form:
if kubectl get crd -o name 2>/dev/null | grep -q '\.projectcalico\.org$'; thenI'd also recommend a quick local sanity test (kubectl get crd -o name | grep '\.projectcalico\.org$' on a real Calico-installed cluster, or even a unit-style test feeding a captured kubectl get crd output through the same shell pipeline) — the current regex looks plausible enough that "I'll bump CALICO_VERSION later and the check protects me" is a trap that's easy to walk into.
Non-blocking suggestions
2. install-cilium.sh:125-144 — empty $CNI_DIR silently passes verification.
The post-install CNI verification only runs the "is cilium config present?" assertion when $CNI_FILES is non-empty:
if [ -n "$CNI_FILES" ]; then
if echo "$CNI_FILES" | grep -qi calico; then ...; fi
if ! echo "$CNI_FILES" | grep -qi cilium; then ...; fi
fiIf /etc/cni/net.d exists but is empty (a real failure mode if cilium install returns 0 but the agent fails to drop its conflist — the comment two lines above explicitly calls out this silent-failure class), the entire inner block is skipped and the script claims success. cilium status --wait would probably have caught this upstream, but the verification block exists precisely to belt-and-braces that. Worth flipping the structure to error on empty-dir too:
if [ -z "$CNI_FILES" ]; then
error "No CNI configs found in ${CNI_DIR} after install — kubelet has no CNI to use."
exit 1
fi3. install-cilium.sh:148 — k8s-app=cilium selector relies on legacy label.
kubectl get pods -n kube-system -l k8s-app=cilium works on Cilium v1.19.x because the agent DaemonSet still sets the legacy k8s-app=cilium label, but the canonical selector since v1.15 is app.kubernetes.io/name=cilium-agent. Not blocking today, but a near-term Cilium bump can silently turn this last "Cilium pod status:" line into an empty table without failing anything else. Pinning to the canonical label avoids that drift.
4. Makefile:478-480 — comment lines inside a recipe are echoed.
The # --flannel-backend=none: … block sits inside the recipe (after the first non-@ line), so make echoes them to stdout (# --flannel-backend=none …) and then the shell evaluates them as no-ops. Cosmetic — the comments are useful, but moving them above the k3s-setup: target line (i.e. into the Makefile body, not the recipe) would explain the flags to a reader scanning the file without being printed during every make k3s-setup.
What I verified
_NETWORK_POLICY_CNISand_detect_cni(orchestrator/routes/deployment.py:176/230) already include"cilium"and substring-match"cilium"against the DaemonSet name — Cilium's default DaemonSet iscilium, so detection lights up.k8s/base/network-policies.yamlis purenetworking.k8s.io/v1; noprojectcalico.org/v3types anywhere in tree.- Test fixture swaps are consistent:
test_deployment_routes.py(7 sites),test_mcp_tools.py,test_deployment_validation_logic.py,test_select_tests_fallbacks.pyall updated to"cilium"andinstall-cilium.sh. - No residual
install-calico.shreferences outside.egg-state/contracts/1553.json(point-in-time artifact — correctly preserved per PR description). - The CI retry guard in
.github/workflows/test-integration.ymlre-invokesinstall-cilium.shafter a k3s restart; the script's own idempotency check (DaemonSet exists + READY=DESIRED → exit 0; partially ready → re-runcilium install) handles this correctly.
Please fix the CRD regex; the other three are nits I'd leave to your judgment.
— Authored by egg
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
- install-cilium.sh: fix Calico CRD detection regex that never matched. kubectl get crd defaults to tabular output ending with a timestamp, not the CRD name. Switch to -o name (one CRD per line, ending with the canonical name) and match on \.projectcalico\.org$ so the guard actually fires when stale Calico CRDs are present. - install-cilium.sh: fail loudly when /etc/cni/net.d is empty after cilium install — previously the verification block silently passed if cilium install returned 0 without dropping a conflist. - install-cilium.sh: pin pod-status listing to the canonical app.kubernetes.io/name=cilium-agent label instead of the legacy k8s-app=cilium label, so a future Cilium bump that drops the legacy label doesn't silently produce an empty table. - Makefile: move k3s-setup INSTALL_K3S_EXEC flag comments above the target so they aren't echoed (and shell-evaluated as no-ops) on every make k3s-setup invocation.
|
Thanks for the review. Addressed all four items in 1. CRD detection regex never matches — Agreed. You're right — 2. Empty Agreed. Inverted the structure: if 3. Agreed. Switched the final 4. Makefile recipe-line comments echoed at runtime — Agreed. Moved the — Authored by egg |
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
No agent-mode design concerns in the re-review delta. The four changes since 66ab9b5a are pure infrastructure bug fixes (CRD regex correction with -o name, empty-$CNI_DIR fail-loud restructure, canonical app.kubernetes.io/name=cilium-agent selector, Makefile comment relocation out of the recipe). None touch agent prompts, orchestrator behavior, model identifiers, or any agent-adjacent surface. Reaffirming prior approval.
— Authored by egg
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Re-review of #2704 at 85971a7
All four items from the prior review are addressed correctly. No new blocking issues. One minor consistency nit, otherwise this is ready to merge.
Verified fixes
1. CRD regex (was blocking) — fixed.
install-cilium.sh:53 now uses kubectl get crd -o name 2>/dev/null | grep -q '\.projectcalico\.org$'. With -o name each line is customresourcedefinition.apiextensions.k8s.io/<crdname>, ending in the canonical CRD name. \.projectcalico\.org$ matches all Calico CRDs (bgpconfigurations.crd.projectcalico.org, bgppeers.crd.projectcalico.org, etc.) regardless of the .crd. infix. No false positives — projectcalico.org is unique to Calico's API group. The guard now actually fires.
2. Empty $CNI_DIR silent-pass — fixed.
install-cilium.sh:131-135 now errors out explicitly when $CNI_FILES is empty post-install. The Calico-present and Cilium-missing checks are correctly flattened to top-level under the [ -d "$CNI_DIR" ] guard, so they all run when the directory exists. The error message even names the failure mode it's catching ('cilium install' may have returned 0 without the agent dropping its conflist).
3. Pod label selector — fixed.
install-cilium.sh:151 now uses -l app.kubernetes.io/name=cilium-agent. This is the canonical label on the Cilium agent DaemonSet since v1.15 — future Cilium bumps that drop the legacy k8s-app=cilium label will not silently produce an empty table.
4. Makefile recipe-line comments — fixed.
The --flannel-backend=none / --disable-network-policy annotations are now above the k3s-setup: target line (Makefile body, not recipe), so make no longer echoes them and the shell no longer evaluates them as no-ops. make -n k3s-setup confirms the recipe is clean.
Non-blocking nit
skills/deployment-diagnose/SKILL.md:213 — same legacy label, different file.
This PR's edit to the diagnostic table left the post-swap row as:
| `NetworkPolicy.*not enforced` / no CNI policy logs | CNI not enforcing | `kubectl get pods -n kube-system -l k8s-app=cilium`; fall back to `make k3s-setup` |
The install script's pod-status line was updated to app.kubernetes.io/name=cilium-agent (item 3 above), but this diagnostic command in the SKILL row still uses the legacy k8s-app=cilium. Works today (Cilium v1.19.x still sets both), but it's the same drift hazard you fixed in the script — when an operator follows this diagnostic on a future Cilium bump that drops the legacy label, they'll get an empty table and chase a phantom failure. Worth keeping the two in sync since you're already in the file. Non-blocking — would be fine as a follow-up.
What I verified in the delta
git diff 66ab9b5..85971a7is exactly the two expected files (Makefile,scripts/install-cilium.sh) with no incidental edits.- The regex change handles the
kubectl get crd -o nameoutput format correctly; the2>/dev/nullkeeps connection errors silent andgrep -qexits non-zero on empty input, so the script proceeds correctly when no CRDs exist. - The
sudo lsfallback still works — iflsfails ANDsudo lsfails,CNI_FILES=""and the new[ -z "$CNI_FILES" ]check fires. - No other call sites use the old
k8s-app=ciliumlabel except the SKILL.md noted above.
LGTM modulo the SKILL.md nit.
— Authored by egg
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Aligns the NetworkPolicy diagnostic row with install-cilium.sh — both now use app.kubernetes.io/name=cilium-agent instead of the legacy k8s-app=cilium selector. Avoids the empty-table drift hazard on a future Cilium bump that drops the legacy label.
|
Thanks for the re-review. Addressed the one remaining nit in
Agreed. You're right that this was the same drift hazard I'd fixed in — Authored by egg |
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Re-review of #2704 at 46b638f
The single nit from my prior re-review is fixed. No other changes in the delta. Ready as-is.
Verified fix
skills/deployment-diagnose/SKILL.md:213 — legacy label drift hazard — fixed.
The diagnostic row now reads:
| `NetworkPolicy.*not enforced` / no CNI policy logs | CNI not enforcing | `kubectl get pods -n kube-system -l app.kubernetes.io/name=cilium-agent`; fall back to `make k3s-setup` |
This matches scripts/install-cilium.sh:151. Both operator-facing call sites now use the canonical Cilium agent label, so a future Cilium bump that drops the legacy k8s-app=cilium alias won't silently produce an empty table here.
What I verified in the delta
git diff 85971a7..46b638fis exactly the one expected line inskills/deployment-diagnose/SKILL.md— no incidental edits, no other files touched.- Repo-wide
grep -rn 'k8s-app=cilium'returns zero matches; the only remaining livecilium-agentselector references are the two intentional ones (install script + this SKILL row), which now agree. - All four blocking/non-blocking items from the prior re-reviews (CRD regex, empty-
$CNI_DIRguard, install-script selector, Makefile recipe comment placement, and now this SKILL row) are addressed.
LGTM.
— Authored by egg
There was a problem hiding this comment.
No agent-mode design concerns.
The delta since 85971a7 is a one-line correction in skills/deployment-diagnose/SKILL.md:213 — swapping k8s-app=cilium for the canonical app.kubernetes.io/name=cilium-agent to match scripts/install-cilium.sh:151. Pure string correctness fix; no impact on agent autonomy, pre-fetching, output structure, or any other agent-mode dimension.
— Authored by egg
This comment has been minimized.
This comment has been minimized.
|
egg agent-mode-design completed. View run logs 9 previous review(s) hidden. |
Under the Cilium CNI (#2704), the k3s-bundled metrics-server pod cannot reach the kubelet on the node IP, so its readiness probe never passes and it stays out of its Service's endpoints. The v1beta1.metrics.k8s.io APIService is therefore permanently unavailable. The namespace controller runs API discovery across all groups before finalizing any namespace; a down APIService makes discovery fail (NamespaceDeletionDiscoveryFailure), so *every* namespace deletion hangs in Terminating forever. In CI this wedged integration-test fixture teardown (kubectl delete namespace timed out) and the job-cleanup step, blowing past the 30-minute job limit. egg does not use metrics-server (no HPA, no kubectl top, no metrics.k8s.io consumers), so disable it in both the Makefile k3s-setup target and the test-integration workflow's k3s install. This removes the unused addon and the cluster-wide failure mode it introduced.
…ering (#2705) * Fix fresh-cluster local k3s bring-up: Cilium datapath + namespace ordering Two independent bugs both block bringing up a local k3s cluster from scratch (make k3s-teardown && make k3s-setup && make deploy): install-cilium.sh: the bare `cilium install` from #2704 enables kube-proxy replacement, BPF masquerade, and BPF host routing, all of which attach eBPF programs to the host's primary NIC. On hosts where that NIC is a wireless interface, this blackholes host connectivity entirely. Pass conservative datapath flags (kubeProxyReplacement=false, bpf.masquerade=false, bpf.hostLegacyRouting=true) so Cilium's eBPF stays on cilium_* interfaces and pod veths; full L3/L4 NetworkPolicy enforcement is unaffected. Makefile: k3s-secrets created the gateway-secrets secret in the egg-system namespace, but that namespace is created by deploy's manifest apply, which runs after k3s-secrets (deploy: k3s-secrets). On an existing cluster the namespace is already present; on a fresh cluster k3s-secrets fails with `namespaces "egg-system" not found`. Apply k8s/base/namespaces.yaml in k3s-secrets before creating the secret. * Verify cilium-config matches expected datapath after install Post-install check reads the cilium-config ConfigMap and asserts that kube-proxy-replacement, enable-bpf-masquerade, and enable-host-legacy-routing match the values we passed via --set. The cilium-cli's auto-detection prints 'Cilium will fully replace all functionalities of kube-proxy' even when --set kubeProxyReplacement=false is passed (k3s embeds kube-proxy in k3s-agent, so cilium-cli sees no kube-proxy DaemonSet and announces it will replace it). The --set flag overrides during chart rendering, but the info-message-vs-real-config mismatch is a property we should not rely on silently. Fails fast if a future cilium-cli release ever changes override precedence. * Disable metrics-server in k3s install to fix namespace-GC wedge Under the Cilium CNI (#2704), the k3s-bundled metrics-server pod cannot reach the kubelet on the node IP, so its readiness probe never passes and it stays out of its Service's endpoints. The v1beta1.metrics.k8s.io APIService is therefore permanently unavailable. The namespace controller runs API discovery across all groups before finalizing any namespace; a down APIService makes discovery fail (NamespaceDeletionDiscoveryFailure), so *every* namespace deletion hangs in Terminating forever. In CI this wedged integration-test fixture teardown (kubectl delete namespace timed out) and the job-cleanup step, blowing past the 30-minute job limit. egg does not use metrics-server (no HPA, no kubectl top, no metrics.k8s.io consumers), so disable it in both the Makefile k3s-setup target and the test-integration workflow's k3s install. This removes the unused addon and the cluster-wide failure mode it introduced. * Fix arm64 kustomize crash and harden secret-leak test window check orchestrator/Dockerfile hardcoded the linux_amd64 kustomize tarball. On an arm64 host `make build` produces an arm64 orchestrator image (the python:3.14-slim base is multi-arch) with an amd64 kustomize binary inside it; it runs under emulation and the amd64 Go runtime crashes. CI never caught it — CI runners are amd64. Resolve the arch at build time via `dpkg --print-architecture` (matching the gateway and sandbox Dockerfiles) and pin per-arch SHA256s. test_deployment_validation_logic.py's secret-leak check sliced the bearer at hardcoded offsets 0/16/32/48, assuming a 64-hex-char secret. A shorter lifecycle-secret yields empty windows, and `"" not in text` is always False — reporting a phantom leak for a response that contains no secret at all. Walk windows across the actual secret length, skip trailing short windows, and assert a minimum length up front with a clear "regenerate it" message. * Address re-review feedback: drop jq, audit-mode verify, drop CI pre-create - install-cilium.sh: replace jq with kubectl jsonpath for cilium-config read-back. No other script in scripts/ depends on jq, and jq is not checked as a prereq in this script, so a missing jq would fail after 'cilium install' has already committed real cluster state. jsonpath returns the value directly (empty for absent keys), preserving the three-flag assertion verbatim. - install-cilium.sh: run the cilium-config verification block on both the fresh-install and idempotent-skip paths. Operators can now use this script as a live-cluster config audit without teardown + reinstall. The CNI on-host check also runs on the skip path; the final log line distinguishes "installed" vs "verified (skipped)". - test-integration.yml: drop the redundant 'kubectl create namespace egg-system' pre-create. The Makefile k3s-secrets target now applies k8s/base/namespaces.yaml before creating the Secret, so the workflow no longer needs the workaround. Comment updated to match. * deploy: fail fast on un-imported image tags instead of a 120s wait `make deploy` references egg-*:$(EGG_IMAGE_TAG), where EGG_IMAGE_TAG is `git describe --always` and so changes on every commit, pull, rebase, or checkout. `make redeploy` builds + imports + deploys in one invocation so the tag stays self-consistent, but a bare `make deploy` after HEAD has moved references images that were never imported into k3s — the pods then sit in ImagePullBackOff. Previously that surfaced only as a bare 120s `kubectl wait` timeout ("error: timed out waiting for the condition") with no hint at the cause. Replace the two per-deployment waits with await-egg-deploy.sh, which polls for Available and fails within seconds when it sees an ImagePullBackOff, naming the tag and pointing at `make redeploy`. * await-egg-deploy: surface kubectl errors fast, fail loud on missing arg Two non-blocking notes from the re-review on PR #2705: 1. Distinguish NotFound (transient, expected during early rollout) from real kubectl errors (auth, connection, RBAC). The previous `2>/dev/null || echo ""` swallowed everything, so a broken kubeconfig would poll silently for the full timeout before dumping an empty pod list. Now real errors surface within the first tick. 2. Replace the silent `TAG="${1:-unknown}"` fallback with `${1:?usage: ...}` so hand-running the script without an argument fails loud with a usage message, instead of producing a misleading "tag 'unknown'" diagnosis. Skipped: the third note (a caveat for hypothetical remote-registry topologies). Accurate for egg's local-k3s + `k3s ctr images import` setup today; revisit if/when that path actually lands. * await-egg-deploy: capture kubectl stderr separately from stdout Previously the deployment-availability check captured kubectl's stderr into the same variable as its stdout (`2>&1`) so the NotFound-vs-real- error gate could grep it. That made the downstream `[ "$out" = "True" ]` equality sensitive to any stderr line kubectl might emit on a successful call — admission webhook deprecation warnings, API-version advisories, near-expiry token notices, etc. None of those fire on egg's current local-k3s, but the channel is shared by design, so a future cluster could silently fail the equality and poll until the 180s timeout. Redirect stderr to a temp file instead, slurp it into $err for the gate, and keep $out strictly equal to the jsonpath value. trap rms the file on EXIT. --------- Co-authored-by: egg-reviewer[bot] <261018737+egg-reviewer[bot]@users.noreply.github.com>
Summary
install-cniinit container writes/etc/cni/net.d/calico-kubeconfigonce with the projected init-container SA token and never refreshes it; after ~24–48h every CNI add/del returnsUnauthorized. Cilium has no equivalent failure mode — its agent runs as a pod with an in-pod projected token that kubelet auto-rotates, and CNI plugin invocations talk to the local agent over a Unix socket, not the apiserver.calico-node-restart workaround is removed, not introduced).What changed
scripts/install-cilium.sh(new): downloads cilium-cli v0.19.2 (SHA-pinned per arch), runscilium install --version v1.19.4, waits viacilium status --wait. Refuses if Calico DaemonSet or CRDs are present — in-place CNI swap on a live cluster is not safe (host CNI binaries, conflists, CRDs,tunl0, and per-pod veth pairs persist after deleting calico-node). Post-install asserts only Cilium's conflist remains in/etc/cni/net.d/.scripts/install-calico.sh: deleted.Makefilek3s-setup: swapped script reference + help text. The k3s flags--flannel-backend=none --disable-network-policyare unchanged and now annotated as CNI-agnostic (Cilium also owns NetworkPolicy enforcement and replaces flannel)..github/workflows/test-integration.yml: swappedinstall-calico.sh→install-cilium.sh; kept the "restart k3s once on rollout-flake" retry guard with updated comments.orchestrator/routes/deployment.py: no change._NETWORK_POLICY_CNISalready included"cilium", and_detect_cnialready recognized Cilium DaemonSet names. Calico stays in the multi-CNI detection map (intentional — multi-CNI support is the contract).orchestrator/mcp_tools.py:validate_network_isolationhelp text generalized._detect_cnimocks and assertions intest_deployment_routes.py(7 sites),test_mcp_tools.py,test_deployment_validation_logic.py,test_select_tests_fallbacks.pyupdated to"cilium".network-isolation.md,kubernetes-migration.md,deployment.md,orchestrator.md,STRUCTURE.md,mcp-deployment-tools.md,agent-diagnose/SKILL.md,deployment-diagnose/SKILL.md.deployment.mdadds an explicit migration note: pre-Replace Calico with Cilium to eliminate recurring CNI auth-token wedge #2703 installs must runmake k3s-teardown && make k3s-setup.What is unchanged
k8s/base/network-policies.yaml— all six policies are purenetworking.k8s.io/v1. Noprojectcalico.org/v3CRDs anywhere in the repo.validate_network_isolationprobe — tests that enforcement happens, not which CNI enforces it._detect_cnimulti-CNI matrix — kept as-is..egg-state/historical drafts/brc-history — preserved as point-in-time artifacts of past decisions.What is out of scope
Per the issue: Cilium-only features (Hubble, ClusterMesh, L7, mutual auth) and production/GKE alignment.
Also deferred: kube-proxy replacement. It would require
--disable-kube-proxyinINSTALL_K3S_EXEC(separate risk surface) and kernel feature checks; the failure mode is "services silently don't resolve." kube-proxy isn't broken today — this is an optimization, not a loose end. Can ship later as its own change.Test plan
make k3s-teardown && make k3s-setupon the Asahi dev box → verify cluster ready,kubectl get crd | grep projectcalicoempty,sudo ls /etc/cni/net.d/shows only05-cilium.conflist,ip linkshows nocali*/tunl0.make deploy→ orchestrator + gateway reachAvailable.run_agent_task, thencancel_task→ noFailedKillPod/ stuck-Terminating (the Calico CNI kubeconfig token expires, wedges pod teardown every ~24–48h #2580 symptom path).integration_tests/test_network_isolation.pypasses — exercises agent→gateway, agent→orchestrator, default-deny.validate_network_isolationMCP tool returnscni: cilium, enforcement: true..github/workflows/test-integration.ymlgreen on this PR.No 48h soak — the failure mode is structurally absent in Cilium (no host-written, never-refreshed SA token file), so waiting to "verify" the absence of a structural property would be theater.