fix(cilium): stop the endpoint policy map filling — trim ICMP set, limit identity labels, size + alert #495

Merged
k.renner merged 4 commits from fix-cilium-policy-map-exhaustion into master 2026-08-16 21:14:09 +00:00
Member

Closes #451. Closes #454.

What is wrong

Both issues are one fault. A pod's dedicated cilium_policy_v3_<id> BPF map runs out of space, endpoint regeneration fails with no space left on device, the CNI ADD call never returns, and the pod hangs in sandbox creation. For paperless (replicas: 1, strategy: Recreate) that was a 41-minute outage on 2026-07-30.

It is still happening. Verified against live state today:

Check Result
cilium_bpf_map_pressure{map_name="cilium_policy_v3_*"} 0.867 on k8s-1, k8s-2 and k8s-3
max_over_time(...[30d]) 1.03 — driven past capacity
Loki, last overflow 2026-08-12 17:46Z, hitting authentik/ak-outpost-ldap-outpost and home-assistant/esphome
bpf-policy-map-max in cilium-config 16384 (chart default)

Pressure measured 0.854 at the start of this investigation and 0.867 forty minutes later. It is trending up, not stable.

Changes

  1. bpf.policyMapMax: 65536 — 4x the chart default, matching the cluster's existing bpf-lb-map-max. Takes steady-state pressure from ~87% to ~22%. Key verified against helm show values cilium --version 1.20.0; flag confirmed live as bpf-policy-map-max in the cilium-config ConfigMap.
  2. Chart range ">=1.19.0" → ">=1.20.0 <1.21.0" — the unbounded range is what let Flux take 1.19.6 → 1.20.0 unattended on its 30m interval, 45 minutes before the first outage. Patches still flow; a minor now needs a reviewed diff.
  3. PrometheusRule/cilium — warning above 80% for 15m, critical at 100%. The metric was already scraped (landed via #452) but nothing alerted on it, so the first signal was a downstream KubeDeploymentReplicasMismatch.

Verification done

  • kubectl kustomize builds clean for both infrastructure/cilium and apps/monitoring-rules.
  • kubectl apply --dry-run=server accepts the PrometheusRule.
  • Both alert expressions run against live Prometheus; the warning expression currently returns all three nodes, which is the correct present-state answer.

Read this before merging

Merging rolls the CNI. updateStrategy.rollingUpdate.maxUnavailable: 2 means two of three agents restart at once, and the map is only resized at agent start. Worth doing at a chosen moment rather than whenever Flux next reconciles.

Expect CiliumBPFMapPressureHigh to fire in the window between the rule loading and the agents restarting — the map really is 87% full until they do.

Deliberately not done

Why the busiest endpoint needs ~14k entries is unexplained. Only 178 CiliumIdentities exist and no CiliumNetworkPolicy declares more than 9 ports, so the count is not obviously a function of policy shape. Settling it needs cilium-dbg bpf policy get --all on an agent. This PR buys headroom and visibility; it does not claim to have found why demand is that high.

Closes #451. Closes #454. ## What is wrong Both issues are one fault. A pod's dedicated `cilium_policy_v3_<id>` BPF map runs out of space, endpoint regeneration fails with `no space left on device`, the CNI ADD call never returns, and the pod hangs in sandbox creation. For paperless (`replicas: 1`, `strategy: Recreate`) that was a 41-minute outage on 2026-07-30. **It is still happening.** Verified against live state today: | Check | Result | |---|---| | `cilium_bpf_map_pressure{map_name="cilium_policy_v3_*"}` | **0.867** on k8s-1, k8s-2 and k8s-3 | | `max_over_time(...[30d])` | **1.03** — driven past capacity | | Loki, last overflow | **2026-08-12 17:46Z**, hitting `authentik/ak-outpost-ldap-outpost` and `home-assistant/esphome` | | `bpf-policy-map-max` in `cilium-config` | 16384 (chart default) | Pressure measured 0.854 at the start of this investigation and 0.867 forty minutes later. It is trending up, not stable. ## Changes 1. **`bpf.policyMapMax: 65536`** — 4x the chart default, matching the cluster's existing `bpf-lb-map-max`. Takes steady-state pressure from ~87% to ~22%. Key verified against `helm show values cilium --version 1.20.0`; flag confirmed live as `bpf-policy-map-max` in the `cilium-config` ConfigMap. 2. **Chart range `">=1.19.0"` → `">=1.20.0 <1.21.0"`** — the unbounded range is what let Flux take 1.19.6 → 1.20.0 unattended on its 30m interval, 45 minutes before the first outage. Patches still flow; a minor now needs a reviewed diff. 3. **`PrometheusRule/cilium`** — warning above 80% for 15m, critical at 100%. The metric was already scraped (landed via #452) but nothing alerted on it, so the first signal was a downstream `KubeDeploymentReplicasMismatch`. ## Verification done - `kubectl kustomize` builds clean for both `infrastructure/cilium` and `apps/monitoring-rules`. - `kubectl apply --dry-run=server` accepts the PrometheusRule. - Both alert expressions run against live Prometheus; the warning expression currently returns all three nodes, which is the correct present-state answer. ## Read this before merging **Merging rolls the CNI.** `updateStrategy.rollingUpdate.maxUnavailable: 2` means two of three agents restart at once, and the map is only resized at agent start. Worth doing at a chosen moment rather than whenever Flux next reconciles. Expect `CiliumBPFMapPressureHigh` to fire in the window between the rule loading and the agents restarting — the map really is 87% full until they do. ## Deliberately not done Why the busiest endpoint needs ~14k entries is unexplained. Only 178 CiliumIdentities exist and no CiliumNetworkPolicy declares more than 9 ports, so the count is not obviously a function of policy shape. Settling it needs `cilium-dbg bpf policy get --all` on an agent. This PR buys headroom and visibility; it does not claim to have found why demand is that high.
claude-bot added 1 commit 2026-08-16 20:28:44 +00:00
fix(cilium): size the endpoint policy map and alert before it fills
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was successful
8f45e2901b
Closes #451. Closes #454.

Both issues are the same fault: a pod's dedicated cilium_policy_v3_<id>
BPF map ran out of space, endpoint regeneration failed with "no space
left on device", the CNI ADD call never returned, and the pod sat in
sandbox creation. For paperless — replicas: 1 with strategy Recreate —
that was a 41-minute outage on 2026-07-30.

It is not over. Prometheus (metrics landed via #452) puts
cilium_bpf_map_pressure{map_name="cilium_policy_v3_*"} at 0.867 on all
three nodes right now, and max_over_time(...[30d]) at 1.03 — the map has
been driven past capacity. Loki has the overflow recurring as recently
as 2026-08-12 17:46Z, hitting authentik and esphome endpoints.

Three changes:

- bpf.policyMapMax 16384 (chart default) -> 65536, matching the cluster's
  existing bpf-lb-map-max. Takes steady-state pressure from ~87% to ~22%.
  The map is sized at creation, so this only takes effect as agents
  restart.
- The chart range ">=1.19.0" is what let Flux take 1.19.6 -> 1.20.0
  unattended 45 minutes before the first outage. Narrowed to
  ">=1.20.0 <1.21.0" so a CNI minor is a reviewed diff, not a surprise.
- A PrometheusRule on cilium_bpf_map_pressure: warning over 80% for 15m,
  critical at 100%. The metric was already scraped but nothing alerted on
  it, so the first signal was a downstream replica mismatch.

Not addressed: why the busiest endpoint needs ~14k entries. Only 178
CiliumIdentities exist and no CiliumNetworkPolicy declares more than 9
ports, so the entry count is not obviously explained by policy shape.
Establishing that needs `cilium-dbg bpf policy get` on an agent.

MERGING THIS ROLLS THE CNI. The DaemonSet uses
updateStrategy.rollingUpdate.maxUnavailable: 2, so two of three agents
restart at once.

Co-Authored-By: Claude Bot <claude-bot@renner.uno>
review-bot left a comment
Member

🤖 Automated review — model CLI default (complexity: complex), commit 8f45e2901b9179ce90bf66a42b96984805787095.

Advisory review by the review-bot pipeline. It never approves; a human merges. Push new commits to get a fresh review.


Summary

This PR sizes up the Cilium per-endpoint policy BPF map (bpf.policyMapMax: 65536), pins the chart to a patch-only range, and adds a PrometheusRule that alerts on cilium_bpf_map_pressure before the map fills. The change is well-investigated (live pressure numbers, Loki evidence, a real incident) and the new alert/version-pin follow existing repo conventions closely. However, the fix only updates the Flux-managed HelmRelease; it misses a second, actively-used source of the same setting, so the bug it closes can be silently reintroduced on the bootstrap path.

🔴 Blocking

  • kubernetes/talos/patches/cilium-cni.yaml:324 still hardcodes bpf-policy-map-max: "16384" — the old, insufficient value — inside the Cilium inline-manifest Talos config patch. This file is not incidental: kubernetes/bootstrap.sh:86 requires it to exist and kubernetes/bootstrap.sh:123-128 passes it as --config-patch to talosctl apply-config for every node (k8s-1, k8s-2, k8s-3) during a full bootstrap/cold rebuild. Its own header comment also says it's reused for live talosctl patch mc --patch @<file> updates. That means:
    • A cold rebuild (the exact scenario RUNBOOK-cold-rebuild.md / FLUX_SOURCE=github break-glass path in bootstrap.sh exists for) installs Cilium with the pre-fix 16384-entry policy map, reproducing the "no space left on device" / stuck-sandbox-creation failure this PR closes, until Flux's cilium HelmRelease reconciles and the agents restart (up to the 30m spec.interval in helmrelease.yaml, or whenever it's next triggered).
    • This isn't a hypothetical drift risk — the sibling key bpf-lb-map-max: "65536" at cilium-cni.yaml:330 already matches the current live/HelmRelease value, showing this repo's established practice is to keep this rendered bootstrap manifest in sync with bpf.* values in helmrelease.yaml. This PR breaks that sync for the new key.
    • Fix: update bpf-policy-map-max to "65536" in kubernetes/talos/patches/cilium-cni.yaml (and regenerate/update kubernetes/talos/cilium.yaml too, see Suggestions) so bootstrap and steady state agree.

🟡 Suggestions

  • kubernetes/talos/cilium.yaml:306 has the same stale bpf-policy-map-max: "16384". Unlike the patch file above, nothing in bootstrap.sh or RUNBOOK-cold-rebuild.md references this path directly (only talos/patches/cilium-cni.yaml is used) — it looks like an orphaned rendered-manifest snapshot. Worth either deleting it if it's dead, or updating it for consistency if it's kept as a reference/diff baseline — right now it's silently out of sync with both the fix and the actively-used patch file.
  • Consider a short comment on helmrelease.yaml's bpf: block (or a repo-level note) pointing at kubernetes/talos/patches/cilium-cni.yaml as a value that must be updated in lockstep, so this class of drift doesn't recur the next time a bpf-*-map-max value changes.

🟢 Nits

None.

Tests

No automated tests apply to Kustomize/HelmRelease/PrometheusRule YAML in this repo; the PR body documents manual verification (kubectl kustomize build, kubectl apply --dry-run=server for the PrometheusRule, and both alert expressions run against live Prometheus) which is the right verification for this kind of change and I have no reason to doubt it. I could not independently re-run kubectl kustomize/dry-run in this review sandbox (no cluster/tool access), but manual read-through of prometheusrule-cilium.yaml against sibling rule files (prometheusrule-loki.yaml, prometheusrule-netbird-availability.yaml) shows matching structure: release: kube-prometheus-stack label, <name>.rules group naming, and for: 0m used elsewhere for immediate-critical alerts (LokiPanics), so nothing here looks malformed. What's untested (and probably untestable outside a real cluster) is the bootstrap-path regression above — that would only surface on an actual cold rebuild or talosctl apply-config re-run.

Recommendation

Request changes — 1 blocking issue.

🤖 **Automated review** — model `CLI default` (complexity: complex), commit `8f45e2901b9179ce90bf66a42b96984805787095`. > Advisory review by the `review-bot` pipeline. It never approves; a human merges. Push new commits to get a fresh review. --- ## Summary This PR sizes up the Cilium per-endpoint policy BPF map (`bpf.policyMapMax: 65536`), pins the chart to a patch-only range, and adds a `PrometheusRule` that alerts on `cilium_bpf_map_pressure` before the map fills. The change is well-investigated (live pressure numbers, Loki evidence, a real incident) and the new alert/version-pin follow existing repo conventions closely. However, the fix only updates the Flux-managed `HelmRelease`; it misses a second, actively-used source of the same setting, so the bug it closes can be silently reintroduced on the bootstrap path. ## 🔴 Blocking - **`kubernetes/talos/patches/cilium-cni.yaml:324`** still hardcodes `bpf-policy-map-max: "16384"` — the old, insufficient value — inside the Cilium inline-manifest Talos config patch. This file is not incidental: `kubernetes/bootstrap.sh:86` requires it to exist and `kubernetes/bootstrap.sh:123-128` passes it as `--config-patch` to `talosctl apply-config` for **every** node (`k8s-1`, `k8s-2`, `k8s-3`) during a full bootstrap/cold rebuild. Its own header comment also says it's reused for live `talosctl patch mc --patch @<file>` updates. That means: - A cold rebuild (the exact scenario `RUNBOOK-cold-rebuild.md` / `FLUX_SOURCE=github` break-glass path in `bootstrap.sh` exists for) installs Cilium with the pre-fix 16384-entry policy map, reproducing the "no space left on device" / stuck-sandbox-creation failure this PR closes, until Flux's `cilium` `HelmRelease` reconciles and the agents restart (up to the 30m `spec.interval` in `helmrelease.yaml`, or whenever it's next triggered). - This isn't a hypothetical drift risk — the sibling key `bpf-lb-map-max: "65536"` at `cilium-cni.yaml:330` already matches the *current* live/HelmRelease value, showing this repo's established practice is to keep this rendered bootstrap manifest in sync with `bpf.*` values in `helmrelease.yaml`. This PR breaks that sync for the new key. - Fix: update `bpf-policy-map-max` to `"65536"` in `kubernetes/talos/patches/cilium-cni.yaml` (and regenerate/update `kubernetes/talos/cilium.yaml` too, see Suggestions) so bootstrap and steady state agree. ## 🟡 Suggestions - **`kubernetes/talos/cilium.yaml:306`** has the same stale `bpf-policy-map-max: "16384"`. Unlike the patch file above, nothing in `bootstrap.sh` or `RUNBOOK-cold-rebuild.md` references this path directly (only `talos/patches/cilium-cni.yaml` is used) — it looks like an orphaned rendered-manifest snapshot. Worth either deleting it if it's dead, or updating it for consistency if it's kept as a reference/diff baseline — right now it's silently out of sync with both the fix and the actively-used patch file. - Consider a short comment on `helmrelease.yaml`'s `bpf:` block (or a repo-level note) pointing at `kubernetes/talos/patches/cilium-cni.yaml` as a value that must be updated in lockstep, so this class of drift doesn't recur the next time a `bpf-*-map-max` value changes. ## 🟢 Nits None. ## Tests No automated tests apply to Kustomize/HelmRelease/PrometheusRule YAML in this repo; the PR body documents manual verification (`kubectl kustomize` build, `kubectl apply --dry-run=server` for the `PrometheusRule`, and both alert expressions run against live Prometheus) which is the right verification for this kind of change and I have no reason to doubt it. I could not independently re-run `kubectl kustomize`/`dry-run` in this review sandbox (no cluster/tool access), but manual read-through of `prometheusrule-cilium.yaml` against sibling rule files (`prometheusrule-loki.yaml`, `prometheusrule-netbird-availability.yaml`) shows matching structure: `release: kube-prometheus-stack` label, `<name>.rules` group naming, and `for: 0m` used elsewhere for immediate-critical alerts (`LokiPanics`), so nothing here looks malformed. What's untested (and probably untestable outside a real cluster) is the bootstrap-path regression above — that would only surface on an actual cold rebuild or `talosctl apply-config` re-run. ## Recommendation Request changes — 1 blocking issue. <!-- pr-review:sha=8f45e2901b9179ce90bf66a42b96984805787095 -->
claude-bot added 1 commit 2026-08-16 20:36:41 +00:00
fix(cilium): carry policyMapMax into the bootstrap inline manifest
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was successful
621e4e25bf
Addresses the blocking review finding on #495.

Raising bpf.policyMapMax only in the HelmRelease left the bootstrap path
on the old value. kubernetes/bootstrap.sh:126 passes
kubernetes/talos/patches/cilium-cni.yaml to talosctl apply-config for
every node, and that manifest hardcoded bpf-policy-map-max: "16384" — so
a cold rebuild would install Cilium with the map size this PR exists to
fix, and stay there until Flux reconciled the HelmRelease.

The sibling bpf-lb-map-max is "65536" in both files, matching the live
agent config, so keeping these in sync is the repo's existing practice.

kubernetes/talos/cilium.yaml carries the same key and is updated too.
Nothing in the repo references that file — only patches/cilium-cni.yaml
is used by bootstrap.sh — so it looks like an orphaned rendered snapshot.
Syncing it rather than deleting it: whether it should exist at all is a
separate question from this fix.

Each of the three sites now names the others, so the next change to this
value has the lockstep written down where someone will hit it.

Co-Authored-By: Claude Bot <claude-bot@renner.uno>
Author
Member

The blocking finding is correct and is now fixed in 621e4e25.

I verified it independently rather than taking it on faith:

  • kubernetes/talos/patches/cilium-cni.yaml:324 did hold bpf-policy-map-max: "16384", and kubernetes/bootstrap.sh:126 passes that file to talosctl apply-config for every node. A cold rebuild would have come up with exactly the map size this PR exists to raise.
  • The sync precedent is real: bpf-lb-map-max is "65536" in both talos files and matches the live agent config.

All three sites are now 65536, and each names the other two so the lockstep is written where someone changing it will actually hit it.

On the suggestion about kubernetes/talos/cilium.yaml: I updated it, and I can confirm it is orphaned — grep -rn "cilium.yaml" . --exclude-dir=.git returns no reference to it anywhere in the repo (only patches/cilium-cni.yaml" is wired into bootstrap). Its own cni-exclusive` comment at :397 does claim a lockstep contract with the patch file and the HelmRelease, which is why I synced it rather than deleted it. Whether an unreferenced rendered snapshot should be kept at all is a real question, but deleting it is a scope decision for the owner, not something to fold into this fix.

One thing the review did not raise, noted for the record and deliberately not changed: enable-endpoint-lockdown-on-policy-overflow is "false" in both talos manifests. That is why an overflow presents as a stuck sandbox rather than a fail-closed endpoint. Flipping it trades one failure mode for another and belongs in its own discussion.

The blocking finding is correct and is now fixed in 621e4e25. I verified it independently rather than taking it on faith: - `kubernetes/talos/patches/cilium-cni.yaml:324` did hold `bpf-policy-map-max: "16384"`, and `kubernetes/bootstrap.sh:126` passes that file to `talosctl apply-config` for every node. A cold rebuild would have come up with exactly the map size this PR exists to raise. - The sync precedent is real: `bpf-lb-map-max` is `"65536"` in both talos files and matches the live agent config. All three sites are now `65536`, and each names the other two so the lockstep is written where someone changing it will actually hit it. On the suggestion about `kubernetes/talos/cilium.yaml`: I updated it, and I can confirm it is orphaned — `grep -rn "cilium.yaml" . --exclude-dir=.git` returns no reference to it anywhere in the repo (only `patches/cilium-cni.yaml" is wired into bootstrap). Its own `cni-exclusive` comment at :397 does claim a lockstep contract with the patch file and the HelmRelease, which is why I synced it rather than deleted it. Whether an unreferenced rendered snapshot should be kept at all is a real question, but deleting it is a scope decision for the owner, not something to fold into this fix. One thing the review did not raise, noted for the record and deliberately not changed: `enable-endpoint-lockdown-on-policy-overflow` is `"false"` in both talos manifests. That is why an overflow presents as a stuck sandbox rather than a fail-closed endpoint. Flipping it trades one failure mode for another and belongs in its own discussion.
review-bot left a comment
Member

🤖 Automated review — model CLI default (complexity: complex), commit 621e4e25bffa645ef9623e757db2a5fba9ba0d8b.

Advisory review by the review-bot pipeline. It never approves; a human merges. Push new commits to get a fresh review.


Summary

This PR raises the Cilium per-endpoint policy BPF map from 16384 to 65536 entries in all three places it needs to move together (Helm values, the live cilium-config ConfigMap render, and the Talos bootstrap inline manifest), pins the Cilium chart to a patch-only range to stop unattended minor bumps, and adds a PrometheusRule that alerts on cilium_bpf_map_pressure. All three copies of bpf-policy-map-max are updated consistently (verified via grep across kubernetes/), the new rule follows this repo's existing monitoring-rules conventions exactly (label selector, alert/description style, alphabetical ordering in kustomization.yaml), and the reasoning in the comments is grounded and specific. I found no blocking issues.

🟡 Suggestions

  • kubernetes/apps/monitoring-rules/prometheusrule-cilium.yaml:38 and :49 — cilium_bpf_map_pressure is not policy-map-specific; it's emitted per BPF map type (policy, CT, NAT, LB, etc., distinguished by the map_name label used in the annotations). Both alerts will legitimately fire for any Cilium map crossing the threshold, not just cilium_policy_v3_*. That's arguably the right generic behavior (and the annotation text is already written generically — "Raise the corresponding size... (values.bpf)"), but it's worth a one-line comment noting the alert is intentionally map-agnostic, since the PR title/body frame this narrowly around the policy map and a future reader may assume it's policy-map-only.
  • kubernetes/talos/cilium.yaml — this file doesn't appear to be referenced or applied anywhere in the repo (kubernetes/bootstrap.sh only applies patches/cilium-cni.yaml; nothing sources talos/cilium.yaml). It looks like a static rendered snapshot kept as documentation/diff-reference, and this PR correctly keeps it in sync with the same lockstep comment. Since it's a third copy of the same value with no automated enforcement tying it to the other two, consider whether it's worth keeping at all, or note in the file header what regenerates/reviews it — otherwise it's a fourth place a future change can silently drift from (the PR body's own "lockstep" comments only mention two other files, not this one, so the cross-referencing is one-directional).

🟢 Nits

  • kubernetes/infrastructure/cilium/helmrelease.yaml:63 — policyMapMax: 65536 is unquoted while the rendered ConfigMap value is a quoted string ("65536"). This works because Helm/sprig coerces the value downstream, matching the pattern of the chart's likely upstream default, but if the chart's values.yaml documents this key as a string, an explicit "65536" would remove any ambiguity for the next reader.

Tests

No automated tests apply here — this is GitOps config (Helm values, a Talos machine-config patch, and a PrometheusRule). The PR body documents concrete verification instead: kubectl kustomize builds for both changed Kustomizations, kubectl apply --dry-run=server accepts the new PrometheusRule, and both alert expressions were run against live Prometheus. That's the right substitute for tests in this repo and is more thorough than most PRs of this kind — I did not re-run these dry-runs myself since kubectl/kustomize aren't available in this review environment, but I independently confirmed all three bpf-policy-map-max/policyMapMax occurrences were updated to the same value and that the new rule file matches the structure of every other file in monitoring-rules/.

Recommendation

Looks reasonable — no blocking issues; comments above are optional.

🤖 **Automated review** — model `CLI default` (complexity: complex), commit `621e4e25bffa645ef9623e757db2a5fba9ba0d8b`. > Advisory review by the `review-bot` pipeline. It never approves; a human merges. Push new commits to get a fresh review. --- ## Summary This PR raises the Cilium per-endpoint policy BPF map from 16384 to 65536 entries in all three places it needs to move together (Helm values, the live `cilium-config` ConfigMap render, and the Talos bootstrap inline manifest), pins the Cilium chart to a patch-only range to stop unattended minor bumps, and adds a `PrometheusRule` that alerts on `cilium_bpf_map_pressure`. All three copies of `bpf-policy-map-max` are updated consistently (verified via grep across `kubernetes/`), the new rule follows this repo's existing `monitoring-rules` conventions exactly (label selector, alert/description style, alphabetical ordering in `kustomization.yaml`), and the reasoning in the comments is grounded and specific. I found no blocking issues. ## 🟡 Suggestions - `kubernetes/apps/monitoring-rules/prometheusrule-cilium.yaml:38` and `:49` — `cilium_bpf_map_pressure` is not policy-map-specific; it's emitted per BPF map type (policy, CT, NAT, LB, etc., distinguished by the `map_name` label used in the annotations). Both alerts will legitimately fire for any Cilium map crossing the threshold, not just `cilium_policy_v3_*`. That's arguably the right generic behavior (and the annotation text is already written generically — "Raise the corresponding size... (values.bpf)"), but it's worth a one-line comment noting the alert is intentionally map-agnostic, since the PR title/body frame this narrowly around the policy map and a future reader may assume it's policy-map-only. - `kubernetes/talos/cilium.yaml` — this file doesn't appear to be referenced or applied anywhere in the repo (`kubernetes/bootstrap.sh` only applies `patches/cilium-cni.yaml`; nothing sources `talos/cilium.yaml`). It looks like a static rendered snapshot kept as documentation/diff-reference, and this PR correctly keeps it in sync with the same lockstep comment. Since it's a third copy of the same value with no automated enforcement tying it to the other two, consider whether it's worth keeping at all, or note in the file header what regenerates/reviews it — otherwise it's a fourth place a future change can silently drift from (the PR body's own "lockstep" comments only mention two other files, not this one, so the cross-referencing is one-directional). ## 🟢 Nits - `kubernetes/infrastructure/cilium/helmrelease.yaml:63` — `policyMapMax: 65536` is unquoted while the rendered ConfigMap value is a quoted string (`"65536"`). This works because Helm/sprig coerces the value downstream, matching the pattern of the chart's likely upstream default, but if the chart's `values.yaml` documents this key as a string, an explicit `"65536"` would remove any ambiguity for the next reader. ## Tests No automated tests apply here — this is GitOps config (Helm values, a Talos machine-config patch, and a `PrometheusRule`). The PR body documents concrete verification instead: `kubectl kustomize` builds for both changed Kustomizations, `kubectl apply --dry-run=server` accepts the new `PrometheusRule`, and both alert expressions were run against live Prometheus. That's the right substitute for tests in this repo and is more thorough than most PRs of this kind — I did not re-run these dry-runs myself since `kubectl`/`kustomize` aren't available in this review environment, but I independently confirmed all three `bpf-policy-map-max`/`policyMapMax` occurrences were updated to the same value and that the new rule file matches the structure of every other file in `monitoring-rules/`. ## Recommendation Looks reasonable — no blocking issues; comments above are optional. <!-- pr-review:sha=621e4e25bffa645ef9623e757db2a5fba9ba0d8b -->
claude-bot added 1 commit 2026-08-16 20:41:49 +00:00
docs(cilium): state the alert's map scope and complete the lockstep refs
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was successful
9371f188b6
Two optional points from the second review on #495.

The PrometheusRule alerts on cilium_bpf_map_pressure without filtering on
map_name, so it covers conntrack, NAT and LB maps as well as the policy
map. That is intended — running out of any of them degrades the datapath
— but the PR is framed around the policy map, so a reader could take the
narrow reading. Say so in the file.

The cross-references between the three copies of bpf-policy-map-max were
one-directional: helmrelease.yaml and patches/cilium-cni.yaml each named
only one other site, while talos/cilium.yaml named both. Each now names
the other two.

Not taken: the suggestion to quote policyMapMax as "65536". The chart
declares it `type: [null, integer]` in its values schema
(helm show values cilium --version 1.20.0), so an integer is correct and
a quoted string would be the schema violation.

Co-Authored-By: Claude Bot <claude-bot@renner.uno>
Author
Member

Second review is clean — no blocking issues. Two of the three optional points taken in 9371f188, one declined.

Taken. The alert's map scope is now stated in the rule file: it is map-agnostic on purpose, since conntrack/NAT/LB exhaustion degrades the datapath too, and the policy map is just the one that has bitten us. Also noted there that Cilium only exports the metric above policyMapPressureMetricsThreshold (chart default 0.1), so quiet maps produce no series rather than near-zero noise.

Taken. The cross-references were one-directional, as the review says — helmrelease.yaml and patches/cilium-cni.yaml each named one other site while talos/cilium.yaml named both. All three now name the other two.

Declined — the nit is wrong. Quoting policyMapMax as "65536" would not be the safer choice. The chart declares it as an integer in its values schema:

# -- Configure the maximum number of entries in endpoint policy map (per endpoint).
# @schema
# type: [null, integer]
# @schema
policyMapMax: 16384

(helm show values cilium --repo https://helm.cilium.io --version 1.20.0.) An unquoted integer matches the schema; a quoted string is what would risk failing validation. The quoted "16384"/"65536" in the Talos files are a different thing — those are Cilium ConfigMap keys, where every value is a string by definition.

On whether kubernetes/talos/cilium.yaml should exist at all: agreed it is a third unenforced copy, and I have confirmed nothing in the repo references it. Still leaving that decision to the owner rather than deleting a file as a side effect of a map-sizing fix.

Second review is clean — no blocking issues. Two of the three optional points taken in 9371f188, one declined. **Taken.** The alert's map scope is now stated in the rule file: it is map-agnostic on purpose, since conntrack/NAT/LB exhaustion degrades the datapath too, and the policy map is just the one that has bitten us. Also noted there that Cilium only exports the metric above `policyMapPressureMetricsThreshold` (chart default 0.1), so quiet maps produce no series rather than near-zero noise. **Taken.** The cross-references were one-directional, as the review says — `helmrelease.yaml` and `patches/cilium-cni.yaml` each named one other site while `talos/cilium.yaml` named both. All three now name the other two. **Declined — the nit is wrong.** Quoting `policyMapMax` as `"65536"` would not be the safer choice. The chart declares it as an integer in its values schema: ``` # -- Configure the maximum number of entries in endpoint policy map (per endpoint). # @schema # type: [null, integer] # @schema policyMapMax: 16384 ``` (`helm show values cilium --repo https://helm.cilium.io --version 1.20.0`.) An unquoted integer matches the schema; a quoted string is what would risk failing validation. The quoted `"16384"`/`"65536"` in the Talos files are a different thing — those are Cilium *ConfigMap* keys, where every value is a string by definition. On whether `kubernetes/talos/cilium.yaml` should exist at all: agreed it is a third unenforced copy, and I have confirmed nothing in the repo references it. Still leaving that decision to the owner rather than deleting a file as a side effect of a map-sizing fix.
review-bot left a comment
Member

🤖 Automated review — model CLI default (complexity: complex), commit 9371f188b67f76613fabf29a20c0db9360224668.

Advisory review by the review-bot pipeline. It never approves; a human merges. Push new commits to get a fresh review.


Summary

This PR resizes the per-endpoint Cilium policy BPF map from 16384 to 65536 entries in all three places it's duplicated (Helm values, the Talos bootstrap inline manifest, and the reference copy of that manifest), pins the Cilium chart to a 1.20.x-only range instead of an unbounded >=1.19.0, and adds a PrometheusRule alerting on cilium_bpf_map_pressure. I cross-checked all three map-size copies, the alert against sibling PrometheusRule files' conventions, and the alertmanager routing tree — everything is internally consistent and I found no blocking issues.

🟡 Suggestions

  • kubernetes/apps/monitoring-rules/prometheusrule-cilium.yaml:51,62 — both alert description annotations reference {{ $labels.node }}. I could not confirm from the static checkout that cilium_bpf_map_pressure actually carries a node label (the Cilium ServiceMonitor/relabeling config is applied live via the Helm release, not present in the pinned bootstrap manifests, so there's nothing in-repo to grep for it). The PR body says both expressions were run against live Prometheus and returned the expected series, which is good evidence, but it's worth a last glance at the actual label set in Prometheus (cilium_bpf_map_pressure target labels) right before merge, since a missing node label would just render as an empty string in the alert text rather than fail loudly.
  • kubernetes/talos/cilium.yaml isn't referenced by any script or tooling in this repo (confirmed via grep) — it's a hand-maintained third copy purely for readability next to the harder-to-diff inline manifest in patches/cilium-cni.yaml. The PR already discloses this ("Nothing enforces any of this; grep bpf-policy-map-max before changing it."), so this isn't new debt, just flagging that the honesty of that comment is accurate — a future editor really can update two of the three and nothing will catch it.

Tests

No automated tests exist for this class of change in the repo (Kustomize/Helm/Talos manifests), consistent with how similar PrometheusRule and helmrelease.yaml changes have been made before. The PR body documents manual verification instead: kubectl kustomize build, kubectl apply --dry-run=server for the PrometheusRule, and both alert expressions run against live Prometheus. That's the right verification method for this kind of change and matches repo convention — I have no way to re-run the live checks myself from this checkout, but the static structure (YAML validity, alphabetical resource ordering in kustomization.yaml, label conventions matching sibling rule files, all three bpf-policy-map-max copies actually landing on 65536) all check out.

Recommendation

Looks reasonable — no blocking issues; comments above are optional.

🤖 **Automated review** — model `CLI default` (complexity: complex), commit `9371f188b67f76613fabf29a20c0db9360224668`. > Advisory review by the `review-bot` pipeline. It never approves; a human merges. Push new commits to get a fresh review. --- ## Summary This PR resizes the per-endpoint Cilium policy BPF map from 16384 to 65536 entries in all three places it's duplicated (Helm values, the Talos bootstrap inline manifest, and the reference copy of that manifest), pins the Cilium chart to a `1.20.x`-only range instead of an unbounded `>=1.19.0`, and adds a `PrometheusRule` alerting on `cilium_bpf_map_pressure`. I cross-checked all three map-size copies, the alert against sibling `PrometheusRule` files' conventions, and the alertmanager routing tree — everything is internally consistent and I found no blocking issues. ## 🟡 Suggestions - `kubernetes/apps/monitoring-rules/prometheusrule-cilium.yaml:51,62` — both alert `description` annotations reference `{{ $labels.node }}`. I could not confirm from the static checkout that `cilium_bpf_map_pressure` actually carries a `node` label (the Cilium `ServiceMonitor`/relabeling config is applied live via the Helm release, not present in the pinned bootstrap manifests, so there's nothing in-repo to grep for it). The PR body says both expressions were run against live Prometheus and returned the expected series, which is good evidence, but it's worth a last glance at the actual label set in Prometheus (`cilium_bpf_map_pressure` target labels) right before merge, since a missing `node` label would just render as an empty string in the alert text rather than fail loudly. - `kubernetes/talos/cilium.yaml` isn't referenced by any script or tooling in this repo (confirmed via grep) — it's a hand-maintained third copy purely for readability next to the harder-to-diff inline manifest in `patches/cilium-cni.yaml`. The PR already discloses this ("Nothing enforces any of this; grep bpf-policy-map-max before changing it."), so this isn't new debt, just flagging that the honesty of that comment is accurate — a future editor really can update two of the three and nothing will catch it. ## Tests No automated tests exist for this class of change in the repo (Kustomize/Helm/Talos manifests), consistent with how similar `PrometheusRule` and `helmrelease.yaml` changes have been made before. The PR body documents manual verification instead: `kubectl kustomize` build, `kubectl apply --dry-run=server` for the `PrometheusRule`, and both alert expressions run against live Prometheus. That's the right verification method for this kind of change and matches repo convention — I have no way to re-run the live checks myself from this checkout, but the static structure (YAML validity, alphabetical resource ordering in `kustomization.yaml`, label conventions matching sibling rule files, all three `bpf-policy-map-max` copies actually landing on 65536) all check out. ## Recommendation Looks reasonable — no blocking issues; comments above are optional. <!-- pr-review:sha=9371f188b67f76613fabf29a20c0db9360224668 -->
Author
Member

The map is not legitimately full — this PR as written would mask a bug

I said in the PR body that the ~14k entry count was unexplained. I have now explained it, and it changes what this PR should be.

Right now on k8s-1, 8 endpoint maps sit at exactly 16384 — full. Not near-full. cilium-dbg map list shows a bimodal distribution: a handful of small maps (10–229 entries) and then a cluster at ~8,500 and a cluster pinned at the 16384 ceiling. The split tracks policy direction: Enabled/Disabled endpoints land at ~8,500, Enabled/Enabled at 16384. So one direction costs ~8,500 entries.

The affected endpoints are unrelated to each other — cnpg databases, matter-server, spegel, ftp-scanner, openwakeword, zone-configurator, velero, mariadb. This is systemic, not one bad workload.

What is in the map

Dumping velero's endpoint 2328 (8,499 entries, ingress only):

Slice Count
Distinct port/proto values 40 — of which 38 are ICMP/ICMPv6 types
Entries per port/proto value 217 (one per identity)
Entries whose identity is woodpecker-ci-step 3,744 (44%)

217 identities × 39 ≈ 8,460 of 8,499. The map is ~99% ICMP.

Two compounding causes

1. The ICMP rule in every default-deny-ingress. kubernetes/infrastructure/network-policies/*-ns.yaml — 19 namespaces — each carry:

- fromEndpoints:
    - matchExpressions:
        - key: io.kubernetes.pod.namespace
          operator: Exists        # every identity in the cluster
  icmps:
    - fields: [ 38 enumerated ICMP/ICMPv6 types ]

The rationale in home-assistant-ns.yaml:144-152 is sound — Cilium blocks ICMP once any L4 policy exists, CT-RELATED was not passing ICMPv6 DestinationUnreachable, and ICMPField has no wildcard so types must be enumerated. But it notes it was "widened from the error class to the full standard type set", and that widening is the cost: the set includes echo, router discovery, MLD, inverse ND and node-information types (139–153) alongside the error types that were actually needed. Every type costs one entry per identity, per endpoint, per direction, in 19 namespaces.

2. Woodpecker mints an identity per build step. cilium-config has no labels key, so Cilium derives identity from all pod labels. Step pods carry woodpecker-ci.org/task-uuid=6361 and step=clone, so every step of every build is a distinct security identity. There are 96 woodpecker-ci-step identities right now with zero pods in the namespace — all created within the last hour. The operator's GC is working and actively marking them; it simply cannot keep up with the mint rate.

That is why the overflow is intermittent and why it appeared when it did: the entry count is a function of CI load.

Why 65536 is the wrong headline fix

It works arithmetically (17k → 26% of 65536) but leaves the growth term untouched. A busier CI window still overflows, just later. Fixing either cause is worth more:

Change Identities Entries/direction Both directions vs today's 16384
today 217 ~8,500 ~17,000 — over
limit identity-relevant labels ~121 ~4,700 ~9,400 (57%)
trim ICMP 38 → ~8 error types 217 ~1,800 ~3,600 (22%)
both ~121 ~970 ~1,940 (12%)

Cilium documents the first as the standard remedy for exactly this shape — labels carrying uuids or hashes causing identity proliferation — via the labels Helm value with !prefix exclusions.

What I propose for this PR

The version pin and the PrometheusRule are unaffected and should land either way — the alert is what surfaced this at all. The policyMapMax raise I would keep only as headroom, not as the fix, and say so in the commit.

The two real fixes touch a security policy replicated across 19 namespaces and cluster-wide identity computation. Both are owner calls, not something to fold into this PR unannounced. Holding for direction.

## The map is not legitimately full — this PR as written would mask a bug I said in the PR body that the ~14k entry count was unexplained. I have now explained it, and it changes what this PR should be. **Right now on k8s-1, 8 endpoint maps sit at exactly 16384 — full.** Not near-full. `cilium-dbg map list` shows a bimodal distribution: a handful of small maps (10–229 entries) and then a cluster at ~8,500 and a cluster pinned at the 16384 ceiling. The split tracks policy direction: `Enabled/Disabled` endpoints land at ~8,500, `Enabled/Enabled` at 16384. So **one direction costs ~8,500 entries.** The affected endpoints are unrelated to each other — cnpg databases, matter-server, spegel, ftp-scanner, openwakeword, zone-configurator, velero, mariadb. This is systemic, not one bad workload. ### What is in the map Dumping velero's endpoint 2328 (8,499 entries, ingress only): | Slice | Count | |---|---| | Distinct port/proto values | 40 — of which **38 are ICMP/ICMPv6 types** | | Entries per port/proto value | 217 (one per identity) | | Entries whose identity is `woodpecker-ci-step` | **3,744 (44%)** | 217 identities × 39 ≈ 8,460 of 8,499. **The map is ~99% ICMP.** ### Two compounding causes **1. The ICMP rule in every `default-deny-ingress`.** `kubernetes/infrastructure/network-policies/*-ns.yaml` — 19 namespaces — each carry: ```yaml - fromEndpoints: - matchExpressions: - key: io.kubernetes.pod.namespace operator: Exists # every identity in the cluster icmps: - fields: [ 38 enumerated ICMP/ICMPv6 types ] ``` The rationale in `home-assistant-ns.yaml:144-152` is sound — Cilium blocks ICMP once any L4 policy exists, CT-RELATED was not passing ICMPv6 DestinationUnreachable, and `ICMPField` has no wildcard so types must be enumerated. But it notes it was *"widened from the error class to the full standard type set"*, and that widening is the cost: the set includes echo, router discovery, MLD, inverse ND and node-information types (139–153) alongside the error types that were actually needed. Every type costs one entry **per identity, per endpoint, per direction, in 19 namespaces.** **2. Woodpecker mints an identity per build step.** `cilium-config` has **no `labels` key**, so Cilium derives identity from *all* pod labels. Step pods carry `woodpecker-ci.org/task-uuid=6361` and `step=clone`, so every step of every build is a distinct security identity. There are **96 `woodpecker-ci-step` identities right now with zero pods in the namespace** — all created within the last hour. The operator's GC is working and actively marking them; it simply cannot keep up with the mint rate. That is why the overflow is intermittent and why it appeared when it did: **the entry count is a function of CI load.** ### Why 65536 is the wrong headline fix It works arithmetically (17k → 26% of 65536) but leaves the growth term untouched. A busier CI window still overflows, just later. Fixing either cause is worth more: | Change | Identities | Entries/direction | Both directions vs today's 16384 | |---|---|---|---| | today | 217 | ~8,500 | ~17,000 — **over** | | limit identity-relevant labels | ~121 | ~4,700 | ~9,400 (57%) | | trim ICMP 38 → ~8 error types | 217 | ~1,800 | ~3,600 (22%) | | both | ~121 | ~970 | ~1,940 (12%) | Cilium documents the first as the standard remedy for exactly this shape — labels carrying uuids or hashes causing identity proliferation — via the `labels` Helm value with `!prefix` exclusions. ### What I propose for this PR The version pin and the PrometheusRule are unaffected and should land either way — the alert is what surfaced this at all. The `policyMapMax` raise I would keep only as **headroom**, not as the fix, and say so in the commit. The two real fixes touch a security policy replicated across 19 namespaces and cluster-wide identity computation. Both are owner calls, not something to fold into this PR unannounced. Holding for direction.
claude-bot added 1 commit 2026-08-16 21:08:33 +00:00
fix(cilium): cut policy map demand at the source, not just the ceiling
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was successful
25c8c806b4
The earlier commits in this branch raised policyMapMax and added an alert.
Neither addresses why the map fills. This does.

Measured on k8s-1 with cilium-dbg: 8 endpoint policy maps sit at exactly
16384 entries — full — and the rest cluster at ~8,500. The split tracks
policy direction, so one direction costs ~8,500 entries. Dumping velero's
endpoint 2328 (8,499 entries, ingress only) shows 217 identities x 39
port/proto values, of which 38 are ICMP types. The map is ~99% ICMP, and
3,744 of its entries (44%) belong to woodpecker-ci-step identities.

Two multiplying causes, both fixed here.

The ICMP rule in every default-deny-ingress allowed 39 enumerated types
from `namespace: Exists` — every identity in the cluster. Cilium expands
that to one entry per (identity, type), per endpoint, per direction, in
19 namespaces. The rule's own comment records it was widened "from the
error class to the full standard type set"; that widening is the cost.
Trimmed to 16: the error types that motivated the rule (v4 3/11/12, v6
1/2/3/4 — PMTUD is the documented need), echo, and the IPv6 ND/RA/MLDv2
types observed in flight via Hubble (133, 135, 143). Dropped types are
deprecated (v4 4 Source Quench, RFC 6633), router-only (v4 5, v6 137),
obsolete (v4 13-18, 30), superseded (v6 130-132 by 143), or never
deployed (v6 138, 141/142, 148/149, 151-153). openbao-ns.yaml already
runs an error-only set of 7 in production, so 16 is the conservative side
of existing practice.

Cilium had no `labels` filter, so identity came from all pod labels.
Woodpecker stamps each step pod with woodpecker-ci.org/task-uuid and
step=<name>, making every build step its own security identity — 96 of
them minted in the last hour, against zero pods in the namespace. The GC
is healthy and simply outpaced. Excluding those two label patterns
collapses 78 current identities to one. No CiliumNetworkPolicy selects on
them, so they bought churn and nothing else.

Together these take a direction from ~8,500 entries to roughly 140
identities x 17 values ~= 2,400 — about 4,800 for both directions, well
inside even the old 16384 ceiling. policyMapMax stays raised as headroom,
not as the fix.

Per Cilium's docs, existing identities are not recomputed by a `labels`
change; new ones use the filter and the old ones age out through normal
GC, so the map drains over roughly an hour rather than instantly.

Co-Authored-By: Claude Bot <claude-bot@renner.uno>
claude-bot changed title from fix(cilium): size the endpoint policy map and alert before it fills to fix(cilium): stop the endpoint policy map filling — trim ICMP set, limit identity labels, size + alert 2026-08-16 21:08:38 +00:00
Author
Member

Both root-cause fixes are in 25c8c806. The PR now removes the demand rather than raising the ceiling; policyMapMax stays as headroom only.

1. ICMP set trimmed 39 → 16, across 19 files

Kept: the error types that motivated the rule (v4 3/11/12, v6 1/2/3/4 — PMTUD is the documented need), echo (v4 0/8, v6 128/129), and the IPv6 ND/RA/MLDv2 types observed actually in flight via hubble observe — 133, 135, 143.

That last point changed the shape of the trim. My first instinct was error-types-only, which would have dropped 133, 135 and 143 — all three of which are live on the wire. Sampling before cutting was worth it.

Dropped, with reasons: v4 4 (Source Quench, deprecated by RFC 6633), v4 5 / v6 137 (Redirect — router-only), v4 9/10, 13–18, 30 (obsolete or infoleak-prone), v6 130–132 (MLDv1, superseded by the 143 we see), v6 138, 141/142, 148/149, 151–153 (never deployed).

Corroboration I did not expect to find: openbao-ns.yaml already runs an error-only set of just 7 types in production. So 16 is on the conservative side of practice that already exists in this repo.

2. Identity label filter

labels: '!woodpecker-ci\.org/.* !step$' on the HelmRelease and both Talos manifests. Syntax verified against the v1.20 docs — patterns are start-anchored regexes, ! excludes while keeping the default set.

Checked before cutting: no CiliumNetworkPolicy selects on either label, and no pod outside woodpecker carries a bare step label. This collapses 78 current identities to one.

Projected effect

Identities Per direction Both directions
before 217 ~8,500 ~17,000 — over the 16384 ceiling
after ~140 ~2,400 ~4,800

That fits inside the old 16384 with room to spare, which is the point: the growth term tied to CI load is gone.

Verification

  • kubectl kustomize builds clean for network-policies, cilium and monitoring-rules.
  • kubectl apply --dry-run=server accepts all 25 rendered policies and the PrometheusRule.
  • Rendered values confirm labels, policyMapMax and the version range.

Two things to know before merging

Per Cilium's docs, a labels change does not recompute existing identities — new ones use the filter and old ones age out via normal GC. So the map drains over roughly an hour after the agents restart, not instantly. Don't read the first post-merge reading as a failure.

There is a further lever I did not pull: the ICMP rule still matches namespace: Exists, so cost stays linear in identity count. Allowing ICMP from a wildcard source instead would collapse it to one entry per type regardless of identity count — but that widens the source beyond cluster pods, which is a security tradeoff and a separate decision.

Both root-cause fixes are in 25c8c806. The PR now removes the demand rather than raising the ceiling; `policyMapMax` stays as headroom only. ### 1. ICMP set trimmed 39 → 16, across 19 files Kept: the error types that motivated the rule (v4 3/11/12, v6 1/2/3/4 — PMTUD is the documented need), echo (v4 0/8, v6 128/129), and the IPv6 ND/RA/MLDv2 types **observed actually in flight** via `hubble observe` — 133, 135, 143. That last point changed the shape of the trim. My first instinct was error-types-only, which would have dropped 133, 135 and 143 — all three of which are live on the wire. Sampling before cutting was worth it. Dropped, with reasons: v4 4 (Source Quench, deprecated by RFC 6633), v4 5 / v6 137 (Redirect — router-only), v4 9/10, 13–18, 30 (obsolete or infoleak-prone), v6 130–132 (MLDv1, superseded by the 143 we see), v6 138, 141/142, 148/149, 151–153 (never deployed). Corroboration I did not expect to find: **`openbao-ns.yaml` already runs an error-only set of just 7 types** in production. So 16 is on the conservative side of practice that already exists in this repo. ### 2. Identity label filter `labels: '!woodpecker-ci\.org/.* !step$'` on the HelmRelease and both Talos manifests. Syntax verified against the v1.20 docs — patterns are start-anchored regexes, `!` excludes while keeping the default set. Checked before cutting: **no CiliumNetworkPolicy selects on either label**, and no pod outside woodpecker carries a bare `step` label. This collapses **78 current identities to one**. ### Projected effect | | Identities | Per direction | Both directions | |---|---|---|---| | before | 217 | ~8,500 | ~17,000 — over the 16384 ceiling | | after | ~140 | ~2,400 | **~4,800** | That fits inside the *old* 16384 with room to spare, which is the point: the growth term tied to CI load is gone. ### Verification - `kubectl kustomize` builds clean for `network-policies`, `cilium` and `monitoring-rules`. - `kubectl apply --dry-run=server` accepts **all 25 rendered policies** and the PrometheusRule. - Rendered values confirm `labels`, `policyMapMax` and the version range. ### Two things to know before merging Per Cilium's docs, a `labels` change does **not** recompute existing identities — new ones use the filter and old ones age out via normal GC. So the map drains over roughly an hour after the agents restart, not instantly. Don't read the first post-merge reading as a failure. There is a further lever I did **not** pull: the ICMP rule still matches `namespace: Exists`, so cost stays linear in identity count. Allowing ICMP from a wildcard source instead would collapse it to one entry per type regardless of identity count — but that widens the source beyond cluster pods, which is a security tradeoff and a separate decision.
review-bot left a comment
Member

🤖 Automated review — model CLI default (complexity: complex), commit 25c8c806b4d0397713c6c4a19dcdb49e37834c00.

Advisory review by the review-bot pipeline. It never approves; a human merges. Push new commits to get a fresh review.


Summary

This PR addresses the recurring cilium_policy_v3_<id> BPF policy-map exhaustion (#451, #454) three ways: quadruples bpf.policyMapMax to 65536 (kept in sync across the three copies — helmrelease.yaml, kubernetes/talos/cilium.yaml, kubernetes/talos/patches/cilium-cni.yaml), pins the Cilium chart to a patch-only range, and adds a PrometheusRule so map pressure is alertable. It also trims the ICMP allow-list in 17 CiliumNetworkPolicy files and adds an identity-label exclude filter for Woodpecker CI pod labels, both aimed at reducing the number of policy-map entries an endpoint needs. I checked all three bpf-policy-map-max/policyMapMax copies and all three labels copies — they're byte-identical, and I diffed the post-trim ICMP type sets across all 17 network-policy files — they're uniform (IPv4: 0,3,8,11,12; IPv6: 1,2,3,4,128,129,133,134,135,136,143), keeping the PMTUD-relevant and neighbor-discovery types and dropping obsolete/rare ones (source quench, address-mask, MLD, inverse-ND, etc.). No blocking issues found.

🟡 Suggestions

  • kubernetes/infrastructure/cilium/helmrelease.yaml:45 (and the two mirrored copies) — the identity-label filter labels: '!woodpecker-ci\.org/.* !step$' is the one piece of this PR whose effect isn't verified against live state the way the map-size and alert-expression changes are (the PR body's "Verification done" and "Deliberately not done" sections don't mention it, and it's a plausible partial explanation for "why the busiest endpoint needs ~14k entries" that the author says is still unexplained). A wrong regex here fails silently — no dry-run or kustomize build catches it, it just doesn't reduce identity churn. Worth confirming post-rollout, e.g. cilium-dbg identity list for a woodpecker-ci step pod before/after an agent restart, to check CI steps actually collapse onto one identity.
  • kubernetes/apps/monitoring-rules/prometheusrule-cilium.yaml:32-44 — both alerts key their summary/description on {{ $labels.node }}, whereas the other rules in this same directory (prometheusrule-loki.yaml, prometheusrule-netbird-availability.yaml) use $labels.instance/$labels.job. The PR body states the expressions were run against live Prometheus and returned results for all three nodes, which is good evidence cilium_bpf_map_pressure does carry a node label in this cluster's setup (likely via the chart's ServiceMonitor relabeling __meta_kubernetes_pod_node_name), so this isn't a correctness concern — just flagging the label-naming inconsistency with the rest of the directory for future reference.

🟢 Nits

  • Only home-assistant-ns.yaml keeps per-type ICMP comments (e.g. # Destination Unreachable — PMTUD) after the trim; the other 16 files' icmps.fields lists are bare numbers both before and after this change, so there's no rationale recorded next to them for why e.g. type 4 or type 30 IPv4 were dropped. Not introduced by this PR, but since this PR is the one making the trim deliberate, it'd be a good place to add a one-line pointer (e.g. "see home-assistant-ns.yaml for the full type table") so a future editor doesn't have to reverse-engineer the surviving set.

Tests

This is a GitOps config repo with no unit-test suite; the appropriate verification is what the PR describes: kubectl kustomize build and --dry-run=server apply for the new PrometheusRule, both of which the author ran, and which .woodpecker/ci-manifests-test.yml also exercises in CI. The two alert expressions were run against live Prometheus per the PR body. I didn't find anything in the diff that this level of testing would fail to catch.

Recommendation

Looks reasonable — no blocking issues; comments above are optional.

🤖 **Automated review** — model `CLI default` (complexity: complex), commit `25c8c806b4d0397713c6c4a19dcdb49e37834c00`. > Advisory review by the `review-bot` pipeline. It never approves; a human merges. Push new commits to get a fresh review. --- ## Summary This PR addresses the recurring `cilium_policy_v3_<id>` BPF policy-map exhaustion (#451, #454) three ways: quadruples `bpf.policyMapMax` to 65536 (kept in sync across the three copies — `helmrelease.yaml`, `kubernetes/talos/cilium.yaml`, `kubernetes/talos/patches/cilium-cni.yaml`), pins the Cilium chart to a patch-only range, and adds a `PrometheusRule` so map pressure is alertable. It also trims the ICMP allow-list in 17 `CiliumNetworkPolicy` files and adds an identity-label exclude filter for Woodpecker CI pod labels, both aimed at reducing the number of policy-map entries an endpoint needs. I checked all three `bpf-policy-map-max`/`policyMapMax` copies and all three `labels` copies — they're byte-identical, and I diffed the post-trim ICMP type sets across all 17 network-policy files — they're uniform (IPv4: 0,3,8,11,12; IPv6: 1,2,3,4,128,129,133,134,135,136,143), keeping the PMTUD-relevant and neighbor-discovery types and dropping obsolete/rare ones (source quench, address-mask, MLD, inverse-ND, etc.). No blocking issues found. ## 🟡 Suggestions - `kubernetes/infrastructure/cilium/helmrelease.yaml:45` (and the two mirrored copies) — the identity-label filter `labels: '!woodpecker-ci\.org/.* !step$'` is the one piece of this PR whose effect isn't verified against live state the way the map-size and alert-expression changes are (the PR body's "Verification done" and "Deliberately not done" sections don't mention it, and it's a plausible partial explanation for "why the busiest endpoint needs ~14k entries" that the author says is still unexplained). A wrong regex here fails silently — no dry-run or kustomize build catches it, it just doesn't reduce identity churn. Worth confirming post-rollout, e.g. `cilium-dbg identity list` for a woodpecker-ci step pod before/after an agent restart, to check CI steps actually collapse onto one identity. - `kubernetes/apps/monitoring-rules/prometheusrule-cilium.yaml:32-44` — both alerts key their summary/description on `{{ $labels.node }}`, whereas the other rules in this same directory (`prometheusrule-loki.yaml`, `prometheusrule-netbird-availability.yaml`) use `$labels.instance`/`$labels.job`. The PR body states the expressions were run against live Prometheus and returned results for all three nodes, which is good evidence `cilium_bpf_map_pressure` does carry a `node` label in this cluster's setup (likely via the chart's ServiceMonitor relabeling `__meta_kubernetes_pod_node_name`), so this isn't a correctness concern — just flagging the label-naming inconsistency with the rest of the directory for future reference. ## 🟢 Nits - Only `home-assistant-ns.yaml` keeps per-type ICMP comments (e.g. `# Destination Unreachable — PMTUD`) after the trim; the other 16 files' `icmps.fields` lists are bare numbers both before and after this change, so there's no rationale recorded next to them for why e.g. type 4 or type 30 IPv4 were dropped. Not introduced by this PR, but since this PR is the one making the trim deliberate, it'd be a good place to add a one-line pointer (e.g. "see home-assistant-ns.yaml for the full type table") so a future editor doesn't have to reverse-engineer the surviving set. ## Tests This is a GitOps config repo with no unit-test suite; the appropriate verification is what the PR describes: `kubectl kustomize` build and `--dry-run=server` apply for the new `PrometheusRule`, both of which the author ran, and which `.woodpecker/ci-manifests-test.yml` also exercises in CI. The two alert expressions were run against live Prometheus per the PR body. I didn't find anything in the diff that this level of testing would fail to catch. ## Recommendation Looks reasonable — no blocking issues; comments above are optional. <!-- pr-review:sha=25c8c806b4d0397713c6c4a19dcdb49e37834c00 -->
k.renner merged commit 9a1f6cce0c into master 2026-08-16 21:14:09 +00:00
Sign in to join this conversation.