Initializing Enclave...

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 NotReady because 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-nginx addon enabled and extraPortMappings correctly declared in your kind-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:

  1. CNI never initializes → kubelet reports NetworkPluginNotReady → node stays NotReady.
  2. CoreDNS pods stay Pending → any pod requiring DNS (everything) fails to start.
  3. Ingress controller never deploys → your Ingress resources silently have no backend.
  4. kubectl port-forward and NodePort services appear to work but ingress-based routing is completely broken, which masks the real failure until integration tests run.
  5. 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.

Related Diagnostics

"Part of the Performance Utility Matrix."

View all 219 Performance Tools →