Compare commits

..
Author SHA1 Message Date
claude-bot 78904a2d62 docs(cilium): VXLAN and BGP are not two layers doing one job
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
The proposal called the tunnel redundant because the underlay already has
routes to the pod CIDRs. That reads the two as alternatives, and they are not:
VXLAN carries pod-to-pod directly between nodes, while the BGP-advertised pod
CIDRs exist so traffic from the LAN can reach pods at all, which necessarily
goes through the UDM.

Removing the encapsulation would therefore not remove a layer. It would move
inter-node pod traffic off a direct node-to-node path and onto the underlay,
through the router.

The observation worth keeping is narrower: routingMode is set nowhere, so the
current behaviour is an unexamined chart default rather than a recorded
decision. It happens to be the right default.
2026-08-28 15:36:15 +02:00
claude-bot fd7e4a6acd docs(cilium): decide the ownership question before changing anything
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
Cilium is currently owned twice. The 22 objects that make up the whole
installation — both DaemonSets, the operator, both ConfigMaps, the RBAC, the
cilium-secrets namespace — are listed in Talos's bootstrap-manifest inventory
and simultaneously carry Helm's release annotations. cilium-config has three
field managers.

That is not a tidiness problem. It produced a bootstrap seed twelve days behind
the chart, where a Kubernetes upgrade would have downgraded the CNI and switched
off socket load balancing, and it left the BGP control-plane switch and both
native-routing CIDRs existing in no file at all, so a cold rebuild would have
come up without BGP while the peering CRDs in git said otherwise.

This records the decision — Flux owns Cilium, Talos provides only cni: none and
proxy: disabled — and the ordered transition, including why the order matters:
Talos prunes what its inventory used to list and no longer does, so removing the
manifest naively deletes the CNI.

Two claims in it are verified rather than assumed. An OS upgrade does not
reconcile bootstrap manifests: Talos went to 1.13.9 four days ago and Cilium
stayed at 1.20.1 despite the seed saying 1.19.2. And the prune can be tested
before it is run, because NoPrune is honoured by the dry run as well as the
apply.

No code changes here. PR #706 stays WIP until this is agreed.
2026-08-28 15:31:24 +02:00
k.renner e205f58b87 Merge pull request 'feat(woodpecker): liveness probe on the agent healthcheck' (#705) from woodpecker-agent-probe into master
ci/woodpecker/cron/issue-fix Pipeline was successful
ci/woodpecker/cron/alert-monitor Pipeline was successful
Reviewed-on: k.renner/homelab#705
2026-08-28 12:33:21 +00:00
claude-bot 1f3afcd7e3 feat(woodpecker): liveness probe on the agent healthcheck
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
The agent was the last workload in this repo running a real service with no
probe of any kind. WOODPECKER_HEALTHCHECK defaults to true and already serves
:3000, so nothing had to be enabled — the port was simply never declared and
never probed. Confirmed against the running pod: /healthz answers 200, / and
/metrics do not.

What it actually asserts is narrower than the name suggests, and narrower than
I assumed when proposing this. State.Healthy in agent/state.go returns false
only when a running workflow has passed its own timeout plus a one-hour buffer;
an agent with nothing to do always answers 200.

So this catches one specific failure — a wedged build holding a slot, which
today nobody would notice — and explicitly does not catch an agent that has
lost its connection to the server. That agent reports healthy while quietly
taking no work, which was the failure I originally wanted to cover. The comment
says so, because a green probe here is easy to misread as "CI is fine".

Liveness only. The agent fronts no Service, so a readiness probe would gate
nothing. Thresholds are short because the upstream condition already carries an
hour of debounce.

Everything else still unprobed is either operator- or chart-owned (Rook, the
authentik outposts, the kube-prometheus-stack sidecars, Multus, cert-manager,
mariadb-operator, velero node-agent) or a sidecar loop with no listening socket
where an exec probe would assert little beyond what container exit already
does.
2026-08-28 14:31:45 +02:00
k.renner 7a278da1df Merge pull request 'feat: give the remaining unprobed workloads a health check' (#704) from add-missing-probes into master
ci/woodpecker/cron/issue-fix Pipeline was successful
ci/woodpecker/cron/e2e Pipeline was successful
ci/woodpecker/cron/security-scan Pipeline was successful
ci/woodpecker/cron/alert-monitor Pipeline was successful
Reviewed-on: k.renner/homelab#704
2026-08-28 11:26:20 +00:00
k.renner b0df454fe4 Merge pull request 'fix(paperless): probe a real Django view instead of a TCP connect' (#703) from paperless-http-probes into master
ci/woodpecker/cron/alert-monitor Pipeline was successful
Reviewed-on: k.renner/homelab#703
2026-08-28 11:03:00 +00:00
k.renner 09c10e3fa6 Merge pull request 'fix(gatus): harden the deployment, probe ical-proxy and Loki, add Paperless' (#702) from gatus-hardening into master
ci/woodpecker/cron/alert-monitor Pipeline was successful
Reviewed-on: k.renner/homelab#702
2026-08-28 10:45:50 +00:00
k.renner f1445a2217 Merge pull request 'fix(gatus): drop the file.renner.uno check left behind by #696' (#701) from gatus-drop-file-check into master
ci/woodpecker/cron/issue-fix Pipeline was successful
ci/woodpecker/cron/alert-monitor Pipeline was successful
Reviewed-on: k.renner/homelab#701
2026-08-28 09:55:25 +00:00
k.renner 64462f540a Merge pull request 'fix(gatus): reload the pod when its config changes' (#700) from gatus-config-reload into master
ci/woodpecker/cron/alert-monitor Pipeline was successful
Reviewed-on: k.renner/homelab#700
2026-08-28 09:45:15 +00:00
2 changed files with 135 additions and 0 deletions
+110
View File
@@ -0,0 +1,110 @@
# Cilium: one owner
## The rule
Cilium is owned by **Flux**, through `infrastructure/cilium/helmrelease.yaml`.
Talos provides the conditions for a CNI and nothing else. Never both.
Talos-only would also be a valid answer — it is the simpler one, and it keeps a
cold rebuild self-forming. It is rejected here only because it costs Flux's
reconciliation, image automation and value ergonomics for the one component
most likely to need a careful, reviewed change.
## Where we are
Both own it, which is the state this document exists to end.
- 22 Cilium objects are listed in the `talos-bootstrap-manifests-inventory`
ConfigMap: both DaemonSets, the operator Deployment, both ConfigMaps, the
ServiceAccounts, the ClusterRoles/Bindings and the `cilium-secrets`
namespace. That is the whole installation, not a fragment.
- The same objects carry `meta.helm.sh/release-name: cilium`.
- `cilium-config` has three field managers: `helm-controller`, `talos` and
`kubectl-patch`.
The consequences are not theoretical. The seed sat at Cilium 1.19.2 for twelve
days while the chart was on 1.20.1, and `upgrade-k8s --dry-run` shows that a
Kubernetes upgrade in that state downgrades the CNI and switches off socket
load balancing. Separately, `enable-bgp-control-plane` and the two
native-routing CIDRs exist in no file at all — applied by hand, so a cold
rebuild would have come up with BGP off while the peering CRDs in git made it
look configured.
Nothing fails when these drift. That is the whole problem.
## Target state
**Talos** keeps exactly this, and gains nothing else:
```yaml
cluster:
network:
cni:
name: none
proxy:
disabled: true
```
No `inlineManifests` entry for cilium. `talos/patches/cilium-cni.yaml` is
deleted.
**Flux** owns everything else. `helmrelease.yaml` is the single source for
every Cilium setting, including the three that were hand-patched.
**A cold rebuild** installs Cilium once, by hand, between `talosctl bootstrap`
and the Flux phase, from the same HelmRelease values. This is the cost of the
decision and it is deliberate: the cluster no longer forms itself from the
machine config alone. Given `bootstrap.sh talos` is already dead and Step 2 of
RUNBOOK-cold-rebuild.md is already a manual procedure, the marginal cost is one
documented command.
## Transition
The order matters, because Talos prunes what its inventory used to list and no
longer does — removing the manifest naively deletes the CNI.
1. **Track the untracked settings first.** `bgpControlPlane.enabled`,
`ipv4NativeRoutingCIDR`, `ipv6NativeRoutingCIDR` into the HelmRelease. Note
this is not a pure change of ownership: enabling bgpControlPlane through the
chart also emits five companion keys the hand-patch never set, so Cilium has
been running on its own defaults for router-id allocation. Verify the BGP
sessions after Flux applies it.
2. **Remove cilium from `cluster.inlineManifests`.** This has no immediate
effect. Evidence: Talos was upgraded to 1.13.9 four days ago and Cilium
stayed at 1.20.1 — an OS upgrade does not reconcile bootstrap manifests.
Only `upgrade-k8s` does.
3. **Prove the prune before running it.** `upgrade-k8s --dry-run
--manifests-no-prune` and confirm the 22 objects are not listed for
deletion. `NoPrune` is honoured by the dry run as well as the apply
(`pkg/cluster/kubernetes/talos_managed.go`), so this is a real test and not
a reading of the flag description.
4. **Run `upgrade-k8s --manifests-no-prune`.** The inventory is rewritten
without Cilium and the objects survive. Helm is then sole owner. This is the
same run that takes Kubernetes 1.35.2 → 1.36.3.
5. **Verify** the inventory lists no Cilium, the agents did not restart, and
the BGP sessions are still established.
6. **Delete `talos/patches/cilium-cni.yaml`** and add the install step to
RUNBOOK-cold-rebuild.md.
## Not part of this, and not a defect either
The cluster runs `routingMode: tunnel` (the chart default, set nowhere) while
also advertising pod CIDRs over BGP. That is not one layer too many — the two
carry different directions:
- **VXLAN** carries pod-to-pod between nodes, directly, node to node.
- **BGP** gives the UDM routes into the pod CIDRs so traffic from the LAN can
reach pods. That path goes through the router by definition.
Dropping the encapsulation in favour of native routing would not simplify
anything; it would push inter-node pod traffic onto the underlay path and
through the UDM, replacing a direct node-to-node hop with a router hop.
`routingMode` being unset is still worth knowing — the current behaviour is a
default nobody chose rather than a decision anyone recorded — but the default
is the right one here, and this document is not the place to revisit it.
+25
View File
@@ -94,6 +94,31 @@ spec:
envFrom:
- secretRef:
name: woodpecker-secret
ports:
# WOODPECKER_HEALTHCHECK defaults to true and serves :3000, so this
# port already existed; declaring it just lets the probe name it.
- name: health
containerPort: 3000
protocol: TCP
# /healthz answers 500 ONLY when a running workflow has passed its own
# timeout plus a one-hour buffer (agent/state.go, State.Healthy). An
# idle agent always answers 200.
#
# So this catches a wedged build holding a slot, which is otherwise
# invisible — and it does NOT catch an agent that has lost its server
# connection, which reports healthy while quietly taking no work. Do
# not read a green probe here as "CI is fine".
#
# Liveness only: the agent fronts no Service, so readiness would gate
# nothing. The upstream condition is already debounced by that hour,
# which is why the thresholds here are short.
livenessProbe:
httpGet:
path: /healthz
port: health
initialDelaySeconds: 20
periodSeconds: 30
timeoutSeconds: 5
resources:
requests:
cpu: 25m