Cloud & DevOps
Hands-onIntermediate 45 min· Cloud · 11 of 16

Kubernetes basics

Run a local Kubernetes cluster with kind, deploy a web app with a Deployment and Service, watch self-healing, scale, do a rolling update and rollback, and learn the kubectl commands used to debug.

In this tutorial you will run a small Kubernetes cluster on your own laptop, deploy a web server with three copies, expose it with a Service, scale it, update it without downtime, break it on purpose and watch Kubernetes repair it. Everything runs locally and costs nothing.

Prerequisites: Docker installed and running (see *Docker: images and containers*), kubectl installed, and either kind (Kubernetes in Docker) or Minikube. This lesson uses kind. The install pages on kubernetes.io and kind.sigs.k8s.io cover Windows, macOS and Linux. Check with kubectl version --client and kind version. You need about 4 GB of free RAM.

What problem Kubernetes solves

Docker runs one container on one machine. Real systems need more: run five copies for traffic, restart a container that crashes, move work to another machine when one dies, roll out a new version without downtime, and give all copies one stable address.

Kubernetes (often written K8s) is a system that does this for you. You tell it the state you want ("three copies of this image, always") and it keeps reality matching that wish. Think of a restaurant manager: you say "always keep three chefs at the counter" and when one goes home sick, the manager calls a replacement without being asked.

You do not need Kubernetes for a small project; Docker Compose or a managed platform is simpler. Learn it because many companies run on it and interviews ask about it.

The objects you need to know

Core objects

ObjectWhat it isEveryday analogy
ClusterA set of machines (nodes) managed togetherThe whole restaurant
NodeOne machine in the clusterA kitchen station
PodThe smallest unit: one or more containers that run togetherOne chef at work
DeploymentKeeps N identical Pods running and handles updatesThe manager's rule: always 3 chefs
ServiceA stable address and load balancer in front of PodsThe counter where orders arrive
ConfigMap / SecretSettings and sensitive values given to PodsThe recipe card / the safe key
NamespaceA named partition inside a clusterSeparate sections of the kitchen

Pods are mortal. Each Pod gets its own IP address that changes whenever it is replaced, so you never talk to a Pod directly. You create a Deployment (which creates and replaces Pods) and a Service (which gives them one steady name).

The cluster has a control plane (the brain: an API server you talk to with kubectl, a store of the desired state, a scheduler that picks nodes and controllers that fix differences) and worker nodes that run your Pods. In managed services such as Amazon EKS, Azure AKS and Google GKE, the provider runs the control plane for you.

Step 1: create a cluster

Start a cluster

  1. 1Make sure Docker is running.
  2. 2Run kind create cluster --name demo. It starts a Kubernetes node as a Docker container and sets up kubectl to talk to it.
  3. 3Run kubectl cluster-info --context kind-demo to confirm the API server answers.
  4. 4Run kubectl get nodes. You should see one node with status Ready.
Terminal
kind create cluster --name demo
kubectl get nodes
Expected output (version will differ)
NAME                 STATUS   ROLES           AGE   VERSION
demo-control-plane   Ready    control-plane   45s   v1.xx.x

Step 2: deploy with a manifest

Kubernetes is driven by YAML files called manifests. Create a file web.yaml. It has two objects separated by ---: a Deployment of three nginx Pods and a Service in front of them.

web.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: nginx
          image: nginx:1.27
          ports:
            - containerPort: 80
          resources:
            requests:
              cpu: 50m
              memory: 64Mi
            limits:
              memory: 128Mi
          readinessProbe:
            httpGet:
              path: /
              port: 80
            initialDelaySeconds: 2
            periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
  name: web
spec:
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 80

replicas: 3 is the desired count. The selector must match the Pod labels: that is how the Deployment knows which Pods are its own, and how the Service finds them. requests tell the scheduler how much to reserve; limits cap use. The readiness probe means a Pod receives traffic only once it answers on port 80.

Terminal
kubectl apply -f web.yaml
kubectl get deployments
kubectl get pods
kubectl get service web

apply is declarative, like Terraform: run it again after editing the file and Kubernetes changes only what differs.

Pod names have random suffixes
NAME   READY   UP-TO-DATE   AVAILABLE   AGE
web    3/3     3            3           20s

NAME                   READY   STATUS    RESTARTS   AGE
web-6d8f9c7b5d-4kq2x   1/1     Running   0          20s
web-6d8f9c7b5d-9wzjn   1/1     Running   0          20s
web-6d8f9c7b5d-tl6vh   1/1     Running   0          20s

Step 3: reach the app

The Service has a cluster-internal IP, so your browser cannot reach it directly. For local testing use port-forward, which tunnels a port on your laptop to the Service.

Terminal
kubectl port-forward service/web 8080:80

Leave this running. In another terminal run curl http://localhost:8080 (or open the URL in a browser). You should get the nginx welcome page. Press Ctrl+C in the first terminal to stop forwarding.

In real clusters you expose apps to the internet with a Service of type LoadBalancer (the cloud creates a public load balancer, which costs money) or with an Ingress (one entry point that routes by host name and path to many Services). Ingress is a good next topic once the basics feel comfortable.

Step 4: see self-healing

Break it on purpose

  1. 1In one terminal run kubectl get pods --watch.
  2. 2In a second terminal copy one Pod name and delete it with kubectl delete pod <pod-name>.
  3. 3Watch the first terminal: a new Pod with a new name appears within seconds and the count returns to 3. The Deployment noticed that reality (2 Pods) no longer matched the wish (3) and fixed it.

Step 5: scale and update

Terminal
kubectl scale deployment web --replicas=5
kubectl get pods

kubectl set image deployment/web nginx=nginx:1.27-alpine
kubectl rollout status deployment/web
kubectl rollout history deployment/web

scale changes the replica count immediately. set image changes the container image, which starts a rolling update: new Pods are started and old ones stopped gradually, and readiness probes make sure traffic only reaches healthy Pods, so users see no downtime.

In real projects you change the image in the YAML file and run kubectl apply -f again, so Git stays the source of truth. kubectl set image is fine for quick experiments. If an update goes wrong, undo it:

Terminal
kubectl rollout undo deployment/web

Returns the Deployment to the previous revision.

Step 6: debugging commands you will use daily

Debugging toolbox

CommandWhat it tells you
kubectl get pods -o wideWhich node each Pod runs on and its IP
kubectl describe pod <name>Events at the bottom: why a Pod is Pending, failing to pull an image, or being killed
kubectl logs <pod>The container's output; add -f to follow and --previous for a crashed container
kubectl exec -it <pod> -- shA shell inside the container
kubectl get events --sort-by=.metadata.creationTimestampRecent cluster events

Try a failure. Run kubectl set image deployment/web nginx=nginx:does-not-exist, then kubectl get pods. New Pods show ErrImagePull or ImagePullBackOff, but the old Pods keep serving because the rolling update waits for new ones to become ready. Run kubectl describe pod on a failing Pod to read the reason, then fix it with kubectl rollout undo deployment/web.

ConfigMaps and Secrets in one minute

Do not bake settings into images. A ConfigMap holds plain settings and a Secret holds sensitive values; both can be passed to a container as environment variables. Example for the Brew & Bean API:

Terminal
kubectl create configmap api-config --from-literal=SHOP_NAME="Brew & Bean" --from-literal=CLOSING_HOUR=22
kubectl create secret generic api-secret --from-literal=DB_PASSWORD=change-me

In the Deployment's container spec you would then add envFrom entries with configMapRef and secretRef naming api-config and api-secret.

Secrets are not magic

A Kubernetes Secret is only base64-encoded by default, not encrypted for people who can read it. Restrict who can read Secrets with RBAC, never commit Secret YAML with real values to Git, and in production use encryption at rest or an external secrets manager.

Step 7: clean up

Terminal
kubectl delete -f web.yaml
kind delete cluster --name demo

Removes the app and then the whole local cluster.

Where to go next and interviews

Next topics: Ingress, persistent volumes for databases, Helm (a package manager for manifests), horizontal pod autoscaling, and a managed cluster on EKS, AKS or GKE. Managed clusters cost money even when idle (the nodes and sometimes a control plane fee), so see *Cloud costs, free tiers and avoiding surprise bills* first.

How this comes up in interviews: What is the difference between a Pod, a Deployment and a Service? What happens when a Pod crashes? How does a rolling update work? What is the difference between a liveness and a readiness probe? (liveness: restart me if I am stuck; readiness: do not send me traffic yet.) How do you debug a Pod stuck in CrashLoopBackOff? (kubectl logs --previous, then describe.)

Prompt · Explain a Kubernetes error
You are a Kubernetes engineer helping a beginner.

My Pod shows the status [status, e.g. CrashLoopBackOff]. Here is the output of `kubectl describe pod [pod-name]` and `kubectl logs [pod-name] --previous`:

<describe>
[paste here]
</describe>

<logs>
[paste here]
</logs>

Explain in plain English what is wrong, list the three most likely causes in order, and give the exact kubectl commands to confirm each one.

What to expect: A good answer reads the Events section and the last log lines, gives commands you can run, and does not tell you to delete the cluster.

Try it: Add a ConfigMap-driven page

Create a ConfigMap whose value is a one-line HTML message, mount it into the nginx Pods as /usr/share/nginx/html/index.html, apply it, and view it through port-forward.

Key takeaways

  1. Kubernetes keeps the state you declare: you say what you want, controllers make it true and keep it true.
  2. Pods are temporary. Use a Deployment to manage them and a Service to give them one stable address.
  3. Labels and selectors connect Deployments, Pods and Services; a mismatch is a classic bug.
  4. Rolling updates replace Pods gradually; readiness probes protect users, and rollout undo reverses a bad release.
  5. get, describe, logs and exec solve most debugging problems.
  6. Secrets are not encrypted by default; managed clusters cost money while they run, so delete practice clusters.

Check yourself

Finishing the quiz marks the lesson as done.

1. What is the main job of a Deployment?

2. You delete one of three Pods from a Deployment. What happens?

3. Why do we create a Service instead of using Pod IP addresses?

4. What does a readiness probe control?

5. A new image tag does not exist. During a rolling update what do users usually see?

Saved on this device. Create a free account to keep it on every device.

Practise this with Sam

A real mock interview, out loud, with an honest scorecard. Free to start.

Start free