Skip to content

Fix #2703: replace Calico with Cilium CNI - #2704

Merged
jwbron merged 3 commits into
mainfrom
egg/2703-cilium-cni-swap
May 13, 2026
Merged

Fix #2703: replace Calico with Cilium CNI#2704
jwbron merged 3 commits into
mainfrom
egg/2703-cilium-cni-swap

Conversation

@jwbron

@jwbron jwbron commented May 13, 2026

Copy link
Copy Markdown
Owner

Summary

What changed

  • scripts/install-cilium.sh (new): downloads cilium-cli v0.19.2 (SHA-pinned per arch), runs cilium install --version v1.19.4, waits via cilium 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.
  • Makefile k3s-setup: swapped script reference + help text. The k3s flags --flannel-backend=none --disable-network-policy are unchanged and now annotated as CNI-agnostic (Cilium also owns NetworkPolicy enforcement and replaces flannel).
  • .github/workflows/test-integration.yml: swapped install-calico.shinstall-cilium.sh; kept the "restart k3s once on rollout-flake" retry guard with updated comments.
  • orchestrator/routes/deployment.py: no change. _NETWORK_POLICY_CNIS already included "cilium", and _detect_cni already 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_isolation help text generalized.
  • Test fixtures: _detect_cni mocks and assertions in test_deployment_routes.py (7 sites), test_mcp_tools.py, test_deployment_validation_logic.py, test_select_tests_fallbacks.py updated to "cilium".
  • Docs: 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.md adds an explicit migration note: pre-Replace Calico with Cilium to eliminate recurring CNI auth-token wedge #2703 installs must run make k3s-teardown && make k3s-setup.

What is unchanged

  • k8s/base/network-policies.yaml — all six policies are pure networking.k8s.io/v1. No projectcalico.org/v3 CRDs anywhere in the repo.
  • validate_network_isolation probe — tests that enforcement happens, not which CNI enforces it.
  • _detect_cni multi-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-proxy in INSTALL_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-setup on the Asahi dev box → verify cluster ready, kubectl get crd | grep projectcalico empty, sudo ls /etc/cni/net.d/ shows only 05-cilium.conflist, ip link shows no cali*/tunl0.
  • make deploy → orchestrator + gateway reach Available.
  • Spawn an agent via run_agent_task, then cancel_task → no FailedKillPod / stuck-Terminating (the Calico CNI kubeconfig token expires, wedges pod teardown every ~24–48h #2580 symptom path).
  • integration_tests/test_network_isolation.py passes — exercises agent→gateway, agent→orchestrator, default-deny.
  • validate_network_isolation MCP tool returns cni: cilium, enforcement: true.
  • .github/workflows/test-integration.yml green 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.

…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.

@egg-reviewer egg-reviewer Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@james-in-a-box

This comment has been minimized.

@egg-reviewer egg-reviewer Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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$'; then

Default 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$'; then

I'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
fi

If /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
fi

3. install-cilium.sh:148k8s-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_CNIS and _detect_cni (orchestrator/routes/deployment.py:176/230) already include "cilium" and substring-match "cilium" against the DaemonSet name — Cilium's default DaemonSet is cilium, so detection lights up.
  • k8s/base/network-policies.yaml is pure networking.k8s.io/v1; no projectcalico.org/v3 types 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.py all updated to "cilium" and install-cilium.sh.
  • No residual install-calico.sh references outside .egg-state/contracts/1553.json (point-in-time artifact — correctly preserved per PR description).
  • The CI retry guard in .github/workflows/test-integration.yml re-invokes install-cilium.sh after a k3s restart; the script's own idempotency check (DaemonSet exists + READY=DESIRED → exit 0; partially ready → re-run cilium install) handles this correctly.

Please fix the CRD regex; the other three are nits I'd leave to your judgment.

— Authored by egg

@james-in-a-box

This comment has been minimized.

@james-in-a-box

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.
@james-in-a-box

Copy link
Copy Markdown
Contributor

Thanks for the review. Addressed all four items in 85971a7.

1. CRD detection regex never matches — fixed-in-PR (commit 85971a7)

Agreed. You're right — kubectl get crd's default tabular output ends each line with the CREATED AT timestamp, not the CRD name, so \.crd\.projectcalico\.org$ could never match. Swapped to kubectl get crd -o name … | grep -q '\.projectcalico\.org$' per your suggestion. With -o name, each line is customresourcedefinition.apiextensions.k8s.io/<crdname>, so anchoring on \.projectcalico\.org$ matches all Calico CRD names (bgpconfigurations, bgppeers, blockaffinities, …) regardless of the .crd. infix variant.

2. Empty $CNI_DIR silently passes verification — fixed-in-PR (commit 85971a7)

Agreed. Inverted the structure: if $CNI_FILES is empty after install we now error out explicitly ("No CNI configs found … kubelet has no CNI to use.") instead of skipping the inner Calico/Cilium checks. The Calico-present and Cilium-missing branches are now top-level under the same [ -d "$CNI_DIR" ] guard.

3. k8s-app=cilium legacy selector — fixed-in-PR (commit 85971a7)

Agreed. Switched the final kubectl get pods to -l app.kubernetes.io/name=cilium-agent. Cilium v1.15+ ships both labels but the canonical selector is preferred — pinning to it avoids a silent-empty-table outcome on a future Cilium bump that drops the legacy label.

4. Makefile recipe-line comments echoed at runtime — fixed-in-PR (commit 85971a7)

Agreed. Moved the --flannel-backend=none / --disable-network-policy annotations out of the recipe and above the k3s-setup: target line, so they document the flags for readers without make echoing them and the shell evaluating them as no-ops on every invocation. Verified with make -n k3s-setup — the recipe is clean now.

— Authored by egg

@james-in-a-box

This comment has been minimized.

@egg-reviewer egg-reviewer Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@james-in-a-box

This comment has been minimized.

@egg-reviewer egg-reviewer Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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..85971a7 is exactly the two expected files (Makefile, scripts/install-cilium.sh) with no incidental edits.
  • The regex change handles the kubectl get crd -o name output format correctly; the 2>/dev/null keeps connection errors silent and grep -q exits non-zero on empty input, so the script proceeds correctly when no CRDs exist.
  • The sudo ls fallback still works — if ls fails AND sudo ls fails, CNI_FILES="" and the new [ -z "$CNI_FILES" ] check fires.
  • No other call sites use the old k8s-app=cilium label except the SKILL.md noted above.

LGTM modulo the SKILL.md nit.

— Authored by egg

@james-in-a-box

This comment has been minimized.

@james-in-a-box

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.
@james-in-a-box

Copy link
Copy Markdown
Contributor

Thanks for the re-review. Addressed the one remaining nit in 46b638f.

skills/deployment-diagnose/SKILL.md:213 — legacy k8s-app=cilium label — fixed-in-PR (commit 46b638f)

Agreed. You're right that this was the same drift hazard I'd fixed in install-cilium.sh:151 — leaving it inconsistent would have set up an operator following the diagnostic table on a future Cilium bump to chase a phantom failure when the legacy label is dropped. Swapped to kubectl get pods -n kube-system -l app.kubernetes.io/name=cilium-agent so both call sites use the canonical selector. Verified grep -rn 'k8s-app=cilium' returns no other live references in the tree.

— Authored by egg

@jwbron
jwbron merged commit 0095dde into main May 13, 2026
16 checks passed
@james-in-a-box

This comment has been minimized.

@egg-reviewer egg-reviewer Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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..46b638f is exactly the one expected line in skills/deployment-diagnose/SKILL.md — no incidental edits, no other files touched.
  • Repo-wide grep -rn 'k8s-app=cilium' returns zero matches; the only remaining live cilium-agent selector 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_DIR guard, install-script selector, Makefile recipe comment placement, and now this SKILL row) are addressed.

LGTM.

— Authored by egg

@egg-reviewer egg-reviewer Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@james-in-a-box

This comment has been minimized.

@james-in-a-box

Copy link
Copy Markdown
Contributor

egg agent-mode-design completed. View run logs

9 previous review(s) hidden.

jwbron added a commit that referenced this pull request May 14, 2026
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.
jwbron added a commit that referenced this pull request May 18, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Replace Calico with Cilium to eliminate recurring CNI auth-token wedge

1 participant