Fixing Kind Cluster 'Control-Plane Node Not Ready' When Ingress Addon Is Disabled
Threat/Impact Level: HIGH | Downtime Risk: HIGH | Time to Fix: 10 mins
TL;DR
- What broke: Kind control-plane node is
NotReadybecause the CNI/ingress addon is disabled, leaving the node network uninitialized and kube-proxy non-functional. - How to fix it: Destroy and recreate the cluster with
ingress-nginxaddon enabled andextraPortMappingscorrectly declared in yourkind-config.yaml. - Fast path: Use our Client-Side Sandbox below to auto-refactor your broken
kind-config.yaml— secrets never leave your browser.
The Incident (What Does the Error Mean?)
You ran kubectl get nodes and got this:
NAME STATUS ROLES AGE VERSION
kind-control-plane NotReady control-plane 2m v1.27.3
Followed by events like:
kubelet: container runtime network not ready: NetworkReady=false
reason:NetworkPluginNotReady message:Network plugin returns error:
cni plugin not initialized
Or after kubectl describe node kind-control-plane:
Conditions:
Ready False KubeletNotReady runtime network not ready:
NetworkPluginNotReady
Immediate consequence: Zero pods will schedule. CoreDNS stays Pending. Any kubectl apply you run silently queues into a black hole. Your local dev loop is dead.
The Attack Vector / Blast Radius
This isn't a transient hiccup — it's a hard cluster bootstrap failure. Here's the cascade:
- CNI never initializes → kubelet reports
NetworkPluginNotReady→ node staysNotReady. - CoreDNS pods stay
Pending→ any pod requiring DNS (everything) fails to start. - Ingress controller never deploys → your
Ingressresources silently have no backend. kubectl port-forwardandNodePortservices appear to work but ingress-based routing is completely broken, which masks the real failure until integration tests run.- In CI pipelines, this causes silent false-negatives: your Helm chart deploys "successfully" (exit 0) but no traffic ever reaches the app.
The root cause is almost always one of two things: the kind-config.yaml is missing the kubeadmConfigPatches CNI toleration, or the cluster was created with --config omitted entirely, falling back to Kind's default which does not enable ingress.
How to Fix It
Basic Fix — Recreate the Cluster With Correct Config
# kind-config.yaml
- kind: Cluster
- apiVersion: kind.x-k8s.io/v1alpha4
- nodes:
- - role: control-plane
+ kind: Cluster
+ apiVersion: kind.x-k8s.io/v1alpha4
+ nodes:
+ - role: control-plane
+ kubeadmConfigPatches:
+ - |
+ kind: InitConfiguration
+ nodeRegistration:
+ kubeletExtraArgs:
+ node-labels: "ingress-ready=true"
+ extraPortMappings:
+ - containerPort: 80
+ hostPort: 80
+ protocol: TCP
+ - containerPort: 443
+ hostPort: 443
+ protocol: TCP
Then:
# Nuke the broken cluster
kind delete cluster --name <your-cluster-name>
# Recreate with corrected config
kind create cluster --config kind-config.yaml
# Deploy ingress-nginx for Kind
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml
# Wait for ingress controller to be ready
kubectl wait --namespace ingress-nginx \
--for=condition=ready pod \
--selector=app.kubernetes.io/component=controller \
--timeout=90s
Enterprise Best Practice — Multi-Node With Explicit CNI and Validation
# kind-config.yaml (production-grade local dev)
- kind: Cluster
- apiVersion: kind.x-k8s.io/v1alpha4
- nodes:
- - role: control-plane
- - role: worker
+ kind: Cluster
+ apiVersion: kind.x-k8s.io/v1alpha4
+ networking:
+ disableDefaultCNI: false
+ podSubnet: "10.244.0.0/16"
+ serviceSubnet: "10.96.0.0/12"
+ nodes:
+ - role: control-plane
+ kubeadmConfigPatches:
+ - |
+ kind: InitConfiguration
+ nodeRegistration:
+ kubeletExtraArgs:
+ node-labels: "ingress-ready=true"
+ extraPortMappings:
+ - containerPort: 80
+ hostPort: 80
+ protocol: TCP
+ - containerPort: 443
+ hostPort: 443
+ protocol: TCP
+ - containerPort: 30000
+ hostPort: 30000
+ protocol: TCP
+ - role: worker
+ - role: worker
Verify node readiness before deploying anything:
# Block until control-plane is actually Ready — don't trust 'cluster created' message
kubectl wait node kind-control-plane \
--for=condition=Ready \
--timeout=120s
# Confirm CNI pods are running
kubectl get pods -n kube-system -l k8s-app=kindnet
💡 Tired of pasting proprietary configs into ChatGPT? Generic AI tools log your company's ARNs, DB strings, and private keys. StackEngine is a zero-backend, pure Client-Side WASM utility. Drop your failing config into the sandbox above. We redact your secrets locally in the browser and auto-generate the refactored code using your own API key.
Prevention in CI/CD
1. Gate cluster creation with a readiness probe in your pipeline:
# .github/workflows/integration.yml
- name: Create Kind Cluster
run: |
kind create cluster --config ./infra/kind-config.yaml --wait 120s
kubectl wait node --all --for=condition=Ready --timeout=120s
kubectl wait pods -n kube-system --all --for=condition=Ready --timeout=120s
2. Lint your kind-config.yaml with kubeval or kubeconform before cluster creation:
kubeconform -strict -schema-location default kind-config.yaml
3. Use a Makefile target that enforces config is always passed:
cluster-up:
@test -f infra/kind-config.yaml || (echo "ERROR: kind-config.yaml missing" && exit 1)
kind create cluster --config infra/kind-config.yaml --wait 120s
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml
kubectl wait --namespace ingress-nginx --for=condition=ready pod --selector=app.kubernetes.io/component=controller --timeout=90s
4. Checkov policy to enforce extraPortMappings presence (custom check):
# checkov custom check: ensure ingress port mappings exist
from checkov.common.models.enums import CheckResult
from checkov.yaml_runner.checks.base_yaml_check import BaseYamlCheck
class KindIngressPortMappingCheck(BaseYamlCheck):
def __init__(self):
super().__init__(
name="Ensure Kind config has extraPortMappings for ingress",
check_id="CKV_KIND_001",
supported_entities=["nodes"],
block_type="yaml"
)
def scan_resource_conf(self, conf):
for node in conf:
if node.get("role") == "control-plane":
if not node.get("extraPortMappings"):
return CheckResult.FAILED
return CheckResult.PASSED
5. Never run kind create cluster without --wait. The command exits 0 the moment the container starts, not when the node is Ready. This is the #1 source of flaky CI pipelines using Kind.