kind Lab¶
kind (Kubernetes IN Docker) runs each cluster node as a Docker container. That makes it fast to create, trivial to script, and โ uniquely useful for local development โ able to load an image straight from your machine's Docker daemon into the cluster without ever pushing it to a registry.
This lab creates a three-node cluster from a config file, builds a small local image, loads it into the cluster, and deploys it.
Prerequisites¶
- Docker installed and running
kubectlinstalled (see the Minikube Lab install steps if you skipped it)- Go is not required โ install the prebuilt binary below
1. Install kind¶
macOS
Linux
curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.24.0/kind-linux-amd64
chmod +x ./kind
sudo mv ./kind /usr/local/bin/kind
Verify:
2. Define a multi-node cluster¶
Create a config file describing one control-plane node and two workers, and pin the node image so every run gives you the same Kubernetes version:
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
image: kindest/node:v1.31.0
extraPortMappings:
- containerPort: 30080
hostPort: 30080
protocol: TCP
- role: worker
image: kindest/node:v1.31.0
- role: worker
image: kindest/node:v1.31.0
extraPortMappings on the control-plane node is what lets a NodePort service inside the cluster be reachable at localhost:30080 on your machine later in this lab.
3. Create the cluster¶
Expected output (abbreviated):
Creating cluster "lab" ...
โ Ensuring node image (kindest/node:v1.31.0) ๐ผ
โ Preparing nodes ๐ฆ ๐ฆ ๐ฆ
โ Writing configuration ๐
โ Starting control-plane ๐น๏ธ
โ Installing CNI ๐
โ Installing StorageClass ๐พ
โ Joining worker nodes ๐
Set kubectl context to "kind-lab"
You can now use your cluster with:
kubectl cluster-info --context kind-lab
kind create cluster already switched your current kubectl context to kind-lab. Confirm all three nodes are Ready:
kubectl config current-context
# kind-lab
kubectl get nodes
# NAME STATUS ROLES AGE VERSION
# lab-control-plane Ready control-plane 60s v1.31.0
# lab-worker Ready <none> 40s v1.31.0
# lab-worker2 Ready <none> 40s v1.31.0
4. Build a local image¶
Create a tiny app so the point of this lab โ loading an image you built locally, with no registry involved โ is obvious:
FROM nginx:1.27
RUN echo "served from a kind-loaded local image" > /usr/share/nginx/html/index.html
5. Load the image into the cluster¶
Image: "local/hello-kind:1.0" with ID "sha256:..." not yet present on node "lab-control-plane", loading...
Image: "local/hello-kind:1.0" with ID "sha256:..." not yet present on node "lab-worker", loading...
Image: "local/hello-kind:1.0" with ID "sha256:..." not yet present on node "lab-worker2", loading...
kind load docker-image copies the image directly into containerd on every node in the named cluster. No registry, no docker push, no image pull secret โ this is the fastest inner loop for testing a locally built image against real Kubernetes scheduling and networking.
Set imagePullPolicy: IfNotPresent
If the Deployment's imagePullPolicy is Always (or unset on a tag Kubernetes treats as mutable), the kubelet will try to pull local/hello-kind:1.0 from a registry, fail, and the pod will sit in ImagePullBackOff โ even though the image is already loaded onto the node. Explicitly setting IfNotPresent is what makes the loaded image actually get used.
6. Deploy and expose it¶
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-kind
labels:
app: hello-kind
spec:
replicas: 3
selector:
matchLabels:
app: hello-kind
template:
metadata:
labels:
app: hello-kind
spec:
containers:
- name: hello-kind
image: local/hello-kind:1.0
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: hello-kind
spec:
type: NodePort
selector:
app: hello-kind
ports:
- port: 80
targetPort: 80
nodePort: 30080
7. Verify¶
Confirm the pods actually landed on different worker nodes โ this is the multi-node scheduling behavior a single-node minikube cluster can't demonstrate:
kubectl get pods -o wide
# NAME READY STATUS NODE
# hello-kind-6c8f9d5b7f-2f4kk 1/1 Running lab-worker
# hello-kind-6c8f9d5b7f-9h2lm 1/1 Running lab-worker2
# hello-kind-6c8f9d5b7f-vx8qc 1/1 Running lab-worker
Because the control-plane node maps container port 30080 to localhost:30080 on your machine (from the extraPortMappings in step 2), you can curl it directly โ no minikube service / kubectl port-forward needed:
Troubleshooting¶
- Pod stuck in
ImagePullBackOffโ almost alwaysimagePullPolicywasn't set toIfNotPresent, or the image tag loaded doesn't exactly match the tag in the manifest. curl localhost:30080connection refused โ theextraPortMappingsblock only takes effect for nodes it's declared on; confirm it's under thecontrol-planenode entry inkind-config.yamland that you created the cluster with--config kind-config.yaml.- New image changes don't show up โ
kind load docker-imagedoesn't overwrite an already-loaded image with the same tag reliably; bump the tag (:1.1) or delete the pods to force a re-pull from containerd's local cache. - Nodes stuck
NotReadyโ kind installs its own CNI automatically; if this happens, checkdocker psshows all threelab-*containers running andkubectl describe node <name>for the actual condition.
Cleanup¶
Next¶
Continue to the Docker Desktop Lab to compare this against the Kubernetes cluster built into Docker Desktop.