TinyPDF.in / Playgrounds

Kubernetes Playground

Deploy a sample web app, watch its pods come up, run a rolling update, break it on purpose, and try kubectl-style commands.

Teaching model. This is a simulated cluster for learning, not a real Kubernetes cluster. It doesn't run containers. The behaviour follows how Deployments, ReplicaSets, readiness and rolling updates work, but exact timings and messages are simplified.

Work through the stages in order. Each one builds on the last, and the stage goal says what you should understand by the end.

Stage 1: the basic loop

Goal: see a Deployment create pods, serve traffic, update, and recover.

  1. Click Apply manifest. A Deployment with 3 replicas and a Service are created. Nothing runs yet.
  2. Click Play (or Step). Pods are scheduled to nodes, start, and become ready. The Service gets endpoints.
  3. Click Send request a few times. Each request goes to a different ready pod.
  4. Change image: hello-web:1.0 to hello-web:2.0, apply, and watch the rolling update keep pods serving.
  5. Change the image to hello-web:broken and apply. The rollout stalls, and only the old pods serve traffic.
  6. Click a pod to delete it, or stop a node, and watch the Deployment replace it.
Stage 2: pods on their own

Goal: see what a pod without a controller does, and how it differs from a Deployment.

  1. Build a pod by hand. Load Plain pod from the Pod types list (or click Pod template), apply it, then play. Delete the pod in the console with kubectl delete pod NAME. Nothing brings it back, because it has no controller.
  2. Run a throwaway pod. kubectl run debug --image=hello-web:1.0, then kubectl get pods.
  3. Build a service. kubectl expose deployment hello-web --port=8080 --name=web, then request web a few times.
Stage 3: observe the cluster

Goal: find out where each event comes from, using the Logs, Events and Control plane panels.

  1. Watch logs. Pick a pod in the Logs panel, send some requests, and see the lines appear with times. Broken images log failing health checks.
  2. Watch the control plane. Scale the deployment, stop a node, or break an image, and see which component (API server, scheduler, controller manager, node controller, kubelet) does the work.
Stage 4: configuration

Goal: pass settings into pods with ConfigMaps and Secrets, and see what happens when a dependency is missing.

  1. Configure with a ConfigMap. Click ConfigMap example and apply it. The service answers with the message. Then run kubectl create configmap app-config --from-literal=MESSAGE=changed: running pods keep the old value until they restart.
  2. Handle a secret. Click Secret example, apply, then kubectl get secret db -o yaml. The value is base64, which is encoding, not encryption. describe pod hides it.
  3. Break the config on purpose. Apply the ConfigMap example without the ConfigMap, and the pods show CreateContainerConfigError until you create it.
  4. Mount config as files. Load Pod with a ConfigMap volume, apply, then kubectl exec config-pod -- ls /etc/config and cat /etc/config/MESSAGE. Change the ConfigMap's MESSAGE, apply, and the file updates in about 10 seconds. The env var would not change until a restart. Writing to the file fails: config volumes are read-only.
  5. Mount a Secret. Load Pod with a Secret volume, apply, and cat /etc/secret/PASSWORD shows the value. The Secret is stored encoded, but this is still a plain file for anything in the pod.
Reference: what to apply first
  1. Secrets and ConfigMaps first. A pod that uses configMapKeyRef, secretKeyRef or envFrom can't start until the object exists. Without it, the pod shows CreateContainerConfigError and retries each tick, so applying them first avoids the wait.
  2. Storage claims before pods that mount them. A pod that names a missing PersistentVolumeClaim stays Pending with persistentvolumeclaim "NAME" not found. Apply the claim first, or in the same file above the pod.
  3. Pods or a Deployment next. Once their config and storage exist, the pods can be scheduled and start.
  4. Service after the pods it selects. A Service with no matching pods has no endpoints yet, so requests fail until the pods are ready.
  5. NetworkPolicy last, or any time. It only applies to traffic when a request is made, so it doesn't change how pods start.
  6. One file, several objects. Separate objects with --- and apply them together in the order above. Re-applying an unchanged object prints unchanged.

A real cluster has the same dependencies, but its controllers keep retrying, so the order matters less. The playground shows each wait so you can see the cause.

Stage 5: health checks

Goal: tell a pod that isn't ready (removed from the Service) apart from one that is stuck (restarted).

  1. Liveness restarts. Load Pod with a liveness probe from the Pod types list (or change the image to hello-web:stuck and add a livenessProbe). Readiness removes the pod from the Service first, then liveness restarts it, and RESTARTS goes up.
Stage 6: storage

Goal: see which data survives a pod restart and which doesn't, and how claims are shared.

  1. Keep data. Click Storage example (or load Pod with a claim and a scratch volume) and apply. Write a file with kubectl exec writer -- write /data/notes.txt hello, delete the pod, and apply again. The file is still there. Then write under /scratch (an emptyDir) and see it vanish with the pod.
  2. Share a claim. Two pods use one ReadWriteOnce claim: the second waits with a message. Change the claim to ReadWriteMany, and both run.
  3. Bind by hand. Load Pod with a hand-made PersistentVolume and apply. The PersistentVolume comes first, so the claim binds to it: kubectl get pv shows Bound. Delete the claim, and with the Retain policy the volume becomes Released and won't bind to a new claim until you clean it up.
  4. Let a StorageClass provision it. Load Claim with the default StorageClass. You didn't write a PersistentVolume: kubectl get pv shows one called pvc-data, made by the standard class. Its Delete policy means deleting the claim deletes the volume and its data.
Stage 7: networking

Goal: reach pods by name, control who can reach them, and route HTTP through an Ingress.

  1. Reach a pod by name. Click Network example (or load Client pod). From the client pod, kubectl exec client -- wget http://hello-web and nslookup hello-web work. Then try request node-1:30080: the policy blocks it too, because outside traffic has no role=client label. Delete the policy with kubectl delete netpol only-client and it works.
  2. Block traffic. The NetworkPolicy allows only pods labelled role=client. Run kubectl run other --image=busybox:1.36 and try to wget from it: it times out.
  3. Match the port. Load Service with the wrong targetPort, apply it, and run request web: the Service sends to 9090 but the app listens on 8080, so the request is refused. Fix targetPort to 8080, apply, and it works. kubectl get endpoints web shows the IP and port the Service sends to.
  4. Route through an Ingress. Pick Service behind an Ingress in the Pod types list, load it and apply. Then run request ingress web-ingress / --host=hello.local, and try a different --host to see the 404. kubectl get ingress shows the host and address.

Manifest

YAML for a Deployment and a Service. Edit it, then apply it. Supported: Deployment, Service.

Start from scratch:
Pod or Deployment type:

Cluster

t = 0s

Green: ready. Amber: starting or not ready. Red: readiness failing. Dashed: pending, not scheduled yet. Click a pod to delete it.

Services

Control plane

Each box is a part of Kubernetes doing work in this model. The count is how many events it caused. Recent actions are listed under it.

Logs


    

Events

    Terminal

    Tab completes commands, names and flags. Up and Down recall earlier commands. Try kubectl get pods -o wide, kubectl get pods -l app=hello-web -o name or clear. Ctrl+L also clears.

    
            
    Command examples

    Each example gives the command, what it does, and the output the playground prints for it. Try them in order from the top.

    kubectl get pods

    Lists the pods. READY shows whether the container is ready. STATUS is the pod phase. RESTARTS counts container restarts.

    NAME                     READY   STATUS    RESTARTS   AGE   NODE
    hello-web-1w4zot-kvzp9   1/1     Running   0          13s   node-2
    hello-web-1w4zot-qrfnj   1/1     Running   0          12s   node-3
    hello-web-1w4zot-vbs99   1/1     Running   0          14s   node-1

    kubectl get pods -o wide

    Adds the pod IP to the table.

    NAME                     READY   STATUS    RESTARTS   AGE   NODE     IP
    hello-web-1w4zot-kvzp9   1/1     Running   0          13s   node-2   10.244.2.3
    hello-web-1w4zot-qrfnj   1/1     Running   0          12s   node-3   10.244.3.4
    hello-web-1w4zot-vbs99   1/1     Running   0          14s   node-1   10.244.1.2

    kubectl get pods -o name

    Prints one name per line, in the form pod/NAME. Useful in scripts.

    pod/hello-web-1w4zot-kvzp9
    pod/hello-web-1w4zot-qrfnj
    pod/hello-web-1w4zot-vbs99

    kubectl get pods -l app=hello-web

    Shows only pods whose labels match. Use --selector=KEY=VALUE for the same thing.

    NAME                     READY   STATUS    RESTARTS   AGE   NODE
    hello-web-1w4zot-kvzp9   1/1     Running   0          13s   node-2
    hello-web-1w4zot-qrfnj   1/1     Running   0          12s   node-3
    hello-web-1w4zot-vbs99   1/1     Running   0          14s   node-1

    kubectl get svc -o yaml

    Shows a Service as a manifest, so you can see its selector, port and targetPort.

    apiVersion: v1
    kind: Service
    metadata:
      name: hello-web
    spec:
      type: ClusterIP
      clusterIP: 10.96.0.10
      selector:
        app: hello-web
      ports:
      - port: 8080
        targetPort: 8080

    kubectl describe pod hello-web-1w4zot-vbs99

    Shows the node, IP, status and recent events for one pod. Replace NAME with a name from kubectl get pods.

    Name:          hello-web-1w4zot-vbs99
    Node:          node-1
    IP:            10.244.1.2
    Status:        Running
    Ready:         true
    Restart Count: 0
    Image:         hello-web:1.0
    Owner:         ReplicaSet/hello-web-1w4zot
    Labels:        app=hello-web

    kubectl scale deployment hello-web --replicas=5

    Changes the replica count. Press Play, or type wait 10, to see the new pods start.

    deployment.apps/hello-web scaled

    kubectl set image deployment/hello-web web=hello-web:2.0

    Starts a rolling update to the new image. Check progress with the next command.

    deployment.apps/hello-web image updated

    kubectl rollout status deployment/hello-web

    Waits until the rollout is complete. In this command the playground prints the final status.

    deployment "hello-web" successfully rolled out

    kubectl create configmap app-config --from-literal=MESSAGE=hi

    Creates a ConfigMap from a literal value. Pods that read it need it to exist before they start.

    configmap/app-config created

    kubectl run debug --image=hello-web:1.0

    Starts a single pod with no controller. Delete it and nothing brings it back.

    Error from server (AlreadyExists): pods "debug" already exists

    kubectl logs --tail=3 debug

    Shows the last three log lines for the pod. Each line has the simulated second it was written.

    [16s] scheduled to node-1 (ip 10.244.1.5)
    [16s] container "debug" started (image hello-web:1.0)
    [16s] listening on :8080

    kubectl expose deployment hello-web --port=8080 --name=web

    Creates a Service called web for the deployment. Then request web sends a request through it.

    service/web exposed
    hello from hello-web-1w4zot-kvzp9 (version 1.0)

    kubectl exec writer -- ls /data

    Runs a command inside the pod. exec only works on paths that are mounted volumes (here /data), not on the root filesystem.

    notes.txt

    request ingress web-ingress / --host=hello.local

    Sends a request through an Ingress. A host or path with no matching rule gets a 404.

    hello from hello-web-pod (version 1.0)
    404 Not Found (no ingress rule matches this host and path)

    kubectl get endpoints hello-web

    Lists the IP and targetPort each Service sends to. An empty list means no ready pods match its selector.

    NAME        ENDPOINTS
    hello-web   10.244.2.3:8080,10.244.3.4:8080,10.244.1.2:8080

    kubectl get pods -n kube-system

    Only the default namespace exists in the playground. Other namespaces report that nothing is there.

    No resources found in kube-system namespace.
    Stage 8: resources

    Goal: see how requests reserve room on a node, and how limits stop a container.

    1. Reserve room. Load Pod with requests and limits, apply it, and run kubectl describe pod resources-pod to see the requests and limits. Then kubectl describe node node-1 shows how much of the node's CPU and memory the requests reserve.
    2. Too big for a node. Load Pod too big for any node and apply it. It stays Pending with Insufficient cpu: each node has 2 CPUs, and the request is 3.
    3. Packing. Apply a Deployment with cpu: "1" per pod and 4 replicas. Two pods fit on each node, and the rest wait until there is room.
    4. Get OOMKilled. Load Pod that is OOMKilled and apply it. The app needs about 48Mi, and the limit is 32Mi, so the kubelet restarts the container and RESTARTS climbs. describe pod shows Last State: Terminated (OOMKilled).
    What this teaches, and what is not modelled

    Modelled: Deployments and ReplicaSets, pod scheduling with per-node pod count and CPU and memory requests, readiness and liveness probes, memory limits (OOMKilled), Services with round-robin endpoints and targetPort, ConfigMaps and Secrets as env vars and files, PersistentVolumes with binding, StorageClasses and reclaim policies, DNS, NetworkPolicy, Ingress, rolling updates with maxSurge and maxUnavailable, self-healing, node failure and eviction, and basic kubectl output.

    Not modelled: real containers and images, CPU limits and throttling, named ports, cloud load balancers, RBAC, and the exact wording and timing of real Kubernetes.