Why start with a local cluster
In Level 1 you met the parts of a cluster: a control plane (API server, scheduler, controllers, etcd) and nodes that run a kubelet. Now you'll build one on your laptop. It's free, it takes a minute or two, and you can delete it when you're done.
You have two good options:
- kind (Kubernetes IN Docker) runs each node as a Docker container. It's fast, light and easy to throw away. It's also what Kubernetes itself uses for CI testing.
- minikube runs a single-node cluster on a container or VM driver (Docker, Podman, Hyper-V, QEMU, VirtualBox and others) and adds extras such as
minikube dashboardand add-ons.
Pick one. Every kubectl command after setup is the same on both, and that's the point. kubectl doesn't care what the cluster runs on. It talks to an API server, and a small config file tells it which one.
This course gives you practical grounding. It is not exam preparation. The CKA and CKAD exams are hands-on, though, so the habits you build here (reading
describeoutput, checking your context) are exactly the ones they reward.
Walkthrough A: kind
You need Docker (or Podman) running, plus the kind and kubectl binaries installed.
# Create a cluster named "dev" (the default name is "kind")
kind create cluster --name dev
# List the kind clusters on this machine
kind get clusters
# dev
# kind added a context called kind-<name> and switched to it
kubectl cluster-info --context kind-dev
# Kubernetes control plane is running at https://127.0.0.1:52341
# CoreDNS is running at https://127.0.0.1:52341/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
The node really is a Docker container. Run docker ps and you'll see dev-control-plane.
Walkthrough B: minikube
minikube needs at least 2 CPUs, 2 GB of free memory and 20 GB of disk.
minikube start # creates a cluster and a context called "minikube"
minikube stop # stops it but keeps it on disk, so the next start is quick
minikube delete --all # removes every minikube cluster
Confirm the cluster is healthy
Run the same two checks on either tool:
kubectl cluster-info # Is the API server reachable?
kubectl get nodes # Are the nodes registered and Ready?
# NAME STATUS ROLES AGE VERSION
# dev-control-plane Ready control-plane 2m v1.xx.x
If a node shows NotReady, wait 30 seconds and try again. Networking add-ons take a moment to start.
Exploring with kubectl: the core four
With these four commands you can find your way around any cluster, even one you've never seen before.
# 1. get: a list, one line per object
kubectl get nodes
kubectl get pods -A # -A = all namespaces; you'll see the system Pods
kubectl get nodes -o wide # extra columns: internal IP, OS image, container runtime
kubectl get node dev-control-plane -o yaml # the full object as the API stores it
# 2. describe: a readable report, including recent Events at the bottom
kubectl describe node dev-control-plane
# 3. api-resources: every kind of object this cluster knows about
kubectl api-resources
# NAME SHORTNAMES APIVERSION NAMESPACED KIND
# nodes no v1 false Node
# pods po v1 true Pod
# deployments deploy apps/v1 true Deployment
# 4. explain: built-in docs for any field, straight from the cluster's schema
kubectl explain node.spec
kubectl explain pods --recursive | head -40
When to use which:
- Use
getto answer "what exists?" - Use
describeto answer "why is it behaving like this?" Events are the first place to look. - Use
-o yamlwhen you need the exact stored fields, includingstatus. - Use
explainwhen you'd otherwise search the web for a field name. It matches the version you're actually running, so it's never out of date. - Short names from
api-resourcessave typing:kubectl get nois the same askubectl get nodes.
kubeconfig and contexts: aiming kubectl
kubectl reads a kubeconfig file: ~/.kube/config by default, or the files listed in the KUBECONFIG environment variable, which it merges. The file has three lists:
- clusters: API server addresses and CA certificates
- users: credentials (certificates, tokens)
- contexts: named pairs of cluster + user, plus an optional default namespace
The current context decides where every command goes. Think of it like a TV remote that controls several TVs: before you press anything, check which one it's pointed at.
kubectl config get-contexts
# CURRENT NAME CLUSTER AUTHINFO NAMESPACE
# * kind-dev kind-dev kind-dev
# minikube minikube minikube default
kubectl config current-context # prints just the active name
kubectl config use-context minikube # switch
kubectl config view --minify # show only the active context's settings
# Run one command against another cluster without switching
kubectl get nodes --context kind-dev
# Merge a second file for this shell session only (Linux/macOS syntax)
export KUBECONFIG=~/.kube/config:~/.kube/work-config
Gotcha: the wrong-cluster delete.
kind create clusterandminikube startboth switch your current context without asking. Engineers who also hold work credentials have run commands meant for a laptop against a shared cluster. Habits that prevent this:
- Run
kubectl config current-contextbefore anything destructive.- In scripts, always pass
--contextexplicitly.- Show the context in your shell prompt (tools like kube-ps1 do this).
- Keep production credentials in a separate file that you load into
KUBECONFIGonly when you need it.
Clean up
kind delete cluster --name dev # also removes its context from kubeconfig
minikube delete --all
kubectl config get-contexts # check what's left
Local clusters cost nothing to recreate, so delete them freely. If you notice you're nursing a long-lived dev cluster along, that's a hint your setup should live in files, which is what the coming levels build toward.
Key takeaways
- kind runs nodes as containers. minikube runs a single node on a driver of your choice.
kubectlworks the same on both. - Check a new cluster with
kubectl cluster-infoandkubectl get nodes(look forReady). - Explore with
get(plus-o wide/-o yaml),describe(read the Events),api-resourcesandexplain. - kubeconfig holds clusters, users and contexts. The current context decides where commands go.
- Creating a cluster switches your context. Check with
current-context, and use--contextin scripts.