Task 4 - Deploy and Scaling Application
Objective
This task deploys an nginx application and demonstrates basic Kubernetes scaling with HPA.
The updated script keeps the original workshop flow but updates the supporting components:
- Local Path Provisioner for simple lab storage
- Metrics Server for HPA CPU metrics
- MetalLB for LoadBalancer testing
- Kong Ingress Controller for ingress testing
- cert-manager for a self-signed test certificate
- nginx deployment and HPA
Preflight check
This task builds on the two-node cluster from Task 3 - Install Kubernetes. Confirm that cluster is healthy and that the default namespace is still empty before you run the deploy script. From Azure Cloud Shell:
kubectl get nodes
kubectl get podsBoth nodes must report Ready:
NAME STATUS ROLES AGE VERSION
node-worker Ready <none> 10m v1.30.x
node-master Ready control-plane 15m v1.30.xThe default namespace should have no workloads yet. kubectl reports an empty list on stderr:
No resources found in default namespace.If a node is missing or NotReady, go back to Task 3 and finish it before continuing.
Deploy the application and HPA demo
Run this from Azure Cloud Shell:
cd $HOME/k8s-101-workshop/terraform/
master=$(terraform output -json | jq -r .linuxvm_master_FQDN.value)
username=$(terraform output -json | jq -r .linuxvm_username.value)
scp -o 'StrictHostKeyChecking=no' $HOME/k8s-101-workshop/scripts/deploy_application_with_hpa_masternode.sh $username@$master:~/deploy_application_with_hpa_masternode.sh
ssh -o 'StrictHostKeyChecking=no' -t $username@$master "export FQDN=${master}; bash ~/deploy_application_with_hpa_masternode.sh"Verify resources
kubectl get nodes
kubectl get pods -A
kubectl get deployment nginx-deployment
kubectl get hpa
kubectl get ingress
kubectl get svc -AExpected nginx deployment:
NAME READY UP-TO-DATE AVAILABLE
nginx-deployment 2/2 2 2Expected HPA object:
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
nginx-hpa Deployment/nginx-deployment <unknown>/50% 2 10 2 34sAfter Metrics Server starts collecting CPU metrics, the HPA target changes from <unknown> to a CPU percentage.
FortiAIGate readiness checks
After this task completes, run these checks before starting a FortiAIGate deployment:
kubectl cluster-infoExpected output
Kubernetes control plane is running at https://k8sXX-master.eastus.cloudapp.azure.com:6443
CoreDNS is running at https://k8sXX-master.eastus.cloudapp.azure.com:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.kubectl get nodes -o wideNAME STATUS ROLES AGE VERSION
node-master Ready control-plane 53m v1.30.14
node-worker Ready <none> 50m v1.30.14kubectl get pods -ANAMESPACE NAME READY STATUS RESTARTS AGE
calico-apiserver calico-apiserver-66f4fd9b6d-9fcwp 1/1 Running 0 53m
calico-apiserver calico-apiserver-66f4fd9b6d-xnb9q 1/1 Running 0 53m
calico-system calico-kube-controllers-7677dd5467-9d2cj 1/1 Running 0 53m
calico-system calico-node-lgddt 1/1 Running 0 53m
calico-system calico-node-rj584 1/1 Running 0 51m
calico-system calico-typha-6f4bcd7686-8lpqc 1/1 Running 0 53m
calico-system csi-node-driver-bzl2x 2/2 Running 0 51m
calico-system csi-node-driver-g6k5j 2/2 Running 0 53m
cert-manager cert-manager-5b98df59d6-fld8m 1/1 Running 0 48m
cert-manager cert-manager-cainjector-f48cd678-8gsrt 1/1 Running 0 48m
cert-manager cert-manager-webhook-76cdf6c497-bzn54 1/1 Running 0 48m
default nginx-deployment-d68499895-d4kn6 1/1 Running 0 48m
default nginx-deployment-d68499895-tzl8x 1/1 Running 0 48m
kube-system coredns-55cb58b774-qddlw 1/1 Running 0 53m
kube-system coredns-55cb58b774-zkf9k 1/1 Running 0 53m
kube-system etcd-node-master 1/1 Running 0 54m
kube-system kube-apiserver-node-master 1/1 Running 0 54m
kube-system kube-controller-manager-node-master 1/1 Running 0 54m
kube-system kube-proxy-nncmh 1/1 Running 0 51m
kube-system kube-proxy-sg5nk 1/1 Running 0 53m
kube-system kube-scheduler-node-master 1/1 Running 0 54m
kube-system metrics-server-75778854d4-c2l97 1/1 Running 0 49m
local-path-storage local-path-provisioner-7dd969c95d-sfkpk 1/1 Running 0 49m
metallb-system controller-589cbf5c44-h96m8 1/1 Running 0 49m
metallb-system speaker-glbw9 1/1 Running 0 49m
metallb-system speaker-nl9fc 1/1 Running 0 49m
tigera-operator tigera-operator-576646c5b6-6twqm 1/1 Running 0 53mhelm version
helm listBoth commands must succeed. The exact version numbers depend on the helm build installed in your environment, so do not compare them literally — what matters is that helm version prints a version.BuildInfo{...} line rather than command not found, and that helm list returns without a connection error.
helm version prints one line in this shape:
version.BuildInfo{Version:"...", GitCommit:"...", GitTreeState:"clean", GoVersion:"...", KubeClientVersion:"..."}helm list confirms helm can reach the cluster with your kubeconfig. With no releases installed it prints only the column header and exits 0. An error mentioning the Kubernetes API server instead means your kubeconfig is not working — recheck the kubeconfig step in Task 3.
kubectl get ingressclassExpected output — the deploy script you ran above installs the Kong Ingress Controller, so one IngressClass named kong must already exist:
NAME CONTROLLER PARAMETERS AGE
kong ingress-controllers.konghq.com/kong <none> 9hThe AGE column just reflects how long ago the deploy script installed Kong, so your value will differ. If this list is empty, the Kong step of the deploy script did not complete — rerun it before continuing.
kubectl get storageclassExpected output
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
local-path (default) rancher.io/local-path Delete WaitForFirstConsumer false 52mGenerate load
Run a temporary load generator from the cluster:
kubectl run -i --tty load-generator --rm --image=busybox:1.36 --restart=Never -- /bin/shInside the shell, run:
while true; do wget -q -O- http://nginx-deployment.default.svc.cluster.local; doneIn another terminal, watch HPA:
watch kubectl get hpaYou can also watch pods scale:
watch kubectl get pods -l app=nginxStop load
Press CTRL+C in the load generator shell, then type:
exitCleanup
kubectl delete hpa nginx-hpa --ignore-not-found
kubectl delete ingress nginx --ignore-not-found
kubectl delete deployment nginx-deployment --ignore-not-found
kubectl delete service nginx-deployment --ignore-not-found
kubectl delete pod load-generator --ignore-not-foundkubectl delete -f https://raw.githubusercontent.com/Kong/kubernetes-ingress-controller/v3.5.0/deploy/single/all-in-one-dbless.yaml --ignore-not-found
kubectl delete -f https://github.com/cert-manager/cert-manager/releases/download/v1.18.2/cert-manager.yaml --ignore-not-found
kubectl delete -f https://raw.githubusercontent.com/metallb/metallb/v0.15.2/config/manifests/metallb-native.yaml --ignore-not-found
kubectl delete -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml --ignore-not-found
kubectl delete -f https://raw.githubusercontent.com/rancher/local-path-provisioner/v0.0.31/deploy/local-path-storage.yaml --ignore-not-foundSummary
This chapter demonstrates deploying an application, exposing it through Kubernetes objects, installing Metrics Server, and using HPA to scale pods based on CPU utilization.
Review Questions
Describe how to make the client application generate more traffic.
How many minutes do you need to wait before nginx pods start increasing?
How do you stop sending traffic to nginx deployment?