Fortinet XPerts 2026

Welcome to the Kubernets(K8s) 101 Workshop

ACME is embarking on deploying a hyperscale web application.

Concerns:

  • Decoupling Applications from Infrastructure
  • Automatic Scaling
  • Seamless Upgrades
  • High Availability (HA)
  • Security
  • Cost Management
  • Performance Monitoring and Logging
  • Disaster Recovery and Data Persistence
  • Compliance and Data Sovereignty
  • Service Mesh Integration
  • Developer Productivity
  • Interoperability and Integration

Considered Alternatives:

  • Docker Compose
  • Docker Swarm
  • Apache Mesos
  • Nomad

Kubernetes Achievements:

  • Decoupling: Application from Infrastructure
  • Scaling: Automatic and Efficient
  • Upgrades: Seamless with Zero Downtime
  • HA: Robust Support for High Availability
  • Security: Enhanced with RBAC, Network Policies, PSP
  • Resource Utilization: Optimized and Efficient
  • Monitoring: Integration with Tools like Prometheus
  • Data Persistence: Support for StatefulSets and Persistent Volumes
  • Flexibility: Across Various Environments
  • Service Mesh: Easy Integration with Istio/Envoy
  • Developer Productivity: Automation of Deployment and Management
  • Integration: With CI/CD, Databases, Monitoring Tools, etc.

Non-Kubernetes solutions face challenges with scalability, automation, complexity in management, cost-effectiveness at scale, and lack of ecosystem support.

This workshop focuses on deploying, scaling, and upgrading applications with Kubernetes, addressing these challenges by leveraging Kubernetes’ comprehensive and extensible platform, designed for efficient container orchestration.

Copyright© 2026 Fortinet, Inc. All rights reserved. Fortinet®, FortiGate®, FortiCare® and FortiGuard®, and certain other marks are registered trademarks of Fortinet, Inc., and other Fortinet names herein may also be registered and/or common law trademarks of Fortinet. All other product or company names may be trademarks of their respective owners. Performance and other metrics contained herein were attained in internal lab tests under ideal conditions, and actual performance and other results may vary. Network variables, different network environments and other conditions may affect performance results. Nothing herein represents any binding commitment by Fortinet, and Fortinet disclaims all warranties, whether express or implied, except to the extent Fortinet enters a binding written contract, signed by Fortinet’s General Counsel, with a purchaser that expressly warrants that the identified product will perform according to certain expressly-identified performance metrics and, in such event, only the specific performance metrics expressly identified in such binding written contract shall be binding on Fortinet. For absolute clarity, any such warranty will be limited to performance in the same ideal conditions as in Fortinet’s internal lab tests. Fortinet disclaims in full any covenants, representations, and guarantees pursuant hereto, whether express or implied. Fortinet reserves the right to change, modify, transfer, or otherwise revise this publication without notice, and the most current version of the publication shall be applicable.

Subsections of Fortinet XPerts 2026

Kubernetes TECWorkshop

Welcome to K8s 101 workshop

About Kubernetes

Kubernetes is an open-source container orchestration platform that automates the deployment, scaling, and management of containerized applications. Originally developed by Google and now maintained by the Cloud Native Computing Foundation (CNCF), Kubernetes has become the de facto standard for managing containerized workloads in production environments. K8s is short name of Kubernetes.

Workshop Objectives

In this workshop, you will:

  • Understand why containers are important, and how they fit into a microservices architecture orchestrated by K8s
  • Build a Kubernetes cluster on a Virtual Machine (VM)in Azure.
  • Gain a fundamental understanding of Kubernetes concepts.
  • Learn how to deploy, manage, and scale applications using Kubernetes.
  • Explore key Kubernetes resources such as Pods, Deployments, Services, and more.
  • Gain hands-on experience with practical examples.

Building a K8s cluster from scratch vs using Managed Services like AKS,EKS,GKE?

  • Building a self-managed cluster instead of relying on managed services for learning Kubernetes offers beginners hands-on experience, deeper understanding of core concepts, and greater control over configurations.

  • Building a cluster offers deeper understanding of core Kubernetes components such as pods, nodes, and controllers.

  • In production environments, many organizations use managed Kubernetes services provided by major cloud providers.

  • These services—Amazon Elastic Kubernetes Service (EKS), Azure Kubernetes Service (AKS), and Google Kubernetes Engine (GKE)—are managed Kubernetes offerings from AWS, Azure, and Google Cloud. They provide features such as automated updates, scaling, monitoring, and seamless integration with other cloud services.

  • However, the choice often depends on factors such as existing cloud provider relationships, specific feature requirements, pricing, and the level of control and customization desired by the organization.

Formatting Conventions

  • Yellow Highlighted Text: Indicates that is a command lines you can execute on shell.
  • Black Box: Contains bash commands intended for you to copy and paste directly into your terminal for your task.
  • Grey Box: Displays the output from executed commands. The text above each box explains the command’s purpose and intent.

Unless explicitly stated otherwise, all CLI commands should be executed directly in the Azure Cloud Shell.

Workshop Logistics

Workshop Quickstart and Setup

  • Setup Azure Cloud Shell
  • Use Terraform to initialize two VMs for a self-managed Kubernetes installation

Self-Managed K8s Workshop Hands-on

K8s install

  • Task 3 : Install a self-managed Kubernetes cluster using kubeadm
  • Task 4 : Deploy and Scale application

Kubernetes in depth

  • Deep dive into Kubernetes, including application deployment, upgrades, and exposure

Guidance for Participants:

  • Beginners:

If you’re new to Kubernetes, work straight through in order: Workshop Quickstart and Setup, then K8s install, then as much of Kubernetes in depth as time allows.

  • Experienced Users:

If you have prior Kubernetes experience, move quickly through K8s install and spend your time in Kubernetes in depth. Do not skip Workshop Quickstart and Setup — it runs the provisioning form and the Terraform that creates the two Azure VMs every later task depends on.

Subsections of Workshop Logistics

Quickstart

Provisioning the Azure environment (40min)

Provision your Azure Environment, enter your Email address and click Provision

Provision your Portal Accounts
Required for hands-on portion of this workshop

Info

Enter your email and click **Provision Accounts** once. You'll see a live progress bar while your account is created — this typically takes a few minutes. When it's done, your credentials (username and sign-in info) will appear directly on this page, and a copy is also sent to your email as a backup. If you reload this page or come back later, your credentials will still be here — no need to re-submit. If you're continuing from an earlier workshop in this session, you'll be offered the option to reuse your existing credentials instead of provisioning new ones.

Tasks

  • Setup Azure Cloud Shell
  • Run Terraform
  • Verify Terraform

Subsections of Quickstart

Task 1 - Setup Azure Cloud Shell

Azure Cloud Shell is a browser-based command-line environment built into the Azure portal. It includes the Azure CLI and common tools needed for Kubernetes tasks, ensuring a consistent environment for everyone without the need to install tools locally.

1. Setup your Azure Cloud Shell

  • Login to Azure Cloud Portal https://portal.azure.com/ with the provided login/password

    cloudshell1
    cloudshell2

  • Click the link “Skip for now (14 days until this is required)” do not click the “Next” button

    cloudshell3
  • Click the “Next” button

    cloudshell4
  • Click on Cloud Shell icon on the Top Right side of the portal

    cloudshell5
  • Select Bash

    cloudshell6
  • Click on Mount Storage Account

    cloudshell7
  • Select

    • Storage Account Subscription - Internal-Training
    • Apply
  • Click Select existing Storage account, Click Next

    cloudshell8
  • in Select Storage account Step,

    • Subscription: Internal-Training
    • Resource Group: Select the Resource group from the drop down: k8sXX-k8s101-workshop, where k8sXX is your assigned lab username
    • Storage Account: Use existing storage account from dropdown.
    • File share: Use cloudshellshare
    • Click Select
    cloudshell9
Warning

Please make sure to use the existing ones. you wont be able to create any Resource Group or Storage account

  • After 1-2 minutes, You should now have access to Azure Cloud Shell console

    cloudshell10

Task 2 - Run Terraform

Task 2 - Launch resources using Terraform

All the components required for Lab are deployed through terraform.

Lab Architecture:

lab001

Perform the following steps in your Cloudshell console to create your environment.

  1. Clone the Github repo git clone https://github.com/FortinetCloudCSE/k8s-101-workshop.git
  2. Change directory to the cd k8s-101-workshop/terraform folder
  3. Run terraform init
git clone https://github.com/FortinetCloudCSE/k8s-101-workshop.git
cd k8s-101-workshop/terraform
terraform init
lab11
  1. Run the following command to apply it

    terraform apply -var="username=$(whoami)" --auto-approve
    IF THE COMMAND ABOVE RESULTS IN AN ERROR

    You can manually specify your username (found in your Azure Account email) in the command
    If your Workshop Azure account login is se31@ftntxxxxx.onmicrosoft.com, your username is se31, and the command to enter is:

    terraform apply  -var='username=se31' --auto-approve
lab12
  1. Terraform deployment takes atleast 10-15 min to complete.
lab13
  1. Once Terraform is complete you should see the output. Please copy the output to notepad.
output
  1. To print the node VM’s login password, you can run this command

    terraform output -raw linuxvm_password

Self Managed K8s Workshop Hands-on Agenda

Workshop tasks

  1. Install Self-Managed K8S on azure VM

  2. Create, Scale, Upgrade, Expose your deployment

Subsections of Self Managed K8s Workshop Hands-on

Kubernetes installation

Objective:

Use kubeadm to install a self-managed Kubernetes cluster with 1 master node and 1 worker node on Azure ubuntu VM.

Choose your Kubernetes

Although Cloud-Managed Kubernetes becoming the popular choice for enteprise to use in production network, But Self Managed Kubernetes give users full control over their Kubernetes environments. Choosing the right method to install Self Managed Kubernetes can vary significantly based on the intended use case, from development and testing environments to production deployments. Here’s a short description of different ways to install Kubernetes, tailored to specific needs:

For Development and Testing

  • Minikube: Best For: Individual developers and small teams experimenting with Kubernetes applications or learning the Kubernetes ecosystem.

  • Kind (Kubernetes in Docker): Best For: Kubernetes contributors, developers working on CI/CD pipelines, and testing Kubernetes configurations.

  • OrbStack Kubernetes: Best for: development and testing on MacOS desktop with Apple Silicon or intel chipset, it eliminates the complexity of setting up and managing full-fledged Kubernetes clusters.

For Production Deployment

  • Kubeadm: Best For: Organizations looking for a customizable production-grade Kubernetes setup that adheres to best practices. Suitable for those with specific infrastructure requirements and those who wish to integrate Kubernetes into existing systems with specific configurations.

  • Kubespray: Best For: Users seeking to deploy Kubernetes on a variety of infrastructure types (cloud, on-premises, bare-metal) and require a tool that supports extensive customization and scalability.

  • Rancher: Best For: Organizations looking for an enterprise Kubernetes management platform that simplifies the operation of Kubernetes across any infrastructure, offering UI and API-based management.

Subsections of K8s install

Task 3 - Install Kubernetes

Use kubeadm to install kubernetes

labafter

This task builds a simple Kubernetes cluster with one control-plane node and one worker node.

The scripts in this workshop now use the Kubernetes package repository at pkgs.k8s.io, containerd as the container runtime, kubeadm for cluster bootstrap, and Calico as the CNI.

Info

This lab pins Kubernetes to `v1.30` for repeatable workshop builds and FortiAIGate readiness. FortiAIGate 8.0.1 requires Kubernetes 1.25.0 or later, plus working CNI, Helm, ingress, and storage prerequisites.

Default versions used by the scripts:

K8S_MINOR=v1.30
CALICO_VERSION=v3.28.2
POD_CIDR=10.244.0.0/16
SERVICE_CIDR=10.96.0.0/12

The master and worker scripts are non-interactive. They automatically overwrite existing Kubernetes apt keyring files and use apt-get -y, so students should not see manual y/N prompts during package setup.

If you need to pin a different supported Kubernetes minor version, export it before running the scripts, for example:

export K8S_MINOR=v1.30

Use Azure Cloud Shell as kubernetes client

To use Azure Cloud Shell as a Kubernetes client, ensure you have completed your Terraform deployment in Azure Cloud Shell. Azure Cloud Shell comes with kubectl pre-installed, facilitating Kubernetes operations.

Preflight check

There is no cluster yet, so this check is about the two lab VMs. Confirm Terraform finished and that both VM DNS names are published. From Azure Cloud Shell:

cd $HOME/k8s-101-workshop/terraform/
terraform output -raw linuxvm_master_FQDN; echo
terraform output -raw linuxvm_worker_FQDN; echo

Expected output — one name per node, with your own username in place of k8sxx:

k8sxx-master.eastus.cloudapp.azure.com
k8sxx-worker.eastus.cloudapp.azure.com

If either command errors or prints nothing, the VMs are not there. Finish Task 2 - Run Terraform before continuing.

  1. Navigate to your project directory where your Kubernetes workshop materials are located:
cd $HOME/k8s-101-workshop
  1. Create helper aliases for SSH access to the master and worker nodes.
cat <<'EOF_ALIAS' >> $HOME/.bashrc
ssh_worker_function() {
    cd $HOME/k8s-101-workshop/terraform/
    nodename=$(terraform output -json | jq -r .linuxvm_worker_FQDN.value)
    username=$(terraform output -json | jq -r .linuxvm_username.value)
    ssh -o "StrictHostKeyChecking=no" $username@$nodename
}
alias ssh_worker="ssh_worker_function"

ssh_master_function() {
    cd $HOME/k8s-101-workshop/terraform/
    nodename=$(terraform output -json | jq -r .linuxvm_master_FQDN.value)
    username=$(terraform output -json | jq -r .linuxvm_username.value)
    export FQDN=${nodename}
    ssh -o "StrictHostKeyChecking=no" -t $username@$nodename "export FQDN=${FQDN}; exec bash"
}
alias ssh_master="ssh_master_function"

alias k='kubectl'
EOF_ALIAS
source $HOME/.bashrc
  1. Generate SSH key and copy it to both nodes.
rm -f ~/.kube/config

rm -f /home/$(whoami)/.ssh/known_hosts

cd $HOME/k8s-101-workshop/terraform/
vmpassword=$(terraform output -json | jq -r .linuxvm_password.value)
echo $vmpassword

[ ! -f ~/.ssh/id_rsa ] && ssh-keygen -q -N "" -f ~/.ssh/id_rsa

cd $HOME/k8s-101-workshop/terraform/
nodename=$(terraform output -json | jq -r .linuxvm_master_FQDN.value)
username=$(terraform output -json | jq -r .linuxvm_username.value)
ssh-copy-id -f -o 'StrictHostKeyChecking=no' $username@$nodename

nodename=$(terraform output -json | jq -r .linuxvm_worker_FQDN.value)
username=$(terraform output -json | jq -r .linuxvm_username.value)
ssh-copy-id -f -o 'StrictHostKeyChecking=no' $username@$nodename
  1. Install Kubernetes on the master node.
cd $HOME/k8s-101-workshop/terraform/
nodename=$(terraform output -json | jq -r .linuxvm_master_FQDN.value)
username=$(terraform output -json | jq -r .linuxvm_username.value)

ssh -o 'StrictHostKeyChecking=no' $username@$nodename sudo kubeadm reset -f || true
scp -o 'StrictHostKeyChecking=no' $HOME/k8s-101-workshop/scripts/install_kubeadm_masternode.sh $username@$nodename:~/install_kubeadm_masternode.sh
ssh -o 'StrictHostKeyChecking=no' -t $username@$nodename "export FQDN=${nodename}; export K8S_MINOR=v1.30; bash ~/install_kubeadm_masternode.sh"

The FQDN value is unique for each student and is pulled from Terraform output. It is added to the Kubernetes API server certificate so kubectl can connect from Azure Cloud Shell without a TLS certificate error.

  1. Install Kubernetes packages and container runtime on the worker node.
cd $HOME/k8s-101-workshop/terraform/
nodename=$(terraform output -json | jq -r .linuxvm_worker_FQDN.value)
username=$(terraform output -json | jq -r .linuxvm_username.value)

ssh -o 'StrictHostKeyChecking=no' $username@$nodename sudo kubeadm reset -f || true
scp -o 'StrictHostKeyChecking=no' $HOME/k8s-101-workshop/scripts/install_kubeadm_workernode.sh $username@$nodename:~/install_kubeadm_workernode.sh
ssh -o 'StrictHostKeyChecking=no' -t $username@$nodename "export K8S_MINOR=v1.30; bash ~/install_kubeadm_workernode.sh"
  1. Join worker node to cluster.
cd $HOME/k8s-101-workshop/terraform/
master=$(terraform output -json | jq -r .linuxvm_master_FQDN.value)
worker=$(terraform output -json | jq -r .linuxvm_worker_FQDN.value)
username=$(terraform output -json | jq -r .linuxvm_username.value)

ssh -o 'StrictHostKeyChecking=no' $username@$master \
"kubeadm token create --print-join-command | sed 's#^kubeadm join#sudo kubeadm join --cri-socket unix:///run/containerd/containerd.sock#' > ~/workloadtojoin.sh && chmod +x ~/workloadtojoin.sh && cat ~/workloadtojoin.sh"

scp -o 'StrictHostKeyChecking=no' $username@$master:~/workloadtojoin.sh ./workloadtojoin.sh
scp -o 'StrictHostKeyChecking=no' ./workloadtojoin.sh $username@$worker:~/workloadtojoin.sh
ssh -o 'StrictHostKeyChecking=no' -t $username@$worker "bash ~/workloadtojoin.sh"
  1. Prepare access Kubernetes from Azure Cloud Shell.
cd $HOME/k8s-101-workshop/terraform/
nodename=$(terraform output -json | jq -r .linuxvm_master_FQDN.value)
username=$(terraform output -json | jq -r .linuxvm_username.value)

rm -rf $HOME/.kube/
mkdir -p ~/.kube/
scp -o 'StrictHostKeyChecking=no' $username@$nodename:~/.kube/config $HOME/.kube/config
kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}'; echo
  1. Verify the installation.
kubectl get nodes -o wide
kubectl get pods -A
kubectl cluster-info

You can also watch the worker become Ready:

watch kubectl get nodes
NAME          STATUS   ROLES           AGE   VERSION
node-worker   Ready    <none>          10m   v1.30.x
node-master   Ready    control-plane   15m   v1.30.x

Container runtime should show containerd, for example:

kubectl get nodes -o wide
# CONTAINER-RUNTIME: containerd://...

Summary

This chapter installs Kubernetes using kubeadm. The workshop creates one control-plane node and one worker node. The current script uses containerd instead of CRI-O and installs Kubernetes through pkgs.k8s.io.

Continue to deploy and scaling application.

Review Questions

  1. What is the kube-API FQDN name in kubeconfig?

    Click for Answer…
    kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}'

    Example:

    https://k8sxx-master.eastus.cloudapp.azure.com:6443

  2. What is the version of this Kubernetes server?

    Click for Answer…
    kubectl version
    kubectl get nodes

    Expected server version is the package available in the configured K8S_MINOR repository, for example v1.30.x.

  3. What is the container runtime name and version?

    Click for Answer…
    kubectl get nodes -o wide

    Expected runtime is containerd://....

  4. Describe the general steps to add a new VM as worker node in this cluster.

    Click for Answer…
    Create a new VM.
    Run scripts/install_kubeadm_workernode.sh on the new VM.
    Get a fresh join command from the master node using kubeadm token create --print-join-command.
    Run the join command on the new worker node.
    Verify with kubectl get nodes.

IN CASE YOU RUNNING INTO PROBLEM

You can reinstall Kubernetes or delete and recreate the VMs.

Re-Install Kubernetes

Warning

If you want a clean reset, run the following commands on both master and worker nodes. This removes Kubernetes state from the node.

sudo kubeadm reset -f
sudo rm -rf /etc/cni/net.d/* ~/.kube /etc/kubernetes /var/lib/etcd
sudo systemctl restart containerd kubelet

Then rerun this task from the master installation step.

Starting Over

Warning

If you want to delete the VMs completely and try again, use Terraform.

Delete VMs:

cd $HOME/k8s-101-workshop/terraform/ && terraform destroy -var="username=$(whoami)" --auto-approve

Create VMs again:

cd $HOME/k8s-101-workshop/terraform/ && terraform apply -var="username=$(whoami)" --auto-approve

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 pods

Both 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.x

The 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 -A

Expected nginx deployment:

NAME               READY   UP-TO-DATE   AVAILABLE
nginx-deployment   2/2     2            2

Expected HPA object:

NAME        REFERENCE                     TARGETS       MINPODS   MAXPODS   REPLICAS   AGE
nginx-hpa   Deployment/nginx-deployment   <unknown>/50%   2         10        2          34s

After 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-info

Expected 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 wide
NAME          STATUS   ROLES           AGE   VERSION
node-master   Ready    control-plane   53m   v1.30.14
node-worker   Ready    <none>          50m   v1.30.14
kubectl get pods -A
NAMESPACE            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          53m
helm version
helm list

Both 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 ingressclass

Expected 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>       9h

The 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 storageclass

Expected output

NAME                   PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
local-path (default)   rancher.io/local-path   Delete          WaitForFirstConsumer   false                  52m

Generate load

Run a temporary load generator from the cluster:

kubectl run -i --tty load-generator --rm --image=busybox:1.36 --restart=Never -- /bin/sh

Inside the shell, run:

while true; do wget -q -O- http://nginx-deployment.default.svc.cluster.local; done

In another terminal, watch HPA:

watch kubectl get hpa

You can also watch pods scale:

watch kubectl get pods -l app=nginx

Stop load

Press CTRL+C in the load generator shell, then type:

exit

Cleanup

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-found
kubectl 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-found

Summary

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

  1. Describe how to make the client application generate more traffic.

    Click for Answer…

    Run a load generator pod and continuously call the nginx service.

  2. How many minutes do you need to wait before nginx pods start increasing?

    Click for Answer…

    It depends on when Metrics Server reports CPU data and when HPA decides to scale. Wait a few minutes and monitor with kubectl get hpa.

  3. How do you stop sending traffic to nginx deployment?

    Click for Answer…

    Stop or delete the load generator pod.

Kubernetes in depth

This chapter is your guide to mastering essential Kubernetes concepts.

  • You’ll start with Pods, the building blocks of applications in Kubernetes, understanding how they work and how to manage them effectively.
  • Then, you’ll explore ConfigMaps and Secrets, learning how to handle configuration data and sensitive information securely.
  • Next, you’ll dive into upgrade strategies, discovering how to update your applications smoothly without causing downtime.
  • Deployment techniques will be covered extensively, showing you how to deploy, update, and roll back application versions with ease.
  • Lastly, you’ll learn about scaling, exploring methods to adjust your application’s capacity to meet changing demands effortlessly.
  • By the end, you’ll have the skills to confidently deploy, manage, and scale applications in Kubernetes environments.

Subsections of Kubernetes in depth

Task 5 - Creating and Managing Pods

Objective: Learn Pod, container and init container

What is Pod

In Kubernetes, a Pod is the smallest and most basic deployable unit. It represents a single instance of a running process in your cluster. Pods are the building blocks of Kubernetes applications and encapsulate one or more containers, storage resources, a unique network IP, and options that govern how the containers should run. More info about Pods:

  • The smallest, most basic unit in Kubernetes.
  • Represents a single instance of a running process in your cluster.
  • Can contain one or more containers that are tightly coupled and share resources, such as storage and networking.
  • Often represents a single microservice, but can also encapsulate multiple closely related containers.
  • Pods are ephemeral by nature, meaning they can be created, destroyed, and replaced dynamically based on the cluster’s needs.
  • Managed by Kubernetes controllers like Deployments, ReplicaSets, or StatefulSets to ensure desired state and high availability.(We will learn about these in next chapters)
  • Provides a unique IP address within the cluster, allowing communication between Pods.
  • Can have one or more volumes attached to it for persistent storage.
  • Logs and metrics of individual containers within a Pod can be accessed using kubectl.

Pods in a Kubernetes cluster are used in two main ways:

Pods that run a single container: The “one-container-per-Pod” model is the most common Kubernetes use case; in this case, you can think of a Pod as a wrapper around a single container; Kubernetes manages Pods rather than managing the containers directly.

Pods that run multiple containers: that need to work together. A Pod can encapsulate an application composed of multiple co-located containers that are tightly coupled and need to share resources. These co-located containers form a single cohesive unit.

Preflight check

This task needs the two-node cluster from Task 3 - Install Kubernetes. Confirm it is healthy and that the default namespace is empty before you create the first Pod. From Azure Cloud Shell:

kubectl get nodes
kubectl get pods

Both nodes must report Ready, and there should be no Pods yet — kubectl reports the empty list on stderr:

NAME          STATUS   ROLES           AGE   VERSION
node-worker   Ready    <none>          10m   v1.30.x
node-master   Ready    control-plane   15m   v1.30.x
No resources found in default namespace.

If a node is missing or NotReady, go back to Task 3 and finish it. If a Pod named nginx or nginx-pod-manual is already listed from an earlier run, delete it with kubectl delete pod nginx or kubectl delete pod nginx-pod-manual first, otherwise the manifests below fail as already existing.

Copy the below manifest file to create a Pod.

cat << EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx:latest
    ports:
    - containerPort: 80
EOF

Init container

Init containers in Kubernetes are specialized containers that run before the main containers in a Pod, serving to prepare the environment or perform initialization tasks. They execute sequentially, ensuring that each init container completes successfully before the next one starts and before the main containers begin execution. Init containers share the same volume mounts as the main containers, facilitating the sharing of resources such as configuration files or secrets. They are transient in nature, running to completion once and then terminating, and are not restarted if they fail.

  • To create Pod with init containers:
cat << EOF |  kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: nginx-pod-manual
spec:
  volumes:
  - name: shared-data
    emptyDir: {}
  containers:
  - name: nginx
    image: nginx:latest
    ports:
    - containerPort: 80
    volumeMounts:
    - name: shared-data
      mountPath: /usr/share/nginx/html
  initContainers:
  - name: init-nginx
    image: busybox
    command: ['sh', '-c', 'echo "Hello, NGINX at $(date)!" >> /usr/share/nginx/html/index.html']
    volumeMounts:
    - name: shared-data
      mountPath: /usr/share/nginx/html
EOF
  • To check Pods, run kubectl get pods

  • To check more detail of Pod, use kubectl describe pod

check the output difference between kubectl describe pod nginx and kubectl describe pod nginx-pod-manual.

  • To access nginx Pod from contaier itself, use
kubectl exec -it po/nginx-pod-manual -- curl http://127.0.0.1

expected outcome

Hello, NGINX at Tue July 8 01:18:50 AM UTC 2026!

Review Questions

  1. What is the purpose of spec.containers.volumeMounts in a Pod definition?
    Click for Answer…
    Specify where volumes should be mounted within containers.
    
  2. Which lines in the Pod nginx-pod-manual define a volume?
    Click for Answer…
    volumeMounts:
    - name: shared-data
      mountPath: /usr/share/nginx/html
    
  3. Why can the init container access the shared-data folder?
    Click for Answer…
    Both the init container and the main nginx container mount this same volume
    
  4. Can you restart a Pod created with kubectl create pod or a YAML file specifying kind: Pod?
    Click for Answer…
    You cannot directly restart a Pod created with kind: Pod
    
  5. If you delete the Pod nginx-pod-manual and recreate it using the same YAML, will the previous data in the shared-data folder persist?"
    Click for Answer…
    No, The volume is defined as an emptyDir, emptyDir volumes are ephemeral and tied to the Pod's lifecycle.
    

Clean up

kubectl delete pod nginx-pod-manual

Task 6 - Configuration

Objective: Master injecting configuration into Pods.

Description: Learn to externalize application configuration using ConfigMaps and the Downward API. Understand how to pass environment variables, configure application settings, and expose Pod and container metadata to applications. Labs include creating ConfigMaps and using the Downward API to expose Pod information.

ConfigMaps:

In Kubernetes, ConfigMaps are a resource object used to store non-sensitive configuration data in key-value pairs that can be consumed by pods or other system components. They provide a way to decouple configuration artifacts from container images, allowing for more flexible and manageable application configurations.

Here are some reasons why ConfigMaps are used in Kubernetes:

  • Separation of Concerns: ConfigMaps allow you to separate configuration data from application code. This separation makes it easier to manage configurations independently of the application’s lifecycle, which can be particularly useful in scenarios where multiple instances of the same application are deployed with different configurations.

  • Dynamic Configuration Updates: ConfigMaps support dynamic updates, meaning that changes to the configuration can be applied to running pods without requiring a restart. This allows for more flexibility and agility in managing application configurations.

  • Environment Agnostic: ConfigMaps are environment-agnostic, meaning that the same configuration can be used across multiple environments (e.g., development, testing, production) without modification. This helps maintain consistency and simplifies the deployment process.

  • Immutable Infrastructure: By externalizing configurations into ConfigMaps, the underlying infrastructure becomes more immutable. This means that changes to configurations do not require modifications to the underlying infrastructure, making deployments more predictable and reliable.

  • Centralized Management: ConfigMaps provide a centralized location for managing configuration data. This can be particularly beneficial in large-scale deployments where multiple applications and components require different configurations.

  • Integration with Other Resources: ConfigMaps can be easily integrated with other Kubernetes resources such as pods, deployments, and services. This allows you to inject configuration data into your application containers at runtime, making them highly configurable and adaptable to different environments.

Overall, ConfigMaps play a crucial role in Kubernetes by providing a flexible and efficient mechanism for managing configuration data in a containerized environment, contributing to improved application deployment, scalability, and maintainability.

Secrets:

In Kubernetes, Secrets are another type of resource object used to store sensitive information, such as passwords, OAuth tokens, and SSH keys, in a secure manner. They are similar to ConfigMaps but are specifically designed to handle sensitive data. Here are some key aspects of Secrets in Kubernetes:

  • Secure Storage: Secrets are stored securely within the Kubernetes cluster, encrypted at rest by default. This ensures that sensitive information is not exposed or accessible to unauthorized users.

  • Base64 Encoding: Secret data is typically stored in Base64 encoded format. While Base64 encoding does not provide encryption, it helps prevent accidental exposure of sensitive data in logs or other places where plaintext might be displayed.

  • Multiple Types of Secrets: Kubernetes supports various types of Secrets, including generic secrets, Docker registry credentials, TLS certificates, and service account tokens. Each type of Secret has its specific use case and configuration options.

  • Usage in Pods: Secrets can be mounted as volumes or exposed as environment variables within pods. This allows containers running within the pod to access the sensitive information stored in the Secret without exposing it directly in the container specification or source code.

  • Access Control: Kubernetes provides role-based access control (RBAC) mechanisms to manage access to Secrets. This ensures that only authorized users or applications can create, read, update, or delete Secrets within the cluster.

  • Automatic Injection: In some cases, Kubernetes can automatically inject certain types of Secrets into pods. For example, service account tokens are automatically mounted as a volume in pods running within the Kubernetes cluster, allowing them to authenticate with the Kubernetes API server.

  • Immutable Once Created: Unlike ConfigMaps, Secrets are immutable once created. This means that you cannot update the data stored in a Secret directly. Instead, you must delete the existing Secret and create a new one with the updated data.

Overall, Secrets in Kubernetes provide a secure and convenient way to manage sensitive information within a cluster, ensuring that sensitive data is protected from unauthorized access while still being accessible to the applications that need it.

Creating ConfigMaps:

cat << EOF | kubectl apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-index-html
data:
  index.html: |
    <!DOCTYPE html>
    <html>
    <head>
      <title>Welcome to NGINX!</title>
    </head>
    <body>
      <h1>Welcome to NGINX!</h1>
      <p>This is a custom index.html page served by NGINX at $(date).</p>
    </body>
    </html>
EOF

To get configmap, try kubectl get configmap

Creating Secret:

  • Firstly, we need encode a secret value to base64. On linux you can use the following commands to encode.

  • Encode a string to base64:

echo -n 'your_secret_value' | base64

for example:

base64_encoded_username=$(echo -n 'ftntadmin' | base64)
base64_encoded_password=$(echo -n 'password123' | base64)

cat << EOF | kubectl apply -f -
apiVersion: v1
kind: Secret
metadata:
  name: nginx-secret
type: Opaque
data:
  username: $base64_encoded_username
  password: $base64_encoded_password
EOF

There are several types of built in Secrets [!https://kubernetes.io/docs/concepts/configuration/secret/#secret-types] in Kubernetes. Opaque is a secret which is an arbitrary user defined data.

Creating a pod using confimap and secret

cat << EOF | tee nginx-pod-with-configmap-secret.yaml
apiVersion: v1
kind: Pod
metadata:
  name: nginx-pod-with-configmap-secret
spec:
  containers:
  - name: nginx-container
    image: nginx:latest
    ports:
    - containerPort: 80
    volumeMounts:
    - name: nginx-config-volume
      mountPath: /usr/share/nginx/html
    - name: nginx-secret-volume
      mountPath: /etc/nginx/secret
      readOnly: true
  volumes:
  - name: nginx-config-volume
    configMap:
      name: nginx-index-html
  - name: nginx-secret-volume
    secret:
      secretName: nginx-secret
EOF
kubectl apply -f nginx-pod-with-configmap-secret.yaml

Review Questions

  1. How to access nginx Pod web page from container inside ?
    Click for Answer…
    kubectl exec -it nginx-pod-with-configmap-secret --  curl localhost:80 
  2. Delete Pod nginx-pod-with-configmap-secret and create again, check the web page of nginx again ? did you see any difference ? why ?
    Click for Answer…

    you should not see any difference, as The ConfigMap and Secret remain unchanged in the cluster.

  3. Modify below nginx-deployment with VolumeMounts to use ConfigMap
    Click for Answer…

    nginx-deployment

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: nginx-deployment
      labels:
        app: nginx
    spec:
      replicas: 2
      selector:
        matchLabels:
          app: nginx
      template:
        metadata:
          labels:
            app: nginx
        spec:
          containers:
          - name: nginx
            image: nginx:latest
            ports:
            - containerPort: 80

    ConfigMap

    cat << EOF | tee nginx_deployment_cm.yaml
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: nginx-webpage
    data:
      index.html: |
        <html>
        <head>
        <title>Welcome to NGINX!</title>
        </head>
        <body>
        <h1>Hello, Kubernetes!</h1>
        </body>
        </html>
    
    kubectl create -f nginx_deployment_cm.yaml

Task 7 - Deploy and Scale Deployments

Objective: Master the Creation and Management of Deployments

Description: This module zeroes in on Deployments as the primary mechanism for deploying applications on Kubernetes. You will delve into the creation of Deployments, learn how to scale them effectively, and update applications with zero downtime. Through hands-on lab exercises, you will experience deploying a multi-replica application and conducting rolling updates to ensure seamless application transitions.

Deploying an Application with a Deployment

We’ve previously seen how to use kubectl create deployment Kubernetes-bootcamp –image=gcr.io/google-samples/kubernetes-bootcamp:v1 to create a deployment directly from the command line. However, Kubernetes also supports deploying applications using YAML or JSON manifests. This approach provides greater flexibility than using the kubectl CLI alone and facilitates version control of your deployment configurations.

By defining deployments in YAML or JSON files, you can specify detailed configurations, manage them through source control systems, and apply changes systematically. This method enhances the maintainability and reproducibility of your deployments within a Kubernetes environment.

Preflight check

This task creates the kubernetes-bootcamp Deployment from scratch, so the default namespace must not already have one. Confirm the cluster from Task 3 - Install Kubernetes is healthy and empty:

kubectl get nodes
kubectl get deployment

Both nodes must report Ready, and no Deployment should be listed — kubectl reports the empty list on stderr:

NAME          STATUS   ROLES           AGE   VERSION
node-worker   Ready    <none>          10m   v1.30.x
node-master   Ready    control-plane   15m   v1.30.x
No resources found in default namespace.

If a node is missing or NotReady, go back to Task 3 and finish it. If kubernetes-bootcamp is already listed from an earlier task, delete it with kubectl delete deployment kubernetes-bootcamp first — kubectl create below fails when the object already exists.

  1. Deployment kubernetes-bootcamp application
cat << EOF | tee kubernetes-bootcamp.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: kubernetes-bootcamp
spec:
  replicas: 1 # Default value for replicas when not specified
  selector:
    matchLabels:
      app: kubernetes-bootcamp
  template:
    metadata:
      labels:
        app: kubernetes-bootcamp
    spec:
      containers:
      - name: kubernetes-bootcamp
        image: gcr.io/google-samples/kubernetes-bootcamp:v1
EOF
kubectl create -f kubernetes-bootcamp.yaml
  • replicas: 1: Specifies that only one replica of the Pod should be running.
  • selector: Defines how the Deployment finds which Pods to manage using matchLabels.
  • template: Describes the Pod template used by the Deployment to create new Pods.
  • metadata.labels: Sets the label app: kubernetes-bootcamp on the Pod, matching the selector in the Deployment spec.
  • spec.containers: Lists the containers to run in the Pod.
  • image: gcr.io/google-samples/kubernetes-bootcamp:v1: Specifies the container image to use.

Scale your deployment

After deploying the Kubernetes Bootcamp application, you might find the need to scale your deployment to accommodate an increased load or enhance availability. Kubernetes allows you to scale a deployment, increasing the number of replicas from 1 to 10, for instance. This ensures your application can handle a higher load. There are several methods to scale a deployment, each offering unique benefits.

1. Using kubectl scale

Benefits

  • Immediate: This command directly changes the number of replicas in the deployment, making it a quick way to scale.
  • Simple: Easy to remember and use for ad-hoc scaling operations.
  1. Check the existing Deployment
    kubectl get deployment

    expected output

    NAME                  READY   UP-TO-DATE   AVAILABLE   AGE
    kubernetes-bootcamp   1/1     1            1           63m
  2. Scaling the deployment:

kubectl scale deployment kubernetes-bootcamp --replicas=10

expected output

deployment.apps/kubernetes-bootcamp scaled
3. Check the Deployment status
kubectl rollout status deployment kubernetes-bootcamp

Expected output

Waiting for deployment "kubernetes-bootcamp" rollout to finish: 1 of 10 updated replicas are available...
Waiting for deployment "kubernetes-bootcamp" rollout to finish: 2 of 10 updated replicas are available...
Waiting for deployment "kubernetes-bootcamp" rollout to finish: 3 of 10 updated replicas are available...
Waiting for deployment "kubernetes-bootcamp" rollout to finish: 4 of 10 updated replicas are available...
Waiting for deployment "kubernetes-bootcamp" rollout to finish: 5 of 10 updated replicas are available...
Waiting for deployment "kubernetes-bootcamp" rollout to finish: 6 of 10 updated replicas are available...
Waiting for deployment "kubernetes-bootcamp" rollout to finish: 7 of 10 updated replicas are available...
Waiting for deployment "kubernetes-bootcamp" rollout to finish: 8 of 10 updated replicas are available...
Waiting for deployment "kubernetes-bootcamp" rollout to finish: 9 of 10 updated replicas are available...
deployment "kubernetes-bootcamp" successfully rolled out
4. Verify the Deployment
kubectl get deployment kubernetes-bootcamp

After scaling, you should observe that the number of Pods has increased to meet the deployment’s requirements.

expected output

NAME                  READY   UP-TO-DATE   AVAILABLE   AGE
kubernetes-bootcamp   10/10   10           10          64m
5. Listing the Pods from Deployment
kubectl get pod -l app=kubernetes-bootcamp

expected output

NAME                                  READY   STATUS    RESTARTS   AGE
kubernetes-bootcamp-bcbb7fc75-5fjhc   1/1     Running   0          44s
kubernetes-bootcamp-bcbb7fc75-5kjd7   1/1     Running   0          44s
kubernetes-bootcamp-bcbb7fc75-5r649   1/1     Running   0          30m
kubernetes-bootcamp-bcbb7fc75-bmzbv   1/1     Running   0          44s
kubernetes-bootcamp-bcbb7fc75-fn29h   1/1     Running   0          44s
kubernetes-bootcamp-bcbb7fc75-fp2d9   1/1     Running   0          44s
kubernetes-bootcamp-bcbb7fc75-jfdvf   1/1     Running   0          44s
kubernetes-bootcamp-bcbb7fc75-nh9sn   1/1     Running   0          44s
kubernetes-bootcamp-bcbb7fc75-q7sqc   1/1     Running   0          44s
kubernetes-bootcamp-bcbb7fc75-t4tkm   1/1     Running   0          44s

This output confirms the deployment now runs 10 replicas of the Kubernetes Bootcamp application, demonstrating successful scaling.

6. delete the deployment

kubectl delete deployment kubernetes-bootcamp 

2. use kubectl apply

Benefits

If the intended resource to update is in yaml file, we can directly edit the yaml file with any editor, then use kubectl apply to update.

  • Version Controlled: Can be version-controlled if using a local YAML file, allowing for tracking of changes and rollbacks.
  • Reviewable: Changes can be reviewed by team members before applying if part of a GitOps workflow.

The kubectl apply -f command is more flexible and is recommended for managing applications in production. It updates resources with the changes defined in the YAML file but retains any modifications that are not specified in the file.It’s particularly suited for scenarios where you might want to maintain manual adjustments or unspecified settings.

  1. create Kubernetes-bootcamp deployment with yaml file
cat << EOF | tee kubernetes-bootcamp.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: kubernetes-bootcamp
spec:
  replicas: 1 # Default value for replicas when not specified
  selector:
    matchLabels:
      app: kubernetes-bootcamp
  template:
    metadata:
      labels:
        app: kubernetes-bootcamp
    spec:
      containers:
      - name: kubernetes-bootcamp
        image: gcr.io/google-samples/kubernetes-bootcamp:v1
EOF
kubectl apply -f kubernetes-bootcamp.yaml 
  1. Verify the deployment
    kubectl get deployment

    expected output

    NAME                  READY   UP-TO-DATE   AVAILABLE   AGE
    kubernetes-bootcamp   1/1     1            1           3s
  2. modify replicas in yaml file

Now, use editor like vim to change the replicas=1 in the yaml file to replicas=10. Alternatively you can also use sed to change it

sed -i 's/replicas: 1/replicas: 10/' kubernetes-bootcamp.yaml

Apply the change with kubectl apply

kubectl apply -f kubernetes-bootcamp.yaml

Verify the result

kubectl get deployment kubernetes-bootcamp

expected outcome

NAME                  READY   UP-TO-DATE   AVAILABLE   AGE
kubernetes-bootcamp   10/10   10           10          99s

clean up

kubectl delete -f kubernetes-bootcamp.yaml

3. use kubectl edit

This approach involves manually editing the resource definition in a text editor (invoked by kubectl edit), where you can change any part of the resource. After saving and closing the editor, Kubernetes applies the changes. This method requires no separate kubectl apply, as kubectl edit directly applies the changes once the file is saved and closed.

Benefit: Ablility to review and modify other configuration which is not in YAML file

  1. create the deployment
kubectl create deployment kubernetes-bootcamp --image=gcr.io/google-samples/kubernetes-bootcamp:v1 --replicas=1
  1. Modify replicas with kubectl edit
kubectl edit deployment kubernetes-bootcamp

above command show you into VI EDITOR with opened yaml file, locate the text

spec:
  progressDeadlineSeconds: 600
  replicas: 1

then change replicas from 1 to 10, save the change and exit the EDITOR with press Ctrl-C follow :wq!.

  1. Verify the deployment
kubectl get deployment kubernetes-bootcamp
  1. delete deployment
kubectl delete deployment kubernetes-bootcamp

4. Kubectl patch

kubectl patch directly updates specific parts of a resource without requiring you to manually edit a file or see the entire resource definition. It’s particularly useful for making quick changes, like updating an environment variable in a Pod or changing the number of replicas in a deployment.

Benefit: Ideal for scripts and automation because you can specify the exact change in a single command line.

  1. Create Deployment
kubectl create deployment kubernetes-bootcamp --image=gcr.io/google-samples/kubernetes-bootcamp:v1 --replicas=1
  1. Verify the Deployment
kubectl get deployment kubernetes-bootcamp

expected result

NAME                  READY   UP-TO-DATE   AVAILABLE   AGE
kubernetes-bootcamp   1/1     1            1           33s
  1. use kubectl patch to change replicas
kubectl patch deployment kubernetes-bootcamp --type='json' -p='[{"op": "replace", "path": "/spec/replicas", "value":10}]'
  1. Verify the Deployment
kubectl get deployment kubernetes-bootcamp

expected result

NAME                  READY   UP-TO-DATE   AVAILABLE   AGE
kubernetes-bootcamp   10/10   10           10          97s
  1. Delete the Deployment
kubectl delete deployment kubernetes-bootcamp

5. use kubectl replace

The kubectl replace -f command replaces a resource with the new state defined in the YAML file. If the resource doesn’t exist, the command fails. This command requires that the resource be defined completely in the file being applied because it replaces the existing configuration with the new one provided.

Deletion and Recreation: Under the hood, replace effectively deletes and then recreates the resource, which can lead to downtime for stateful applications or services. This method does not preserve unspecified fields or previous modifications made outside the YAML file.

Usage: Use kubectl replace -f when you want to overwrite the resource entirely, and you are certain that the YAML file represents the complete and desired state of the resource.

  1. create deployment with image=gcr.io/google-samples/kubernetes-bootcamp:v1
cat << EOF | tee kubernetes-bootcamp.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: kubernetes-bootcamp
spec:
  replicas: 5 # Default value for replicas when not specified
  selector:
    matchLabels:
      app: kubernetes-bootcamp
  template:
    metadata:
      labels:
        app: kubernetes-bootcamp
    spec:
      containers:
      - name: kubernetes-bootcamp
        image: gcr.io/google-samples/kubernetes-bootcamp:v1
        
EOF
kubectl create -f kubernetes-bootcamp.yaml
  1. verify the deployment
kubectl get pod -l app=kubernetes-bootcamp

expected output

NAME                                   READY   STATUS        RESTARTS   AGE
kubernetes-bootcamp-5485cc6795-5wdrb   1/1     Running       0          4s
kubernetes-bootcamp-5485cc6795-79qkm   1/1     Running       0          4s
kubernetes-bootcamp-5485cc6795-ltqst   1/1     Running       0          4s
kubernetes-bootcamp-5485cc6795-szgqt   1/1     Running       0          4s
kubernetes-bootcamp-5485cc6795-xw7l6   1/1     Running       0          4s

the Pod has name from deployment template hash kubernetes-bootcamp-5485cc6795

  1. Modify image in yaml file

Modify container image in kubernetes-bootcamp.yaml. Modify this will affect the Pod template hash

sed -i 's|image: gcr.io/google-samples/kubernetes-bootcamp:v1|image: jocatalin/kubernetes-bootcamp:v2|' kubernetes-bootcamp.yaml
  1. apply the change
kubectl replace -f kubernetes-bootcamp.yaml
  1. Verify deployment v
kubectl get pod -l app=kubernetes-bootcamp

Expected output

NAME                                   READY   STATUS        RESTARTS   AGE
kubernetes-bootcamp-5485cc6795-5wdrb   1/1     Terminating   0          78s
kubernetes-bootcamp-5485cc6795-79qkm   1/1     Terminating   0          78s
kubernetes-bootcamp-5485cc6795-ltqst   1/1     Terminating   0          78s
kubernetes-bootcamp-5485cc6795-szgqt   1/1     Terminating   0          78s
kubernetes-bootcamp-5485cc6795-xw7l6   1/1     Terminating   0          78s
kubernetes-bootcamp-7c6644499c-559fq   1/1     Running       0          7s
kubernetes-bootcamp-7c6644499c-9zdg2   1/1     Running       0          7s
kubernetes-bootcamp-7c6644499c-fb9dn   1/1     Running       0          6s
kubernetes-bootcamp-7c6644499c-l9xhn   1/1     Running       0          6s
kubernetes-bootcamp-7c6644499c-nzhj9   1/1     Running       0          7s

You should observe the creation of new Pods with a new hash in their names, indicating an update. Concurrently, all previous Pods with the old hash in their names will be deleted, suggesting that the entire resource has been recreated.

  1. Delete the deployment
kubectl delete deployment kubernetes-bootcamp

Risk of Downtime: For some resources, using kubectl replace can cause downtime since it may delete and recreate the resource, depending on the type and changes made. It’s important to use this command with caution, especially for critical resources in production environments.

Summary

  • kubectl scale: Quickly scales the number of replicas for a deployment, ideal for immediate, ad-hoc adjustments.

  • kubectl edit: Offers an interactive way to scale by manually editing the deployment’s YAML definition in a text editor, providing a chance to review and adjust other configurations simultaneously.

  • kubectl patch: Efficiently updates the replicas count with a single command, suitable for scripts and automation without altering the rest of the deployment’s configuration.

  • kubectl replace -f: Replaces the entire deployment with a new configuration from a YAML file, used when you have a prepared configuration that includes the desired replicas count.

  • kubectl apply -f: Applies changes from a YAML file to the deployment, allowing for version-controlled and incremental updates, including scaling operations.

Task 8 - Auto Scaling Deployment

Objective: Automate Deployment Scaling with HPA

Learn to configure the Horizontal Pod Autoscaler (HPA) for a deployment and simulate traffic to test scaling.

Using kubectl autoscale

When Kubernetes has Resouce Metrics API installed, We can using the kubectl autoscale command kubectl autoscale deployment to automatically scale a deployment based on CPU utilization (or any other metric) requires that the Kubernetes Metrics Server (or an equivalent metrics API) is installed and operational in your cluster. The Metrics Server collects resource metrics from Kubelets and exposes them in the Kubernetes API server through the Metrics API for use by Horizontal Pod Autoscaler (HPA) and other components.

using the kubectl autoscale command to automatically scale a deployment based on CPU utilization (or any other metric) requires that the Kubernetes Metrics Server (or an equivalent metrics API) is installed and operational in your cluster. The Metrics Server collects resource metrics from Kubelets and exposes them in the Kubernetes API server through the Metrics API for use by Horizontal Pod Autoscaler (HPA) and other components.

Enable resource-API

The Resource Metrics API in Kubernetes is crucial for providing core metrics about Pods and nodes within a cluster, such as CPU and memory usage to enable feature like Horizontal Pod Autoscaler (HPA), Vertical Pod Autoscaler (VPA) and enable efficent resource scheduling.

Preflight check

This task installs the Metrics Server and then creates its own nginx-deployment Deployment, Service and HPA, so the default namespace must not already have them. Confirm the cluster from Task 3 - Install Kubernetes is healthy and empty:

kubectl get nodes
kubectl get deployment

Both nodes must report Ready, and no Deployment should be listed — kubectl reports the empty list on stderr:

NAME          STATUS   ROLES           AGE   VERSION
node-worker   Ready    <none>          10m   v1.30.x
node-master   Ready    control-plane   15m   v1.30.x
No resources found in default namespace.

If a node is missing or NotReady, go back to Task 3 and finish it. If nginx-deployment is already listed, it is left over from Task 4 - Scaling Application, whose scripts/deploy_application_with_hpa_masternode.sh creates a deployment with the same name. Remove it with kubectl delete deployment nginx-deployment and kubectl delete svc nginx-deployment before continuing.

  1. copy/paste below command to enable resource-api
curl  --insecure --retry 3 --retry-connrefused -fL "https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml" -o components.yaml
sed -i '/- --metric-resolution/a \ \ \ \ \ \ \ \ - --kubelet-insecure-tls' components.yaml

kubectl apply -f components.yaml
kubectl rollout status deployment metrics-server -n kube-system

use kubectl top node and kubectl top pod to check the Pod and node resource usage

Create a deployment with resource constrain

  • In this deployment , we add some resource restriction like memory and cpu for a POD.

  • when POD reach the CPU or memory limit, if HPA configured, new POD will be created according HPA policy.

  1. create deployment with CPU and Memory constraints
    cat <<EOF | tee nginx-deployment_resource.yaml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: nginx-deployment
      labels:
        app: nginx
    spec:
      replicas: 2
      selector:
        matchLabels:
          app: nginx
      template:
        metadata:
          labels:
            app: nginx
        spec:
          containers:
          - name: nginx
            image: nginx:latest
            ports:
            - containerPort: 80
            resources:
              requests:
                memory: "64Mi"  # Minimum memory requested to start the container
                cpu: "10m"     # 100 millicpu (0.1 CPU) requested to start the container
              limits:
                memory: "128Mi" # Maximum memory limit for the container
                cpu: "40m"     # 200 millicpu (0.2 CPU) maximum limit for the container
    EOF
    kubectl apply -f nginx-deployment_resource.yaml
    kubectl rollout status deployment nginx-deployment
    
    cat << EOF | tee nginx-deployment_clusterIP.yaml
    apiVersion: v1
    kind: Service
    metadata:
      labels:
        app: nginx
      name: nginx-deployment
      namespace: default
    spec:
      ipFamilies:
      - IPv4
      ipFamilyPolicy: SingleStack
      ports:
      - port: 80
        protocol: TCP
        targetPort: 80
      selector:
        app: nginx
      sessionAffinity: None
      type: ClusterIP
    EOF
    kubectl apply -f nginx-deployment_clusterIP.yaml

    check the deployment and service

    kubectl get deployment nginx-deployment
    kubectl get svc nginx-deployment

Use autoscale (HPA) to scale your application

  1. We can use kubectl autoscale command or use create a hpa yaml file then follow a kubectl apply -f to create hpa.

use kubectl command to create hpa

kubectl autoscale deployment nginx-deployment --name=nginx-deployment-hpa --min=2 --max=10 --cpu-percent=50  --save-config

expected Outcome

horizontalpodautoscaler.autoscaling/nginx-deployment-hpa autoscaled

or use yaml file to create hpa

cat << EOF | tee > nginx-deployment-hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: nginx-deployment-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: nginx-deployment
  minReplicas: 2
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 50
EOF
kubectl apply -f nginx-deployment-hpa.yaml
  • Target CPU Utilization: This is set to 50%. It means the HPA will aim to adjust the number of Pods so that the average CPU utilization across all Pods is around 50% of the allocated CPU resources for each Pod.
  • Scaling Out: If the average CPU utilization across all Pods in the nginx-deployment exceeds 50%, the HPA will increase the number of Pods, making more resources available to handle the workload, until it reaches the maximum limit of 10 Pods.
  • Scaling In: If the average CPU utilization drops below 50%, indicating that the resources are underutilized, the HPA will decrease the number of Pods to reduce resource usage, but it won’t go below the minimum of 2 Pod.
  1. Check Result

use kubectl get hpa nginx-deployment-hpa to check deployment

kubectl get hpa nginx-deployment-hpa

Expected Outcome

NAME                   REFERENCE                     TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
nginx-deployment-hpa   Deployment/nginx-deployment   0%/50%    2         10        2          23s
For a deeper understanding, consider using the following command to conduct further observations"

use kubectl get deployment nginx-deployment to check the change of deployment. use kubectl get hpa and kubectl describe hpa to check the size of replicas.

Send http traffic to application

since the nginx-deployment service is cluster-ip type service which can only be accessed from cluster internal, so we need to create a POD which can send http traffic to nginx-deployment service.

  1. create deployment for generate http traffic, in this deployment, we will use wget to similuate the real traffic towards ngnix-deployment cluster-ip service which has service name http://nginx-deployment.default.svc.cluster.local.
cat <<EOF | tee infinite-calls-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: infinite-calls
  labels:
    app: infinite-calls
spec:
  replicas: 2
  selector:
    matchLabels:
      app: infinite-calls
  template:
    metadata:
      name: infinite-calls
      labels:
        app: infinite-calls
    spec:
      containers:
      - name: infinite-calls
        image: busybox
        command:
        - /bin/sh
        - -c
        - "while true; do wget -q -O- http://nginx-deployment.default.svc.cluster.local; done"
EOF
kubectl apply -f infinite-calls-deployment.yaml
  1. check the creation of infinite-calls deployment
kubectl get deployment infinite-calls
  1. check the log from infinite-calls Pods. {.items[0]} means use first Pod
podName=$(kubectl get pod -l app=infinite-calls -o=jsonpath='{.items[0].metadata.name}')
kubectl logs  po/$podName

you will see the response from nginx web server container. use ctr-c to stop.

use kubectl top pod and kubectl top node to check the resource usage status user expected to see the number of Pod increased

  1. You shall see that expected Pod now increased automatically without use attention.

    kubectl get pod -l app=nginx

    expected outcome

    NAME                                READY   STATUS    RESTARTS   AGE
    nginx-deployment-55c7f467f8-f2qbp   1/1     Running   0          19m
    nginx-deployment-55c7f467f8-hxs79   1/1     Running   0          2m2s
    nginx-deployment-55c7f467f8-jx2k9   1/1     Running   0          19m
    nginx-deployment-55c7f467f8-r7vdv   1/1     Running   0          3m2s
    nginx-deployment-55c7f467f8-w6r8l   1/1     Running   0          3m17s

  2. Use kubectl get hpa shall tell you that hpa is action which increased the replicas from 2 to other numbers.

    kubectl get hpa

    expected outcome

    NAME        REFERENCE                     TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
    nginx-deployment-hpa   Deployment/nginx-deployment   50%/50%   2         10        5          11m

  3. check hpa detail

    kubectl describe hpa

    expected outcome

    Name:                                                  nginx-deployment-hpa
    Namespace:                                             default
    Labels:                                                <none>
    Annotations:                                           <none>
    CreationTimestamp:                                     Wed, 08 Jul 2026 16:21:40 +0000
    Reference:                                             Deployment/nginx-deployment
    Metrics:                                               ( current / target )
      resource cpu on pods  (as a percentage of request):  117% (11m) / 50%
    Min replicas:                                          2
    Max replicas:                                          10
    Deployment pods:                                       10 current / 10 desired
    Conditions:
      Type            Status  Reason            Message
      ----            ------  ------            -------
      AbleToScale     True    ReadyForNewScale  recommended size matches current size
      ScalingActive   True    ValidMetricFound  the HPA was able to successfully calculate a replica count from cpu resource utilization (percentage of request)
      ScalingLimited  True    TooManyReplicas   the desired replica count is more than the maximum replica count
    Events:
      Type    Reason             Age   From                       Message
      ----    ------             ----  ----                       -------
      Normal  SuccessfulRescale  58s   horizontal-pod-autoscaler  New size: 4; reason: cpu resource utilization (percentage of request) above target
      Normal  SuccessfulRescale  43s   horizontal-pod-autoscaler  New size: 8; reason: cpu resource utilization (percentage of request) above target
      Normal  SuccessfulRescale  28s   horizontal-pod-autoscaler  New size: 10; reason: cpu resource utilization (percentage of request) above target

  4. delete infinite-calls to stop generate the traffic

    kubectl delete deployment infinite-calls

    after few minutes later, due to no more traffic is hitting the nginx server. hpa will scale in the number of Pod to save resource.

    kubectl get hpa

    expected output

    NAME        REFERENCE                     TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
    nginx-deployment-hpa   Deployment/nginx-deployment   0%/50%   2         10        5          12m

  5. use kubectl describe hpa will tell you the reason why hpa scale in the number of Pod.

kubectl describe hpa

expected outcome

Name:                                                  nginx-deployment-hpa
Namespace:                                             default
Labels:                                                <none>
Annotations:                                           <none>
CreationTimestamp:                                     Wed, 08 Jul 2026 16:21:40 +0000
Reference:                                             Deployment/nginx-deployment
Metrics:                                               ( current / target )
  resource cpu on pods  (as a percentage of request):  0% (0) / 50%
Min replicas:                                          2
Max replicas:                                          10
Deployment pods:                                       2 current / 2 desired
Conditions:
  Type            Status  Reason            Message
  ----            ------  ------            -------
  AbleToScale     True    ReadyForNewScale  recommended size matches current size
  ScalingActive   True    ValidMetricFound  the HPA was able to successfully calculate a replica count from cpu resource utilization (percentage of request)
  ScalingLimited  True    TooFewReplicas    the desired replica count is less than the minimum replica count
Events:
  Type    Reason             Age   From                       Message
  ----    ------             ----  ----                       -------
  Normal  SuccessfulRescale  10m   horizontal-pod-autoscaler  New size: 4; reason: cpu resource utilization (percentage of request) above target
  Normal  SuccessfulRescale  10m   horizontal-pod-autoscaler  New size: 8; reason: cpu resource utilization (percentage of request) above target
  Normal  SuccessfulRescale  10m   horizontal-pod-autoscaler  New size: 10; reason: cpu resource utilization (percentage of request) above target
  Normal  SuccessfulRescale  96s   horizontal-pod-autoscaler  New size: 2; reason: All metrics below target

clean up

kubectl delete hpa nginx-deployment-hpa
kubectl delete deployment nginx-deployment
kubectl delete svc nginx-deployment

Summary

Choosing the right scaling method depends on your specific needs, such as whether you need to quickly adjust resources, maintain performance under varying loads, or integrate scaling into a CI/CD pipeline. Manual methods like kubectl scale or editing the deployment are straightforward for immediate needs, while kubectl autoscale and HPA provide more dynamic, automated scaling based on actual usage, making them better suited for production environments with fluctuating workloads.

Task 9 - Upgrade Deployment

Objective: Mastering Deployment Upgrades and Downgrades

Discover upgrade strategies and learn how to effectively upgrade or downgrade deployments

Upgrade the deployment

Upgrading a deployment in Kubernetes, particularly changing the version of the application your Pods are running, can be smoothly managed using Kubernetes’ built-in strategies to ensure minimal downtime and maintain stability. The most popular strategies for upgrading a deployment are:

  • Rolling Update (Default Strategy)

How It Works: This strategy updates the Pods in a rolling fashion, gradually replacing old Pods with new ones. Kubernetes automatically manages this process, ensuring that a specified number of Pods are running at all times during the update. Advantages: Zero downtime, as the service remains available during the update. It allows for easy rollback in case the new version is faulty.

  • Blue/Green Deployment This strategy involves running two versions of the application simultaneously - the current (blue) and the new (green) versions. Once the new version is ready and tested, traffic is switched from the old version to the new version, either gradually or all at once.

  • Canary Deployment A small portion of the traffic is gradually shifted to the new version of the application. Based on feedback and metrics, the traffic is slowly increased to the new version until it handles all the traffic.

  • ReCreate Strategy The “Recreate” strategy is a deployment strategy in Kubernetes that is particularly useful for managing stateful applications during updates. Unlike the default “RollingUpdate” strategy, which updates Pods in a rolling fashion to ensure no downtime, the “Recreate” strategy works by terminating all the existing Pods before creating new ones with the updated configuration or image.

the use case for Recreate Strategy is for stateful application where it’s critical to avoid running multiple versions of the application simultaneously.

Performing a Rolling Update

  • Objectives Perform a rolling update using kubectl. Updating an application Users expect applications to be available all the time, and developers are expected to deploy new versions of them several times a day. In Kubernetes this is done with rolling updates. A rolling update allows a Deployment update to take place with zero downtime. It does this by incrementally replacing the current Pods with new ones. The new Pods are scheduled on Nodes with available resources, and Kubernetes waits for those new Pods to start before removing the old Pods.

  • In the previous module we scaled our application to run multiple instances. This is a requirement for performing updates without affecting application availability. By default, the maximum number of Pods that can be unavailable during the update and the maximum number of new Pods that can be created, is one. Both options can be configured to either numbers or percentages (of Pods). In Kubernetes, updates are versioned and any Deployment update can be reverted to a previous (stable) version.

  • Rolling updates overview

Alt text for the image Alt text for the image

  • Similar to application Scaling, if a Deployment is exposed publicly, the Service will load-balance the traffic only to available Pods during the update. An available Pod is an instance that is available to the users of the application.

  • Rolling updates allow the following actions:

  • Promote an application from one environment to another (via container image updates) Rollback to previous versions Continuous Integration and Continuous Delivery of applications with zero downtime

  • If a Deployment is exposed publicly, the Service will load-balance the traffic only to available Pods during the update.

  • In the following interactive tutorial, we’ll update our application to a new version, and also perform a rollback.

Perform Rolling Update

Preflight check

Step 1 below creates the kubernetes-bootcamp Deployment and Service from scratch, so neither may already exist. Confirm the cluster from Task 3 - Install Kubernetes is healthy and empty:

kubectl get nodes
kubectl get deployment

Both nodes must report Ready, and no Deployment should be listed — kubectl reports the empty list on stderr:

NAME          STATUS   ROLES           AGE   VERSION
node-worker   Ready    <none>          10m   v1.30.x
node-master   Ready    control-plane   15m   v1.30.x
No resources found in default namespace.

If a node is missing or NotReady, go back to Task 3 and finish it. If kubernetes-bootcamp is already listed from Task 7 - Deploy and Scale Deployments, run kubectl delete deployment kubernetes-bootcamp and kubectl delete svc kubernetes-bootcamp first — kubectl create deployment and kubectl expose both fail when the object already exists.

  1. Create deployment with image set to kubernetes-bootcamp:v1

    kubectl create deployment kubernetes-bootcamp --image=gcr.io/google-samples/kubernetes-bootcamp:v1 --replicas=4
    kubectl expose deployment kubernetes-bootcamp --target-port=8080 --port=80
    1. Verify the deployment
    kubectl get deployment

    Expected outcome

    NAME                  READY   UP-TO-DATE   AVAILABLE   AGE
    kubernetes-bootcamp   4/4   4           4          23s
    1. check the service

    use curlpod from cluster-internal to check the service

    kubectl run curlpod --image=appropriate/curl --restart=Never --rm -it --  curl  http://kubernetes-bootcamp.default.svc.cluster.local

    expected outcome showing v=1

    Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-5485cc6795-4m7p9 | v=1
    pod "curlpod" deleted

  2. upgrade deployment

Upgrade the deployment with image set to kubernetes-bootcamp:v2

kubectl set image deployments/kubernetes-bootcamp kubernetes-bootcamp=jocatalin/kubernetes-bootcamp:v2
kubectl rollout status deployment/kubernetes-bootcamp

expected outcome

deployment.apps/kubernetes-bootcamp image updated
Waiting for deployment spec update to be observed...
Waiting for deployment "kubernetes-bootcamp" rollout to finish: 1 out of 4 new replicas have been updated...
Waiting for deployment "kubernetes-bootcamp" rollout to finish: 1 out of 4 new replicas have been updated...
Waiting for deployment "kubernetes-bootcamp" rollout to finish: 1 out of 4 new replicas have been updated...
Waiting for deployment "kubernetes-bootcamp" rollout to finish: 2 out of 4 new replicas have been updated...
Waiting for deployment "kubernetes-bootcamp" rollout to finish: 2 out of 4 new replicas have been updated...
Waiting for deployment "kubernetes-bootcamp" rollout to finish: 2 out of 4 new replicas have been updated...
Waiting for deployment "kubernetes-bootcamp" rollout to finish: 2 out of 4 new replicas have been updated...
Waiting for deployment "kubernetes-bootcamp" rollout to finish: 3 out of 4 new replicas have been updated...
Waiting for deployment "kubernetes-bootcamp" rollout to finish: 3 out of 4 new replicas have been updated...
Waiting for deployment "kubernetes-bootcamp" rollout to finish: 2 old replicas are pending termination...
Waiting for deployment "kubernetes-bootcamp" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "kubernetes-bootcamp" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "kubernetes-bootcamp" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "kubernetes-bootcamp" rollout to finish: 1 old replicas are pending termination...
deployment "kubernetes-bootcamp" successfully rolled out

you can find above that create new replicas first then delete old replicas to avoid service disruption.

  1. check the service

check the service use curlpod

kubectl run curlpod --image=appropriate/curl --restart=Never --rm -it --  curl  http://kubernetes-bootcamp.default.svc.cluster.local

expected outcome showing v=2

Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-7c6644499c-lsxm9 | v=2

  1. rollback to old version

To rollback to orignal version, simple change the container image

kubectl set image deployments/kubernetes-bootcamp kubernetes-bootcamp=gcr.io/google-samples/kubernetes-bootcamp:v1 
kubectl rollout status deployment/kubernetes-bootcamp

expected outcome

kubectl rollout status deployment/kubernetes-bootcamp
deployment.apps/kubernetes-bootcamp image updated
Waiting for deployment "kubernetes-bootcamp" rollout to finish: 0 out of 4 new replicas have been updated...
Waiting for deployment "kubernetes-bootcamp" rollout to finish: 0 out of 4 new replicas have been updated...
Waiting for deployment "kubernetes-bootcamp" rollout to finish: 1 out of 4 new replicas have been updated...
Waiting for deployment "kubernetes-bootcamp" rollout to finish: 1 out of 4 new replicas have been updated...
Waiting for deployment "kubernetes-bootcamp" rollout to finish: 2 out of 4 new replicas have been updated...
Waiting for deployment "kubernetes-bootcamp" rollout to finish: 2 out of 4 new replicas have been updated...
Waiting for deployment "kubernetes-bootcamp" rollout to finish: 2 out of 4 new replicas have been updated...
Waiting for deployment "kubernetes-bootcamp" rollout to finish: 2 out of 4 new replicas have been updated...
Waiting for deployment "kubernetes-bootcamp" rollout to finish: 3 out of 4 new replicas have been updated...
Waiting for deployment "kubernetes-bootcamp" rollout to finish: 3 out of 4 new replicas have been updated...
Waiting for deployment "kubernetes-bootcamp" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "kubernetes-bootcamp" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "kubernetes-bootcamp" rollout to finish: 1 old replicas are pending termination...
deployment "kubernetes-bootcamp" successfully rolled out
  1. check the service use curlpod
kubectl run curlpod --image=appropriate/curl --restart=Never --rm -it --  curl  http://kubernetes-bootcamp.default.svc.cluster.local

expected outcome showing v=1

Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-5485cc6795-gvw6l | v=1
pod "curlpod" deleted

Restart the deployment

  • Restarting a deployment using kubectl rollout restart deployment can be necessary or beneficial for Refreshing the Application, Troubleshooting etc in a Kubernetes environment.
    1. check existing deployment
    kubectl get pod -o wide -l app=kubernetes-bootcamp

    expected output

    NAME                                   READY   STATUS    RESTARTS   AGE     IP               NODE          NOMINATED NODE   READINESS GATES
    kubernetes-bootcamp-5485cc6795-brnv6   1/1     Running   0          4m54s   10.244.152.110   node-worker   <none>           <none>
    kubernetes-bootcamp-5485cc6795-hqkd9   1/1     Running   0          4m54s   10.244.152.109   node-worker   <none>           <none>
    kubernetes-bootcamp-5485cc6795-qb2jh   1/1     Running   0          4m55s   10.244.152.108   node-worker   <none>           <none>
    kubernetes-bootcamp-5485cc6795-zgcrd   1/1     Running   0          4m55s   10.244.152.107   node-worker   <none>           <none>
    1. restart the deployment

    Restart the deployment with default rolling update.

    kubectl rollout restart deployment kubernetes-bootcamp
    1. Verify the deployment
    kubectl rollout status deployment kubernetes-bootcamp

    expected output

    deployment "kubernetes-bootcamp" successfully rolled out
    1. Verify the deployment after restart
    kubectl get pod -o wide -l app=kubernetes-bootcamp

    expected output

    NAME                                  READY   STATUS    RESTARTS   AGE    IP               NODE          NOMINATED NODE   READINESS GATES
    kubernetes-bootcamp-d9f576d69-2q27m   1/1     Running   0          110s   10.244.152.113   node-worker   <none>           <none>
    kubernetes-bootcamp-d9f576d69-dwwc2   1/1     Running   0          110s   10.244.152.112   node-worker   <none>           <none>
    kubernetes-bootcamp-d9f576d69-hhqqm   1/1     Running   0          108s   10.244.152.114   node-worker   <none>           <none>
    kubernetes-bootcamp-d9f576d69-w5tpc   1/1     Running   0          108s   10.244.152.115   node-worker   <none>           <none>

    Notice that the Pod’s IP address has changed, and the deployment’s Pod template hash has also been updated to new prefix (d9f576d69). This indicates that all resources have been recreated following the kubectl rollout restart command.

clean up

kubectl delete svc kubernetes-bootcamp
kubectl delete deployment kubernetes-bootcamp

Review and Questions

  1. Use kubectl run to create a POD with juice-shop image and add a label owner=dev
    Click for Answer…
    kubectl run juice-shop --image=juice-shop --labels=owner=dev
  2. Use kubectl create deployment to create a deployment for juice-shop with replicas=2 and image=bkimminich/juice-shop:v15.0.0
    Click for Answer…
    kubectl create deployment juice-shop --image=bkimminich/juice-shop:v15.0.0 --replicas=2
  3. scale out juice-shop deployment from 2 replicas to 6 replicas
    Click for Answer…
    kubectl scale deployment juice-shop --replicas=6
  4. use rolling upgrade to upgrade your juice-shop deployment to use version v16.0.0.
    Click for Answer…
    kubectl set image deployment/juice-shop juice-shop=bkimminich/juice-shop:v16.0.0
  5. Use kubectl to find the specifcation for imagePullPolicy which is need for create a deployment. and set the juice-shop deployment container imagePullPolicy to use “IfNotPresent”.
    Click for Answer…
    kubectl explain deployment.spec.template.spec.containers.imagePullPolicy
    kubectl patch deployment juice-shop --type=json -p='[{"op": "add", "path": "/spec/template/spec/containers/0/imagePullPolicy", "value": "IfNotPresent"}]'

Task 10 - Exposing Applications with Loadbalancer and Ingress

Objective: Expose Your Deployment Externally

Learn to expose your deployment through NodePort, LoadBalancer, and Ingress methods, and how to secure services with HTTPS certificates

Preflight check — do not re-install the addons if they are already present

Task 4 may have already installed MetalLB, the Kong ingress controller and cert-manager on this cluster, at newer versions than the manifests used later on this page. Run these three checks first and follow the matching branch below.

kubectl get ipaddresspool -n metallb-system
kubectl get svc kong-proxy -n kong
kubectl get namespace cert-manager

If the addons are present (MetalLB shows the first-pool IP pool, Kong shows a kong-proxy LoadBalancer with an external IP, and the cert-manager namespace exists), for example:

NAME         AUTO ASSIGN   AVOID BUGGY IPS   ADDRESSES
first-pool   true          false             ["10.0.0.5/32"]

kong-proxy   LoadBalancer   10.97.121.60   10.0.0.5      80:32477/TCP,443:31364/TCP   2m10s

then skip the install steps on this page and continue from the configuration/verification steps:

  • Under metallb loadbalancer — skip step 1 Install metallb LoadBalancer and step 2 create ippool for metallb. Continue from step 3 Verify created ipaddresspool.
  • Under Install Kong ingress controller — skip step 1 Install Kong as Ingress Controller. Continue from step 2 check installed load balancer.
  • Under Install cert-manager — skip step 8 deploy cert-manager. Continue from step 9 Create certficate.

Everything else on this page still applies, including the whole NodePort section, which does not depend on these addons.

If the addons are absent — for example because you ran the Cleanup Addons step of Task 4, so the commands above report not found / no resources — then run every step on this page exactly as written, including the install steps.

Never “fix” a version difference by re-applying the older manifests below over a newer installation, and do not delete a working newer installation in order to install the older one: leftover CRDs from the newer release can break the older controller in ways that are hard to diagnose during the lab.

Version note

The install commands and the configuration steps on this page were authored against MetalLB v0.14.3, Kong ingress controller v2.10.0 and cert-manager v1.3.1. If Task 4 already installed the newer versions, the configuration steps below (ingress annotations, ingressClassName, apiVersion values) may need small adjustments for the newer Kong and cert-manager releases. If a step does not behave as documented, check the resource with kubectl describe before changing anything, and raise it with your lab administrator.

  • In the previous chapter, we discussed Kubernetes ClusterIP services. This chapter will focus on exposing applications externally using NodePort, LoadBalancer, and Ingress services..
    1. Create kubernetes-bootcamp Deployment
    kubectl create deployment kubernetes-bootcamp --image=gcr.io/google-samples/kubernetes-bootcamp:v1 --replicas=2
    1. Verify the Deployment
    kubectl get deployment kubernetes-bootcamp

    expected Outcome

    NAME                  READY   UP-TO-DATE   AVAILABLE   AGE
    kubernetes-bootcamp   2/2     2            2           4s

NodePort

  • To enable external access to an app, such as from the internet, we can expose the service using NodePort or LoadBalancer

  • NodePort: Exposes the app through a NATted port on the worker node running the container. If an external firewall exists, you may need to whitelist this port. NodePort uses a default range of 30000-32767. When creating a NodePort service without specifying a port, Kubernetes automatically allocates one from this range.

  • To expose the service, we can use the kubectl expose command or create a service YAML definition and apply it with kubectl create -f, which offers more flexibility..

To create a NodePort service, you can use the kubectl expose command, for example

kubectl expose deployment kubernetes-bootcamp --port 80 --type=NodePort --target-port=8080 --name kubernetes-bootcamp-nodeportsvc --save-config However, using a YAML file provides more flexibility. Let’s create a NodePort service using a YAML file.

  1. Create NodePort service with YAML file
cat << EOF | tee kubernetes-bootcamp-nodeportsvc.yaml
apiVersion: v1
kind: Service
metadata:
  labels:
    app: kubernetes-bootcamp
  name: kubernetes-bootcamp-nodeportsvc
spec:
  ports:
  - nodePort: 30913
    port: 80
    protocol: TCP
    targetPort: 8080
  selector:
    app: kubernetes-bootcamp
  type: NodePort
EOF
kubectl apply -f kubernetes-bootcamp-nodeportsvc.yaml
  1. Verify the result
kubectl get svc kubernetes-bootcamp-nodeportsvc

expected Outcome

NAME                              TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
kubernetes-bootcamp-nodeportsvc   NodePort   10.103.189.68   <none>        80:30913/TCP   24s

the NATTED PORT on worker node that running POD is 30913.

NodePort service will exposes the service on a static port which is 30913 in this example on every node in the cluster, including both master and worker nodes. This means you can access the service using the IP address of any node in the cluster followed by the NodePort.

  1. Verify the service

from azure shell , access the application via nodeport service

use

curl http://$(whoami)-master.centralus.cloudapp.azure.com:30913

or

curl http://$(whoami)-worker.centralus.cloudapp.azure.com:30913

expected outcome

Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-bcbb7fc75-q7sqc | v=1
  1. Check the endpoints of service

you can find the actual endpoints for nodeport service through command

kubectl get ep  -l app=kubernetes-bootcamp

expected output

NAME                              ENDPOINTS                                 AGE
kubernetes-bootcamp-nodeportsvc   10.244.152.116:8080,10.244.152.117:8080   20m

If the curl to port 30913 times out

Reaching http://$(whoami)-master.centralus.cloudapp.azure.com:30913 from the Azure Cloud Shell requires inbound TCP port 30913 to be permitted by the network security group attached to the lab subnet. This workshop’s Terraform does not create or modify any network security group — it only reads the pre-created resource group — so port 30913 is not opened by anything in this repository.

A timeout does not by itself mean the NodePort service is broken. Use the check endpoints step above to tell the two apart:

kubectl get ep -l app=kubernetes-bootcamp

If endpoints are listed, the service is healthy and the timeout is a network-path problem rather than a Kubernetes one. Ask your lab administrator to allow inbound TCP 30913 on the lab subnet’s network security group if you need external access to the NodePort.

summary

Using NodePort services in Kubernetes, while useful for certain scenarios, comes with several disadvantages, especially when considering the setup where traffic is directed through the IP of a single worker node has limitation of Inefficient Load Balancing, Exposure to External Traffic,Lack of SSL/TLS Termination etc., so NodePort services are often not suitable for production environments, especially for high-traffic applications that require robust load balancing, automatic scaling, and secure exposure to the internet. For scenarios requiring exposure to external traffic, using an Ingress controller or a cloud provider’s LoadBalancer service is generally recommended. These alternatives offer more flexibility, better load distribution, and additional features like SSL/TLS termination and path-based routing, making them more suitable for production-grade applications.

clean up

kubectl delete svc kubernetes-bootcamp-nodeportsvc
kubectl delete deployment kubernetes-bootcamp

What is LoadBalancer Service

A LoadBalancer service in Kubernetes is a way to expose an application running on a set of Pods to the external internet in a more accessible manner than NodePort.

we can use the kubectl expose command as follow to create a loadbalancer service for deployment kubernetes-bootcamp.

LoadBalancer service require an external IP to use which we use metallb and create an ippool to assign external ip to loadbalancer

metallb loadbalancer

In a self-managed Kubernetes environment, external traffic management and service exposure are not handled automatically by the infrastructure, unlike managed Kubernetes services in cloud environments (e.g., AWS ELB with EKS, Azure Load Balancer with AKS, or Google Cloud Load Balancer with GKE). This is where solutions like MetalLB and the Kong Ingress Controller become essential

MetalLB provides a network load balancer implementation for Kubernetes clusters that do not run on cloud providers, offering a LoadBalancer type service. In cloud environments, when you create a service of type LoadBalancer, the cloud provider provisions a load balancer for your service. In contrast, on-premises or self-managed clusters do not have this luxury. MetalLB fills this gap by allocating IP addresses from a configured pool and managing access to services through these IPs, enabling external traffic to reach the cluster services.

  1. Install metallb LoadBalancer
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.3/config/manifests/metallb-native.yaml
kubectl rollout status deployment controller -n metallb-system
  1. create ippool for metallb

The IP address in the metallb IP pool is designated for assignment to the load balancer. Since Azure VMs have only one IP, which serves as the Node IP, you can retrieve the IP address using the `kubectl get node -o wide`` command.

cd $HOME
#local_ip=$(ip route get 8.8.8.8 | awk -F"src " 'NR==1{split($2,a," ");print a[1]}') 
local_ip=$(kubectl get node -o wide | grep 'control-plane' | awk '{print $6}')
cat <<EOF | tee metallbippool.yaml
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: first-pool
  namespace: metallb-system
spec:
  addresses:
  - $local_ip/32
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: example
  namespace: metallb-system
EOF
kubectl apply -f metallbippool.yaml
  1. Verify created ipaddresspool
kubectl get ipaddresspool -n metallb-system

expected Outcome

NAME         AUTO ASSIGN   AVOID BUGGY IPS   ADDRESSES
first-pool   true          false             ["10.0.0.5/32"]

Create loadBalacncer Service

  1. create kubernetes-bootcamp deployment
kubectl create deployment kubernetes-bootcamp --image=gcr.io/google-samples/kubernetes-bootcamp:v1 --replicas=2
  1. delete exist kong-proxy LoadBalancer if exist

since loadBalancer service require an dedicated external ip, if IP has already occupied by other loadBalancer, we will not able to create new loadBalancer. so if you have kong loadbalaner installed, delete it first

kubectl get svc kong-proxy -n kong && kubectl delete svc kong-proxy -n kong
  1. Create new LoadBalancer
kubectl expose deployment kubernetes-bootcamp --port=80 --type=LoadBalancer --target-port=8080 --name=kubernetes-bootcamp-lb-svc 
  1. Verify service

check external ip assigned to LoadBalancer

kubectl get svc kubernetes-bootcamp-lb-svc

expected outcome

NAME                         TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
kubernetes-bootcamp-lb-svc   LoadBalancer   10.106.121.27   10.0.0.5      80:32537/TCP   26s
  1. Verify with curl or external browser
curl http://$(whoami)-master.centralus.cloudapp.azure.com

expected outcome

Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-bcbb7fc75-nh9sn | v=1

How it works

When we use the kubectl expose command to create a LoadBalancer service in a Kubernetes cluster with MetalLB installed in Layer 2 (L2) advertisement mode, the process simplifies to these key points:

Creating the Service: The command creates a LoadBalancer type service named kubernetes-bootcamp-lb-svc, which targets the kubernetes-bootcamp deployment.

Assigning an External IP: MetalLB automatically assigns an external IP address from its configured IP pool to the service, making it accessible outside the Kubernetes cluster.

L2 Advertisement: MetalLB advertises the assigned IP address across the local network using ARP, directing traffic to the Kubernetes node responsible for the service.

Traffic Routing: Incoming traffic to the external IP is routed to the targeted Pods within the cluster, enabling external access to the application.

This streamlined process allows MetalLB to provide external IPs for services, enabling external access in environments without native cloud provider LoadBalancer support.

If you use cloud managed kubernetes like EKS, GKE, AKE, then cloud provider will responsible for create loadbalancer instance and assign ip address , then Metallb is not reqiured in that case.

clean up

kubectl delete svc kubernetes-bootcamp-lb-svc
kubectl delete deployment kubernetes-bootcamp

What is ingress and ingress controller

Ingress is not classified as a type of Kubernetes Service because it operates at a higher layer in the network stack and serves a different purpose.

Ingress operates at the application layer (Layer 7 of the OSI model), dealing with HTTP and HTTPS traffic. It allows for more complex routing based on the request’s URL path or host, and can manage SSL/TLS termination, name-based virtual hosting, and more.

It’s designed to give developers more control over the access to services from outside the Kubernetes cluster, including URL path-based routing, domain name support, and managing SSL/TLS certificates.

An Ingress typically routes traffic to one or more Kubernetes Services. It acts as an entry point to the cluster that forwards incoming requests to the appropriate Services based on the configured rules. In this sense, Ingress depends on Services to function, but it provides a more flexible and sophisticated way to expose those Services to the outside world.

Ingress requires an Ingress controller to be running in the cluster, which is a separate component that watches the Ingress resources and processes the rules they define. While Kubernetes supports Ingress resources natively, the actual routing logic is handled by this external component. There are many Ingress controller you can use for example, nginx based ingress controller, kong ingress controller, also some vendor like fortinet offer fortiweb as ingress controller.

Install Kong ingress controller

  1. Install Kong as Ingress Controller

The Kong Ingress Controller is an Ingress controller for Kubernetes that manages external access to HTTP services within a cluster using Kong Gateway. It processes Ingress resources to configure HTTP routing, load balancing, authentication, and other functionalities, leveraging Kong’s powerful API gateway features for Kubernetes services. Kong will use the ippool that managed by metallb.

kubectl apply -f  https://raw.githubusercontent.com/Kong/kubernetes-ingress-controller/v2.10.0/deploy/single/all-in-one-dbless.yaml
kubectl rollout status deployment proxy-kong -n kong
kubectl rollout status deployment ingress-kong -n kong
  1. check installed load balancer
kubectl get svc kong-proxy -n kong

expected outcome

kong-proxy   LoadBalancer   10.97.121.60   10.0.0.5      80:32477/TCP,443:31364/TCP   2m10s
  1. Verify the default ingressClasses

When Kong installed, Kong automatically configures itself as the default IngressClass for the cluster. With a default IngressClass set, you have the option to omit specifying ingressClassName: kong in your Ingress specifications.

kubectl get ingressclasses

expected outcome

NAME   CONTROLLER                            PARAMETERS   AGE
kong   ingress-controllers.konghq.com/kong   <none>       9h

Create nginx deployment

Create nginx deployment

  1. Create nginx deployment with replicas set to 2. the container also configured resource usage limition for cpu and memory.
cd $HOME
cat <<EOF | tee nginx-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80
        resources:
          requests:
            memory: "64Mi"  # Minimum memory requested to start the container
            cpu: "10m"     # 100 millicpu (0.1 CPU) requested to start the container
          limits:
            memory: "128Mi" # Maximum memory limit for the container
            cpu: "40m"     # 200 millicpu (0.2 CPU) maximum limit for the container
EOF
kubectl apply -f nginx-deployment.yaml
kubectl rollout status deployment nginx-deployment
  1. create nginx clusterIP svc for nginx-deployment
cat << EOF | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
  labels:
    app: nginx
  name: nginx-deployment
  namespace: default
spec:
  ipFamilies:
  - IPv4
  ipFamilyPolicy: SingleStack
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: nginx
  sessionAffinity: None
  type: ClusterIP
EOF
  1. check created deployment
kubectl get deployment nginx-deployment

expected outcome

NAME               READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   2/2     2            2           5m41s
  1. check created svc
kubectl get svc nginx-deployment

expected outcome

NAME               TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
nginx-deployment   ClusterIP   10.103.221.158   <none>        80/TCP    4m53s

Install cert-manager

to support https, a certificate for ingress controller is required. user can choose “cert-manager” for manage and deploy certificate.

  1. use below cli to deploy cert-manager which is used to issue certificate needed for service
kubectl get namespace cert-manager || kubectl create namespace cert-manager 
kubectl apply --validate=false -f https://github.com/jetstack/cert-manager/releases/download/v1.3.1/cert-manager.yaml
kubectl rollout status deployment cert-manager -n cert-manager
kubectl rollout status deployment cert-manager-cainjector -n cert-manager
kubectl rollout status deployment cert-manager-webhook -n cert-manager 

once deployed. we need to create a certificate for service.

  1. Create certficate
cd $HOME/k8s-101-workshop/terraform/
nodename=$(terraform output -json | jq -r .linuxvm_master_FQDN.value)
cd $HOME
cat << EOF | tee certIssuer-${nodename}.yaml
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: selfsigned-issuer-test
spec:
  selfSigned: {}
EOF
sleep 10
kubectl apply -f certIssuer-${nodename}.yaml
cat << EOF | tee cert-${nodename}.yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: test-tls-test
spec:
  secretName: test-tls-test
  duration: 2160h # 90d
  renewBefore: 360h # 15d
  issuerRef:
    name: selfsigned-issuer-test
    kind: ClusterIssuer
  commonName: kong.example
  dnsNames:
  - ${nodename}
EOF
kubectl apply -f cert-${nodename}.yaml

use kubectl get ClusterIssuer, kubectl get secret test-tls-test and kubectl get cert test-tls-test to check deployment

  1. create ingress rule for nginx svc
cd $HOME/k8s-101-workshop/terraform/
nodename=$(terraform output -json | jq -r .linuxvm_master_FQDN.value)
cd $HOME
cat <<EOF  | tee nginx_ingress_rule_with_cert_${nodename}.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx
  annotations:
    konghq.com/strip-path: 'true'
    cert-manager.io/cluster-issuer: selfsigned-issuer-test
spec:
  tls:
  - hosts:
    - ${nodename} 
  ingressClassName: kong
  rules:
  - host: ${nodename}
    http:
      paths:
      - path: /default
        pathType: ImplementationSpecific
        backend:
          service:
            name: nginx-deployment
            port:
              number: 80
EOF
kubectl apply -f nginx_ingress_rule_with_cert_${nodename}.yaml
  1. Verify ingress rule
kubectl get ingress nginx

expected outcome

NAME    CLASS   HOSTS               ADDRESS    PORTS     AGE
nginx   kong    k8s50-master.centralus.cloudapp.azure.com,k8s50-master.centralus.cloudapp.azure.com    10.0.0.5   80, 443   7m58s
  1. Check ingress detail
kubectl describe ingress nginx

expected outcome

Name:             nginx
Labels:           <none>
Namespace:        default
Address:          10.0.0.5
Ingress Class:    kong
Default backend:  <default>
TLS:
  SNI routes k8s50-master.centralus.cloudapp.azure.com
Rules:
  Host                                    Path  Backends
  ----                                    ----  --------
  k8s50-master.centralus.cloudapp.azure.com  
                                          /default   nginx-deployment:80 (10.244.152.118:80,10.244.152.119:80)
  
Annotations:                              cert-manager.io/cluster-issuer: selfsigned-issuer-test
                                          konghq.com/strip-path: true
Events:                                   <none>
  1. verify service
curl -I -k https://$nodename/default

both shall return 200 OK with response from nginx server

  1. verify with not configured path
curl -k https://$nodename/

expected result

{
  "message":"no Route matched with those values"
}

this is because in ingress rule, we did not config path “/”. therefore, ingress controller will complain there is no Route match.

now let’s create another path but point to a different service.

  1. create Kubernetes-bootcamp deployment and service
cat << EOF | tee kubernetes-bootcamp.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: kubernetes-bootcamp
spec:
  replicas: 1 # Default value for replicas when not specified
  selector:
    matchLabels:
      app: kubernetes-bootcamp
  template:
    metadata:
      labels:
        app: kubernetes-bootcamp
    spec:
      containers:
      - name: kubernetes-bootcamp
        image: gcr.io/google-samples/kubernetes-bootcamp:v1
EOF
kubectl create -f kubernetes-bootcamp.yaml

cat << EOF | tee kubernetes-bootcamp-clusterip.yaml
apiVersion: v1
kind: Service
metadata:
  labels:
    app: kubernetes-bootcamp
  name: kubernetes-bootcamp-deployment
  namespace: default
spec:
  ipFamilies:
  - IPv4
  ipFamilyPolicy: SingleStack
  ports:
  - port: 80
    protocol: TCP
    targetPort: 8080
  selector:
    app: kubernetes-bootcamp
  sessionAffinity: None
  type: ClusterIP
EOF

kubectl create -f kubernetes-bootcamp-clusterip.yaml
  1. update the ingress rule

also add a new rule with path configured to /bootcamp with backendservice set to kubernetes-bootcamp-deployment

cd $HOME/k8s-101-workshop/terraform/
nodename=$(terraform output -json | jq -r .linuxvm_master_FQDN.value)
cd $HOME
cat <<EOF  |  tee nginx_ingress_rule_with_cert_${nodename}_two_svc.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx
  annotations:
    konghq.com/strip-path: 'true'
    cert-manager.io/cluster-issuer: selfsigned-issuer-test
spec:
  tls:
  - hosts:
    - ${nodename}
  ingressClassName: kong
  rules:
  - host: ${nodename}
    http:
      paths:
      - path: /default
        pathType: ImplementationSpecific
        backend:
          service:
            name: nginx-deployment
            port:
              number: 80
  - host: ${nodename}
    http:
      paths:
      - path: /bootcamp
        pathType: ImplementationSpecific
        backend:
          service:
            name: kubernetes-bootcamp-deployment
            port:
              number: 80
EOF
kubectl apply -f nginx_ingress_rule_with_cert_${nodename}_two_svc.yaml
  1. Verify ingress rule with

verify bootcamp ingress rule with https url

curl -k https://${nodename}/bootcamp 

or plain http url

curl http://${nodename}/bootcamp

Expected outcome

Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-5485cc6795-rsqs9 | v=1

verify nginx ingress rule with https url

curl -k https://${nodename}/default

or plain http url

curl http://${nodename}/default

Expected outcome

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

Review Questions

  1. Create juice-shop with v15.0.0 deployment and ClusterIP svc
    Click for Answer…
       kubectl create deployment juice-shop --image=bkimminich/juice-shop:v15.0.0
       kubectl expose deployment juice-shop --port=3000 --target-port=3000 --type=ClusterIP
  2. Create juice-shop with v16.0.0 deployment and ClusterIP svc
    Click for Answer…
       kubectl create deployment juice-shop-v16 --image=bkimminich/juice-shop:v16.0.0
       kubectl expose deployment juice-shop-v16 --port=3000 --target-port=3000 --type=ClusterIP
  3. Create https ingress rule with path /v15 point to v15.0.0 deployment
    Click for Answer…
    cat <<EOF  | tee nginx_ingress_rule_with_cert_${nodename}.yaml
    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      name: nginx
      annotations:
        konghq.com/strip-path: 'true'
        cert-manager.io/cluster-issuer: selfsigned-issuer-test
    spec:
      tls:
      - hosts:
        - ${nodename}
      ingressClassName: kong
      rules:
      - host: ${nodename}
        http:
          paths:
          - path: /v15
            pathType: Prefix
            backend:
              service:
                name: juice-shop-v15
                port:
                  number: 3000
    EOF
    kubectl apply -f nginx_ingress_rule_with_cert_${nodename}.yaml
  4. Create https ingress rule with path /v16 point to v16.0.0 deployment
    Click for Answer…
    cat <<EOF  | tee nginx_ingress_rule_with_cert_${nodename}.yaml
    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      name: nginx
      annotations:
        konghq.com/strip-path: 'true'
        cert-manager.io/cluster-issuer: selfsigned-issuer-test
    spec:
      tls:
      - hosts:
        - ${nodename}
      ingressClassName: kong
      rules:
      - host: ${nodename}
        http:
          paths:
          - path: /v16
            pathType: Prefix
            backend:
              service:
                name: juice-shop-v16
                port:
                  number: 3000
    EOF
    kubectl apply -f nginx_ingress_rule_with_cert_${nodename}.yaml

clean up

kubectl delete ingress nginx
kubectl delete svc nginx-deployment 
kubectl delete svc kubernetes-bootcamp-deployment
kubectl delete deployment nginx-deployment
kubectl delete deployment kubernetes-bootcamp

you can also remove below if you no longer need loadbalancer , ingress controller and cert-manager.

kubectl delete -f https://raw.githubusercontent.com/metallb/metallb/v0.14.3/config/manifests/metallb-native.yaml
kubectl delete -f https://raw.githubusercontent.com/Kong/kubernetes-ingress-controller/v2.10.0/deploy/single/all-in-one-dbless.yaml
kubectl delete -f https://github.com/jetstack/cert-manager/releases/download/v1.3.1/cert-manager.yaml

Task 11 - Cleanup

Warning

Only complete this cleanup step if you are finished with the workshop. If you plan to continue with other labs like [AI-101](https://fortinetcloudcse.github.io/ai-101/) or [FortiAIGate Workshop](https://fortinetcloudcse.github.io/faig-training-workshop/), skip the cleanup.

After completing all tasks with the Self-Managed Kubernetes, use the following command to delete the two Azure VMs:

cd $HOME/k8s-101-workshop/terraform && terraform destroy -var="username=$(whoami)" --auto-approve

Appendix - More info

Pod life-cycle

The life cycle of a Kubernetes Pod involves several key stages from creation to termination. Here’s a brief overview of these stages, illustrated with commands related to deploying a Pod using the gcr.io/google-samples/kubernetes-bootcamp:v1 image:

  1. Pod Creation

A Pod is created when you deploy it using a YAML file or directly via the kubectl command.

  1. Pending The Pod enters the Pending state as Kubernetes schedules the Pod on a node and the container image is being pulled from the registry.

  2. Running Once the image is pulled and the Pod is scheduled, it moves to the Running state. The Pod remains in this state until it is terminated or stopped for some reason.

  3. Succeeded/Failed

A Pod reaches Succeeded if all of its containers exit without error and do not restart. A Pod is marked as Failed if any of its containers exit with an error.

  1. CrashLoopBackOff

This status indicates that a container in the Pod is failing to start properly and Kubernetes is repeatedly trying to restart it.

  1. Termination Pods can be terminated gracefully by deleting them. Kubernetes first sends a SIGTERM signal to allow containers to shut down gracefully.

  2. Deletion The Pod’s entry remains in the system for a period after termination, allowing you to inspect its status posthumously. Eventually, Kubernetes cleans it up automatically.

Through these stages, Kubernetes manages the application’s lifecycle, ensuring that the desired state specified by the Deployment configurations is maintained. Monitoring the Pod’s lifecycle helps in managing and troubleshooting applications running on Kubernetes.

Preflight check

Every command on this page selects the kubernetes-bootcamp Deployment with -l app=kubernetes-bootcamp, but this page never creates it — and the clean up at the end of Task 10 - Exposing application deletes it. Check it is there first, otherwise every command below returns nothing:

kubectl get deployment kubernetes-bootcamp

Expected output:

NAME                  READY   UP-TO-DATE   AVAILABLE   AGE
kubernetes-bootcamp   1/1     1            1           63m

If kubectl reports it was not found, recreate it before continuing:

kubectl create deployment kubernetes-bootcamp --image=gcr.io/google-samples/kubernetes-bootcamp:v1 --replicas=1

Delete it again with kubectl delete deployment kubernetes-bootcamp when you are done with this page.

we can use kubectl get pod -l app=kubernetes-bootcamp and kuectl describe pod -l app=kubernetes-bootcamp to check the detail state for a Pod.

Useful Command for Pod

You can try a few useful command for operating a Pod

  • get the Pod name only

To retrieve just the name(s) of the Pod(s) with a specific label (app=kubernetes-bootcamp), use the following command:

kubectl get pods -l app=kubernetes-bootcamp -o=jsonpath='{.items[*].metadata.name}'
  • shell into the Pod To access the shell of the default container in a Pod labeled with app=kubernetes-bootcamp, first capture the Pod name in a variable, then use kubectl exec:
PODNAME=$(kubectl get pods -l app=kubernetes-bootcamp -o=jsonpath='{.items[*].metadata.name}')
kubectl exec -it po/$PODNAME -- bash

Note: This command assumes that your selection returns a single Pod name or you are only interested in the first Pod. Use exit to leave the container shell. some of container in Pod does not have bash or sh , then you will not able to shell into the container in that Pod.

you will be drop into Pod’s default container shell, use exit to exit the container.

  • check log for a Pod To view the logs from the container in real-time:
PODNAME=$(kubectl get pods -l app=kubernetes-bootcamp -o=jsonpath='{.items[*].metadata.name}')
kubectl logs -f po/$PODNAME

You will see logs output from the container. Press Ctrl-C to exit the log stream.

Kubernetes Bootcamp App Started At: 2024-02-21T05:41:33.993Z | Running On:  kubernetes-bootcamp-5485cc6795-cdwz7 
  • Delete Pod and Observe IP Address Change First, check the current Pod’s IP address:
kubectl get pod -l app=kubernetes-bootcamp -o wide

then delete Pod

PODNAME=$(kubectl get pods -l app=kubernetes-bootcamp -o=jsonpath='{.items[*].metadata.name}')
kubectl delete po/$PODNAME

You will see an output similar to:

pod "kubernetes-bootcamp-5485cc6795-cdwz7" deleted

After deletion, check the Pods again. You will find a new Pod has been automatically recreated with a new IP address. This behavior is due to the Kubernetes Controller Manager ensuring the actual state matches the desired state specified by the Deployment’s replicas. A new Pod is generated to maintain the desired number of replicas.

kubectl get pod -l app=kubernetes-bootcamp -o wide

The IP address assigned to a Pod is ephemeral and will assign next available ip for recreation.

These commands provide a basic but powerful set of tools for interacting with Pods in a Kubernetes environment, from accessing shells and viewing logs to managing Pod lifecycles.

ServiceAccount

A ServiceAccounts are primarily designed for use by processes running in Pods is like an identity for processes running in a Pod, allowing them to interact with the Kubernetes API securely. When you create a Pod, Kubernetes can automatically give it access to a ServiceAccount, so your applications can ask Kubernetes about other parts of the system without needing a separate login. It’s a way for your apps to ask Kubernetes “Who am I?” and “What am I allowed to do?”

check the default service account for a POD

podName=$(kubectl get pod -l app=kubernetes-bootcamp  -o=jsonpath='{.items[*].metadata.name}')
kubectl describe pod $podName | grep 'Service Account' | uniq

Expected output:

Service Account:  default

Kubernetes adheres to the principle of least privilege, meaning the default ServiceAccount is assigned minimal permissions necessary for a Pod’s basic operations. Should your Pod require additional permissions, you must create a new ServiceAccount with the requisite permissions and associate it with your Pod. use kubectl create rolebinding to bind pre-defined role or custom role to serviceAccount.

Kubernetes API-resources

Kubernetes is fundamentally built around APIs that adhere to the OpenAPI specification, defining resources and their operations. Based on API input, Kubernetes creates objects and stores them in the etcd database. Let’s explore using the Kubernetes API to create a Pod, utilizing the kubectl api-resources and kubectl explain commands for guidance.

  • Finding the API Resource for Pods

First, identify the API resource needed to create a Pod. You can list all API resources with `kubectl api-resources``:

This command filters the list of API resources to show only those related to Pods. The output will look similar like this:

kubectl  api-resources | head  -n 1
kubectl  api-resources | grep pods

expect to see output

 kubectl  api-resources | head  -n 1
NAME 
NAME                              SHORTNAMES                                      APIVERSION                             NAMESPACED   KIND
kubectl  api-resources | grep pods
pods                              po                                              v1                                     true         Pod
pods                                                                              metrics.k8s.io/v1beta1                 true         PodMetrics

From the output, we see that the “KIND” for Pods is “Pod”, and the API version is v1.

  • Understanding Pod Specifications

Next, use kubectl explain to understand how to structure a YAML definition for a Pod specification. Execute the following commands to explore the Pod resource specifications:

kubectl explain Pod

and

kubectl explain Pod.apiVersion

and

kubectl explain Pod.kind

and

kubectl explain Pod.metadata
  • Crafting a Minimal YAML Definition for a Pod

Now, we can construct a minimal YAML file to create a Pod. The essential elements include the Pod’s name and the container image:

cat << EOF | sudo tee minimalyamlforpod.yaml 
apiVersion: v1
kind: Pod
metadata: 
  name: test-pod 
spec:
  containers: 
    - name: nginx
      image: nginx
EOF
  • Creating the Pod

With the YAML file ready, create the Pod using:

kubectl create -f minimalyamlforpod.yaml

Verifying Pod Creation

To see the details of the created Pod, including any default values Kubernetes applied during creation, use:

kubectl get pod test-pod -o yaml

This command outputs the complete configuration of the Pod, test-pod, showing all properties set by Kubernetes, many of which use default values that you can customize in the Pod YAML definition.

use kubectl delete pod test-pod to delete pod or use yaml file below

kubectl  delete -f minimalyamlforpod.yaml