StackMonsters
Switch to light mode
Blog

Learn Kubernetes Hands-On: 8 Free Labs From Your First Pod to a Rollout

· 7 min read · StackMonsters

You can read about Kubernetes for weeks and still freeze the first time kubectl get pods shows something you didn't expect. The concepts only stick once you've watched them happen: a Pod that doesn't come back, a ReplicaSet that replaces one, a rollout that stops halfway. This tutorial is a first hands-on session, in the order the ideas build on each other. Every step has the commands to run, what you should see, and why. Each one also links a free lab with a cluster in your browser, so you don't need to install anything.

If you'd rather run a cluster on your own machine, kind, minikube and k3d all give you one in a few minutes, and every command below works there too. For the bigger question of what to learn in which order and how to know you're ready for CKA or CKAD, see The Best Way to Learn Kubernetes in 2026.

1. Look around the cluster

Start by asking the cluster what it has:

console
$ kubectl get nodes
NAME           STATUS   ROLES           AGE   VERSION
control-plane  Ready    control-plane   12d   v1.33.1
$ kubectl get pods
No resources found in default namespace.
$ kubectl get deployments -n kube-system
NAME      READY   UP-TO-DATE   AVAILABLE   AGE
coredns   2/2     2            2           12d

Two things to notice. First, kubectl get pods only looked in the default namespace; namespaces split a cluster into separate groups of objects, and most commands take -n <namespace> or -A for all of them. Second, the cluster's own add-ons, such as DNS, are ordinary Deployments in kube-system. You'll manage your own workloads with the same tools.

Free lab · Kubernetes Foundations · 8 minExplore a live clusterRuns in your browser. No install, no account.

2. Run your first Pod

A Pod is the smallest thing Kubernetes runs: one or more containers that share a network address and can share volumes. Create one directly:

console
$ kubectl run podling --image=nginx:1.27
pod/podling created
$ kubectl get pods
NAME      READY   STATUS              RESTARTS   AGE
podling   0/1     ContainerCreating   0          3s
$ kubectl get pods
NAME      READY   STATUS    RESTARTS   AGE
podling   1/1     Running   0          9s

ContainerCreating is the node pulling the image and starting the container. READY 1/1 means one of one containers is ready. kubectl describe pod podling shows everything Kubernetes knows about it, and its Events section, at the bottom, is the first place to look whenever a Pod isn't doing what you expect.

Free lab · Kubernetes Foundations · 8 minYour first PodRuns in your browser. No install, no account.

3. Find things with labels

Labels are key-value pairs on any object, and they're how Kubernetes objects find each other. A Service finds its Pods by label, and a ReplicaSet counts its Pods by label. You use them the same way from the command line:

bash
kubectl label pod podling owner=me                 # add a label
kubectl get pods --show-labels                     # see every label
kubectl get pods -A -l tier=backend                # every backend Pod, in every namespace
kubectl get pods -l 'tier in (backend,legacy)'     # set-based selector
kubectl delete pods -l tier=legacy                 # act on whatever matches

The last line is the reason to be careful with selectors: a delete by label acts on everything that matches, not on what you meant. Run the same selector with get first.

Free lab · Kubernetes Foundations · 8 minLabel and findRuns in your browser. No install, no account.

4. Pods don't come back. Controllers bring them back.

Delete the Pod you made:

console
$ kubectl delete pod podling
pod "podling" deleted
$ kubectl get pods
No resources found in default namespace.

It's gone for good. A bare Pod has nothing watching over it, and the same goes for a Pod on a node that dies. That's why you almost never create Pods directly. You create a controller that keeps a number of Pods running. The simplest is a ReplicaSet:

yaml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: web
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: web
          image: nginx:1.27

Apply it, delete one of its Pods, and look again:

console
$ kubectl apply -f web-rs.yaml
replicaset.apps/web created
$ kubectl delete pod web-7xk2p
pod "web-7xk2p" deleted
$ kubectl get pods -l app=web
NAME        READY   STATUS    RESTARTS   AGE
web-hq9vd   1/1     Running   0          2m
web-m4c8z   1/1     Running   0          2m
web-s2l6n   1/1     Running   0          4s

Still three, and one of them is four seconds old. The ReplicaSet doesn't remember specific Pods. It counts the Pods that match its selector and creates or deletes Pods until the count equals replicas. That loop of comparing what you asked for with what exists, and closing the gap, is how every Kubernetes controller works.

Free lab · Kubernetes Foundations · 10 minPods don't come backRuns in your browser. No install, no account.

5. Deployments: scale and change without downtime

In practice you create a Deployment, which manages ReplicaSets for you. The extra layer exists for updates: when you change the Pod template, the Deployment creates a new ReplicaSet and shifts Pods over gradually.

console
$ kubectl create deployment market-web --image=nginx:1.27 --replicas=3
deployment.apps/market-web created
$ kubectl scale deployment market-web --replicas=5
deployment.apps/market-web scaled
$ kubectl get rs
NAME                    DESIRED   CURRENT   READY   AGE
market-web-7c5ddbdf54   5         5         5       1m

Scaling changes how many Pods there are, not what they look like, so there's still one ReplicaSet. Change the image and you get a second one:

console
$ kubectl set image deployment/market-web nginx=nginx:1.26
deployment.apps/market-web image updated
$ kubectl rollout status deployment/market-web
deployment "market-web" successfully rolled out
$ kubectl get rs
NAME                    DESIRED   CURRENT   READY   AGE
market-web-6f8b9d7c48   5         5         5       20s
market-web-7c5ddbdf54   0         0         0       3m

The old ReplicaSet is kept at zero replicas. That's what makes kubectl rollout undo deployment/market-web instant: it scales the old one back up. The Pod names also tell you who owns them: market-web-7c5ddbdf54-x7wlp is Deployment, then ReplicaSet hash, then a random suffix.

Free lab · Kubernetes Foundations · 6 minScale the MarketRuns in your browser. No install, no account.

How the Pods are swapped is the Deployment's strategy. The default, RollingUpdate, replaces Pods a few at a time so some are always serving. Recreate deletes every old Pod before creating new ones, which causes downtime but guarantees two versions never run at once. That matters for an application that can't share a database schema across versions.

Free lab · Kubernetes Foundations · 8 minRecreate vs rollingRuns in your browser. No install, no account.

6. Write it down: declarative YAML

So far you've created everything with commands. That's quick, but it leaves no record: nobody can review, version or recreate what you typed. The usual workflow is to let kubectl write the first draft of a manifest, then keep the file:

bash
kubectl create deployment web --image=nginx:1.27 --replicas=3 \
  --dry-run=client -o yaml > web.yaml       # generates the YAML without creating anything
# edit web.yaml: add resources, probes, labels
kubectl apply -f web.yaml                   # create or update the cluster to match the file

kubectl apply is idempotent: run it again with the same file and nothing changes; run it with an edited file and only the difference is applied. When you don't remember a field, kubectl explain deployment.spec.strategy documents it from the cluster's own schema, which is faster than searching and always matches your cluster's version.

Free lab · Kubernetes Foundations · 10 minGenerate, then applyRuns in your browser. No install, no account.

7. Catch drift before it catches you

The file is only the truth until someone changes the cluster by hand. Someone scales up during an incident and forgets to scale back:

console
$ kubectl scale deployment market-web --replicas=5
$ kubectl diff -f market-web.yaml
...
-  replicas: 5
+  replicas: 2
...

kubectl diff shows what apply would change, so it's how you notice the live cluster no longer matches the file. Here, applying the file brings the Deployment back to 2 and the extra Pods terminate. Whether that's the right move is a real decision. If the extra replicas were needed, the fix is to change the file, not the cluster. Teams that use GitOps tools like Argo CD or Flux automate exactly this comparison.

Free lab · Kubernetes Foundations · 10 minFind the driftRuns in your browser. No install, no account.

What you've practised, and what's next

You've seen the core loop Kubernetes runs on: you declare what you want, and controllers keep making the cluster match it. Pods are disposable, ReplicaSets keep a count, Deployments manage changes, and labels connect all of it.

The next skill is finding out why that loop isn't converging: a Pod in CrashLoopBackOff, an image that won't pull, a rollout that stops halfway. Kubernetes Troubleshooting Labs walks through those failures with a broken cluster for each one. After that come Services and DNS, configuration with ConfigMaps and Secrets, health checks, and storage.