Create is easy; the lesson is what heals it
Standing up a Kubernetes cluster takes one command and ten minutes, which is exactly why so many people have "created an AKS cluster" on their résumé and so few can explain what happened next. This lab is built around the moment that turns the first group into the second: you will deploy an app, delete one of its running pods by hand, and watch a controller notice the gap and recreate it — because you did not deploy a container, you deployed a statement that a container should exist, and the cluster spends its life defending that statement. Understand that, and every later feature — probes, autoscaling, upgrades — is the same idea again.
You will create a one-node cluster on the free control-plane tier, connect kubectl to it, run a Deployment, break it and watch it heal, reach it through a Service, then delete everything. Nothing here needs Docker on your machine and nothing here needs a credit card beyond the few cents the single node bills while it lives.
You did not deploy a container. You deployed a promise the cluster keeps.
You need a free Azure account and the Azure CLI (az), signed in with az login. First time? The 15-minute Set up your machine page covers the account, the installs, and sign-in. Prefer zero installs? Run everything in Azure Cloud Shell (Bash) — it has az and kubectl preinstalled and is already signed in.
The AKS control plane is free on the Free tier. The node is not: it is one Standard_B2s virtual machine that bills by the second it exists — cents per hour, not zero. Run the lab start-to-finish in one sitting and delete the resource group at the end and the whole thing costs well under a dollar. Walk away with the node running and it bills all week. Set a phone timer if you must step away.
One node, free control plane
Create a resource group and the smallest sensible cluster: a single-node system pool on the free tier, with a managed identity and monitoring left off to keep it lean.
# Windows/Git Bash: stop it mangling /subscriptions/... arguments (harmless on macOS/Linux) export MSYS_NO_PATHCONV=1 RG="campux-lab-aks-rg" az group create -n "$RG" -l eastus # --tier free = free managed control plane, no uptime SLA (right for a lab) # one small burstable node; managed identity for the cluster az aks create -n campux-aks -g "$RG" \ --tier free \ --node-count 1 \ --node-vm-size Standard_B2s \ --enable-managed-identity \ --generate-ssh-keys
az aks create returns a large JSON block with "provisioningState": "Succeeded". You now have a real Kubernetes control plane that Azure operates for free, attached to exactly one node that is quietly billing. That asymmetry — free brain, paid muscle — is the single most important cost fact about AKS.Point kubectl at the cluster
Everything from here is kubectl talking to the API server. Merge the cluster's credentials into your kubeconfig, then confirm the node is ready.
az aks get-credentials -n campux-aks -g "$RG"
kubectl get nodes
kubectl get pods -A # -A = all namespaces, incl. the system ones AKS runs
kubectl get nodes lists one node with status Ready. kubectl get pods -A shows pods already running in kube-system — CoreDNS, the metrics server, networking — the machinery AKS placed there for you. You never deployed those; the control plane keeps them running the same way it will keep yours.Deploy a statement of intent
Create a Deployment of two replicas. Read the command as a sentence: "two copies of this image should exist." Then watch the cluster make it true.
kubectl create deployment web \
--image=mcr.microsoft.com/k8se/quickstart:latest \
--replicas=2
kubectl get pods -l app=web -w # -w = watch; Ctrl-C once both are Running
ContainerCreating, and settle on Running. You did not start two containers — you told the API server that two should exist, and a controller created them to close the gap between desired (2) and observed (0). Note the pod names: a Deployment stamps each with a random suffix, because pods are cattle, not pets.Break it — the moment Kubernetes clicks
Now delete one running pod by hand and immediately list them again. Do it quickly so you catch the cluster in the act.
# grab one pod's name, delete it, then look right away
POD=$(kubectl get pods -l app=web -o jsonpath='{.items[0].metadata.name}')
kubectl delete pod "$POD"
kubectl get pods -l app=web
Running. You are still at two. Nobody restarted anything by hand; the Deployment's desired count is 2, a controller observed 1, and it recreated a pod to close the difference. This is the reconciliation loop, and it is the whole of Kubernetes in one command. Everything else is this, applied to health, scale, and traffic.Reach the app
The pods run, but nothing outside the cluster can reach them yet. Expose the Deployment behind a Service — a stable name and address in front of the shifting pods — then forward a local port to it. Port-forward keeps the lab free; a real internet-facing app would use a LoadBalancer Service (a note below shows the one-line version, and why it costs a little more).
kubectl expose deployment web --port=80 --target-port=80
kubectl get service web
# forward localhost:8080 to the Service; open the URL, then Ctrl-C
kubectl port-forward service/web 8080:80
kubectl get service web shows a ClusterIP Service. With port-forward running, open http://localhost:8080 (in Cloud Shell, use the web preview on port 8080) and you get the quickstart welcome page — served by whichever pod the Service chose. Delete a pod again while the page is up and reload: still served, because the Service routes to whichever replica is healthy. That indirection is why pods can be disposable.Tear it down — before the node bills overnight
One resource group holds the cluster and its node. Delete it, and delete the node's automatically-created infrastructure group too if it lingers.
az group delete -n campux-lab-aks-rg --yes --no-wait
az group exists -n campux-lab-aks-rg # -> false once complete
MC_campux-lab-aks-rg_campux-aks_eastus) for the node infrastructure; deleting the cluster's group tears that down too. Confirm both are gone in the portal if you want to be certain no VM is left running.What you can now honestly claim
You created an AKS cluster, connected kubectl to its API server, deployed a Deployment, demonstrated the reconciliation loop by deleting a pod and watching it heal, exposed the app behind a Service, and tore the whole thing down for pocket change. More important than any of the commands: you can now explain, from having seen it, why deleting a pod is not an outage and why the cluster is a promise-keeper rather than a container-starter. That sentence, said in an interview with the confidence of someone who watched it happen, is worth more than a memorised kubectl cheatsheet.1
- A production-facing app would be exposed with a
Serviceof typeLoadBalancer(kubectl expose deployment web --type=LoadBalancer --port=80), which provisions a public Azure load balancer and a static IP — a small standing cost, which is why this lab usesport-forwardinstead. In front of many apps you would put an ingress controller rather than one load balancer per Service; that is the subject of Class 27c. ↩ - The Free tier gives a managed control plane with no financially-backed uptime SLA — correct for a lab and fine for many workloads that tolerate the occasional control-plane blip. The Standard tier adds the SLA for a per-cluster hourly fee. Neither tier changes the node bill, which is separate and always present. Quote the current Azure pricing page before you promise anyone a number. ↩