Azure Kubernetes Service: Deploying and Managing Workloads
Lesson 24. Introduction
This project demonstrates the end-to-end process of provisioning and managing Azure Kubernetes Service (AKS) clusters, deploying containerized applications, and exposing them publicly. It covers two main scenarios. In the first project, I created an AKS cluster with Azure CLI, deployed my Hopewell application, scaled the deployment to three replicas, and exposed it through a public LoadBalancer IP.
In the second project, I used a Kubernetes YAML manifest to deploy a sample online store with RabbitMQ and application services. I applied the manifest with kubectl and checked the pods and services to confirm that the workload was running. The screenshots document every major step — from SSH key generation and cluster creation through application deployment, service exposure, verification, and final cleanup.
Tools Used
Labs
Project1: Creating and Scaling Workloads on Azure AKS with Azure CLI
Project 2: Deploying an Application on an AKS Cluster Using a Kubernetes Manifest File.
Steps
> Open VS Code and select a folder (k8S)
> Create a folder called Hopewell
> Change directory to the folder created i.e run cd hopewell
> Run the command: mkdir .ssh — This is to create a hidden SSH folder where the SSH key would be generated.
> Run the command: ssh-keygen -f .ssh/aks-ssh — This helps to generate the SSH key and hit the Enter button twice
cd hopewell
mkdir .ssh
ssh-keygen -f .ssh/aks-sshThis screenshot shows the navigation to the hopewell lab folder, creates a .ssh directory, and generates an SSH key pair (aks-ssh) using ssh-keygen. This key will later be used for secure node access when creating the AKS cluster.
Creating a Resource Group
> Run the command: az group create — name akss-rg — location eastus — You can replace RG name with the name of your choice.
> Run the command: az group list -o table — This command lists all the available RGs in a tabular form.
az group create --name akss-rg --location eastus
az group list -o tableAn Azure resource group named akss-rg is created in the East US region with the az group create command. The subsequent az group list confirms that the new resource group appears alongside existing ones and is in the Succeeded state.
Creating an AKS Cluster
> Run the command: az aks create — resource-group akss-rg — name CJAKSCluster — node-count 1 — nodepool-name cjsystem — node-resource-group CJNodeRG — ssh-key-value .ssh/aks-ssh.pub — network-plugin azure — enable-cluster-autoscaler — min-count 1 — max-count 2 — zones 1
az aks create --resource-group akss-rg --name CJAKSCluster --node-count 1 --nodepool-name cjsystem --node-resource-group CJNodeRG --ssh-key-value .ssh/aks-ssh.pub --network-plugin azure --enable-cluster-autoscaler --min-count 1 --max-count 2 The AKS cluster CJAKSCluster is provisioned inside akss-rg with one system node, SSH authentication, the Azure CNI network plugin, and cluster autoscaler enabled (min 1, max 2 nodes). The command returns a large JSON object describing the newly created cluster configuration.
Azure portal Confirmation
> Open the Azure portal to confirm the resource that has been created.
In the Azure Portal, the akss-rg resource group is selected, revealing the managed Kubernetes service resource named CJAKSCluster. This confirms that the cluster created via the CLI is visible and managed through the portal.
The automatically created node resource group CJNodeRG is examined. It contains the underlying infrastructure resources (public IP, network security group, virtual machine scale set, virtual network, managed identity, and load balancer) that support the AKS cluster.
Connect to the Cluster
> Run the command: az aks get-credentials — resource-group akss-rg — name CJAKSCluster
az aks get-credentials --resource-group akss-rg --name CJAKSClusterCredentials for the CJAKSCluster are retrieved with az aks get-credentials and merged into the local kubeconfig file. This step enables kubectl commands to target the new cluster.
Verifying the Cluster
> Run the command: kubectl get nodes
kubectl get nodesThe command kubectl get nodes shows a single ready node (aks-cjsystem-…) running Kubernetes version 1.36.4, confirming that the cluster is healthy and accessible from the local machine.
Create the “frontend” Namespace
> Run the command: kubectl create namespace frontend
kubectl create namespace frontendHere, a new Kubernetes namespace called frontend is created with kubectl create namespace frontend. The command returns a success message indicating the namespace is ready for use.
Verify the created Namespace
> Run the command: kubectl get namespaces # or
> Run the command: kubectl get namespaces -A
kubectl get namespaces
kubectl get namespaces -AThe command kubectl get namespaces (and the all-namespaces variant) lists the newly created frontend namespace together with the default system namespaces, verifying that the namespace exists and is Active.
Use the Namespace in Deployments
> Run the command: kubectl create deployment my-app — image=emelecj/hopewell:v2 — replicas=3 -n frontend — port=80
kubectl create deployment my-app --image=emelecj/hopewell:v2 --replicas=3 -n frontend -- port=80In this screenshot, you will notice that an initial attempt to create a deployment named “my-app” fails because of an invalid label; a corrected command successfully creates the deployment using the nginx image with three replicas in the frontend namespace, exposing port 80.
Verify the Deployment
> Run the command: kubectl get deployments or
> Run the command: kubectl get deployments -A or
> Run the command: kubectl get deployment — namespace default
kubectl get deployments or
kubectl get deployments -A or
kubectl get deployment --namespace defaultThe command kubectl get deployments -A shows the my-app deployment in the frontend namespace (still scaling) along with the system deployments that ship with AKS, confirming the application workload has been scheduled.
Expose the deployment
> Run the command: kubectl expose deployment my-app — name=app-service — type=LoadBalancer — port=80 — protocol=TCP -n frontend
kubectl expose deployment my-app --name=app-service --type=LoadBalancer --port=80 --protocol=TCP -n frontendThe my-app deployment is exposed as a LoadBalancer service named app-service on port 80. Kubernetes creates the corresponding service object so the application can be reached from outside the cluster.
Get Emele Chijioke’s stories in your inbox
Join Medium for free to get updates from this writer.
Get the External IP
> Run the command: kubectl get services or
> Run the command: kubectl get services -A or
> Run the command: kubectl get services nginx-service — watch
kubectl get services
kubectl get services -A
kubectl get services nginx-service --watchThe command kubectl get services -A lists the app-service LoadBalancer together with its external IP address (135.237.71.73). This public IP can now be used to access the nginx application.
Access App Via a Browser
Open your web browser and navigate to http://<EXTERNAL-IP>/. You should see the default home page of the application.
Clean Up
> Run the command: az group delete — name akss-rg — yes — no-wait
> Run the command: az aks delete — resource-group asks-rg — name MyAKSCluster — yes — no-wait
az group delete --name akss-rg --yes --no-wait
az aks delete --resource-group asks-rg --name MyAKSCluster --yes --no-waitHere, the original resource group akss-rg (and implicitly its managed node group) is deleted with az group delete. A subsequent list shows the groups in a Deleting state, beginning the cleanup of the first experiment.
Project 2: Deploying an Application on an AKS Cluster Using a Kubernetes Manifest File.
Steps
> Create a folder in the K8s main folder and call is aks-store
In Visual Studio Code, the new workspace folder named aks-store that will contain the multi-service Contoso application manifests.
> Inside the aks-store folder, create a file called aks-store.yaml
The file aks-store.yaml is opened in the editor; it is still empty at this moment, ready to receive the Kubernetes manifests for the microservices application.
apiVersion: apps/v1
kind: Deployment
metadata:
name: rabbitmq
spec:
replicas: 1
selector:
matchLabels:
app: rabbitmq
template:
metadata:
labels:
app: rabbitmq
spec:
nodeSelector:
"kubernetes.io/os": linux
containers:
- name: rabbitmq
image: mcr.microsoft.com/mirror/docker/library/rabbitmq:3.10-management-alpine
ports:
- containerPort: 5672
name: rabbitmq-amqp
- containerPort: 15672
name: rabbitmq-http
env:
- name: RABBITMQ_DEFAULT_USER
value: "username"
- name: RABBITMQ_DEFAULT_PASS
value: "password"
resources:
requests:
cpu: 10m
memory: 128Mi
limits:
cpu: 250m
memory: 256Mi
volumeMounts:
- name: rabbitmq-enabled-plugins
mountPath: /etc/rabbitmq/enabled_plugins
subPath: enabled_plugins
volumes:
- name: rabbitmq-enabled-plugins
configMap:
name: rabbitmq-enabled-plugins
items:
- key: rabbitmq_enabled_plugins
path: enabled_plugins
---
apiVersion: v1
data:
rabbitmq_enabled_plugins: |
[rabbitmq_management,rabbitmq_prometheus,rabbitmq_amqp1_0].
kind: ConfigMap
metadata:
name: rabbitmq-enabled-plugins
---
apiVersion: v1
kind: Service
metadata:
name: rabbitmq
spec:
selector:
app: rabbitmq
ports:
- name: rabbitmq-amqp
port: 5672
targetPort: 5672
- name: rabbitmq-http
port: 15672
targetPort: 15672
type: ClusterIP
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
spec:
replicas: 1
selector:
matchLabels:
app: order-service
template:
metadata:
labels:
app: order-service
spec:
nodeSelector:
"kubernetes.io/os": linux
containers:
- name: order-service
image: ghcr.io/azure-samples/aks-store-demo/order-service:latest
ports:
- containerPort: 3000
env:
- name: ORDER_QUEUE_HOSTNAME
value: "rabbitmq"
- name: ORDER_QUEUE_PORT
value: "5672"
- name: ORDER_QUEUE_USERNAME
value: "username"
- name: ORDER_QUEUE_PASSWORD
value: "password"
- name: ORDER_QUEUE_NAME
value: "orders"
- name: FASTIFY_ADDRESS
value: "0.0.0.0"
resources:
requests:
cpu: 1m
memory: 50Mi
limits:
cpu: 75m
memory: 128Mi
initContainers:
- name: wait-for-rabbitmq
image: busybox
command: ['sh', '-c', 'until nc -zv rabbitmq 5672; do echo waiting for rabbitmq; sleep 2; done;']
resources:
requests:
cpu: 1m
memory: 50Mi
limits:
cpu: 75m
memory: 128Mi
---
apiVersion: v1
kind: Service
metadata:
name: order-service
spec:
type: ClusterIP
ports:
- name: http
port: 3000
targetPort: 3000
selector:
app: order-service
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: product-service
spec:
replicas: 1
selector:
matchLabels:
app: product-service
template:
metadata:
labels:
app: product-service
spec:
nodeSelector:
"kubernetes.io/os": linux
containers:
- name: product-service
image: ghcr.io/azure-samples/aks-store-demo/product-service:latest
ports:
- containerPort: 3002
resources:
requests:
cpu: 10m
memory: 64Mi
limits:
cpu: 100m
memory: 128Mi
---
apiVersion: v1
kind: Service
metadata:
name: product-service
spec:
type: ClusterIP
ports:
- name: http
port: 3002
targetPort: 3002
selector:
app: product-service
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: store-front
spec:
replicas: 1
selector:
matchLabels:
app: store-front
template:
metadata:
labels:
app: store-front
spec:
nodeSelector:
"kubernetes.io/os": linux
containers:
- name: store-front
image: ghcr.io/azure-samples/aks-store-demo/store-front:latest
ports:
- containerPort: 8080
name: store-front
env:
- name: VUE_APP_ORDER_SERVICE_URL
value: "http://order-service:3000/"
- name: VUE_APP_PRODUCT_SERVICE_URL
value: "http://product-service:3002/"
resources:
requests:
cpu: 1m
memory: 200Mi
limits:
cpu: 1000m
memory: 512Mi
---
apiVersion: v1
kind: Service
metadata:
name: store-front
spec:
ports:
- port: 80
targetPort: 8080
selector:
app: store-front
type: LoadBalancer
The complete aks-store.yaml is displayed. It defines a RabbitMQ Deployment (with management plugin, environment variables, resource limits, and a ConfigMap volume) that will later be accompanied by the order, product, and storefront services.
Create Resource Group
> Ensure that you are in the Hopewell directory. Else, run the command: cd hopewell
> Run the command: az group create — name newaks-rg — location westus3
cd hopewell
az group create --name newaks-rg --location westus3A fresh resource group newaks-rg is created in the West US 3 region. The JSON output confirms successful provisioning of the new group that will host the second AKS cluster.
Create an AKS Cluster
> Run the command: az aks create — resource-group newaks-rg — name my-aks-cluster — node-count 1 — generate-ssh-keys
az aks create --resource-group newaks-rg --name my-aks-cluster --node-count 1 --generate-ssh-keysA new AKS cluster named my-aks-cluster is created inside newaks-rg with a single node and automatically generated SSH keys. The returned JSON describes the cluster’s configuration and node pool.
Connect to AKS Cluster
> Run the command: az aks get-credentials — resource-group newaks-rg — name my-aks-cluster
> Run the command: kubectl get nodes
az aks get-credentials --resource-group newaks-rg --name my-aks-cluster
kubectl get nodesIn this screenshot, Credentials for my-aks-cluster are retrieved, and kubectl get nodes verifies that a single node is Ready and running Kubernetes 1.35.8, confirming the second cluster is operational.
Verify Creation in Azure Portal
The Azure Portal resource-groups view shows the newaks-rg group together with its associated managed cluster resource group, confirming both the control-plane and node resources exist.
Note: Change directory to the aks-store before running the commands below.
Then run the Following Commands, one at a time.
> Run the command: kubectl apply -f aks-store.yaml
kubectl apply -f aks-store.yamlFrom the aks-store directory, the full aks-store.yaml manifest is applied with kubectl apply -f. The output lists the successful creation of the ConfigMap, RabbitMQ service/deployment, and the order-service, product-service, and store-front deployments/services.
> Run the command: kubectl get pods
kubectl get podsThe command kubectl get pods is used to show all four application pods (order-service, product-service, rabbitmq, and store-front) in the Running state with 1/1 ready containers, indicating a healthy microservices deployment.
> Run the command: kubectl get service
kubectl get serviceThe command kubectl get service reveals that the store-front service has been assigned a public LoadBalancer IP (4.236.69.41) while the internal services remain ClusterIP-only. This external IP is the entry point to the Contoso storefront.
Access App Via a Browser
> Open your web browser and navigate to http://<EXTERNAL-IP>/. You should see the default home page of the application. E.g http://4.236.69.41
Browsing to the LoadBalancer IP displays the fully functional Contoso pet-store website, complete with product catalog, prices, and “Add to Cart” buttons — proving that the entire microservices application is reachable from the internet.
Clean Up
> Run the command: az group list -o table
> Run the command: az group delete — resource-group newaks-rg — yes — no-wait
az group delete - resource-group newaks-rg - yes - no-waitAll remaining resource groups (newaks-rg, its managed node group, and NetworkWatcherRG) are deleted. The final az group list confirms that the resources are in the Deleting state, completing the laboratory cleanup.
Conclusion
The sequence successfully illustrates the complete AKS lifecycle: cluster provisioning with SSH keys, namespace and deployment management, service exposure via LoadBalancer, verification of a working multi-tier application, and thorough resource cleanup. The project demonstrates both a simple single-container workload and a realistic microservices architecture (RabbitMQ-backed Contoso store), giving practical experience with Azure CLI, kubectl, and the Azure Portal.








