Kubernetes on Windows Runbook & Cheatsheet
Kubernetes on Windows Runbook & Cheatsheet
Run a real Kubernetes cluster on a Windows machine for development and learning. Part one picks a local cluster tool, installs kubectl, creates a cluster, and deploys a workload with services, config, and storage; part two is the kubectl and manifest reference.
Choose a local cluster
| Tool | Pick when |
|---|---|
| kind | Fast, disposable clusters as Docker containers. Great for CI and multi-node testing. Used below. |
| minikube | Learning; rich add-ons (dashboard, ingress, registry) and easy resets. |
| Docker Desktop | One checkbox in Settings turns on Kubernetes. Simplest if you already run it (mind the licence). |
| Rancher Desktop | Open source (Apache 2.0), bundles kubectl, Helm, nerdctl and k3s. No Docker Desktop licence. |
k3d is a fifth option worth knowing: k3s (a lightweight distribution) in Docker, very fast to start and light on RAM. Any of these gives you a conformant API to practise against.
Prerequisites
# Enable WSL2 (reboot when asked) wsl --install wsl --status wsl --set-default-version 2 # A container runtime: Docker Desktop or Rancher Desktop winget install -e --id Docker.DockerDesktop # or: winget install -e --id SUSE.RancherDesktop
Resources Give WSL2 enough headroom. A %USERPROFILE%\.wslconfig with [wsl2] and memory=8GB / processors=4 avoids the mysterious pod evictions that come from a starved VM.
Install kubectl
winget install -e --id Kubernetes.kubectl winget install -e --id Kubernetes.kind # winget install -e --id Kubernetes.minikube # winget install -e --id Helm.Helm kubectl version --client # PowerShell tab completion (add to your $PROFILE) kubectl completion powershell | Out-String | Invoke-Expression
Version skew Keep kubectl within one minor version of the cluster. A much newer or older client produces confusing field and API errors.
Create a cluster
kind create cluster --name dev kind get clusters kubectl cluster-info --context kind-dev kubectl get nodes
# Multi-node + a host port mapped in for ingress kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 nodes: - role: control-plane extraPortMappings: - containerPort: 30080 hostPort: 8080 protocol: TCP - role: worker - role: workerkind create cluster --name dev --config kind-cluster.yaml kind delete cluster --name dev # tear it all downPorts do not just work A kind cluster runs inside Docker, so reaching a service from Windows needs either an extraPortMappings entry (declared at creation time) or kubectl port-forward. This surprises everyone once.
Deploy a workload
apiVersion: apps/v1 kind: Deployment metadata: name: web spec: replicas: 2 selector: matchLabels: { app: web } template: metadata: labels: { app: web } spec: containers: - name: web image: nginx:alpine ports: - containerPort: 80 resources: requests: { cpu: 50m, memory: 64Mi } limits: { cpu: 500m, memory: 256Mi } readinessProbe: httpGet: { path: /, port: 80 } initialDelaySeconds: 3kubectl apply -f deployment.yaml kubectl get deploy,pods kubectl rollout status deploy/web kubectl scale deploy/web --replicas=4
Apply, do not create kubectl apply is declarative and re-runnable; imperative create / run are fine for scratch work but leave nothing you can version-control and replay.
Expose it
apiVersion: v1 kind: Service metadata: name: web spec: type: NodePort # ClusterIP by default; NodePort for local access selector: { app: web } ports: - port: 80 targetPort: 80 nodePort: 30080 # matches kind extraPortMappings above
kubectl apply -f service.yaml kubectl get svc # Simplest local access - works with any cluster type kubectl port-forward svc/web 8080:80 # then browse http://localhost:8080
| Service type | Reachable from |
|---|---|
| ClusterIP | Inside the cluster only (the default). |
| NodePort | A high port on each node; the usual local option. |
| LoadBalancer | Cloud provisions an external IP; stays pending locally without MetalLB. |
port-forward is your friend It needs no service type changes, no ingress, and no cluster config — ideal for poking at something during development.
Config & secrets
kubectl create configmap app-config \ --from-literal=LOG_LEVEL=debug --from-file=./app.conf kubectl create secret generic app-secret \ --from-literal=DB_PASSWORD=s3cr3t kubectl get configmap app-config -o yaml kubectl describe secret app-secret
# Consume them in a pod spec envFrom: - configMapRef: { name: app-config } - secretRef: { name: app-secret }Secrets are only base64 Kubernetes Secrets are encoded, not encrypted, and readable by anyone with API access to the namespace. For anything real, enable encryption at rest and pull from an external store — see the Vault sheet.
Use your own images
A locally built image is not visible to the cluster’s node containers until you load it, which is why fresh builds keep showing ErrImagePull.
docker build -t bubim/app:dev . # kind kind load docker-image bubim/app:dev --name dev # minikube (alternative: eval its docker-env and build inside) minikube image load bubim/app:dev
Set imagePullPolicy Use imagePullPolicy: IfNotPresent for locally loaded images. With Always (the default for the latest tag) the kubelet tries the registry and fails.
Debug
kubectl get pods -o wide # 1. what state is it in? kubectl describe pod POD # 2. events at the bottom = why kubectl logs POD [-c CONTAINER] [-f] # 3. what did the app say? kubectl exec -it POD -- sh # 4. look around inside kubectl logs POD --previous # logs from a crashed instance kubectl get events --sort-by=.lastTimestamp
Read describe first The Events list at the bottom of describe pod explains nearly every failure: image pull errors, failed scheduling, probe failures, OOM kills.
kubectl
| Command | Does |
|---|---|
| kubectl get TYPE [NAME] | List resources; -A all namespaces, -o wide|yaml|json. |
| kubectl describe TYPE NAME | Detail plus the Events list. |
| kubectl apply -f FILE|DIR | Create or update declaratively. |
| kubectl delete -f FILE | Remove what the file defines. |
| kubectl logs POD [-f] [–previous] | Container logs. |
| kubectl exec -it POD — sh | Shell into a container. |
| kubectl port-forward svc/NAME 8080:80 | Tunnel a service to localhost. |
| kubectl scale deploy/NAME –replicas=N | Change replica count. |
| kubectl rollout status / undo | Watch a rollout / roll back. |
| kubectl set image deploy/N c=img:tag | Update an image in place. |
| kubectl top pods / nodes | Resource use (needs metrics-server). |
| kubectl explain TYPE.field | Built-in schema documentation. |
| kubectl api-resources | Every resource type and its short name. |
| kubectl diff -f FILE | Preview what apply would change. |
Dry run to author manifests kubectl create deploy web –image=nginx –dry-run=client -o yaml > deploy.yaml generates a valid starting file instead of typing YAML from memory.
Core objects
| Object | Represents |
|---|---|
| Pod | One or more containers sharing network and storage. The unit of scheduling. |
| Deployment | Declarative replicas with rolling updates. The usual way to run a stateless app. |
| StatefulSet | Stable identities and per-pod storage (databases). |
| DaemonSet | One pod on every node (agents, log shippers). |
| Job / CronJob | Run to completion / on a schedule. |
| Service | Stable virtual IP and DNS name for a set of pods. |
| Ingress | HTTP routing into the cluster (needs a controller). |
| ConfigMap / Secret | Non-confidential config / sensitive values. |
| PersistentVolumeClaim | A request for durable storage. |
| Namespace | A scope for names, quotas, and access. |
Manifest anatomy
| Field | Meaning |
|---|---|
| apiVersion | API group and version (v1, apps/v1). |
| kind | Object type. |
| metadata.name / labels | Identity and selectable key-value tags. |
| spec | Desired state — the part you write. |
| status | Observed state — written by the cluster, never by you. |
| spec.selector | Which pods this object governs; must match the template labels. |
| resources.requests / limits | Scheduling guarantee / hard ceiling. |
| readiness / liveness Probe | Ready to serve / restart if unhealthy. |
Selector mismatch If spec.selector.matchLabels does not match spec.template.metadata.labels, the Deployment manages nothing and no pods appear. It is the most common YAML bug.
Contexts & namespaces
kubectl config get-contexts kubectl config current-context kubectl config use-context kind-dev # Default namespace for the current context kubectl config set-context --current --namespace=dev kubectl get ns kubectl create ns dev kubectl get pods -n dev kubectl get pods -A # every namespace
kubeconfig on Windows Lives at %USERPROFILE%\.kube\config. A cluster created inside WSL2 writes to the WSL home instead — set KUBECONFIG or copy the file if kubectl on Windows cannot see the cluster.
Windows troubleshooting
| Symptom | Fix |
|---|---|
| “connection refused” to the API | Cluster not running, or wrong context — kind get clusters, then config use-context. |
| kubectl sees no cluster after WSL install | kubeconfig is inside WSL; set KUBECONFIG or copy to %USERPROFILE%\.kube\config. |
| ErrImagePull / ImagePullBackOff | Local image not loaded (kind load docker-image), typo in the tag, or a private registry needing a pull secret. |
| CrashLoopBackOff | App exits on start — kubectl logs POD –previous. |
| Pending | Nothing can schedule it: not enough CPU/memory, or an unbound PVC. Check describe events. |
| Service unreachable from the browser | Use port-forward, or map the port at cluster creation via extraPortMappings. |
| Everything is very slow | Raise WSL2 memory/CPU in .wslconfig; keep project files inside the WSL filesystem, not /mnt/c. |
| Pods evicted at random | Node under memory pressure — the WSL2 VM is too small. |
Optimize
| Area | Lever |
|---|---|
| Resources | Size the WSL2 VM in .wslconfig; set pod requests/limits so scheduling is predictable. |
| Iteration speed | kind or k3d clusters are seconds to create and delete — reset instead of debugging a broken cluster. |
| Declarative | Keep manifests in Git and use apply; diff before applying. |
| Authoring | –dry-run=client -o yaml to generate manifests; kubectl explain for fields. |
| Filesystem | Keep repos in the WSL2 filesystem; /mnt/c is roughly ten times slower. |
| Packaging | Helm or Kustomize once manifests multiply across environments. |
| Secrets | Secrets are base64, not encrypted — use an external store for anything real. |
| Cleanup | Delete clusters you are not using; each holds real RAM in the VM. |
References
| Resource | Use | Link |
|---|---|---|
| Kubernetes docs | Concepts and API reference | kubernetes.io/docs |
| kubectl cheat sheet | Official command list | kubectl quick reference |
| kind | Local clusters in Docker | kind.sigs.k8s.io |
| minikube | Local cluster with add-ons | minikube.sigs.k8s.io |

0 comments