Azure (AKS)
Azure (AKS)
Prerequisites
You will need:
- An Azure account with appropriate permissions
- Azure CLI installed and authenticated (
az login) - Terraform >= 1.5
- kubectl installed
- helm installed
The Terraform runs az on your machine to attach the SIP and RTP network security groups to the node pools, so the CLI must be authenticated before you apply — not just installed.
Provision the AKS Cluster
The Terraform templates for provisioning an AKS cluster are available here.
- Clone the Terraform repository to your local machine.
- Navigate to
azure/provision-aks-cluster. - Copy
terraform.tfvars.exampletoterraform.tfvarsand edit it with your desired settings. - Run
terraform init && terraform plan && terraform applyto provision the cluster. - Configure kubectl:
az aks get-credentials --resource-group <rg> --name <cluster-name>
The cluster takes roughly 10 minutes, then the node pools, then a minute for the NSG association.
vCPU quota is per VM family, not just per region. A default deployment uses Standard_D2s_v3 / Standard_D4s_v3, all in the DSv3 family, and Azure caps each family separately — commonly at 10 vCPUs on a new subscription. If you see
you have regional headroom but no family headroom. Either request an increase, or put one pool on a different family — e.g. sip_vm_size = "Standard_D2as_v4", which draws on the DASv4 allowance instead. Check with az vm list-usage --location <region>.
Verify all three node pools, with their labels and taints:
The Helm chart’s SBC DaemonSets select on voip-environment=sip and voip-environment=rtp, so a missing label leaves pods Pending rather than producing an error.
How SIP and RTP are opened
This is worth understanding, because the failure mode is silent. AKS attaches its own NSG to every node pool’s VMSS NICs, and Azure requires traffic to be permitted by both the NIC NSG and the subnet NSG. Subnet rules alone are not enough — the NIC NSG’s default deny drops SIP and RTP, and then everything looks healthy (all pods Running, drachtio listening on 5060, an explicit allow visible on the subnet NSG) while no call can arrive.
The Terraform handles this: it creates sip-nodes-nsg and rtp-nodes-nsg in the AKS-managed resource group and attaches each to its own node pool, so SIP ports open only on SIP nodes and the media range only on RTP nodes. You should see this near the end of the apply:
Confirm it took, and give it a couple of minutes:
This reaches into the AKS-managed resource group, and AKS may reset a NIC’s NSG reference during some reconcile or upgrade operations. If SIP stops arriving after a cluster upgrade, re-run terraform apply before looking anywhere else.
Deploy jambonz
Create the namespace and install the Traefik ingress controller:
externalTrafficPolicy=Local is required on AKS, not a preference. With the default (Cluster) the Azure load balancer distributes across every node, including the SIP and RTP pools — whose NSGs permit only VoIP ports, not the ingress nodePort. Traffic landing there is dropped, so roughly half of all requests to the portal and API hang, which presents as random flakiness rather than a clear failure. Measured on a four-node cluster: 4 of 8 requests timed out with Cluster, 8 of 8 succeeded with Local.
Then install the chart from a clone of the Helm chart repository, replacing the domain with your own:
baseUrl drives every hostname: the portal is jambonz.example.com, the API api.jambonz.example.com, Grafana grafana.jambonz.example.com.
sbc.eipAllocator.enabled=false because there is nothing for it to do on Azure: AKS gives the SIP and RTP nodes public IPs directly (the SIP pool from a reserved prefix), so no address needs claiming. Left enabled it does no harm — the init container logs EIP allocation not supported for cloud provider: azure ... skipping and exits cleanly — but turning it off keeps the deployment honest.
Databases are initialized by two Jobs which must complete before the application pods start:
Confirm the SBCs are advertising their public addresses, since media depends on it:
which on a healthy cluster prints something like:
drachtio should show --external-ip <public IP>, and rtpengine interface=public/<private>!<public>. If rtpengine shows only a private address with no !, media will be one-way silence — upgrade the rtpengine image, as older ones could not discover a public IP on AKS.
Set up DNS
Get the load balancer’s address:
Azure gives an IP address, so create A records for your portal hostname plus the api. and grafana. subdomains.
Create the records before you look them up. A DNS zone’s SOA sets how long a “does not exist” answer is cached, and it is often long — 86400 seconds (24 hours) is a common default. Resolve a hostname once before its record exists and your resolver may refuse to see it for a day, even though the record is live and public resolvers return it. If you have already done this, use a different hostname rather than waiting it out.
Enable HTTPS
Install cert-manager, then set global.traefik.tls.enabled=true, global.traefik.clusterIssuer=letsencrypt-prod and global.traefik.email in your values and run helm upgrade.
Log In
Browse to your portal hostname and log in as admin / admin. You will be prompted to change the password immediately.
Then complete the Post-Install Steps and generate a license key as described in Software Licensing.
Cleanup
Order matters. Kubernetes created the load balancer, and Terraform does not know about it.
Wait until no LoadBalancer services remain, then:
Destroying the cluster removes the AKS-managed resource group, and with it the node NSGs and the SIP public IP prefix — no separate cleanup is needed for those. Do check for managed disks left behind by the PVCs, since deleting the namespace can race the PVC deletion: