A Cluster on Your Laptop
Install kind or minikube, get kubectl talking to it, and understand the kubeconfig file well enough never to deploy into the wrong cluster.
Install kind or minikube, get kubectl talking to it, and understand the kubeconfig file well enough never to deploy into the wrong cluster.
Kubernetes has a reputation for needing a data centre. It does not need one to learn on: a full cluster — real control plane, real kubelet, real scheduler — runs inside containers on your own machine in about thirty seconds.
By the end of this lesson you will have that cluster, kubectl
talking to it, and a clear understanding of the kubeconfig file. That
last part is not filler. The most expensive mistake in this whole
subject is running a command against the wrong cluster, and the
defence is knowing where kubectl gets its target from.
Three sensible options, and any of them works for this course.
kind — Kubernetes IN Docker. It runs each node as a Docker container, starts in seconds, and can make multi-node clusters trivially. It needs Docker, which you already have. This course uses kind for its examples.
minikube — the oldest and most featureful. It runs a node as a virtual machine or a container, and bundles add-ons for ingress, metrics and a dashboard behind one command each.
Docker Desktop's built-in Kubernetes — a checkbox in settings. The least to install, the least control, and no multi-node option.
| kind | minikube | Docker Desktop | |
|---|---|---|---|
| Start time | ~30s | ~60s | ~60s |
| Multi-node | Yes, easily | Yes | No |
| Add-ons | Manual | Built in | Manual |
| Needs | Docker | Docker or a VM | Docker Desktop |
kubectl is the client, and it is separate from the cluster. Install
it first.
# macOS
brew install kubectl
# Linux
curl -LO "https://dl.k8s.io/release/$(curl -Ls \
https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
# Windows (PowerShell, with winget)
winget install -e --id Kubernetes.kubectlkubectl version --clientOnly the client version prints, because there is no cluster yet.
With kind:
# macOS
brew install kind
# Linux
curl -Lo ./kind \
https://kind.sigs.k8s.io/dl/latest/kind-linux-amd64
chmod +x ./kind && sudo mv ./kind /usr/local/bin/kindkind create cluster --name learningCreating cluster "learning" ...
✓ Ensuring node image (kindest/node:v1.31.0)
✓ Preparing nodes
✓ Writing configuration
✓ Starting control-plane
✓ Installing CNI
✓ Installing StorageClass
Set kubectl context to "kind-learning"Read that list against the previous lesson: it started a control
plane, installed a network plugin so pods can talk to each other, and
installed a storage class so volumes can be provisioned. Then it
pointed kubectl at the result.
Confirm it is alive:
kubectl get nodesNAME STATUS ROLES AGE VERSION
learning-control-plane Ready control-plane 45s v1.31.0One machine doing both jobs. Now look at the components themselves:
kubectl get pods -n kube-systemNAME READY STATUS AGE
coredns-6f6b679f8f-4x2mn 1/1 Running 60s
etcd-learning-control-plane 1/1 Running 66s
kube-apiserver-learning-control-plane 1/1 Running 66s
kube-controller-manager-learning-... 1/1 Running 66s
kube-proxy-9tzql 1/1 Running 60s
kube-scheduler-learning-control-plane 1/1 Running 66sEvery component from the last lesson, running as a pod. coredns is
new — it is the DNS server that makes Service names resolve, and it
matters from the Services lesson onwards.
For minikube, the equivalent is one command:
minikube start --profile learningOne node is enough for most of this course. A second and third make scheduling visible — you can watch pods land on different machines, and you can delete a node and watch its work move.
# kind-cluster.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
- role: workerkind create cluster --name learning --config kind-cluster.yaml
kubectl get nodesNAME STATUS ROLES VERSION
learning-control-plane Ready control-plane v1.31.0
learning-worker Ready <none> v1.31.0
learning-worker2 Ready <none> v1.31.0Three containers on your laptop, behaving as three machines. Worth doing once, if only because the abstraction stops feeling like a diagram.
kubectl had no arguments telling it where to go, so where did it
look?
At ~/.kube/config — the kubeconfig file. It holds three lists
and one pointer, and the pointer is the dangerous part.
clusters
API server addresses, with their certificates. One entry per cluster you have ever connected to.
users
The credentials to authenticate with. Separate from clusters, so one identity can be used against several.
contexts
Named pairings of a cluster, a user and a default namespace.
current-context
Which one of those is active. A single line, invisible in your prompt, and the target of every command you do not qualify.
kubectl config get-contextsCURRENT NAME CLUSTER NAMESPACE
* kind-learning kind-learning
docker-desktop docker-desktop
prod-eu prod-eu paymentsThe asterisk is the entire mechanism: every kubectl command with no
explicit target goes to that context. Nothing in your prompt says
which one it is.
kubectl config current-context # which cluster am I on
kubectl config use-context kind-learning # switch
kubectl config view --minify # the active one, in detailBad — trusting your memory about which cluster is active:
kubectl delete deployment apiGood — saying it in the command, so the target cannot be wrong:
kubectl --context kind-learning delete deployment apiDeletes from whichever cluster is active.
And the active one changes without you: a tool adds a context, a colleague's config is merged in, or you switched three hours ago and forgot.
Nothing in your terminal tells you which it is.
Impossible to get wrong.
The command names its own target, so it does not matter what the file says.
Worth doing for anything destructive, and for anything against a cluster that has users on it.
Kubeconfig can also be pointed at a file explicitly, which is the cleanest way to keep a work cluster out of your default config entirely:
export KUBECONFIG=~/.kube/prod-config
kubectl get nodesYou will type kubectl hundreds of times per session. Three things
to set up now.
An alias, and completion:
# ~/.zshrc or ~/.bashrc
alias k=kubectl
source <(kubectl completion zsh) # or bash
complete -o default -F __start_kubectl kCompletion is the one that matters most — it completes resource
types, object names and flags, which removes most typos and most
kubectl get pods calls made only to copy a name.
Short names, which the tool accepts everywhere:
kubectl get po # pods
kubectl get deploy # deployments
kubectl get svc # services
kubectl get ns # namespaces
kubectl get cm # configmaps
kubectl api-resources # every type, with its short nameThe built-in documentation, which is better than searching the web for field names:
kubectl explain pod.spec
kubectl explain deployment.spec.strategy --recursiveYou will do this properly with manifests in two lessons' time. One imperative command is worth trying now, to confirm the cluster genuinely works:
kubectl run web --image=nginx --port=80
kubectl get podsNAME READY STATUS RESTARTS AGE
web 1/1 Running 0 12sA container is running on the cluster. Reach it by forwarding a port from your machine:
kubectl port-forward pod/web 8080:80Visit http://localhost:8080 and nginx answers. Stop the forward
with Ctrl + C, and clean up:
kubectl delete pod webport-forward is a development tool — a tunnel from your machine to
one pod, for as long as the command runs. It is how you poke at
something before Services and Ingress exist, and it stays useful for
debugging long afterwards.
# Creating and destroying a local cluster
kind create cluster --name learning
kind create cluster --name learning --config kind-cluster.yaml
kind get clusters
kind delete cluster --name learning
minikube start --profile learning # the alternative
# Confirming it works
kubectl version # client and server
kubectl get nodes -o wide
kubectl cluster-info
kubectl get pods -n kube-system # the control plane itself
# Contexts — where your commands are going
kubectl config current-context # check before anything
kubectl config get-contexts
kubectl config use-context kind-learning
kubectl --context kind-learning get pods # explicit beats implicit
export KUBECONFIG=~/.kube/prod-config # a separate config file
# Quality of life
alias k=kubectl
source <(kubectl completion zsh) # or bash
kubectl api-resources # types and their short names
kubectl explain pod.spec # field documentation
# A first workload, imperatively
kubectl run web --image=nginx --port=80
kubectl get pods
kubectl port-forward pod/web 8080:80 # tunnel from your machine
kubectl delete pod webYou have a cluster and a client that can reach it. The next lesson starts on the objects, with the pod — the thing you actually deploy, why it wraps containers rather than being one, and why you will almost never create one directly.
Before that, one habit worth starting immediately: run
kubectl config current-context before any command you would not
want to run twice. It takes a second, and it is the cheapest
insurance in this entire course.