0098. The Cilium bootstrap manifest is the embedded chart rendered with Helm

Status: Accepted · Date: 2026-10-28 · Design section: CNI · Extends: 0013, 0024

Decided: BootstrapManifest for the Cilium preset returns the pinned Cilium chart expanded with the preset values into plain manifests, rendered with Helm's Go SDK from a chart archive embedded in the binary, and the tunnel protocol is a preset option that defaults to Cilium's VXLAN

Context

ADR 0013 left chart expansion "to a later unit", so BootstrapManifest returned only the Helm values. ichor init --cni cilium put that values file in cluster.inlineManifests, which Talos cannot apply, so no CNI was installed and the first node stayed NotReady. On the e2e harness Cilium's default VXLAN tunnel also passes no traffic between nodes, while geneve works.

Decision

  • The chart cilium-1.18.0.tgz (the version ChartVersion pins, from https://helm.cilium.io/) is checked in under pkg/cni/cilium/chart/ and embedded with go:embed. make cilium-chart fetches it and refuses a file whose SHA-256 differs from the digest recorded in the Makefile. A test checks that the embedded chart's name and version equal ChartName and ChartVersion.
  • BootstrapManifest loads the archive, renders it with helm.sh/helm/v3 (engine, release name cilium, namespace kube-system) and the preset values, drops hooks and NOTES, and joins the rest in Helm's install order. There is no cluster access and no network. The output is deterministic. The 1.18.0 chart has no crds/ directory (the operator installs Cilium's CRDs itself), so none are included. The cilium-secrets namespace and its Roles come from the chart.
  • Rendered size with kube-proxy replaced is about 58 KB, which fits in the machine config comfortably.
  • Preset.WithTunnel("vxlan"|"geneve") sets tunnelProtocol in the values. The default is unset, so Cilium's VXLAN. The same values go to the Argo CD Application, so the hand-over still shows no diff (ADR 0024).

Why

Embedding means no network at init time and the same bytes every run, which helm template against a live repo cannot promise. Downloading at render time was rejected for that reason. Helm v3 is used because it builds with the Kubernetes libraries Ichor already pins; v4 would pull a second set.

Consequences

Moving to a newer Cilium means bumping ChartVersion, the Makefile digest and the archive together. The binary grows by about 230 KB plus Helm's dependencies. ichor init --cni-tunnel picks the tunnel protocol and stores it in spec.cni.tunnel on the TalosCluster, so ichor gitops export reproduces it in the Argo CD Application instead of reverting to Cilium's default.

Amendment 2026-11-03: the bootstrap renders for the release set's Kubernetes and pins one operator replica per control plane

Found on the rs2 hardware run (ADR 0113 amendment). The bootstrap render used Helm's default capabilities, a Kubernetes version older than 1.33, so the chart omitted hostUsers from the operator and agent pod templates. The hand-over runs real Helm on the cluster (1.36.6), which renders hostUsers: true. The pod template changed, the operator Deployment rolled, and on a one-node cluster the rollout could not finish (the second replica has no node and no free host port), so the hand-over stalled.

  • The render targets one Kubernetes version. renderChart takes its capabilities from RenderKubernetesVersion (v1.36.6, the set's Kubernetes; pkg/release checks they agree), so the bootstrap and the hand-over render the same templates. The goldens now carry hostUsers and the native AppArmor profile that the chart gates on this version.
  • The operator has one replica for one control plane. operator.replicas is 1 when the TalosCluster has one control plane (spec.controlPlane.count == 1, which covers the single preset and single-control-plane clusters whose workers come later). Any other count keeps the chart's default of 2. Cilium's docs (Context7, docs.cilium.io/en/stable/overview/component-overview, read 2026-11-03) say the operator "is not in the critical path for any forwarding or network policy decision" and that "a cluster will generally continue to function if the operator is temporarily unavailable"; its failures delay IPAM and the kvstore heartbeat. They recommend a high-availability setup for resilience (the BGP operation page), which a one-node cluster cannot have. The chart requires one operator pod per node through podAntiAffinity (chart 1.20.4 templates/cilium-operator/deployment.yaml), so a second replica on one node never schedules. The value is not an owned key: a user may still set operator.replicas in spec.cni.cilium.values.
  • Readback. The release record's operator.replicas says whether it was made for one control plane, so an adopt readback reconstructs the same values.

Covered by TestOperatorReplicasFollowTheControlPlaneCount (counts 0, 1, 2, 3) and the goldens.

The rule above covered only count == 1. An HA cluster has the same problem while it has one node: see ADR 0113, amendment 2026-11-04, where the replicas follow the nodes that exist.

Search the docs

Search pages and headings