Exercise 7: Event-Driven Autoscaling with KEDA
Kubernetes Event-driven Autoscaling (KEDA) allows you to scale your Kubernetes workloads based on the number of events that need to be processed. Unlike the standard Horizontal Pod Autoscaler (HPA) which scales based on CPU and memory metrics, KEDA can scale based on external metrics such as queue length, event hub message count, and more.
Task 1: Install KEDA using the AKS Add-on
AKS offers KEDA as a managed add-on, making it simple to deploy and integrate with your cluster.
Enable the KEDA add-on for your AKS cluster:
az aks update --enable-keda --name $AKS_NAME --resource-group $RESOURCE_GROUPaz aks update --enable-keda --name $AKS_NAME --resource-group $RESOURCE_GROUPVerify that KEDA has been installed correctly:
kubectl get po -n kube-system | select-string 'keda'kubectl get po -n kube-system | grep 'keda'You should see several KEDA pods running, such as the KEDA Operator, KEDA Metrics Server, and KEDA Admission Webhooks.
Create a dedicated namespace for our KEDA demo:
kubectl create namespace keda-demokubectl create namespace keda-demo
Task 2: Create an Azure Service Bus Queue
KEDA can scale based on various event sources. For this exercise, we’ll use an Azure Service Bus queue.
Create an Azure Service Bus namespace:
$SB_NAMESPACE = "kedasb$((New-Guid).ToString().Substring(0,8))" az servicebus namespace create ` --name $SB_NAMESPACE ` --resource-group $RESOURCE_GROUP ` --location $LOCATION ` --sku StandardSB_NAMESPACE="kedasb$(openssl rand -hex 4)" az servicebus namespace create \ --name $SB_NAMESPACE \ --resource-group $RESOURCE_GROUP \ --location $LOCATION \ --sku StandardCreate a queue in the Service Bus namespace:
az servicebus queue create ` --name kedaqueue ` --namespace-name $SB_NAMESPACE ` --resource-group $RESOURCE_GROUPaz servicebus queue create \ --name kedaqueue \ --namespace-name $SB_NAMESPACE \ --resource-group $RESOURCE_GROUPGet the connection string for the Service Bus namespace:
$SB_CONNECTION_STRING = az servicebus namespace authorization-rule keys list ` --name RootManageSharedAccessKey ` --namespace-name $SB_NAMESPACE ` --resource-group $RESOURCE_GROUP ` --query primaryConnectionString ` -o tsvSB_CONNECTION_STRING=$(az servicebus namespace authorization-rule keys list \ --name RootManageSharedAccessKey \ --namespace-name $SB_NAMESPACE \ --resource-group $RESOURCE_GROUP \ --query primaryConnectionString \ -o tsv)Create a Kubernetes secret with the connection string:
kubectl create secret generic servicebus-connection ` --from-literal=connection-string=$SB_CONNECTION_STRING ` --namespace keda-demokubectl create secret generic servicebus-connection \ --from-literal=connection-string="$SB_CONNECTION_STRING" \ --namespace keda-demoGrant your user account the
Azure Service Bus Data Ownerrole on the Service Bus namespace:az role assignment create ` --assignee $(az ad signed-in-user show --query userPrincipalName -o tsv) ` --role "Azure Service Bus Data Owner" ` --scope $(az servicebus namespace show --name $SB_NAMESPACE --resource-group $RESOURCE_GROUP --query id -o tsv)az role assignment create \ --assignee "$(az ad signed-in-user show --query userPrincipalName -o tsv)" \ --role "Azure Service Bus Data Owner" \ --scope "$(az servicebus namespace show --name $SB_NAMESPACE --resource-group $RESOURCE_GROUP --query id -o tsv)"
Task 3: Deploy a Sample Application and ScaledObject
Now, let’s deploy the official KEDA sample application that will process messages from the Service Bus queue and scale based on queue length.
Create a deployment for the KEDA sample application in the keda-demo namespace:
$deploymentYaml = @" apiVersion: apps/v1 kind: Deployment metadata: name: order-processor namespace: keda-demo labels: app: order-processor spec: replicas: 1 selector: matchLabels: app: order-processor template: metadata: labels: app: order-processor spec: containers: - name: order-processor image: k8sonazureworkshoppublic.azurecr.io/ghcr.io/kedacore/sample-dotnet-worker-servicebus-queue:latest env: - name: KEDA_SERVICEBUS_AUTH_MODE value: ConnectionString - name: KEDA_SERVICEBUS_QUEUE_CONNECTIONSTRING valueFrom: secretKeyRef: name: servicebus-connection key: connection-string - name: KEDA_SERVICEBUS_QUEUE_NAME value: kedaqueue "@ $deploymentYaml | kubectl apply -f - ```cat << EOF | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: name: order-processor namespace: keda-demo labels: app: order-processor spec: replicas: 1 selector: matchLabels: app: order-processor template: metadata: labels: app: order-processor spec: containers: - name: order-processor image: k8sonazureworkshoppublic.azurecr.io/ghcr.io/kedacore/sample-dotnet-worker-servicebus-queue:latest env: - name: KEDA_SERVICEBUS_AUTH_MODE value: ConnectionString - name: KEDA_SERVICEBUS_QUEUE_CONNECTIONSTRING valueFrom: secretKeyRef: name: servicebus-connection key: connection-string - name: KEDA_SERVICEBUS_QUEUE_NAME value: kedaqueue EOFCreate a ScaledObject to enable KEDA autoscaling:
$scaledObjectYaml = @" apiVersion: keda.sh/v1alpha1 kind: TriggerAuthentication metadata: name: trigger-auth-servicebus namespace: keda-demo spec: secretTargetRef: - parameter: connection name: servicebus-connection key: connection-string --- apiVersion: keda.sh/v1alpha1 kind: ScaledObject metadata: name: order-processor-scaler namespace: keda-demo spec: scaleTargetRef: name: order-processor minReplicaCount: 1 maxReplicaCount: 10 pollingInterval: 5 cooldownPeriod: 30 triggers: - type: azure-servicebus metadata: queueName: kedaqueue messageCount: '5' authenticationRef: name: trigger-auth-servicebus "@ $scaledObjectYaml | kubectl apply -f - ```cat <<EOF | kubectl apply -f - apiVersion: keda.sh/v1alpha1 kind: TriggerAuthentication metadata: name: trigger-auth-servicebus namespace: keda-demo spec: secretTargetRef: - parameter: connection name: servicebus-connection key: connection-string --- apiVersion: keda.sh/v1alpha1 kind: ScaledObject metadata: name: order-processor-scaler namespace: keda-demo spec: scaleTargetRef: name: order-processor minReplicaCount: 1 maxReplicaCount: 10 pollingInterval: 5 cooldownPeriod: 30 triggers: - type: azure-servicebus metadata: queueName: kedaqueue messageCount: '5' authenticationRef: name: trigger-auth-servicebus EOFCheck that the ScaledObject has been created correctly:
kubectl get scaledobjects -n keda-demo kubectl get triggerauthentications -n keda-demokubectl get scaledobjects -n keda-demo kubectl get triggerauthentications -n keda-demo
Task 4: Test KEDA Autoscaling
Now, let’s test KEDA by sending messages to the Service Bus queue and observing how KEDA scales our application.
First, check the current number of pods:
kubectl get pods -l app=order-processor -n keda-demokubectl get pods -l app=order-processor -n keda-demoYou should see one pod running.
Send multiple messages to the Service Bus queue using Azure CLI:
$requestUri="https://$SB_NAMESPACE.servicebus.windows.net/kedaqueue/messages" $authToken = az account get-access-token --resource "https://servicebus.azure.net/" --query accessToken -o tsv for ($i = 1; $i -le 200; $i++) { $messageBody = @{ Id = "order-$i" Amount = Get-Random -Minimum 1 -Maximum 1000 ArticleNumber = "Item-$i" Customer = @{ FirstName = "Test" LastName = "User-$i" } } | ConvertTo-Json -Compress Invoke-RestMethod -Uri $requestUri -Method Post -Headers @{ "Authorization" = "Bearer $authToken" "Content-Type" = "application/json" } -Body $messageBody Write-Host "Sent message $i to Service Bus queue" }requestUri="https://$SB_NAMESPACE.servicebus.windows.net/kedaqueue/messages" authToken=$(az account get-access-token --resource "https://servicebus.azure.net/" --query accessToken -o tsv) for i in {1..200}; do messageBody=$(cat <<EOF { "Id": "order-$i", "Amount": $((RANDOM % 1000 + 1)), "ArticleNumber": "Item-$i", "Customer": { "FirstName": "Test", "LastName": "User-$i" } } EOF ) curl -X POST "$requestUri" \ -H "Authorization: Bearer $authToken" \ -H "Content-Type: application/json" \ -d "$messageBody" echo "Sent message $i to Service Bus queue" doneWatch the pods scale up as KEDA detects the messages in the queue:
kubectl get pods -l app=order-processor -n keda-demo -wkubectl get pods -l app=order-processor -n keda-demo -wYou should see new pods being created to handle the messages. KEDA scales the deployment based on the number of messages in the queue.
In another terminal, you can also watch the HPA that KEDA created:
kubectl get hpa -n keda-demo -wkubectl get hpa -n keda-demo -wKEDA works by creating an HPA object with custom metrics.
You can check the logs of one of the processor pods to see the messages being processed:
kubectl logs -f $(kubectl get pods -l app=order-processor -n keda-demo -o name | Select-Object -First 1) -n keda-demokubectl logs -f $(kubectl get pods -l app=order-processor -n keda-demo -o name | head -1) -n keda-demo
After a few minutes, as the messages are processed, KEDA will scale down the deployment back to the minimum number of replicas.
Task 5: Clean Up Resources
Delete the ScaledObject, TriggerAuthentication, and the deployment:
kubectl delete scaledobject order-processor-scaler -n keda-demo kubectl delete triggerauthentication trigger-auth-servicebus -n keda-demo kubectl delete deployment order-processor -n keda-demokubectl delete scaledobject order-processor-scaler -n keda-demo kubectl delete triggerauthentication trigger-auth-servicebus -n keda-demo kubectl delete deployment order-processor -n keda-demoDelete the Service Bus queue and namespace:
az servicebus queue delete ` --name kedaqueue ` --namespace-name $SB_NAMESPACE ` --resource-group $RESOURCE_GROUP az servicebus namespace delete ` --name $SB_NAMESPACE ` --resource-group $RESOURCE_GROUPaz servicebus queue delete \ --name kedaqueue \ --namespace-name $SB_NAMESPACE \ --resource-group $RESOURCE_GROUP az servicebus namespace delete \ --name $SB_NAMESPACE \ --resource-group $RESOURCE_GROUPDelete the namespace:
kubectl delete namespace keda-demokubectl delete namespace keda-demo