Skip to content

Kured

Kured applies the updates the nodes have staged. It watches every node for a sentinel file and, when it finds one, takes a cluster-wide lock, cordons and drains the node, reboots it and uncordons it when it is back. Flatcar's own locksmithd is masked because it reboots without draining, on a cluster where every node is a storage node.

At a glance

Namespace kured
Depends on Monitoring for the alerts it gates on
If it is down Nothing visible: staged OS and sysext updates simply never get applied
Health check kubectl -n kured logs -l app.kubernetes.io/name=kured --tail=50
Files payload/platform/kured/application.yaml

Configuration

An update stages on disk and signals it by touching /run/reboot-required, Kured's default sentinel. /run is tmpfs, so the marker clears itself on reboot.

Mechanism Stages Signals by
Flatcar OS (update-engine) New image on the passive A/B partition flatcar-reboot-sentinel.timer
Kubernetes sysext New .raw under /opt/extensions/kubernetes systemd-sysupdate drop-in
flowchart TD
    OS["update-engine<br/>OS image on the passive partition"] --> SEN
    KUBE["systemd-sysupdate<br/>kubernetes sysext"] --> SEN

    SEN["/run/reboot-required<br/>sentinel, on tmpfs"] --> TICK{"Kured checks<br/>every period"}

    TICK -->|"no sentinel"| SLEEP["Nothing to do until<br/>an update stages one"]
    TICK -->|"sentinel present"| ALERT{"Blocking alert firing?<br/>Ceph, etcd, node, API, Velero"}
    ALERT -->|"yes"| HELD["Reboot deferred while<br/>the cluster is unhealthy"]
    ALERT -->|"no"| LOCK{"Cluster lock free?"}
    LOCK -->|"another node<br/>is rebooting"| TAINT["Taint PreferNoSchedule,<br/>wait for the lock"]
    LOCK -->|"acquired"| DRAIN["Cordon and drain"]
    DRAIN -->|"drain fails"| GIVEUP["No reboot: uncordon,<br/>release the lock, retry"]
    DRAIN -->|"drained"| BOOT["systemctl reboot;<br/>new partition and<br/>sysexts take effect"]
    BOOT --> UP["Node comes back,<br/>sentinel gone with tmpfs"]
    UP --> DONE["Uncordon, drop the annotation,<br/>release the lock after a delay"]

    style SEN stroke-width:3px

The values are in payload/platform/kured/application.yaml; the reasoning:

Setting Why
No reboot window Kured acts as soon as it sees a sentinel; the guards below make that safe, not the clock
concurrency and lockReleaseDelay One node down at a time. The rebooted node is uncordoned as soon as Kured's pod is back; the delay only holds the next node, long enough for the OSD to rejoin and the few minutes of backfill a reboot leaves, and as long as CephOSDDown's for, so an OSD that did not come back is a firing alert before the next drain
forceReboot: false A node that will not drain is a node worth looking at
preferNoScheduleTaint A node pending reboot stops attracting pods about to be evicted again
annotateNodes: true kubectl get node -o yaml shows why a node is cordoned
VeleroBackupInProgress on the alert list Velero marks any backup it finds running at startup as Failed, and draining its node restarts it. The rule in payload/platform/velero/application.yaml compares attempts with outcomes per live Velero pod over a window longer than Velero's default limit on asynchronous operations; a backup that hangs drops out of the window, so it holds reboots for that long at most. A backup that starts in a Velero pod's first scrape interval has no zero sample to increase from and does not hold the gate. It routes to the null receiver because it fires every night
alertFilterRegexp with alertFilterMatchOnly: true Kured asks Prometheus before taking a node down and refuses while any Ceph, etcd, node or API alert on the list fires. This is the automated "confirm Ceph has recovered" step from Rebooting a node: a second reboot during a backfill can take a placement group below its minimum replica count. Kured matches the alert name only, so Prometheus loads rules from platform namespaces only

The regex means the opposite of what it looks like

alertFilterRegexp normally lists alerts to ignore. With alertFilterMatchOnly: true these become the only alerts that block, because something is almost always firing in a homelab and blocking on any alert would mean never rebooting. An alert not on the list does not stop a reboot, so widen it if something turns out to matter — and read the flag twice before editing it. A name that no PrometheusRule defines blocks nothing and fails silently; the list once carried eight such names. Check every entry against the rules that exist, with the last command under Health check.

Usage

To stop Kured rebooting anything without uninstalling it, take its cluster lock by hand; it holds until the annotation is removed. Clearing the sentinel does not work, because flatcar-reboot-sentinel.timer in ansible/templates/butane_node_config.yaml.j2 re-touches it while an update is staged, and a DaemonSet cannot be scaled.

kubectl -n kured annotate ds kured weave.works/kured-node-lock='{"nodeID":"manual"}'
kubectl -n kured annotate ds kured weave.works/kured-node-lock-    # release it

To force a node to reboot on the next pass:

ssh core@<node> sudo touch /run/reboot-required

NodeRebootPending in payload/platform/monitoring/node-update-rules.yaml fires when the sentinel has been there for two days: Kured reboots one node at a time and refuses while Ceph or etcd is unhealthy, so a marker that old is a blocked node, not a queued one.

Health check

# Which nodes want a reboot: kured_reboot_required is the sentinel's presence, per node
kubectl -n monitoring port-forward svc/kube-prometheus-stack-prometheus 9090:9090 &
curl -s localhost:9090/api/v1/query --data-urlencode 'query=kured_reboot_required == 1' \
  | jq -r '.data.result[].metric.node'

# Which node is being rebooted right now
kubectl get nodes -o json | jq -r '.items[] | select(.metadata.annotations["weave.works/kured-reboot-in-progress"]) | .metadata.name'

# Whether a node has staged anything
ssh core@<node> 'ls -l /run/reboot-required; update_engine_client -status'

kubectl -n kured logs -l app.kubernetes.io/name=kured --tail=50

# The alert names the gate may use: every entry in alertFilterRegexp must be here
kubectl get prometheusrule -A -o json | jq -r '.items[].spec.groups[].rules[] | select(.alert) | .alert' | sort -u | grep -E 'Ceph|etcd|KubeNode|KubeAPI|VeleroBackupInProgress'