infra_wiki

Kubernetes

The folly and offsite Kubernetes clusters, how Flux applies clusters/ from git, and the sandbox runtimes and storage the clusters offer.

The lab runs two Kubernetes clusters on Talos Linux nodes: folly at home and offsite at the remote site. clusters/<site>/talos/ declares each cluster's nodes. Flux, the GitOps controller in each cluster, applies clusters/ from main. Workloads lists what runs where.

Parts#

PartJobWhere it runs
Control planeRuns the API server, etcd and podsoptiplex on folly, retrofit on offsite
WorkersRun podsriptide and shale on folly, oldschool on offsite
Bootstrap rootThe OpenTofu root module that installs CoreDNS, flux-operator and the FluxInstance, the object that flux-operator installs Flux fromAtlantis
FluxApplies clusters/ from the infra GitRepositoryThe flux-system namespace
Shared controllersCloudNativePG, External Secrets, Kyverno and othersBoth clusters
Storagelocal-path volumes on each node's data volumeBoth clusters
GPU pluginOffers riptide's GPU to podsfolly

How state reaches a cluster#

A Flux Kustomization is a Flux object that applies one directory.

  1. instance.sync in the bootstrap root's flux-values.yaml points Flux at clusters/<site>/flux-system on main.
  2. That directory holds the Flux Kustomizations of the cluster, and includes clusters/base/flux-system/ for the shared controllers.
  3. Each Flux Kustomization applies its directory in dependsOn order. It decrypts SOPS files only with spec.decryption, and substitutes ${VAR} only from postBuild.substituteFrom.

Sandbox runtimes#

A pod that names no RuntimeClass runs on runc. Every node also has these:

RuntimeClassIsolation
gvisorgVisor, a user-space kernel (runsc)
kata-clhA Cloud Hypervisor microVM

Rowbutt sandboxes run on kata-clh. The packages/charts/app/ chart runs pods on gvisor unless its sandbox value is false.

On offsite, hotrod is the sandbox node. clusters/offsite/talos/talos.tf gives it the lolwtf.ca/sandbox label and a NoSchedule taint of the same key, so only a pod that selects the label and tolerates the taint lands there: the Rowbutt sandboxes, the GitHub Actions runners and the kthx engine's in-cluster build Jobs.

A DaemonSet that should run on every node tolerates the taint in its chart values. On folly, a node's labels and taints come from its class's workload in clusters/folly/fleet/classes.json, through the workloads map in clusters/folly/talos/talos.tf, which both the Talos root and fleetd's render read. Every folly class is general today, which sets none, so folly has no sandbox node. Talos hands a node's taints to the kubelet, which applies them when it registers the Node, and NodeRestriction refuses the kubelet's later change, so a taint declared for a registered node is applied once by hand with kubectl taint.

Signed Talos images#

.github/workflows/talos-images.yml builds folly's Secure Boot images with the siderolabs imager, and its netboot images with Nix. It uses the talos_version, extensions and any extra_kernel_args in clusters/folly/talos/talos.tf, and signs with the keys in PKI.

OutputWhereUse
Installerghcr.io/jonpulsifer/talos-installer-folly:<version>Installs and upgrades a node. Its UKI's talos.config is CONFIG_URL in the workflow: a node's first boot from disk, which has no STATE, fetches its config by the install code in META 0x0b, and every later boot loads the config from STATE. sd-boot's "Reset to maintenance mode" entry wipes STATE but not META, so that boot polls the URL with a burned code instead of entering maintenance mode. It carries no OAuth arguments.
ISOmetal-amd64-secureboot.iso on the release talos-folly/<version>Enrolls the Secure Boot keys at the machine, and rescues a node
iPXEipxe-amd64-secureboot.efi on the same releaseBoots HookOS from its menu. Talos boot has the details.
HookOS UKIhookos-amd64-secureboot-uki.efi on the same releaseRuns the Tinkerbell agent, which wipes a disk before a reinstall

A push to main rebuilds them when the version, the extensions, the kernel arguments, the public keys, clusters/folly/talos/netboot.json or the pipeline change. Nothing builds while netboot.json pins the all-zero agent digest, which the containers workflow's delivery replaces. The nodes still run the Image Factory installer that terraform/modules/talos-cluster/main.tf names. Build the signed Talos images is the procedure.

Rules#

  • Deploy a new containerd handler to every node from main before a workload names its RuntimeClass. The kubelet rejects a pod whose handler is missing.
  • Put a new CRD in its own Flux Kustomization, and add that to the dependsOn of each consumer. Flux dry-runs every object first, so one unknown kind stops the Flux Kustomization. If a HelmRelease installs the CRD, health-check that HelmRelease, as clusters/folly/flux-system/nodes.yaml does.
  • Keep helm.sh/resource-policy: keep in the prometheus-operator-crds HelmRelease values. Without it, an uninstall deletes the Prometheus Operator CRDs and every object of those kinds.
  • A change to only flux-values.yaml in clusters/<site>/bootstrap/ gets no autoplan. Plan and apply it as OpenTofu and Atlantis says.

Where it lives#

  • clusters/<site>/flux-system/ and clusters/base/flux-system/: the Flux Kustomizations
  • clusters/<site>/bootstrap/ and terraform/modules/flux-bootstrap/: the bootstrap root, with its state in gs://homelab-ng/clusters/<site>/bootstrap
  • clusters/base/platform/, clusters/*/storage/ and clusters/folly/nodes/: the shared controllers, storage and GPU plugin
  • clusters/folly/node-features/: the NodeFeatureRule that labels the GPU node
  • clusters/base/cluster-runtimeclass.yaml: the RuntimeClasses. The kata-containers and gvisor extensions in clusters/<site>/talos/talos.tf provide the handlers.
  • clusters/<site>/talos/talos.tf: each node's labels and taints, through terraform/modules/talos-cluster
  • clusters/<site>/config/: the settings, secrets and topology that Flux substitutes. See Topology.

Edit on GitHub

↑↓ move ↵ open esc close