When a software process inside a container crashes, the local kubelet restarts the container in seconds. When a node’s physical hardware fails, the kernel deadlocks, or the network interface drops, the problem transcends container orchestration: the underlying compute instance itself must be diagnosed, isolated, and remediated.
In unmanaged cloud environments, operators often rely on cloud autoscaling groups (ASGs, MIGs) for instance health. However, cloud health checks only monitor basic hypervisor liveness. They are completely blind to Kubernetes-level failures: corrupt Container Network Interface (CNI) state, deadlocked storage drivers, unmounted kubelet partitions, or silent etcd communication partitions.
Cluster API solves this through the MachineHealthCheck (MHC) controller. MHC bridges workload cluster Node health signals into declarative management cluster lifecycle operations.
Crucially, MachineHealthCheck is a detection and gating engine, not a repair script. It evaluates whether machines violate health policies and authorizes remediation, delegating actual machine replacement or power-cycling to higher-level controllers under strict safety limits.
1. The Detection vs. Remediation Architectural Split
A common misconception is that MachineHealthCheck deletes VMs or runs reboot commands. In idiomatic Kubernetes controller design, responsibilities are cleanly decoupled:
flowchart TD
NODE["Workload Node<br/>(Reports conditions: Ready, DiskPressure)"] -->|"Informer watches Node status"| MHC["MachineHealthCheck Controller<br/>(Detection & Safeguard Gating)"]
M["Machine CR<br/>(Binds providerID to Node)"] -->|"Monitored by"| MHC
MHC -->|"Condition: RemediationAllowed=True"| DECIDE{"Remediation Strategy"}
DECIDE -->|"Owner-Based (Default)"| OWNER["Owner Controller<br/>(MachineSet / KubeadmControlPlane)"]
DECIDE -->|"External Template"| EXT["External Remediation Controller<br/>(Metal3, AWS, SelfNodeRemediation)"]
OWNER -->|"Drains, deletes & replaces"| NEW_M["New Machine & Cloud VM"]
EXT -->|"In-place power-cycle / reboot"| BMC["Hardware BMC / Cloud API"]
Anchor on this division of responsibilities:
| Component | Primary Responsibility | Failure Mode Handled |
|---|---|---|
Node (Workload) | Reports local operating system and kubelet health conditions | Kubelet unreachability, disk full, container runtime crash |
MachineHealthCheck | Continuously evaluates health conditions against timeout thresholds and validates cluster-wide blast-radius guards | Transient flap suppression, cascading failure mitigation |
MachineSet (Worker Owner) | Enforces replica counts: drains and terminates unhealthy machines, provisioning fresh replacement hosts | Permanent host degradation, hardware lockup |
KubeadmControlPlane (CP Owner) | Orchestrates control-plane remediation while strictly preserving etcd quorum safety | Control-plane node isolation, corrupt control-plane services |
| External Remediator | Implements domain-specific out-of-band recovery (e.g., BMC IPMI power reset or hypervisor reboot) | Bare-metal recovery, persistent storage preserved |
By separating health observation from remediation execution, Cluster API ensures that broken machines are replaced according to the same declarative blueprints used for routine provisioning.
2. Anatomy of a MachineHealthCheck Resource
A production MachineHealthCheck manifest specifies which machines to watch, the conditions that define unhealthiness, and the timeout thresholds required before triggering remediation:
apiVersion: cluster.x-k8s.io/v1beta1
kind: MachineHealthCheck
metadata:
name: worker-healthcheck
namespace: default
spec:
clusterName: prod-cluster
# Target specific worker pools using label selectors
selector:
matchLabels:
nodepool: general-workers
# Maximum duration to wait for a newly provisioned Machine to register a Node
nodeStartupTimeout: 10m
# Criteria defining an unhealthy workload node
unhealthyConditions:
- type: Ready
status: "False"
timeout: 300s
- type: Ready
status: "Unknown"
timeout: 300s
- type: DiskPressure
status: "True"
timeout: 10m
# Blast-radius safeguard: halt remediation if too many nodes are failing
maxUnhealthy: "20%"
The Three Core Elements
- Target Selector (
selector): Identifies the pool ofMachineresources to monitor. Never combine control plane and worker machines in the same health check policy; control plane nodes have distinct failure characteristics and quorum requirements. - Health Conditions (
unhealthyConditions): Maps directly toNode.status.conditions. A machine is marked for remediation only when an observed condition matches the target status continuously for the entire duration oftimeout. - Startup Timeout (
nodeStartupTimeout): Defines how long aMachinecan remain in the management cluster without a correspondingNoderegistering in the workload cluster.
3. Detecting Failure: Two Critical Signals
Cluster API monitors two distinct categories of failure: running host degradation and initialization stall (zombie hosts).
sequenceDiagram
autonumber
participant Node as Workload Node
participant M as Machine Controller
participant MHC as MachineHealthCheck Controller
participant MS as MachineSet Controller
Note over Node,MHC: Scenario A: Node Degrades in Service
Node->>M: Kubelet stops sending heartbeats
M->>M: Node condition transitions to Ready=Unknown
MHC->>MHC: Starts unhealthy timer (timeout: 300s)
Note over MHC: If Ready recovers before 300s, timer aborts
MHC->>MHC: 300s elapsed: Evaluates maxUnhealthy guards
MHC->>M: Sets condition: RemediationAllowed=True
MHC->>M: Sets annotation: cluster.x-k8s.io/remediate-machine
MS->>M: Initiates node drain and machine deletion
MS->>M: Creates replacement Machine from template
Note over M,MHC: Scenario B: Startup Initialization Failure
M->>M: Machine provisioned, cloud VM launched
Note over M: No Node registers with matching providerID
MHC->>MHC: nodeStartupTimeout (10m) timer elapses
MHC->>M: Sets condition: RemediationAllowed=True
MS->>M: Deletes stuck VM, provisions fresh replacement
Signal 1: The Node Condition Timeout
The Kubernetes node lifecycle controller marks a node Ready=Unknown when it stops receiving heartbeats (defaulting to after 40 seconds of missed heartbeats). However, taking down a cloud VM immediately after 40 seconds is dangerous: temporary network congestion or a routine kubelet binary upgrade would trigger catastrophic node replacement.
MachineHealthCheck introduces an intentional dampening period via timeout:
- If the node recovers to
Ready=Truebefore the timeout expires, the timer resets and no action is taken. - If the condition persists across the full window (e.g., 300 seconds), MHC transitions the machine to an unhealthy state.
Signal 2: The Startup Timeout (Zombie Hosts)
A common cloud infrastructure failure mode is a VM that launches successfully in AWS/Azure/GCP, but never becomes a Kubernetes node. Common causes include:
- Exhausted subnet IP addresses during CNI initialization.
- Corrupted cloud-init userdata or expired kubeadm join tokens.
- Security group rules blocking outbound traffic to the control plane on port 6443.
In this scenario, the Machine status shows Running (the VM exists), but Machine.status.nodeRef remains nil. Without nodeStartupTimeout, this machine would sit idle forever, consuming cloud budget while starving application capacity. Once nodeStartupTimeout expires without a bound node, MHC marks the machine unhealthy and requests a replacement.
4. Remediation Mechanisms: Owner-Based vs. External
Once a machine is declared unhealthy, how is it remediated? Cluster API supports two architectural paradigms:
flowchart TD
UNHEALTHY["Unhealthy Machine Confirmed"] --> GATE{"Evaluate maxUnhealthy Guard"}
GATE -->|"Threshold Exceeded"| PAUSE["Pause Remediation & Emit Warning Event"]
GATE -->|"Within Threshold"| ROUTE{"Configured Remediation Mode"}
ROUTE -->|"Default (Owner-Based)"| WORKER_CHECK{"Is Worker or Control Plane?"}
WORKER_CHECK -->|"Worker (MachineSet)"| MS_REM["1. Cordon & Drain Node<br/>2. Delete Machine CR<br/>3. MachineSet spawns replacement"]
WORKER_CHECK -->|"Control Plane (KCP)"| KCP_CHECK{"Is etcd Quorum Safe?"}
KCP_CHECK -->|"Quorum Safe (3/3 healthy)"| KCP_REM["1. Remove etcd member<br/>2. Delete Machine CR<br/>3. KCP launches replacement"]
KCP_CHECK -->|"Quorum at Risk"| KCP_BLOCK["Block Remediation to Prevent etcd Split-Brain"]
ROUTE -->|"External (Template)"| EXT_REM["1. Instantiate External CR<br/>2. External controller reboots host / fences BMC<br/>3. Clears remediation annotation on recovery"]
1. Owner-Based Remediation (Default)
In the default configuration, MHC does not specify a remediationTemplate. It delegates the remediation lifecycle directly to the object that owns the machine:
Worker Nodes (MachineSet Owner)
The MachineSet controller sees the remediation signal. It initiates the standard machine deletion sequence:
- Cordons the workload node.
- Invokes the Pod Eviction API respecting
PodDisruptionBudgets. - Detaches CSI volumes.
- Deletes the cloud VM.
- Decrements observed replicas and immediately provisions a brand-new
Machinefrom the template.
Control-Plane Nodes (KubeadmControlPlane Owner)
Remediating a control plane node is high risk because etcd state is stateful. If a control plane member is deleted while etcd quorum is degraded, the cluster can lose its state store entirely.
KubeadmControlPlane implements strict preflight safety checks before executing remediation:
- The control plane must have at least 3 replicas.
- All other control plane machines must report
Ready=True. - Etcd membership checks confirm that removing the failed member will not cause quorum loss.
- If quorum is at risk, KCP blocks remediation until healthy nodes recover or an operator intervenes.
2. External Remediation
For bare-metal environments or edge deployments, deleting and reprovisioning a host is slow and disruptive. External remediation allows plugging in specialized controllers:
spec:
remediationTemplate:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: Metal3RemediationTemplate
name: metal3-remediation
Instead of deleting the Machine, MHC instantiates a concrete Metal3Remediation CR. An external controller intercepts this object and performs out-of-band recovery: triggering an IPMI power cycle, resetting a hardware Baseboard Management Controller (BMC), or rebooting the hypervisor. If the host recovers, the external controller clears the remediation condition, avoiding VM recreation entirely.
5. Blast-Radius Safeguards: The maxUnhealthy Guard
The most dangerous operational failure in automated infrastructure self-healing is cascading remediation storms.
Consider a top-of-rack network switch failure or an accidental DNS misconfiguration that isolates an entire availability zone. Instantly, 40 out of 50 worker nodes transition to Ready=Unknown.
If an automated controller indiscriminately deleted all 40 machines simultaneously:
- Workloads would face catastrophic cluster-wide eviction.
- Cloud APIs would be hammered with dozens of simultaneous VM provisioning calls, triggering rate limits and quota lockouts.
- Any stateful volume mounts would become trapped in unmount storms.
To prevent this, MachineHealthCheck enforces strict blast-radius guards:
spec:
# May be specified as an absolute integer (e.g., 2) or a percentage (e.g., "20%")
maxUnhealthy: "20%"
How the Guard Evaluates
- MHC continuously tallies the total number of machines matched by its
selector. - It calculates the current number of unhealthy machines.
- If the percentage of unhealthy machines exceeds
maxUnhealthy, remediation is immediately suspended for all machines in the pool:
Pool size: 10 machines
maxUnhealthy: 20% (threshold: 2 machines)
Case A: 1 machine unhealthy → 1 <= 2 → Remediation executes.
Case B: 3 machines unhealthy → 3 > 2 → Remediation HALTED. Alert emitted.
When suspended, MHC sets the condition RemediationAllowed = False on the health check resource and emits a Kubernetes Warning event: RemediationRestricted. This forces automated recovery to pause, leaving the cluster in place for operator investigation rather than destroying surviving nodes.
6. Practical Inspection and Diagnostic Workflows
When implementing automated self-healing, you must be able to verify whether health checks are tracking correctly and diagnose why remediation might be blocked.
flowchart TD
START["Machine Unhealthy but Not Remediated"] --> C1{"Does MHC Selector Match?"}
C1 -->|"No"| F1["Fix Label Selector on MHC or Machine"]
C1 -->|"Yes"| C2{"Has Timeout Elapsed?"}
C2 -->|"No"| F2["Normal behavior: Awaiting timeout window"]
C2 -->|"Yes"| C3{"Is maxUnhealthy Exceeded?"}
C3 -->|"Yes"| F3["Remediation Blocked: Circuit breaker tripped<br/>Inspect cluster-wide network/infrastructure"]
C3 -->|"No"| C4{"Is Owner Controller Supported?"}
C4 -->|"Standalone Machine"| F4["Unsupported: Machine requires MachineSet or KCP owner"]
C4 -->|"Supported Owner"| C5{"Is Control Plane Quorum Degraded?"}
C5 -->|"Yes"| F5["KCP Protection: etcd quorum at risk<br/>Restore other CP nodes first"]
C5 -->|"No"| F6["Inspect Owner Controller Logs for Drain/Cloud API Errors"]
Essential Diagnostic Commands
1. Inspect Health Check Status
kubectl get machinehealthchecks -o wide
Output displays:
EXPECTEDMACHINES: Count of machines matched by the selector.CURRENTHEALTHY: Number of nodes currently reporting healthy conditions.MAXUNHEALTHY: Configured threshold limit.
2. Detailed Condition Inspection
kubectl describe machinehealthcheck <mhc-name>
Look at the Status conditions:
HealthCheckSucceeded = True: All evaluated nodes are currently passing checks.RemediationAllowed = Falsewith reasonTooManyUnhealthy: Alerts that the circuit breaker has tripped and automated replacement is disabled.
3. Inspect the Target Machine
kubectl describe machine <machine-name>
Look for:
Conditions: CheckNodeReadyandMachineHealthCheckSucceded.Annotations: Look forcluster.x-k8s.io/remediate-machine. If present, MHC has signaled the owner to initiate replacement.
7. Key Takeaways
- Decoupled Responsibilities:
MachineHealthCheckdetects unhealthy states and gates remediation safety. The machine’s owner controller (MachineSetorKubeadmControlPlane) executes the actual drain and replacement. - Node Conditions as Telemetry: MHC bridges workload
Node.status.conditions(Ready,DiskPressure) to managementMachinelifecycles, using mandatory timeout dampening to prevent flapping. - Startup Timeout Prevents Zombies:
nodeStartupTimeoutensures that machines whose infrastructure provisioned but whose OS bootstrap or CNI failed are automatically purged and retried. - Circuit Breaker Protection: The
maxUnhealthyguard prevents automated self-healing from converting a partial infrastructure blip into a catastrophic cluster-wide deletion cascade. - Control Plane Quorum Safety: Control plane machine remediation is gated by etcd quorum availability; Cluster API refuses to remediate a control plane node if doing so risks losing cluster consensus.
Next in the series
Now that we understand how Cluster API automates node failure detection and remediation, explore Part 10: Kubernetes Upgrades with Cluster API to learn how CAPI orchestrates zero-downtime, rolling control plane and worker fleet upgrades across Kubernetes versions.