CNI Readiness
In many Kubernetes clusters, the CNI plugin runs as a DaemonSet. When a new node joins the cluster, there is a race condition:
- The Node object is created and marked
Readyby the Kubelet. - The Scheduler sees the node as
Readyand schedules application pods. - However, the CNI DaemonSet might still be initializing networking on that node.
This guide demonstrates how to use the Node Readiness Controller to prevent pods from being scheduled on a node until the Container Network Interface (CNI) plugin (e.g., Calico) is fully initialized and ready.
The high-level steps are:
- Node is bootstrapped with a startup taint
readiness.k8s.io/NetworkReady=pending:NoScheduleimmediately upon joining. - A sidecar is patched to the cni-agent to monitor the CNI’s health and report it to the API server as node-condition (
network.k8s.io/CalicoReady). - Node Readiness Controller will untaint the node only when the CNI reports it is ready.
Step-by-Step Guide
This example uses Calico, but the pattern applies to any CNI.
Note: You can find all the manifests used in this guide in the
examples/cni-readinessdirectory.
1. Deploy the Readiness Condition Reporter
We need to bridge Calico’s internal health status to a Kubernetes Node Condition. We will add a sidecar container to the Calico DaemonSet.
This sidecar checks Calico’s local health endpoint (http://localhost:9099/readiness) and updates a node condition network.k8s.io/CalicoReady.
Patch your Calico DaemonSet:
# cni-patcher-sidecar.yaml
- name: cni-status-patcher
image: registry.k8s.io/node-readiness-controller/node-readiness-reporter:v0.1.1
imagePullPolicy: IfNotPresent
env:
- name: NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: CHECK_ENDPOINT
value: "http://localhost:9099/readiness" # update to your CNI health endpoint
- name: CONDITION_TYPE
value: "network.k8s.io/CalicoReady" # update this node condition
- name: CHECK_INTERVAL
value: "15s"
resources:
limits:
cpu: "10m"
memory: "32Mi"
requests:
cpu: "10m"
memory: "32Mi"
Note: In this example, the CNI pod health is monitored by a side-car, so watcher’s lifecycle is same as the pod lifecycle. If the Calico pod is crashlooping, the sidecar will not run and cannot report readiness. For robust ‘continuous’ readiness reporting, the watcher should be ‘external’ to the pod.
2. Grant Permissions (RBAC)
The sidecar needs permission to update the Node object’s status.
# calico-rbac-node-status-patch-role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: node-status-patch-role
rules:
- apiGroups: [""]
resources: ["nodes/status"]
verbs: ["patch", "update"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: calico-node-status-patch-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: node-status-patch-role
subjects:
# Bind to CNI's ServiceAccount
- kind: ServiceAccount
name: calico-node
namespace: kube-system
3. Create the Node Readiness Rule
Now define the rule that enforces the requirement. This tells the controller: “Keep the readiness.k8s.io/NetworkReady taint on the node until network.k8s.io/CalicoReady is True.”
# network-readiness-rule.yaml
apiVersion: readiness.node.x-k8s.io/v1alpha1
kind: NodeReadinessRule
metadata:
name: network-readiness-rule
spec:
# The condition(s) to monitor
conditions:
- type: "network.k8s.io/CalicoReady"
requiredStatus: "True"
# The taint to manage
taint:
key: "readiness.k8s.io/NetworkReady"
effect: "NoSchedule"
value: "pending"
# "bootstrap-only" means: once the CNI is ready once, we stop enforcing.
enforcementMode: "bootstrap-only"
# Update to target only the nodes that need to be protected by this guardrail
nodeSelector:
matchLabels:
node-role.kubernetes.io/worker: ""
Test scripts
-
Create the Readiness Rule:
cd examples/cni-readiness kubectl apply -f network-readiness-rule.yaml -
Install Calico CNI and Apply the RBAC:
chmod +x apply-calico.sh sh apply-calico.sh
Verification
To test this, add a new node to the cluster.
-
Check the Node Taints: Immediately upon joining, the node should have the taint:
readiness.k8s.io/NetworkReady=pending:NoSchedule. -
Check Node Conditions: Watch the node conditions. You will initially see
network.k8s.io/CalicoReadyasFalseor missing. Once Calico starts, the sidecar will update it toTrue. -
Check Taint Removal: As soon as the condition becomes
True, the Node Readiness Controller will remove the taint, and workloads will be scheduled.