---
title: Azure Kubernetes Service (AKS)
description: "Install OpenLM Platform on Azure Kubernetes Service (AKS): ingress, Azure Disk storage, node pools, NAT egress, and Azure data services."
product: OpenLM Platform
---

Read this page before [step 1](https://www.openlm.com/documentation/cloud/deployment-operations/on-premise/deployment-guide/kubernetes/install/step-1-prepare-cluster). It covers what's specific to Azure Kubernetes Service (AKS). Everything else follows the standard installation steps.

## Before you start

Plan the cluster with these recommendations:

| Item | Recommendation |
|---|---|
| Kubernetes | A version AKS supports without the LTS plan (check `az aks get-versions`) |
| Nodes | A system pool for AKS add-ons, plus a user pool for OpenLM Platform. For the standard set: 3 × `Standard_D8s_v5` (8 vCPU / 32 GiB) in the user pool. Larger sites: see [Sizing](https://www.openlm.com/documentation/cloud/deployment-operations/on-premise/deployment-guide/kubernetes/before-you-start/sizing). |
| Networking | Azure CNI Overlay. Outbound traffic through a **NAT gateway with a static public IP**, so you can allow that IP in the Azure SQL and Redis firewalls |
| API access | If you restrict the API server to authorized IP ranges, include the NAT gateway IP, or nodes can't join (`K8SAPIServerConnFail`) |

## Ingress

Install **ingress-nginx** (or Traefik) behind the Azure Standard Load Balancer:

```bash
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm install ingress-nginx ingress-nginx/ingress-nginx -n ingress --create-namespace \
  --set controller.service.annotations."service\.beta\.kubernetes\.io/azure-load-balancer-health-probe-request-path"=/healthz \
  --set controller.config.proxy-body-size=50m
kubectl -n ingress get service ingress-nginx-controller    # EXTERNAL-IP for your DNS A record
```

Set `INGRESS_CLASS=nginx` in your worksheet. The AKS **application routing add-on** (managed nginx) also works: its class is `webapprouting.kubernetes.azure.com`.

:::warning[Not the Application Gateway Ingress Controller]
AGIC terminates TLS on the Application Gateway, with its own certificate handling and annotations. The OpenLM Platform Ingress isn't built for it. Put ingress-nginx or Traefik in the cluster instead. An Application Gateway or Front Door *in front of* that controller works, as long as it passes the `Host` header and uses a certificate for the same `DOMAIN`.
:::

## Storage

Use **`managed-csi`** (Azure Disk, `disk.csi.azure.com`). Don't use an Azure Files class for OpenLM Platform volumes or for databases you run in the cluster, even if it's the cluster default.

## Node settings

Set the [file-watch limits](https://www.openlm.com/documentation/cloud/deployment-operations/on-premise/deployment-guide/kubernetes/platforms/index#node-settings-that-apply-everywhere) with the DaemonSet, or with the node pool's custom node configuration (`linuxOSConfig.sysctls`) if your AKS version supports those keys.

## Node pools

To use [node placement](https://www.openlm.com/documentation/cloud/deployment-operations/on-premise/deployment-guide/kubernetes/concepts/node-placement), which is optional, create the pools with the label and the taint:

```bash
az aks nodepool add -g <rg> --cluster-name <aks> -n openlmmain \
  --node-vm-size Standard_D4s_v5 --node-count 4 \
  --labels openlm.com/role=main --node-taints openlm.com/role=main:NoSchedule
az aks nodepool add -g <rg> --cluster-name <aks> -n openlmspark \
  --node-vm-size Standard_D4s_v5 --node-count 1 \
  --labels openlm.com/role=spark --node-taints openlm.com/role=spark:NoSchedule
```

Pool names can have at most 12 characters. The label values are exactly `main` and `spark`.

## Values

AKS needs no values beyond what step 4 gives you. If you use labeled pools, also set `global.affinity.enabled: true` and `global.taints.enabled: true`.

## Data services

Use these Azure services, or your own, for the data services:

| Service | Azure option | Worksheet settings |
|---|---|---|
| SQL Server | **Azure SQL Database** (single databases or an Elastic Pool) or SQL Managed Instance | `SQL_HOST=<server>.database.windows.net`, `SQL_ENCRYPT=True`. Allow the NAT gateway IP in the server firewall, or use a private endpoint. |
| Redis | **Azure Managed Redis** or Azure Cache for Redis | `REDIS_TLS=true`. Azure Managed Redis: port `10000`, clustering policy **non-clustered**. Azure Cache for Redis: port `6380`. Use an access key as `REDIS_PASSWORD`. |
| Kafka | Your own Kafka (in the cluster or on VMs), or Confluent Cloud | Plaintext listener inside your network is the standard. OpenLM hasn't validated Azure Event Hubs (Kafka endpoint) with OpenLM Platform. Confluent Cloud needs SASL: contact OpenLM first. |
| MongoDB | **MongoDB Atlas** (private endpoint or peering), or MongoDB in the cluster | **Azure Cosmos DB for MongoDB isn't supported.** |

:::warning[Azure Managed Redis: use non-clustered]
With the clustered policies, commands that touch several keys fail with `CROSSSLOT` errors in the API gateway and in the services' background jobs. Choose the **non-clustered** policy when you create the cache.
:::

On **Azure SQL**, create the four databases in the portal, with `az sql db create`, or in an Elastic Pool, named as in [step 2](https://www.openlm.com/documentation/cloud/deployment-operations/on-premise/deployment-guide/kubernetes/install/step-2-data-services). The `sqlcmd` loop from step 2 works too, connected to the `master` database. Azure SQL Database has no SQL Agent and no cross-database queries. OpenLM Platform doesn't need either.

## Checks specific to AKS

Confirm the AKS settings with these commands:

- `kubectl get nodes -L openlm.com/role`: nodes `Ready`, labels as you intended.
- `kubectl -n ingress get service`: the controller has a public (or internal) IP.
- From a pod, SQL Server and Redis answer (the checks in [step 2](https://www.openlm.com/documentation/cloud/deployment-operations/on-premise/deployment-guide/kubernetes/install/step-2-data-services)). A timeout usually means the firewall doesn't allow the NAT gateway IP.
