Skip to content

Azure AKS + Kubernetes

This tutorial walks you through deploying a production-grade AKS cluster with Kubernetes workloads, all defined in TypeScript using two chant lexicons. The Azure lexicon produces an ARM template for infrastructure; the K8s lexicon produces kubectl-ready YAML for workloads.

┌─────────────────────────────────────────────────────────┐
│ Azure Lexicon (ARM) (~14 resources) │
│ ├── VNet + Subnets + NSG + Route Table │
│ ├── AKS Cluster (SystemAssigned identity) │
│ ├── Container Registry (Premium, admin disabled) │
│ ├── 3× Managed Identities (app, dns, monitor) │
│ ├── Role Assignments (ACR Pull, DNS Contributor, etc.) │
│ └── Azure DNS Zone │
└────────────────────┬────────────────────────────────────┘
│ client IDs via .env → config.ts
┌────────────────────▼────────────────────────────────────┐
│ K8s Lexicon (~20 resources) │
│ ├── Namespace (quotas, limits, network policy) │
│ ├── AutoscaledService (Deployment + HPA + PDB) │
│ ├── WorkloadIdentityServiceAccount (AKS) │
│ ├── AGIC Ingress + AksExternalDnsAgent │
│ ├── AzureDiskStorageClass │
│ └── AzureMonitorCollector │
└─────────────────────────────────────────────────────────┘
Source fileWhat it produces
src/infra/networking.tsVNet, 2 subnets, NSG, Route Table
src/infra/cluster.tsAKS cluster, ACR, 3 managed identities, role assignments
src/infra/dns.tsAzure DNS Zone
src/k8s/namespace.tsNamespace with quotas, limits, default-deny policy
src/k8s/app.tsAutoscaledService + WorkloadIdentityServiceAccount + ConfigMap
src/k8s/ingress.tsAGIC Ingress + AksExternalDnsAgent
src/k8s/storage.tsAzureDiskStorageClass
src/k8s/observability.tsAzureMonitorCollector
src/config.tsCross-lexicon config — reads .env for real client IDs
  • Azure account with an active subscription
  • Azure CLI (az) logged in (az account show should work)
  • kubectl installed
  • Bun runtime and chant installed (Installation guide)
  • Subscription must have Microsoft.ContainerService, Microsoft.Network, Microsoft.ManagedIdentity, Microsoft.ContainerRegistry, and Microsoft.Authorization resource providers registered
  • Minimum 6 vCPUs quota in your target region (3 x Standard_B2s nodes)

“Build the ARM template for the AKS microservice example and deploy it.”

Terminal window
cd examples/k8s-aks-microservice
# Set the resource group name
export AZURE_RESOURCE_GROUP=aks-microservice-rg
# Create the resource group
az group create --name $AZURE_RESOURCE_GROUP --location eastus
# Build both ARM template and K8s manifests
npm run build
# Deploy the ARM template
npm run deploy-infra

The deployment creates the VNet, AKS cluster, container registry, managed identities with role assignments, and a DNS zone. AKS cluster creation takes ~5-10 minutes.

Step 2: Configure kubectl and load outputs

Section titled “Step 2: Configure kubectl and load outputs”

“Configure kubectl for the AKS cluster and load the deployment outputs into .env.”

Terminal window
# Update kubeconfig
npm run configure-kubectl
kubectl get nodes # verify — should show 3 nodes
# Write real client IDs from ARM outputs into .env
npm run load-outputs

The load-outputs target queries the ARM deployment, extracts managed identity client IDs, the tenant ID, and the cluster name, and writes them to .env. Bun auto-loads .env at runtime, so the next K8s build uses real values instead of placeholders.

“Rebuild the K8s manifests with real client IDs and deploy them.”

Terminal window
# Rebuild K8s manifests with real client IDs from .env
npm run build:k8s
# Validate against the live cluster (optional)
npm run validate
# Apply all K8s resources
npm run apply
# Wait for the app deployment to roll out
npm run wait

The K8s output includes ~20 resources across 5 files: namespace with quotas, autoscaled app deployment, AGIC ingress with ExternalDNS, Azure Disk storage class, and Azure Monitor collector.

“Check that everything is running.”

Terminal window
# Quick status check
npm run status
# Application pods (should show 2+ Running)
kubectl get pods -n microservice
# Ingress (Application Gateway address appears after 2-3 min)
kubectl get ingress -n microservice
# Observability agents
kubectl get daemonsets -A
# Test the endpoint (once DNS propagates)
GATEWAY_IP=$(kubectl get ingress -n microservice microservice-agic -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
curl -s -o /dev/null -w "%{http_code}" http://$GATEWAY_IP/
  • Application Gateway takes 2-3 minutes to configure after kubectl apply. Check kubectl describe ingress for events if it’s slow.
  • Delete order matters — always delete K8s resources before the resource group. If you delete the resource group first, AGIC can’t clean up the Application Gateway backend configuration.
  • AKS ships metrics-server — HPA works out of the box. Do not deploy a separate MetricsServer composite.
  • Workload Identity requires federated credentials — the managed identity must have a federated credential configured for the AKS OIDC issuer and the specific service account.
  • K8s-only changes (app config, scaling, new workloads): edit source, run npm run build:k8s && npm run apply. No infra redeploy needed.
  • Infrastructure changes (node count, new identities, new role assignments): edit source, run npm run build && npm run deploy-infra. Then run npm run load-outputs if outputs changed, followed by npm run build:k8s && npm run apply.
Terminal window
npm run teardown

This runs: kubectl delete → 30s drain wait → az group delete --no-wait.