cilium: chart-native metrics Service, attended helm upgrade (REN-166) #1032

Merged
Ghost merged 2 commits from atlas/ren-166-cilium-verdict-metric into master 2026-10-05 00:01:56 +00:00

REN-166. HA window (REN-243): merge only Mon 2026-10-05, 00:00–03:00 UTC (02:00–05:00 Europe/Berlin). This is a CNI PR, so the Home Assistant protection rule applies: Flux applies on merge, and the merge needs Hestia's HA-impact review first. Hestia is asked for it on REN-166; she posts it on REN-166 and on this PR. No Hestia review, or no pre-check output on REN-166 by 03:00 UTC: do not merge. The 2026-10-04 09:00–11:00 UTC window was not used because it was outside the HA window (REN-243); the 2026-09-27 window was missed because pre-check 1 failed (REN-297). Merging starts the helm upgrade. Scope and abort criteria are in the cilium-roll-window document on REN-166. The cluster owner accepted the roll on REN-166 (2026-09-26 15:01 UTC).

What changed (head df5e425b)

This follows Zeus's decision (a) on REN-166. prometheus.metrics: ["+cilium_policy_verdict_total"] is removed, because that metric does not exist in cilium 1.20.2 (Argus, review 1113). No Hubble metric replaces it.

  1. infrastructure/cilium/helmrelease.yaml: prometheus.metricsService: true. The chart now renders the headless cilium-agent Service. serviceMonitor.enabled stays false; the file says why.
  2. apps/monitoring-rules/cilium-servicemonitor.yaml: the hand-declared Service cilium-agent-metrics is deleted. The ServiceMonitor selector is now name: cilium-agent + part-of: cilium, which matches only the chart's Service. monitoring-rules has prune: true, so there is never a moment with two matching Services and nothing is scraped twice.

helm template diff, chart 1.20.2, origin/master → this head

I re-rendered this at df5e425b: helm template with values taken from both HelmRelease files, plus --api-versions monitoring.coreos.com/v1. The only other lines that differ are the Hubble TLS material, which the chart regenerates on every render.

+# Source: cilium/templates/cilium-agent/service.yaml
+kind: Service
+metadata: {name: cilium-agent, namespace: kube-system, labels: {k8s-app: cilium, app.kubernetes.io/name: cilium-agent, app.kubernetes.io/part-of: cilium}}
+spec: {clusterIP: None, selector: {k8s-app: cilium}, ports: [{name: metrics, port: 9962, targetPort: prometheus}]}

cilium-config renders byte-identical before and after, and so does bgp-router-id-allocation-mode: "default". This PR adds no ConfigMap key and changes no DaemonSet template. So if live state matches the last helm release, the reloader has nothing to react to and the agents do not roll. The agents roll only if the upgrade's three-way merge corrects live drift in cilium-config. Pre-check 1 exists to find exactly that drift.

Pre-checks 1 and 2: read-only; Argus re-runs them in the window (from 00:00 UTC) and pastes the output on REN-166

The Paperclip pod SA can read kube-system ConfigMaps. These checks need no owner.

Pre-check 1 is amended (REN-297). The old rule was "abort if any manager other than helm-controller owns a data key". It aborts on harmless co-ownership. helm-controller writes with server-side apply (Apply). A field keeps an Update manager only while the applied value is identical to live. So co-ownership shows that the values agree. It does not show drift. The rule now checks values:

New abort rule: abort if status.lastAttemptedRevision of HR cilium is not 1.20.2 (the range >=1.20.0 <1.21.0 would take 1.20.3, and the reading below would no longer apply). Abort if any key in the rendered cilium-config differs in value from live. Also abort if a manager other than helm-controller, talos or kubectl-patch appears, or if a manager's time or key count differs from the table below.

Reading 2026-09-27, pod SA. Render: helm template chart 1.20.2 with the spec.values of this head, --kube-version v1.36.3 (the live server). Key counts exclude the . marker.

manager op time data keys
helm-controller Apply 2026-09-08T14:45:35Z 174
talos Update 2026-05-09T06:56:45Z 147
kubectl-patch Update 2026-05-19T19:40:51Z 3
  • The 3 kubectl-patch keys are all co-owned by helm-controller. Rendered = live: enable-bgp-control-plane true, ipv4-native-routing-cidr 10.0.0.0/8, ipv6-native-routing-cidr fd00::/8. The values are the ones in helmrelease.yaml (bgpControlPlane.enabled, ipv4/ipv6NativeRoutingCIDR).
  • 143 of the 147 talos keys are co-owned by helm-controller.
  • debug-verbose, nodeport-addresses and policy-cidr-match-mode are owned by talos only. They render as "", and live is "", so applying them changes no data.
  • enable-tunnel-big-tcp (false) is owned by talos only and is not rendered. Helm never owned it, so the upgrade leaves it untouched.
  • All 177 rendered keys equal live. cilium-config renders byte-identical on origin/master and on this head.

Result: the upgrade changes no cilium-config data, the reloader has nothing to hash differently, and the agents do not roll. The stale managers are left in place on purpose. Removing them would take a live write to the CNI ConfigMap outside git. That is more risk than the metadata it cleans up.

# Pre-check 1, read-only. Pod SA + helm + yq + jq; run from a worktree of this PR head.
SA=/var/run/secrets/kubernetes.io/serviceaccount
k8s() { curl -sf --cacert "$SA/ca.crt" -H "Authorization: Bearer $(cat "$SA/token")" "https://kubernetes.default.svc$1"; }
# 1a. Chart revision: must print 1.20.2, otherwise abort.
REV=$(k8s /apis/helm.toolkit.fluxcd.io/v2/namespaces/kube-system/helmreleases/cilium | jq -r .status.lastAttemptedRevision); echo "rev $REV"
KV=$(k8s /version | jq -r .gitVersion); echo "server $KV"
# 1b. Values: render with the spec.values of this head; the diff must print nothing.
yq 'select(.kind=="HelmRelease").spec.values' kubernetes/infrastructure/cilium/helmrelease.yaml > values.yaml
helm template cilium cilium --repo https://helm.cilium.io/ --version "$REV" -n kube-system --kube-version "$KV" -f values.yaml \
  | yq -o=json 'select(.kind=="ConfigMap" and .metadata.name=="cilium-config").data' | jq -S . > rendered.json
k8s /api/v1/namespaces/kube-system/configmaps/cilium-config > cm.json
jq -S .data cm.json > live.json
echo "rendered $(jq length rendered.json) live $(jq length live.json)"
jq -r --slurpfile L live.json 'to_entries[]|select($L[0][.key]!=.value)|.key' rendered.json
# 1c. Managers and key counts. Expected: helm-controller 174, talos 147, kubectl-patch 3 (times as in the table).
jq -r '.metadata.managedFields[]|"\(.manager) \(.operation) \(.time) \([.fieldsV1["f:data"]|keys[]|select(.!=".")]|length)"' cm.json

# 2. BGP companion keys. Expected, read 2026-09-27: default||kube-system|true|false (ip-pool is empty, the chart default)
kubectl -n kube-system get configmap cilium-config -o jsonpath='{.data.bgp-router-id-allocation-mode}{"|"}{.data.bgp-router-id-allocation-ip-pool}{"|"}{.data.bgp-secrets-namespace}{"|"}{.data.enable-bgp-control-plane-status-report}{"|"}{.data.enable-bgp-legacy-origin-attribute}{"\n"}'

Output 2026-09-27 (pod SA, head df5e425b): 1a rev 1.20.2, server v1.36.3; 1b rendered 177 live 178, diff prints nothing; 1c helm-controller Apply 2026-09-08T14:45:35Z 174, talos Update 2026-05-09T06:56:45Z 147, kubectl-patch Update 2026-05-19T19:40:51Z 3. Pre-check 1 passes.

Pre-check 2 passes: all five keys are present and owned by helm-controller.

BGP baseline (pre-check 3), read 2026-09-26 around 15:00 UTC; re-read it before the merge

cilium_bgp_control_plane_session_state is 1 for all 6 sessions: k8s-1/2/3 × 10.2.47.1 and fd02:47::1, ASN 64513→64512. Advertised routes per AFI: k8s-1 = 4, k8s-2 = 5, k8s-3 = 5, the same for IPv4 and IPv6.

Blast radius

  • The helm upgrade on the CNI release creates one Service. If pre-check 1 finds drift, the upgrade rewrites cilium-config, and the reloader then rolls the agents one at a time. The watch items and abort rules are in the document.
  • The scrape job label changes from cilium-agent-metrics to cilium-agent. No rule or dashboard in the repo filters on it (checked with grep).
  • The apps and infrastructure Kustomizations reconcile separately, so for up to one reconcile cilium_* can have no target. Run flux reconcile hr cilium -n kube-system right after the merge.

Verification

This PR has been rendered with helm template and has not been applied to a cluster. The merge is the apply. Done when: the ServiceMonitor matches only the chart's cilium-agent Service, up{job="cilium-agent"} is 3/3, all 6 BGP sessions are at 1 with the route counts above, and no workload lost connectivity.

Revert

git revert <merge commit>. Flux restores the hand-declared Service and the old selector, and helm-controller removes the chart Service. cilium-config is unchanged either way.

🤖 Generated with Claude Code

REN-166. **HA window (REN-243): merge only Mon 2026-10-05, 00:00–03:00 UTC (02:00–05:00 Europe/Berlin).** This is a CNI PR, so the Home Assistant protection rule applies: Flux applies on merge, and the merge needs Hestia's HA-impact review first. Hestia is asked for it on REN-166; she posts it on REN-166 and on this PR. **No Hestia review, or no pre-check output on REN-166 by 03:00 UTC: do not merge.** The 2026-10-04 09:00–11:00 UTC window was not used because it was outside the HA window (REN-243); the 2026-09-27 window was missed because pre-check 1 failed (REN-297). Merging starts the helm upgrade. Scope and abort criteria are in the `cilium-roll-window` document on REN-166. The cluster owner accepted the roll on REN-166 (2026-09-26 15:01 UTC). ## What changed (head `df5e425b`) This follows Zeus's decision (a) on REN-166. `prometheus.metrics: ["+cilium_policy_verdict_total"]` is **removed**, because that metric does not exist in cilium 1.20.2 (Argus, review 1113). No Hubble metric replaces it. 1. `infrastructure/cilium/helmrelease.yaml`: `prometheus.metricsService: true`. The chart now renders the headless `cilium-agent` Service. `serviceMonitor.enabled` stays `false`; the file says why. 2. `apps/monitoring-rules/cilium-servicemonitor.yaml`: the hand-declared `Service cilium-agent-metrics` is deleted. The ServiceMonitor selector is now `name: cilium-agent` + `part-of: cilium`, which matches only the chart's Service. `monitoring-rules` has `prune: true`, so there is never a moment with two matching Services and nothing is scraped twice. ## `helm template` diff, chart 1.20.2, `origin/master` → this head I re-rendered this at `df5e425b`: `helm template` with values taken from both HelmRelease files, plus `--api-versions monitoring.coreos.com/v1`. The only other lines that differ are the Hubble TLS material, which the chart regenerates on every render. ```diff +# Source: cilium/templates/cilium-agent/service.yaml +kind: Service +metadata: {name: cilium-agent, namespace: kube-system, labels: {k8s-app: cilium, app.kubernetes.io/name: cilium-agent, app.kubernetes.io/part-of: cilium}} +spec: {clusterIP: None, selector: {k8s-app: cilium}, ports: [{name: metrics, port: 9962, targetPort: prometheus}]} ``` **`cilium-config` renders byte-identical before and after**, and so does `bgp-router-id-allocation-mode: "default"`. This PR adds no ConfigMap key and changes no DaemonSet template. So if live state matches the last helm release, the reloader has nothing to react to and **the agents do not roll**. The agents roll only if the upgrade's three-way merge corrects live drift in `cilium-config`. Pre-check 1 exists to find exactly that drift. ## Pre-checks 1 and 2: read-only; Argus re-runs them in the window (from 00:00 UTC) and pastes the output on REN-166 The Paperclip pod SA can read kube-system ConfigMaps. These checks need no owner. **Pre-check 1 is amended (REN-297).** The old rule was "abort if any manager other than helm-controller owns a data key". It aborts on harmless co-ownership. helm-controller writes with server-side apply (`Apply`). A field keeps an `Update` manager only while the applied value is identical to live. So co-ownership shows that the values agree. It does not show drift. The rule now checks values: **New abort rule:** abort if `status.lastAttemptedRevision` of HR `cilium` is not `1.20.2` (the range `>=1.20.0 <1.21.0` would take 1.20.3, and the reading below would no longer apply). Abort if any key in the rendered `cilium-config` differs in value from live. Also abort if a manager other than `helm-controller`, `talos` or `kubectl-patch` appears, or if a manager's `time` or key count differs from the table below. Reading 2026-09-27, pod SA. Render: `helm template` chart 1.20.2 with the `spec.values` of this head, `--kube-version` v1.36.3 (the live server). Key counts exclude the `.` marker. | manager | op | time | data keys | |---|---|---|---| | helm-controller | Apply | 2026-09-08T14:45:35Z | 174 | | talos | Update | 2026-05-09T06:56:45Z | 147 | | kubectl-patch | Update | 2026-05-19T19:40:51Z | 3 | - The 3 `kubectl-patch` keys are all co-owned by helm-controller. Rendered = live: `enable-bgp-control-plane` `true`, `ipv4-native-routing-cidr` `10.0.0.0/8`, `ipv6-native-routing-cidr` `fd00::/8`. The values are the ones in `helmrelease.yaml` (`bgpControlPlane.enabled`, `ipv4/ipv6NativeRoutingCIDR`). - 143 of the 147 `talos` keys are co-owned by helm-controller. - `debug-verbose`, `nodeport-addresses` and `policy-cidr-match-mode` are owned by `talos` only. They render as `""`, and live is `""`, so applying them changes no data. - `enable-tunnel-big-tcp` (`false`) is owned by `talos` only and is not rendered. Helm never owned it, so the upgrade leaves it untouched. - All 177 rendered keys equal live. `cilium-config` renders byte-identical on `origin/master` and on this head. **Result:** the upgrade changes no `cilium-config` data, the reloader has nothing to hash differently, and the agents do not roll. The stale managers are left in place on purpose. Removing them would take a live write to the CNI ConfigMap outside git. That is more risk than the metadata it cleans up. ```sh # Pre-check 1, read-only. Pod SA + helm + yq + jq; run from a worktree of this PR head. SA=/var/run/secrets/kubernetes.io/serviceaccount k8s() { curl -sf --cacert "$SA/ca.crt" -H "Authorization: Bearer $(cat "$SA/token")" "https://kubernetes.default.svc$1"; } # 1a. Chart revision: must print 1.20.2, otherwise abort. REV=$(k8s /apis/helm.toolkit.fluxcd.io/v2/namespaces/kube-system/helmreleases/cilium | jq -r .status.lastAttemptedRevision); echo "rev $REV" KV=$(k8s /version | jq -r .gitVersion); echo "server $KV" # 1b. Values: render with the spec.values of this head; the diff must print nothing. yq 'select(.kind=="HelmRelease").spec.values' kubernetes/infrastructure/cilium/helmrelease.yaml > values.yaml helm template cilium cilium --repo https://helm.cilium.io/ --version "$REV" -n kube-system --kube-version "$KV" -f values.yaml \ | yq -o=json 'select(.kind=="ConfigMap" and .metadata.name=="cilium-config").data' | jq -S . > rendered.json k8s /api/v1/namespaces/kube-system/configmaps/cilium-config > cm.json jq -S .data cm.json > live.json echo "rendered $(jq length rendered.json) live $(jq length live.json)" jq -r --slurpfile L live.json 'to_entries[]|select($L[0][.key]!=.value)|.key' rendered.json # 1c. Managers and key counts. Expected: helm-controller 174, talos 147, kubectl-patch 3 (times as in the table). jq -r '.metadata.managedFields[]|"\(.manager) \(.operation) \(.time) \([.fieldsV1["f:data"]|keys[]|select(.!=".")]|length)"' cm.json # 2. BGP companion keys. Expected, read 2026-09-27: default||kube-system|true|false (ip-pool is empty, the chart default) kubectl -n kube-system get configmap cilium-config -o jsonpath='{.data.bgp-router-id-allocation-mode}{"|"}{.data.bgp-router-id-allocation-ip-pool}{"|"}{.data.bgp-secrets-namespace}{"|"}{.data.enable-bgp-control-plane-status-report}{"|"}{.data.enable-bgp-legacy-origin-attribute}{"\n"}' ``` Output 2026-09-27 (pod SA, head `df5e425b`): 1a `rev 1.20.2`, `server v1.36.3`; 1b `rendered 177 live 178`, diff prints nothing; 1c `helm-controller Apply 2026-09-08T14:45:35Z 174`, `talos Update 2026-05-09T06:56:45Z 147`, `kubectl-patch Update 2026-05-19T19:40:51Z 3`. Pre-check 1 passes. Pre-check 2 passes: all five keys are present and owned by helm-controller. ## BGP baseline (pre-check 3), read 2026-09-26 around 15:00 UTC; re-read it before the merge `cilium_bgp_control_plane_session_state` is 1 for all 6 sessions: k8s-1/2/3 × `10.2.47.1` and `fd02:47::1`, ASN 64513→64512. Advertised routes per AFI: k8s-1 = 4, k8s-2 = 5, k8s-3 = 5, the same for IPv4 and IPv6. ## Blast radius - The helm upgrade on the CNI release creates one Service. If pre-check 1 finds drift, the upgrade rewrites `cilium-config`, and the reloader then rolls the agents one at a time. The watch items and abort rules are in the document. - The scrape `job` label changes from `cilium-agent-metrics` to `cilium-agent`. No rule or dashboard in the repo filters on it (checked with grep). - The apps and infrastructure Kustomizations reconcile separately, so for up to one reconcile `cilium_*` can have no target. Run `flux reconcile hr cilium -n kube-system` right after the merge. ## Verification This PR has been rendered with `helm template` and **has not been applied to a cluster**. The merge is the apply. Done when: the ServiceMonitor matches only the chart's `cilium-agent` Service, `up{job="cilium-agent"}` is 3/3, all 6 BGP sessions are at 1 with the route counts above, and no workload lost connectivity. ## Revert `git revert <merge commit>`. Flux restores the hand-declared Service and the old selector, and helm-controller removes the chart Service. `cilium-config` is unchanged either way. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Ghost added 1 commit 2026-09-26 15:04:49 +00:00
feat(cilium): enable cilium_policy_verdict_total, chart owns the metrics Service (REN-166)
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 canceled
c7e61e7dce
- prometheus.metrics: +cilium_policy_verdict_total (opt-in; per-direction
  policy verdict attribution on top of cilium_drop_count_total).
- prometheus.metricsService: true, so the chart renders the headless
  `cilium-agent` metrics Service.
- Drop the hand-declared `cilium-agent-metrics` Service from
  cilium-servicemonitor.yaml and point the ServiceMonitor at the chart's
  Service, so the endpoints are scraped once, not twice.

This is a values change: helm-controller upgrades the CNI release, the
cilium-config `metrics` key changes, and the reloader annotation rolls the
agents node by node. Merge only inside the window Zeus set on REN-166.

Revert: git revert this commit; Flux restores the old Service and selector
and rolls the agents back the same way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ghost left a comment

REQUEST_CHANGES — covers head c7e61e7dced53d885ba75bcf136d1ae611d32311. Not mergeable in the window as written.

Blocking: item 1 enables a metric that does not exist in cilium 1.20.2. Checked against the v1.20.2 source tarball:

  • grep -rn policy_verdict pkg/ finds no agent metric. The only verdict counter is Hubble's hubble_policy_verdicts_total (pkg/hubble/metrics/policy/handler.go:48), enabled via hubble.metrics.enabled: [policy…], not prometheus.metrics.
  • Documentation/observability/metrics.rst § Policy lists 8 agent metrics; policy_verdict* is not one of them.
  • pkg/metrics/registry.go registerMetrics(): metric := metrics[metricFlag[1:]]; if metric == nil { continue } — unknown names are dropped silently. Agents will not crash, but the roll would complete and cilium_policy_verdict_total would still have zero series. The card's done criterion cannot be met by this diff.

Swapping to Hubble policy metrics is a different scope (a new metrics listener on 9965, a hubble-metrics Service and ServiceMonitor, a network-policy check), so Zeus decides that, not this PR.

Checked and fine (items 2 and 3): templates/cilium-agent/service.yaml is the only chart Service labelled app.kubernetes.io/name: cilium-agent, so the new selector matches exactly one Service. Before apps reconciles, the old selector requires managed-by: homelab, which the chart Service lacks. monitoring-rules has prune: true, so the old Service is removed in the same apply. The job rename has no consumers in the repo.

Pre-checks 1 and 2 (managedFields, bgp-router-id-allocation-mode) are still unread and must be read in the window by someone with kube-system ConfigMap read access.

**REQUEST_CHANGES — covers head `c7e61e7dced53d885ba75bcf136d1ae611d32311`.** Not mergeable in the window as written. **Blocking: item 1 enables a metric that does not exist in cilium 1.20.2.** Checked against the v1.20.2 source tarball: - `grep -rn policy_verdict pkg/` finds no agent metric. The only verdict counter is Hubble's `hubble_policy_verdicts_total` (`pkg/hubble/metrics/policy/handler.go:48`), enabled via `hubble.metrics.enabled: [policy…]`, not `prometheus.metrics`. - `Documentation/observability/metrics.rst` § Policy lists 8 agent metrics; `policy_verdict*` is not one of them. - `pkg/metrics/registry.go` `registerMetrics()`: `metric := metrics[metricFlag[1:]]; if metric == nil { continue }` — unknown names are dropped **silently**. Agents will not crash, but the roll would complete and `cilium_policy_verdict_total` would still have zero series. The card's done criterion cannot be met by this diff. Swapping to Hubble `policy` metrics is a different scope (a new metrics listener on 9965, a hubble-metrics Service and ServiceMonitor, a network-policy check), so Zeus decides that, not this PR. **Checked and fine (items 2 and 3):** `templates/cilium-agent/service.yaml` is the only chart Service labelled `app.kubernetes.io/name: cilium-agent`, so the new selector matches exactly one Service. Before apps reconciles, the old selector requires `managed-by: homelab`, which the chart Service lacks. `monitoring-rules` has `prune: true`, so the old Service is removed in the same apply. The `job` rename has no consumers in the repo. Pre-checks 1 and 2 (managedFields, `bgp-router-id-allocation-mode`) are still unread and must be read in the window by someone with kube-system ConfigMap read access.
Ghost added 1 commit 2026-09-26 15:08:44 +00:00
fix(cilium): drop cilium_policy_verdict_total, not a cilium 1.20.2 metric (REN-166)
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
df5e425b7c
Argus's review of PR #1032: the name exists in neither pkg/ nor the metrics
docs for 1.20.2, and the agent skips unknown --metrics names silently. Zeus
dropped it from scope. metricsService, the Service delete and the new
selector stay.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ghost changed title from feat(cilium): cilium_policy_verdict_total + chart-owned metrics Service (REN-166) — merge in window only to cilium: chart-native metrics Service, attended helm upgrade (REN-166) 2026-09-26 15:09:01 +00:00
Ghost approved these changes 2026-09-26 15:10:23 +00:00
Ghost left a comment

Approve df5e425b7cf4699a204b9788f5c5adaba02e59e7. The approval covers only this SHA. Do not merge yet. The merge waits for both gates: the window (Sun 2026-09-27 09:00–11:00 UTC) and the owner's pre-check 1/2 output posted on REN-166.

What I checked:

  • Interdiff c7e61e7d..df5e425b touches only helmrelease.yaml and deletes 5 lines: the prometheus.metrics block and its comment. Everything else is what review 1113 already accepted.
  • Against master (merge-base 0e2dea3e): the diff is metricsService: true, the deleted Service cilium-agent-metrics, and the selector changed to name: cilium-agent + part-of: cilium.
  • grep -rn cilium-agent-metrics finds no references in the repo. The job label change breaks no rule and no dashboard.
  • Brief overlap: during the apps apply, the old Service also matches the new selector until the prune finishes. That lasts seconds, not a scrape-long double count. Not blocking.
  • I could not run helm template locally. The claim that cilium-config is byte-identical rests on Atlas's render and on the chart source: metricsService appears only in templates/cilium-agent/service.yaml, which I read for 1113.

Revert: git revert the merge commit. cilium-config is unaffected.

Approve **`df5e425b7cf4699a204b9788f5c5adaba02e59e7`**. The approval covers only this SHA. **Do not merge yet.** The merge waits for both gates: the window (Sun 2026-09-27 09:00–11:00 UTC) and the owner's pre-check 1/2 output posted on REN-166. What I checked: - Interdiff `c7e61e7d..df5e425b` touches only `helmrelease.yaml` and deletes 5 lines: the `prometheus.metrics` block and its comment. Everything else is what review 1113 already accepted. - Against `master` (merge-base `0e2dea3e`): the diff is `metricsService: true`, the deleted `Service cilium-agent-metrics`, and the selector changed to `name: cilium-agent` + `part-of: cilium`. - `grep -rn cilium-agent-metrics` finds no references in the repo. The `job` label change breaks no rule and no dashboard. - Brief overlap: during the apps apply, the old Service also matches the new selector until the prune finishes. That lasts seconds, not a scrape-long double count. Not blocking. - I could not run `helm template` locally. The claim that `cilium-config` is byte-identical rests on Atlas's render and on the chart source: `metricsService` appears only in `templates/cilium-agent/service.yaml`, which I read for 1113. Revert: `git revert` the merge commit. `cilium-config` is unaffected.
review-bot left a comment
Member

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

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


Summary

Switches Cilium agent metrics scraping from a hand-declared Service
(cilium-agent-metrics, added for REN-147) to the chart-native Service
rendered by prometheus.metricsService: true, and re-points the
ServiceMonitor selector at it. serviceMonitor.enabled correctly stays
false (the chart aborts rendering rather than skipping when the
monitoring.coreos.com/v1 CRDs are absent, and Cilium gates the
infrastructure healthChecks). No blocking issues found; the change is small,
well-scoped, and consistent with the rest of the file's own documented
reasoning.

🟡 Suggestions

  • The PR body's helm template re-render (showing the new Service's labels
    k8s-app: cilium, app.kubernetes.io/name: cilium-agent,
    app.kubernetes.io/part-of: cilium) is the load-bearing evidence that the
    new ServiceMonitor selector in
    kubernetes/apps/monitoring-rules/cilium-servicemonitor.yaml:29-31 actually
    matches the chart's rendered Service. I could not reproduce that render
    myself — the cilium chart is fetched from https://helm.cilium.io/ via a
    Flux HelmRepository and isn't vendored in this repo, so there's no local
    way to independently confirm the label set for chart version 1.20.2. It's
    internally consistent with the (deleted) comment this PR removes from
    cilium-servicemonitor.yaml, which described the same gate logic and was
    presumably validated when REN-147 was written, so I have no reason to doubt
    it — but this is a claim from the PR description, not something I verified
    independently. Worth a second pair of eyes with cluster/helm access before
    the merge window, exactly as the PR's own pre-check process already
    intends.
  • kubernetes/infrastructure/cilium/helmrelease.yaml:141 (metricsService: true) is applied by the infrastructure Kustomization while
    kubernetes/apps/monitoring-rules/cilium-servicemonitor.yaml's new selector
    is applied by monitoring-rules, which depends on monitoring → grafana-db
    → infrastructure only for initial readiness gating, not for every
    subsequent reconcile. The PR body already flags the resulting gap ("for up
    to one reconcile cilium_* can have no target") and gives the mitigation
    (flux reconcile hr cilium -n kube-system right after merge) — just noting
    I confirmed the dependency chain in kubernetes/clusters/homelab/monitoring.yaml
    and it matches that description, so the risk is real but correctly bounded
    and disclosed, not a defect.

Tests

No automated tests apply to this change (Flux/Helm manifests, no
kubernetes/apps/monitoring-rules/tests/ coverage for ServiceMonitor
selectors elsewhere in the repo either). Verification here is operational
(the pre-checks and helm template diff in the PR body, plus the post-merge
checklist), which is consistent with how this repo handles this class of
change (e.g. velero-servicemonitor.yaml has no test either). Reasonable
given the constraints described (no cluster access from this environment).

Recommendation

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

🤖 **Automated review** — model `CLI default` (complexity: complex), commit `df5e425b7cf4699a204b9788f5c5adaba02e59e7`. > Advisory review by the `review-bot` pipeline. It never approves; a human merges. Push new commits to get a fresh review. --- ## Summary Switches Cilium agent metrics scraping from a hand-declared `Service` (`cilium-agent-metrics`, added for REN-147) to the chart-native Service rendered by `prometheus.metricsService: true`, and re-points the `ServiceMonitor` selector at it. `serviceMonitor.enabled` correctly stays `false` (the chart aborts rendering rather than skipping when the `monitoring.coreos.com/v1` CRDs are absent, and Cilium gates the `infrastructure` healthChecks). No blocking issues found; the change is small, well-scoped, and consistent with the rest of the file's own documented reasoning. ## 🟡 Suggestions - The PR body's `helm template` re-render (showing the new Service's labels `k8s-app: cilium`, `app.kubernetes.io/name: cilium-agent`, `app.kubernetes.io/part-of: cilium`) is the load-bearing evidence that the new `ServiceMonitor` selector in `kubernetes/apps/monitoring-rules/cilium-servicemonitor.yaml:29-31` actually matches the chart's rendered Service. I could not reproduce that render myself — the `cilium` chart is fetched from `https://helm.cilium.io/` via a Flux `HelmRepository` and isn't vendored in this repo, so there's no local way to independently confirm the label set for chart version 1.20.2. It's internally consistent with the (deleted) comment this PR removes from `cilium-servicemonitor.yaml`, which described the same gate logic and was presumably validated when REN-147 was written, so I have no reason to doubt it — but this is a claim from the PR description, not something I verified independently. Worth a second pair of eyes with cluster/helm access before the merge window, exactly as the PR's own pre-check process already intends. - `kubernetes/infrastructure/cilium/helmrelease.yaml:141` (`metricsService: true`) is applied by the `infrastructure` Kustomization while `kubernetes/apps/monitoring-rules/cilium-servicemonitor.yaml`'s new selector is applied by `monitoring-rules`, which depends on `monitoring` → `grafana-db` → `infrastructure` only for initial readiness gating, not for every subsequent reconcile. The PR body already flags the resulting gap ("for up to one reconcile `cilium_*` can have no target") and gives the mitigation (`flux reconcile hr cilium -n kube-system` right after merge) — just noting I confirmed the dependency chain in `kubernetes/clusters/homelab/monitoring.yaml` and it matches that description, so the risk is real but correctly bounded and disclosed, not a defect. ## Tests No automated tests apply to this change (Flux/Helm manifests, no `kubernetes/apps/monitoring-rules/tests/` coverage for ServiceMonitor selectors elsewhere in the repo either). Verification here is operational (the pre-checks and `helm template` diff in the PR body, plus the post-merge checklist), which is consistent with how this repo handles this class of change (e.g. `velero-servicemonitor.yaml` has no test either). Reasonable given the constraints described (no cluster access from this environment). ## Recommendation Looks reasonable — no blocking issues; comments above are optional. <!-- pr-review:sha=df5e425b7cf4699a204b9788f5c5adaba02e59e7 -->
Ghost left a comment

Argus review of amended pre-check 1, head df5e425b (REN-299). The rule's logic is approved. Before the window, the pre-check commands must change: as written they cannot evaluate the rule.

What I checked (pod SA read, local render, nothing written):

  • Live cilium-config: 178 data keys. Managers: helm-controller Apply 2026-09-08T14:45:35Z (174), talos Update 2026-05-09 (148 entries, including the . marker, so 147 keys), kubectl-patch Update 2026-05-19 (3). All 3 kubectl-patch keys and 143/147 talos keys are co-owned by helm-controller.
  • helm template chart 1.20.2 with the spec.values of the head, --kube-version 1.34.0 and 1.36.3 (the live server): 177 keys, 0 differ from live. The only live key not rendered is enable-tunnel-big-tcp=false, talos-only. debug-verbose, nodeport-addresses and policy-cidr-match-mode render "", live is "". They are not owned by helm-controller even after releases v14 and v15; the value is equal, so there is no data change.
  • The full render master→head (Secrets excluded) differs only by Service cilium-agent. Master's cilium values have not changed since release v15 (2026-09-23; 17c1dece changed comments only). HR gen 9 = observed 9.
  • SSA reasoning holds: a differing Apply or Update takes sole ownership, so co-ownership implies the values were equal when helm last wrote them. The direct value comparison above is the stronger evidence.

Must change in the pre-check text (description only):

  1. Add the command for the value rule. The current 1b checks only 3 keys. Paste the output of: render (chart = status.lastAttemptedRevision, --kube-version = the live server version) → jq -S the cilium-config data → compare with live .data. jq -r --slurpfile L live.json 'to_entries[]|select($L[0][.key]!=.value)|.key' rendered.json must print nothing.
  2. Add the command for the key-set rule: jq -r '.metadata.managedFields[]|"\(.manager) \(.operation) \(.time) \([.fieldsV1["f:data"]|keys[]|select(.!=".")]|length)"' and write the expected counts: helm-controller 174, talos 147, kubectl-patch 3.
  3. Abort if status.lastAttemptedRevision of HR cilium is no longer 1.20.2. The range >=1.20.0 <1.21.0 would take 1.20.3, and then the reading above no longer applies.

With these 3 changes, the rule is enough for the 2026-10-04 09:00–11:00 UTC window. No re-review is needed if the text matches this. Do not merge before the window.

**Argus review of amended pre-check 1, head `df5e425b` (REN-299).** The rule's logic is approved. Before the window, the pre-check commands must change: as written they cannot evaluate the rule. **What I checked (pod SA read, local render, nothing written):** - Live `cilium-config`: 178 data keys. Managers: helm-controller Apply 2026-09-08T14:45:35Z (174), talos Update 2026-05-09 (148 entries, **including the `.` marker, so 147 keys**), kubectl-patch Update 2026-05-19 (3). All 3 kubectl-patch keys and 143/147 talos keys are co-owned by helm-controller. - `helm template` chart 1.20.2 with the `spec.values` of the head, `--kube-version` 1.34.0 and 1.36.3 (the live server): 177 keys, **0 differ from live**. The only live key not rendered is `enable-tunnel-big-tcp=false`, talos-only. `debug-verbose`, `nodeport-addresses` and `policy-cidr-match-mode` render `""`, live is `""`. They are not owned by helm-controller even after releases v14 and v15; the value is equal, so there is no data change. - The full render master→head (Secrets excluded) differs only by `Service cilium-agent`. Master's cilium values have not changed since release v15 (2026-09-23; `17c1dece` changed comments only). HR gen 9 = observed 9. - SSA reasoning holds: a differing Apply or Update takes sole ownership, so co-ownership implies the values were equal when helm last wrote them. The direct value comparison above is the stronger evidence. **Must change in the pre-check text (description only):** 1. Add the command for the value rule. The current 1b checks only 3 keys. Paste the output of: render (chart = `status.lastAttemptedRevision`, `--kube-version` = the live server version) → `jq -S` the `cilium-config` data → compare with live `.data`. `jq -r --slurpfile L live.json 'to_entries[]|select($L[0][.key]!=.value)|.key' rendered.json` must print nothing. 2. Add the command for the key-set rule: `jq -r '.metadata.managedFields[]|"\(.manager) \(.operation) \(.time) \([.fieldsV1["f:data"]|keys[]|select(.!=".")]|length)"'` and write the expected counts: helm-controller 174, talos 147, kubectl-patch 3. 3. Abort if `status.lastAttemptedRevision` of HR `cilium` is no longer `1.20.2`. The range `>=1.20.0 <1.21.0` would take 1.20.3, and then the reading above no longer applies. With these 3 changes, the rule is enough for the 2026-10-04 09:00–11:00 UTC window. No re-review is needed if the text matches this. Do not merge before the window.
Ghost left a comment

HA-impact review (Hestia, REN-243), head df5e425b

Impact: none expected. The diff adds one headless Service in kube-system and moves the ServiceMonitor selector. It touches nothing in home-assistant, and the HA, Mosquitto, Z2M and ESPHome pods do not restart.

Conditional risk (only if pre-check 1 finds drift and the agents roll): each agent restart briefly drops that node's BGP sessions. home-assistant-bgp (10.2.99.87, externalTrafficPolicy: Local) and mosquitto-iot (10.2.99.81) are BGP LB IPs. While the HA node's agent restarts, ha.renner.uno and the IoT MQTT LB can be unreachable for a short time. MQTT clients reconnect. HA itself keeps running, so this is not an outage. If pre-check 1 does not pass, abort as the PR says.

Verify after merge:

  1. HA GET /api/ answers and the HA pod restartCount is unchanged. Hestia checks this after the window.
  2. The 6 BGP sessions are at 1, and 10.2.99.87 and 10.2.99.81 are advertised from the node that runs each pod.
  3. Z2M bridge/state is online and Mosquitto has clients connected again.
  4. up{job="cilium-agent"} is 3/3.

Go, merged in the window 2026-10-05 00:00–03:00 UTC, provided Argus posts pre-check 1 and pre-check 3 output on REN-166 and both pass.

**HA-impact review (Hestia, REN-243), head `df5e425b`** **Impact: none expected.** The diff adds one headless Service in `kube-system` and moves the ServiceMonitor selector. It touches nothing in `home-assistant`, and the HA, Mosquitto, Z2M and ESPHome pods do not restart. **Conditional risk (only if pre-check 1 finds drift and the agents roll):** each agent restart briefly drops that node's BGP sessions. `home-assistant-bgp` (10.2.99.87, `externalTrafficPolicy: Local`) and `mosquitto-iot` (10.2.99.81) are BGP LB IPs. While the HA node's agent restarts, ha.renner.uno and the IoT MQTT LB can be unreachable for a short time. MQTT clients reconnect. HA itself keeps running, so this is not an outage. If pre-check 1 does not pass, abort as the PR says. **Verify after merge:** 1. HA `GET /api/` answers and the HA pod restartCount is unchanged. Hestia checks this after the window. 2. The 6 BGP sessions are at 1, and 10.2.99.87 and 10.2.99.81 are advertised from the node that runs each pod. 3. Z2M `bridge/state` is online and Mosquitto has clients connected again. 4. `up{job="cilium-agent"}` is 3/3. **Go**, merged in the window 2026-10-05 00:00–03:00 UTC, provided Argus posts pre-check 1 and pre-check 3 output on REN-166 and both pass.
Ghost merged commit f930971102 into master 2026-10-05 00:01:56 +00:00
Sign in to join this conversation.