Six short steps in one hour. Build a cluster, run an app on it, open it in your browser, scale it, update it, and fix it when it breaks.
Do not worry. Your whole cluster is three Docker containers on your laptop. The last command deletes them. One step breaks on purpose, so you can see what a real error looks like. Nothing here can harm your computer.
π Want to go deeper later? Kubernetes, Deep Dive adds the scheduler, taints, draining a node, health probes and the REST API. It uses this same cluster, so do this hour first and nothing will need rebuilding.
| Min | Step | What you can say after it |
|---|---|---|
| 0β10 | Setup | "I built a Kubernetes cluster on my laptop" |
| 10β15 | 1 Look at your cluster | "I know what my three machines do" |
| 15β26 | 2 Run your first app | "I wrote one file and Kubernetes ran it on two machines" |
| 26β37 | 3 Open it in a browser | "My app is on a real port" |
| 37β44 | 4 Scale it, then kill it | "I deleted my running app and nothing went down" |
| 44β52 | 5 Update, redeploy, undo | "I shipped a new version and took it back" |
| 52β60 | 6 Break it and fix it | "I can read an error and find the cause" |
Kubernetes is a program you give a wish to. It then works non-stop to make that wish true.
You do not tell it how. You tell it what you want: "three copies of this app should run". It checks again and again, forever, and fixes things when they are wrong.
π‘οΈ Think of a thermostat. You do not switch the heater on and off all evening. You set a dial to 22Β°C and walk away. The thermostat reads the room, compares it to 22, and acts.
Kubernetes is a thermostat for software. You set the dial: "three copies of nginx". It checks the cluster, compares, and acts. Delete a copy and it makes a new one.
In step 4 you will delete a running app. A new one will appear in about two seconds. Nobody will ask you anything.
This is the big difference from Docker. docker run is an order: do this, once, now. A Kubernetes file is a standing instruction: keep this true, forever.
| Word | What it means |
|---|---|
| Cluster | A group of machines managed as one. Yours will have three. |
| Node | One machine. Yours are Docker containers acting as servers. |
| Control plane | The node that thinks. It stores your wishes and decides where things go. It does not run your apps. |
| Worker | A node that runs your apps. More workers means more space. |
| Pod | One running copy of your app. Pods are disposable. They get replaced often. That is normal. |
| Deployment | Your instruction: "keep N copies of this image running". This is the file you write. |
| Service | One fixed address in front of Pods that keep changing. |
PS C:\Users\..., type wsl and press Enter. You will need two terminal tabs today. In Windows Terminal, press Ctrl+Shift+T for a new tab.You need three tools. Check what you already have:
docker ps
kubectl version --client
kind version
If docker ps gives an error, start Docker first. If the other two say "command not found", open the box below.
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl && rm kubectl
curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.30.0/kind-linux-amd64
sudo install -o root -g root -m 0755 kind /usr/local/bin/kind && rm kind
kubectl version --client && kind version
On an ARM laptop or an Apple Silicon Mac, change amd64 to arm64 in both links.
Make a folder for today and stay inside it:
mkdir -p ~/k8s-lab && cd ~/k8s-lab
You will build one control plane and two workers. You will also open ports 30000 to 30050, so your browser can reach apps inside the cluster later.
Copy this whole block at once. It writes the config file for you, then builds the cluster:
{
echo 'kind: Cluster'
echo 'apiVersion: kind.x-k8s.io/v1alpha4'
echo 'name: k8s-lab'
echo 'nodes:'
echo ' - role: control-plane'
echo ' extraPortMappings:'
for p in $(seq 30000 30050); do
echo " - containerPort: $p"
echo " hostPort: $p"
echo " protocol: TCP"
done
echo ' - role: worker'
echo ' - role: worker'
} > kind-config.yaml
kind create cluster --config kind-config.yaml
The first run downloads an image, so wait two or three minutes. Then check:
kubectl get nodes
docker ps --format "{{.Names}}"
NAME STATUS ROLES AGE VERSION
k8s-lab-control-plane Ready control-plane 2m v1.34.0
k8s-lab-worker Ready <none> 1m v1.34.0
k8s-lab-worker2 Ready <none> 1m v1.34.0
k8s-lab-control-plane
k8s-lab-worker
k8s-lab-worker2
π¬ Read both outputs together. Kubernetes says you have three nodes. Docker says you have three containers. They are the same three things.
kind means "Kubernetes IN Docker". It runs a full cluster inside containers, so you can learn on a laptop. This is also why cleanup is fast: deleting the cluster means deleting three containers.
| What you see | What to do |
|---|---|
Cannot connect to the Docker daemon | Start Docker: sudo systemctl start docker, or open Docker Desktop |
C:\ in your prompt | Type wsl and press Enter |
| It stops at "Joining worker nodes" | Not enough memory. Run kind delete cluster --name k8s-lab, delete one - role: worker line, try again |
port is already allocated | Another program uses a port in that range. Change seq 30000 30050 to seq 30010 30030 and try again |
connection to the server was refused | The containers stopped. This is normal after a restart: docker start k8s-lab-control-plane k8s-lab-worker k8s-lab-worker2 |
Meet your three machines, and see Kubernetes running itself.
kubectl config current-context
kubectl get nodes -o wide
kubectl get pods
kind-k8s-lab
NAME STATUS ROLES AGE VERSION INTERNAL-IP
k8s-lab-control-plane Ready control-plane 8m v1.34.0 172.18.0.4
k8s-lab-worker Ready <none> 7m v1.34.0 172.18.0.2
k8s-lab-worker2 Ready <none> 7m v1.34.0 172.18.0.3
No resources found in default namespace.
"No resources found" is correct, not an error. You have not deployed anything yet.
The context tells kubectl which cluster to talk to. You have only one today. In a job you will have three or four, including production. Check your context before you run anything that deletes. It takes two seconds and it prevents very bad days.
The control plane is healthy and has free space. Your Pods will still not go there. Here is the reason:
kubectl describe node k8s-lab-control-plane | grep -i -A1 taints
Taints: node-role.kubernetes.io/control-plane:NoSchedule
A taint is a "keep out" sign on a node. Your app does not have a pass, so it is never placed there. This is on purpose. If your app used all the memory on the control plane, you would lose the ability to fix anything.
kubectl get pods -n kube-system -o wide
A namespace is a folder inside the cluster. Your work goes in default. Kubernetes keeps its own parts in kube-system. Look at the NODE column and you will see a pattern:
| Pod | Where | Job |
|---|---|---|
kube-apiserver | control plane | The front door. Every command you type goes here. |
etcd | control plane | The notebook. It stores everything. |
kube-scheduler | control plane | Chooses which node runs each new Pod. |
kube-controller-manager | control plane | The thermostat. It compares what you want with what exists. |
coredns | control plane | Turns names into IP addresses. |
kube-proxy, kindnet | all three nodes | Networking. One copy per node, added automatically. |
kube-system. That is how you break your own cluster.Write one file. Watch Kubernetes build three things and place them on two machines.
You will run nginx, a real web server. In Docker that was docker run -d nginx. Here you write the wish down instead.
nano deployment.yaml
Type this in. YAML uses spaces, never tabs. Two spaces per level. If your editor adds tabs, fix it now.
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:stable
ports:
- containerPort: 80
resources:
requests:
cpu: "50m"
memory: "64Mi"
Save with Ctrl+O, Enter. Exit with Ctrl+X.
| Line | What it means |
|---|---|
kind: Deployment | The type of wish: "keep copies of an app running". |
replicas: 3 | The dial. Three copies, always. |
app: nginx (twice) | A label, like a sticker. The Deployment uses it to find its own Pods. In step 3 the Service will use it too. |
template | The cookie cutter. Everything below it describes one Pod. |
image: nginx:stable | Which image to run. Same idea as Docker. |
requests | How much CPU and memory to reserve. 50m means a very small slice of one CPU. |
app: nginx labels must match exactly. Kubernetes compares labels character by character. app: nginx and app: nginx-web are different things to it, and a mismatch fails silently.Check the file first, then apply it. Then ask when it is ready instead of guessing:
kubectl apply --dry-run=client -f deployment.yaml
kubectl apply -f deployment.yaml
kubectl wait --for=condition=Available deployment/nginx --timeout=120s
kubectl get pods -o wide
deployment.apps/nginx created
deployment.apps/nginx condition met
NAME READY STATUS RESTARTS AGE IP NODE
nginx-7c9f4d8b6d-k2xvp 1/1 Running 0 20s 10.244.1.4 k8s-lab-worker
nginx-7c9f4d8b6d-m8ztq 1/1 Running 0 20s 10.244.2.3 k8s-lab-worker2
nginx-7c9f4d8b6d-p4wnc 1/1 Running 0 20s 10.244.1.5 k8s-lab-worker
π¬ Look at the NODE column. You never chose a machine, and your app is on two of them. The scheduler looked at your cluster, found space, and decided. You only said what you wanted.
kubectl get deployments
kubectl get replicasets
kubectl get pods
Read the three names. The whole story is in them:
Deployment: nginx
ReplicaSet: nginx-7c9f4d8b6d
Pod: nginx-7c9f4d8b6d-k2xvp
You created one thing. Kubernetes built the other two and keeps them correct, forever.
π¬ That code in the middle name, 7c9f4d8b6d, comes from your Pod template. Write yours down. In step 5 you will change the image, and a new ReplicaSet with a different code will appear. That is how updates and rollbacks work.
This is worth 60 seconds, because it explains most confusing errors later.
kubectl sends your file to the API server over HTTPS.created.Nobody in that list talks to anybody else. They all watch the API server and react. This is why created means "your wish was saved", not "your app is running", and why you used kubectl wait above.
You can see the handover written down:
kubectl describe deployment nginx | tail -n 8
That Events block is the object's diary. When something breaks later today, look here first.
Give three moving Pods one fixed address, then reach it from Windows on a real port.
Those Pod IP addresses disappear when the Pod does. You will see that in step 4. You cannot build anything on an address that disappears. That is why we need a Service.
nano service.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
type: NodePort
selector:
app: nginx
ports:
- protocol: TCP
port: 80 # the Service's port inside the cluster
targetPort: 80 # the port inside the container
nodePort: 30000 # the port you type in your browser
Note apiVersion: v1 here, but apps/v1 in the Deployment. Services are older and belong to the core group. Copying the wrong one gives a confusing error.
selector: app: nginx must match the label in deployment.yaml exactly. If it does not match, the Service accepts connections and sends them nowhere. Nothing turns red. This is the most common Kubernetes bug there is.kubectl apply -f service.yaml
kubectl get services
kubectl get endpoints nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 18m
nginx NodePort 10.96.142.88 <none> 80:30000/TCP 4s
NAME ENDPOINTS AGE
nginx 10.244.1.4:80,10.244.1.5:80,10.244.2.3:80 4s
π¬ Stop at the ENDPOINTS line. It must show three IP addresses, one per Pod. This proves your labels matched. If it shows <none>, fix the labels now. Nothing below will work.
Nobody writes that list by hand. A controller builds it by matching labels, and updates it whenever a Pod appears or disappears.
Now open your normal browser on Windows, Chrome or Edge:
http://localhost:30000
You should see the "Welcome to nginx!" page. π No tunnel. No terminal held open. You could close Ubuntu and the page would still load.
| Field | Where it lives | What it is |
|---|---|---|
nodePort: 30000 | every node | The outside door. It must be between 30000 and 32767. Your setup opened 30000 to 30050, so pick from that range today. |
port: 80 | the Service | The port other Pods inside the cluster use. |
targetPort: 80 | the Pod | The port the container listens on. |
Your request reached a Pod on a worker, but you knocked on the control plane. A NodePort opens on every node, and Kubernetes forwards it to whichever node has a Pod.
Sometimes you just want to look at an app without opening a port. Open your second terminal:
kubectl port-forward service/nginx 8080:80
The command looks frozen. That is correct. It is holding a tunnel open. Visit http://localhost:8080 and you get the same page. Press Ctrl+C to stop it.
port-forward | NodePort | |
|---|---|---|
| Who can reach it | Only your laptop, only while the command runs | Anyone who can reach the machine |
| Shared across all Pods | No. It sticks to one Pod | Yes. Requests are spread across all of them |
| Use it for | A quick look at one thing | Demos, and anything another person opens |
Turn the dial up, then delete a running app and watch nothing bad happen.
kubectl scale deployment nginx --replicas=5
kubectl get pods -o wide
kubectl get endpoints nginx
Two new Pods. The old three were not restarted. Kubernetes only created what was missing.
The Service picked up the new Pods on its own. You did not touch service.yaml. It matches by label, so any new Pod with app=nginx joins as soon as it is ready. Go back to three:
kubectl scale deployment nginx --replicas=3
In your second terminal, start watching:
kubectl get pods -o wide -w
In the first terminal, copy a real Pod name from your output and delete it:
kubectl get pods
kubectl delete pod <paste-one-of-your-pod-names>
nginx-7c9f4d8b6d-k2xvp 1/1 Terminating 0 8m k8s-lab-worker
nginx-7c9f4d8b6d-z9rtb 0/1 Pending 0 0s <none>
nginx-7c9f4d8b6d-z9rtb 0/1 ContainerCreating 0 0s k8s-lab-worker2
nginx-7c9f4d8b6d-z9rtb 1/1 Running 0 2s k8s-lab-worker2
A new Pod appeared in about two seconds. It has a new name, a new IP, and it may be on a different machine. Press Ctrl+C to stop watching.
Now reload http://localhost:30000. The site never went down. The other two Pods served every request while this happened.
π¬ That is the thermostat working. The ReplicaSet counted two Pods. It wanted three. So it made one more. Nobody was called.
The new Pod is a different Pod, not the old one started again. New name, new IP, no memory of the old one. This is why we say Pods are disposable, and why you must never write a Pod IP into your code. The Service exists because Pod IPs always change.
kubectl delete deployment nginx. The instruction is permanent. The Pods are temporary. Do not run it yet.Ship a new version with no downtime. Force a redeploy when nothing changed. Then take it all back.
kubectl set image deployment/nginx nginx=nginx:stable-alpine
kubectl rollout status deployment/nginx
Reload http://localhost:30000 while it runs. The site stays up. New Pods start and are checked before old ones are removed.
In that command, deployment/nginx is the Deployment name and nginx= is the container name from your file. They are the same word here by chance.
There are two common ways to ship a new version:
kubectl set image. This is fast and useful when you are experimenting or fixing something quickly.kubectl apply -f deployment.yaml. This is the standard production workflow because the cluster is told the desired state from a file.apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: burgxy/webapps:v4
ports:
- containerPort: 80
resources:
requests:
cpu: "250m"
memory: "164Mi"
kubectl apply -f deployment.yaml
kubectl rollout status deployment/nginx
This declarative approach is often preferred in real projects because the change is stored in Git, is easy to review, and is easy to reproduce.
kubectl get replicasets
NAME DESIRED CURRENT READY AGE
nginx-5d7b9c4f8a 3 3 3 40s
nginx-7c9f4d8b6d 0 0 0 14m
Two ReplicaSets now, with a new code in the name. The new one runs your Pods. The old one is empty but kept. That empty one is your undo button:
kubectl rollout undo deployment/nginx
kubectl rollout status deployment/nginx
kubectl get deployment nginx -o jsonpath='{.spec.template.spec.containers[0].image}'; echo
Back to nginx:stable in about two seconds. Nothing was rebuilt or downloaded again, because the old ReplicaSet was never deleted.
This is the job you will do most often at work. Try applying the file you have not edited:
kubectl apply -f deployment.yaml
deployment.apps/nginx unchanged
unchanged is not a warning. apply compares your file with the cluster and only acts on differences. No difference means no action.
But sometimes you do want all Pods replaced. Maybe you rebuilt the image with the same tag. Maybe a setting changed. Note the Pod names first, so you can compare:
kubectl get pods -l app=nginx # before
kubectl rollout restart deployment/nginx
kubectl rollout status deployment/nginx
kubectl get pods -l app=nginx # after
NAME READY STATUS RESTARTS AGE
nginx-7c9f4d8b6d-m8ztq 1/1 Running 0 14m
nginx-7c9f4d8b6d-p4wnc 1/1 Running 0 14m
nginx-7c9f4d8b6d-t8kqn 1/1 Running 0 14m
deployment.apps/nginx restarted
deployment "nginx" successfully rolled out
NAME READY STATUS RESTARTS AGE
nginx-8a3e1c7d25-6rzv2 1/1 Running 0 21s
nginx-8a3e1c7d25-hq4nd 1/1 Running 0 17s
nginx-8a3e1c7d25-xp9ck 1/1 Running 0 13s
| Reading | Before | After |
|---|---|---|
| Image | nginx:stable | nginx:stable β the same |
| Your YAML file | Not changed at all | |
| Pod names | nginx-7c9f4d8b6d-β¦ | nginx-8a3e1c7d25-β¦ β all new |
| AGE | 14m | 13 to 21 seconds, a few seconds apart |
| RESTARTS | 0 | 0 |
π¬ Look at the RESTARTS column: still 0. Nothing restarted. The old Pods were deleted and new Pods were created, a few seconds apart, one at a time. "Restart" is a misleading name. Pods are replaced, never restarted in place.
The ages are a few seconds apart on purpose. That is the rolling update doing one Pod at a time, which is why the site stayed up.
kubectl delete pod -l app=nginx removes every copy at once, so for a few seconds there is nothing to answer requests. Users see errors. rollout restart reaches the same result with no downtime.rollout undo brings back old containers. It does not undo database changes, emails already sent, or data already written. Also, a successful rollout does not mean a working app. Pods can be Running while every request fails. Always check behaviour, not just status.Cause a real failure, find the cause in 30 seconds, and repair it.
kubectl set image deployment/nginx nginx=nginx:this-tag-does-not-exist
Wait about 20 seconds, then:
kubectl get pods
curl -s http://localhost:30000 | head -n 1
NAME READY STATUS RESTARTS AGE
nginx-8a3e1c7d25-6rzv2 1/1 Running 0 6m
nginx-8a3e1c7d25-hq4nd 1/1 Running 0 6m
nginx-6b4d9f2c1e-t5qmx 0/1 ImagePullBackOff 0 22s
<!DOCTYPE html>
π¬ See what Kubernetes did before you even looked. The old Pods are still running and still serving. The update stopped instead of destroying a working app. Reload the browser. The site is fine.
A failed deployment is not automatically an outage.
Two commands. Knowing which one to use is the real skill. Use your broken Pod name:
kubectl describe pod <your-broken-pod-name>
Scroll to Events at the bottom:
Normal Scheduled 45s default-scheduler Successfully assigned default/nginx-6b4d9f2c1e-t5qmx to k8s-lab-worker
Normal Pulling 44s kubelet Pulling image "nginx:this-tag-does-not-exist"
Warning Failed 42s kubelet Failed to pull image: not found
Warning Failed 15s kubelet Error: ImagePullBackOff
The answer is right there in plain English: the image tag does not exist. You can also see which part failed. The scheduler worked. The kubelet could not pull the image.
Now try the other command:
kubectl logs <your-broken-pod-name>
Error from server (BadRequest): container "nginx" is waiting to start:
trying and failing to pull image
π¬ This is the lesson of the step. logs had nothing to show, because the container never started. You cannot read the output of a program that does not exist.
Use describe when it never started. Use logs when it started but behaves wrongly. Choosing the right one is the difference between a two-minute fix and a lost afternoon.
Also notice: that red error message was useful information, not a wall. Read errors. Do not panic at them.
kubectl rollout undo deployment/nginx
kubectl rollout status deployment/nginx
kubectl get pods -o wide
kubectl get endpoints nginx
You must finish with: 3/3, three Pods Running, three endpoints, and http://localhost:30000 still showing the nginx page.
| Status | What is wrong | First command |
|---|---|---|
Pending | No machine had space for it | kubectl describe pod <name> |
ContainerCreating | Still pulling the image. Normal for a few seconds | Wait. If stuck, describe |
ImagePullBackOff | Wrong image name or tag | kubectl describe pod <name> |
CrashLoopBackOff | It starts, then keeps dying. Usually your code | kubectl logs <name> --previous |
Running but 0/1 | Alive, but not ready for traffic yet | kubectl describe pod <name> |
Running, users see errors | Usually a label mismatch, so no endpoints | kubectl get endpoints nginx |
Six steps, six sentences. If you can say these, you have got it.
created means "saved", not "running".kubectl get endpoints is the proof it worked.rollout restart β Pods are replaced, never restarted.describe when it never started, logs when it started but is wrong. A failed update is not automatically an outage.| When you want toβ¦ | Run |
|---|---|
| Check which cluster you are on (always do this first) | kubectl config current-context |
| See everything you made | kubectl get all |
| See Pods with IPs and machines | kubectl get pods -o wide |
| Watch changes live | kubectl get pods -w |
| Find out why something is unhappy | kubectl describe pod NAME |
| Read an app's output | kubectl logs NAME |
| Check a Service found its Pods | kubectl get endpoints NAME |
| Change the number of copies | kubectl scale deployment NAME --replicas=N |
| Ship a new image | kubectl set image deployment/NAME container=image:tag |
| Replace all Pods safely | kubectl rollout restart deployment/NAME |
| Undo the last change | kubectl rollout undo deployment/NAME |
| Check a YAML file before applying | kubectl apply --dry-run=client -f file.yaml |
| Look up any field | kubectl explain deployment.spec.replicas |
| What you see | What to do |
|---|---|
connection to the server was refused | docker start k8s-lab-control-plane k8s-lab-worker k8s-lab-worker2, wait 60 seconds |
error validating data: unknown field | YAML spacing, usually tabs. Run kubectl apply --dry-run=client -f deployment.yaml to see the line |
mapping values are not allowed | Missing space after a colon. YAML needs key: value |
Endpoints show <none> | Your labels do not match. Compare them letter by letter |
localhost:30000 refuses to connect | Run docker port k8s-lab-control-plane | grep 30000. Nothing? Your cluster was built without the config file |
| Browser cannot reach localhost at all (WSL) | Run hostname -I | awk '{print $1}', then open http://<that-ip>:30000 |
A Pod is stuck in Pending | kubectl describe pod NAME. The Events line says exactly why |
| Commands "not found" that worked before | You are in PowerShell. Type wsl |
| Everything is very slow | Your files are under /mnt/c/. Run cd ~ and work there |
This removes your app but keeps the cluster, so you can restart any step:
kubectl delete deployment nginx --ignore-not-found
kubectl delete service nginx --ignore-not-found
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
kubectl wait --for=condition=Available deployment/nginx --timeout=120s
Try to answer out loud before opening each one.
replicas: 3. Which objects exist now, and who made them?A Deployment, one ReplicaSet, and three Pods, each running one container. The Deployment made the ReplicaSet. The ReplicaSet made the Pods. You wrote only the Deployment. Controllers did the rest.
Because the desired state is still three. The ReplicaSet controller saw two, wanted three, and made one more. It is a new Pod with a new name and new IP, not the old one started again. To really remove the app you must change the wish: kubectl delete deployment nginx.
app: nginx-web but your Pods say app: nginx. Every Pod is Running. What does the user see?A connection error or a timeout. The Service has zero endpoints, so there is nothing to send traffic to. Labels must match exactly, and the failure is silent because the Pods really are healthy. Fastest check: kubectl get endpoints nginx shows <none>.
kubectl rollout restart. The Pod names changed but RESTARTS is still 0. Is that correct?Yes, and this is the point. Nothing restarted. The old Pods were deleted and new Pods were created, one at a time. If a container had truly restarted in place, RESTARTS would be 1 and the Pod name would be the same. The image and your YAML file did not change at all.
ImagePullBackOff. Why do users still get a working site, and how do you recover?Because new Pods must become healthy before old ones are removed. The new Pod never became healthy, so the update stopped and the old Pods kept serving. The Service also never added the broken Pod. Recover with kubectl rollout undo deployment/nginx.
Two options. Choose based on whether you will continue later.
kubectl delete -f service.yaml
kubectl delete -f deployment.yaml
kubectl get all
Only the built-in kubernetes Service should remain. Notice you never deleted a Pod or a ReplicaSet. Deleting the Deployment removed everything it owned. That is the chain from step 2, working backwards.
kind delete cluster --name k8s-lab
kind get clusters # prints nothing
This deletes all three containers. Everything inside is gone. Your YAML files on disk are safe, so you can rebuild the whole lab in about four minutes.
π¬ Keep your three files. kind-config.yaml, deployment.yaml and service.yaml are the real result of today. Not the Pods, which were always disposable. Not the cluster, which you can rebuild from a file. Commit them to Git.
Take the app you built in Docker and deploy it on Kubernetes.
Earlier in the Docker practical, you built the CampusConnect app in the Docker section. That app was created as a Python Flask service and ran locally with a Docker image. Now your task is to take that same image and deploy it on Kubernetes.
Open the Docker Practical Lab: https://labs.docker.learnwithdutta.com/
This is the real next step: Docker builds the image, Kubernetes runs it at scale.
FROM python:3.12-slim, EXPOSE 8000, and ran python app.py.docker build -t campusconnect:v1 -f Dockerfile.v1 .deployment.yaml for the Python app, with the correct container image and port mapping.kubectl apply -f deployment.yaml and check the rollout with kubectl rollout status deployment/<name>.Goal: Take the app you built in the Docker practical and run it as a Kubernetes Deployment, just like a real production workflow.