Deploying Workloads to AKS
Why this matters
You already know Deployments, Services, and ConfigMaps/Secrets from local Kubernetes. The API objects don't change on AKS — but the environment around them does: real DNS, real cloud load balancers, real resource limits tied to real VM sizes, and real cost if you oversize something. This module is about confirming your existing Kubernetes muscle memory transfers, and learning the handful of things that are genuinely different on a managed cloud cluster.
Concepts
Same API, different substrate. A Deployment YAML you wrote for
kind/minikube will kubectl apply unchanged against AKS — the Kubernetes
API is the Kubernetes API. What changes is what's underneath: kubectl get pods -o wide on AKS shows pods scheduled onto real Azure VM nodes with
real (limited) CPU/memory, not a laptop's whole resource pool shared
across a fake multi-node setup.
Resource requests/limits matter more here. On a local cluster, a Pod
with no resource requests just uses whatever your laptop has free. On a
small 2-node AKS cluster of Standard_B2s VMs, each node has a hard,
small CPU/memory ceiling (some of which is already reserved by
system/kubelet overhead). Pods without requests can starve each other or,
worse, get stuck Pending because nothing fits. This is the most common
new failure mode you'll meet in this module.
Default storage class. AKS ships a default StorageClass backed by
Azure Disks (and one backed by Azure Files) out of the box — kubectl get storageclass shows them immediately, unlike a bare-bones kind cluster
where you may have had to install a provisioner yourself. A PVC with no
storageClassName binds to whatever the cluster marks as default.
Namespaces work identically. kubectl create namespace, per-namespace
ConfigMaps/Secrets, kubectl config set-context --current --namespace=...
— all unchanged from local Kubernetes.
What AKS manages vs. what you own here: Azure manages node
provisioning, the container runtime, and kubelet health-reporting to the
control plane. You still own: what resource requests/limits your
workloads declare, how many replicas you run relative to node capacity,
your ConfigMap/Secret content, and reading kubectl describe output when
something won't schedule — none of that is automated away by AKS.
Command reference
| Command | What it does | Example |
|---|---|---|
kubectl create namespace |
Creates a namespace | kubectl create namespace demo |
kubectl config set-context --current --namespace |
Sets the default namespace for your current context | kubectl config set-context --current --namespace=demo |
kubectl apply -f |
Creates/updates objects from a manifest | kubectl apply -f deployment.yaml |
kubectl get pods -o wide |
Lists pods with node/IP columns | kubectl get pods -o wide |
kubectl describe pod |
Shows detailed status/events for a pod | kubectl describe pod <name> |
kubectl get events --sort-by=.lastTimestamp |
Shows recent cluster events, oldest first | kubectl get events --sort-by=.lastTimestamp |
kubectl top nodes |
Shows current CPU/memory usage per node (requires metrics-server, included by default in AKS) | kubectl top nodes |
kubectl top pods |
Shows current CPU/memory usage per pod | kubectl top pods |
kubectl get storageclass |
Lists available storage classes | kubectl get storageclass |
kubectl create configmap |
Creates a ConfigMap from literals or files | kubectl create configmap app-config --from-literal=ENV=demo |
kubectl create secret generic |
Creates a Secret | kubectl create secret generic app-secret --from-literal=API_KEY=xyz |
kubectl rollout status |
Watches a Deployment rollout to completion | kubectl rollout status deployment/demo-app |
kubectl logs |
Shows container logs | kubectl logs <pod-name> |
Hands-on exercises
Confirm your cluster context. Run
kubectl config current-context— confirm it saysaks-learn(recreate the cluster per module 01 if you deleted it). Create a working namespace:kubectl create namespace demothenkubectl config set-context --current --namespace=demo.Deploy something you already know. Write a small Deployment manifest for any simple image (e.g.
nginxor an app image you built in the Docker track) with 2 replicas, andkubectl apply -fit. Verify:kubectl get deploymentshows2/2ready, andkubectl get pods -o wideshows both podsRunningwith aNODEcolumn populated (this is new versus a single-node local cluster — confirm the two pods can land on different nodes: check theNODEcolumn across both pods).Expose it with a ClusterIP Service (not LoadBalancer yet — that's module 04). Write a Service manifest selecting your Deployment's labels,
kubectl apply -fit, then verify connectivity from inside the cluster:kubectl run tmp-curl --rm -it --image=curlimages/curl --restart=Never -- curl -s <service-name>.<namespace>.svc.cluster.local. Verify: you get a response body back.Add a ConfigMap and Secret, wire them into the Deployment. Create a ConfigMap and Secret with
kubectl create configmap/kubectl create secret generic, mount or inject them as env vars in your Deployment spec, re-apply, and verify withkubectl exec -it <pod-name> -- env | grep <YOUR_VAR>that the value is present in the running container.Check the default storage class. Run
kubectl get storageclass. Verify: at least one StorageClass exists and is marked(default)next to its name — note itsPROVISIONER(an Azure Disks or Azure Files CSI driver), confirming AKS wires this up without you installing anything, unlike a bare kind cluster.Check real resource usage. Run
kubectl top nodesandkubectl top pods. Verify: both commands return numbers (metrics-server is preinstalled on AKS). Compare theCPU(cores)/MEMORY(bytes)columns against the total capacity of aStandard_B2snode (2 vCPU) — note how little headroom two of these small nodes actually have once system pods (kube-systemnamespace) are accounted for: runkubectl get pods -n kube-systemto see what's already running there.Diagnose and fix: pods stuck Pending from insufficient resources. Deliberately request more CPU than the cluster can give: edit your Deployment to request
resources.requests.cpu: "4"per replica (more than a wholeStandard_B2snode has) andkubectl apply -fit. Watchkubectl get pods— the new pods will sitPending. Runkubectl describe pod <pending-pod-name>and read theEventssection — you should see a message like0/2 nodes are available: 2 Insufficient cpu. Fix it by editing the request back down to something realistic (e.g."250m"), re-apply, and verify the pods becomeRunning.Clean up. Delete the namespace to remove everything you created in it:
kubectl delete namespace demo. This does not touch the cluster itself (still billing per module 01's note) — if you're done for the day, also runaz aks stop --resource-group rg-aks-learn --name aks-learnoraz group delete --name rg-aks-learn --yes --no-waitif you don't need the cluster again soon.
Independent challenge
No commands given here — figure it out yourself using what you know from this module and earlier ones.
Task: Given a fixed two-node Standard_B2s cluster (from module 01 — conceptually building on that module's node sizing), figure out empirically how many replicas of a modest workload it can actually run before the scheduler runs out of room, and prove the failure is a resource problem rather than anything else. Pick any simple image, give each replica a deliberate, realistic CPU/memory request, and scale the replica count up until new pods stop being placed. Confirm from the pods' own status why the last ones won't schedule, then bring the workload back down to a count that fully fits. Do all of this in a throwaway namespace and delete that namespace when you're done, and decide whether the cluster itself should keep running afterward — the nodes bill whether or not anything is scheduled on them.
Stuck? One hint
The scheduler compares each pod's resources.requests against each node's allocatable capacity; when a pod can't fit, kubectl describe pod on the stuck pod spells out the reason in its Events, using a phrase like "Insufficient cpu."
Common mistakes & troubleshooting
- No resource requests/limits on a small cluster. Locally this was
often harmless; on a 2-node
Standard_B2sAKS cluster it's the single most common cause of surprising scheduling behavior. Set requests deliberately. - Assuming
kubectl top"just works" like on a local cluster without checking. It does on AKS (metrics-server ships by default), but if you ever see it fail, that's a real signal something about the cluster add-ons is broken, not just "expected on this platform." - Forgetting which namespace you're in.
kubectl config set-context --current --namespace=demopersists in your kubeconfig — if objects seem to have vanished, runkubectl config view --minify | grep namespaceor add--all-namespacesto yourgetcommand before assuming something is broken. - Confusing
Pending(can't schedule) withCrashLoopBackOff(scheduled but the container keeps failing) orImagePullBackOff(scheduled but the image can't be pulled — covered in module 03).kubectl describe podalways tells you which one you're looking at in theEventssection; read it before guessing. - Cost pitfall: leaving a Deployment scaled up "to test scheduling." Deployment replica count doesn't add nodes and isn't separately billed beyond the node VMs you already have running, but combined with the next module's LoadBalancer Services, forgotten test Services can quietly rack up their own IP/LB charges — clean up namespaces you're done with instead of leaving them "for later."
Checkpoint quiz
Write down your answer to each question before expanding it — checking without attempting first is the single easiest way to fool yourself into thinking you've learned this.
- Why can a Deployment manifest that worked on kind/minikube fail to fully schedule on a small AKS cluster with no changes to the YAML itself?
- What command shows current CPU/memory usage per node, and what component makes it work?
- What does AKS provide out of the box, storage-wise, that a bare kind cluster typically does not?
- If
kubectl get podsshows a podPending, what's the first command you run to find out why, and what section of its output matters most? - What's the difference between a pod stuck
Pendingand one inCrashLoopBackOff? - Why doesn't scaling a Deployment's replica count by itself change your Azure bill?
Show answers
- Because scheduling now competes for real, limited node CPU/memory (each node is a small VM with its own hard ceiling, plus kube-system/kubelet overhead), whereas a local cluster often had a whole laptop's resources effectively available.
kubectl top nodes(andkubectl top podsfor pod-level); both rely onmetrics-server, which ships preinstalled on AKS.- A default
StorageClass(or two — Azure Disk and Azure Files backed) already registered and ready to satisfy PVCs with nostorageClassNamespecified, without installing a provisioner yourself. kubectl describe pod <name>; theEventssection at the bottom, which shows scheduler messages like "Insufficient cpu" or "Insufficient memory."Pendingmeans the pod hasn't been scheduled to any node yet (usually a resource or constraint problem);CrashLoopBackOffmeans it was scheduled and started, but the container keeps exiting and Kubernetes is backing off restart attempts.- Because you're billed for the node VMs that exist, not for how many pods/replicas run on them — scaling replicas up (until you run out of node capacity) doesn't add or remove billable infrastructure by itself.
Next
03-acr-integration-with-aks — pull your own images from Azure Container Registry into AKS securely.