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_GROUP
az aks update --enable-keda --name $AKS_NAME --resource-group $RESOURCE_GROUP
Verify 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-demo
kubectl 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 Standard
SB_NAMESPACE="kedasb$(openssl rand -hex 4)" az servicebus namespace create \ --name $SB_NAMESPACE \ --resource-group $RESOURCE_GROUP \ --location $LOCATION \ --sku Standard
Create a queue in the Service Bus namespace:
az servicebus queue create ` --name kedaqueue ` --namespace-name $SB_NAMESPACE ` --resource-group $RESOURCE_GROUP
az servicebus queue create \ --name kedaqueue \ --namespace-name $SB_NAMESPACE \ --resource-group $RESOURCE_GROUP
Get 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 tsv
SB_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-demo
kubectl create secret generic servicebus-connection \ --from-literal=connection-string="$SB_CONNECTION_STRING" \ --namespace keda-demo
Grant your user account the
Azure Service Bus Data Owner
role 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 EOF
Create 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 EOF
Check that the ScaledObject has been created correctly:
kubectl get scaledobjects -n keda-demo kubectl get triggerauthentications -n keda-demo
kubectl 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-demo
kubectl get pods -l app=order-processor -n keda-demo
You 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" done
Watch the pods scale up as KEDA detects the messages in the queue:
kubectl get pods -l app=order-processor -n keda-demo -w
kubectl get pods -l app=order-processor -n keda-demo -w
You 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 -w
kubectl get hpa -n keda-demo -w
KEDA 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-demo
kubectl 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-demo
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-demo
Delete 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_GROUP
az servicebus queue delete \ --name kedaqueue \ --namespace-name $SB_NAMESPACE \ --resource-group $RESOURCE_GROUP az servicebus namespace delete \ --name $SB_NAMESPACE \ --resource-group $RESOURCE_GROUP
Delete the namespace:
kubectl delete namespace keda-demo
kubectl delete namespace keda-demo