> For the complete documentation index, see [llms.txt](https://docs.openiam.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.openiam.com/installation/kubernetes/azure-aks.md).

# Azure AKS

Create and prepare an Azure AKS cluster for OpenIAM — node sizing, disk and file storage, node settings, and the entry point.

This page covers what AKS needs **in addition to** [*Prepare the cluster*](/installation/kubernetes/prepare-the-cluster.md). Work through that page as well; it carries the requirements common to every cluster.

## What this covers, and how long it takes

|                |                                                                               |
| -------------- | ----------------------------------------------------------------------------- |
| **You get**    | An AKS cluster ready for an OpenIAM deployment                                |
| **Time**       | 60–90 minutes, most of it waiting on Azure                                    |
| **Hosts**      | Your Azure subscription, and a machine with `kubectl`, Helm and the Azure CLI |
| **Downtime**   | None                                                                          |
| **Reversible** | Yes                                                                           |

## Before you start

* Permission to create an AKS cluster, node pools, storage accounts and a managed database.
* The Azure CLI, `kubectl` and Helm on the machine you will deploy from.
* [*Managed databases*](/installation/rpm/cloud/managed-databases.md), because the database is provisioned separately and its parameters must be right before the deployment runs.

## Set these first

{% code overflow="wrap" %}

```bash
export AZ_RG=<your resource group>
export CLUSTER=<your AKS cluster name>
export POOL=<your node pool name>
export DIAG_IMAGE=<a small utility image with a shell, reachable from your cluster>
```

{% endcode %}

**Pin the diagnostic image to a version.** `busybox:1.36` is a common choice. On a restricted network, mirror one into your own registry and use that rather than pulling `latest` from a public registry.

***

## Prepare it

### 1. Create the cluster

**The Kubernetes version this release is configured for** — see [*System requirements*](/installation/system-requirements.md). The Terraform route sets it for you.

Use the **Azure CNI** network plugin, and give the cluster its own subnet. If you make the cluster private, you will need a machine inside the virtual network — a jump box or a build agent — to run `kubectl` and Helm from, because the API server is not reachable from outside.

### 2. Size the node pool

The deployment needs 32 GB of memory for non-production, 40 GB for production, and 8 CPU — as a total across the nodes running it.

| Nodes | VM size           | Gives                                                                  |
| ----- | ----------------- | ---------------------------------------------------------------------- |
| 2     | `Standard_D8s_v5` | 16 vCPU, 64 GB — comfortable headroom for non-production               |
| 3     | `Standard_D8s_v5` | Room for production, and for a node to be lost during a rolling update |

Set the OS disk to at least **128 GB** on each node.

**Size for a node loss.** A single OpenIAM deployment tolerates losing a node only if the remaining nodes can take its pods — that is what makes the cluster the availability layer rather than the platform.

### 3. Set `vm.max_map_count` on the node pool

The search tier requires it and the chart does not set it. AKS takes it as node pool configuration rather than a start-up script, which is the cleaner of the two providers here.

**`--linux-os-config` takes the path to a JSON file**, not inline JSON. Write the file first:

{% code overflow="wrap" %}

```bash
cat > linux-os-config.json <<'EOF'
{
  "sysctls": {
    "vmMaxMapCount": 262144
  }
}
EOF
```

{% endcode %}

Then create the node pool with it:

{% code overflow="wrap" %}

```bash
az aks nodepool add \
  --resource-group "$AZ_RG" --cluster-name "$CLUSTER" --name "$POOL" \
  --node-count 3 --node-vm-size Standard_D8s_v5 --node-osdisk-size 128 \
  --linux-os-config linux-os-config.json
```

{% endcode %}

**It is a creation-time setting.** To apply it to a pool that already exists, add a new pool with the setting, move the workload to it, and remove the old one.

In Terraform, the same setting is `linux_os_config { sysctl_config { vm_max_map_count = 262144 } }` inside the node pool block — [*AKS with Terraform*](/installation/kubernetes/terraform-aks.md) uses it.

**Confirm before you deploy:**

{% code overflow="wrap" %}

```bash
kubectl debug "node/$(kubectl get nodes -o name | head -1 | cut -d/ -f2)" \
  -it --image="$DIAG_IMAGE" -- sysctl vm.max_map_count
```

{% endcode %}

**Expected: `262144`.**

### 4. Confirm a default storage class

AKS installs its CSI drivers and storage classes for you. Confirm exactly one class is default:

{% code overflow="wrap" %}

```bash
kubectl get storageclass
```

{% endcode %}

**Expected:** `managed-csi` or `managed-csi-premium` marked `(default)`. Premium SSD is the better choice for the graph, search and broker tiers.

### 5. Provide `ReadWriteMany` storage with Azure Files

**Azure Disk cannot do `ReadWriteMany`**, and several OpenIAM components need it — see [*Prepare the cluster*](/installation/kubernetes/prepare-the-cluster.md) step 3. On AKS that means Azure Files, and the driver is already installed.

The built-in `azurefile-csi` class works. For a deployment of this size, a premium class performs noticeably better for the components that use it:

{% code overflow="wrap" expandable="true" %}

```yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: openiam-rwx
provisioner: file.csi.azure.com
parameters:
  skuName: Premium_LRS
mountOptions:
  - dir_mode=0777
  - file_mode=0777
  - actimeo=30
allowVolumeExpansion: true
reclaimPolicy: Delete
```

{% endcode %}

**Test it** with the `ReadWriteMany` PVC in [*Prepare the cluster*](/installation/kubernetes/prepare-the-cluster.md) step 3 before going further. This is the check most worth doing early — it is the one that fails late and confusingly if it is wrong.

**If the cluster is private or restricted**, the storage account needs a private endpoint or a service endpoint from the cluster's subnet, or the volumes will not mount.

### 6. Provision the database

[*Managed databases*](/installation/rpm/cloud/managed-databases.md) covers the supported Azure services and the server parameters that must be applied before the deployment runs.

Use a **private endpoint**, link its private DNS zone to the cluster's virtual network, and test the connection from the cluster with the check in [*Prepare the cluster*](/installation/kubernetes/prepare-the-cluster.md) step 7. An unlinked DNS zone is the most common reason a database that is reachable from a jump box is not reachable from a pod.

### 7. Plan the entry point

Users reach the platform through the reverse proxy's service. Expose it with a **Load Balancer** service, or with an **Application Gateway** through the Application Gateway Ingress Controller if you want TLS termination or a WAF at the edge.

| Setting                       | Value                                                                    |
| ----------------------------- | ------------------------------------------------------------------------ |
| **Health probe path**         | `/idp/login`                                                             |
| **Backend protocol and port** | HTTP, the reverse proxy service's port                                   |
| **TLS**                       | Your certificate at the Application Gateway, or terminate in the cluster |

**Probe `/idp/login`, not `/`.** It exercises the proxy and a real application behind it.

Whatever DNS name resolves to that entry point is the address you give the platform during first sign-in — [*First sign-in*](/installation/rpm/rpm-single-node/first-sign-in.md) covers the content provider, which works the same way on Kubernetes.

***

## Verify it

{% code overflow="wrap" %}

```bash
kubectl version -o json | jq -r '.serverVersion.gitVersion'
kubectl get nodes -o wide
kubectl get storageclass
kubectl get csidrivers
```

{% endcode %}

**Expected:** the release's Kubernetes version, nodes `Ready`, a default disk class, an Azure Files class, and both CSI drivers registered. Then run the full pass in [*Prepare the cluster*](/installation/kubernetes/prepare-the-cluster.md).

## If it goes wrong

| What you see                                                        | What it means                                                       | What to do                                                         |
| ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------ |
| Only the shared-storage PVCs stay `Pending`                         | Azure Files cannot be reached, or the class is wrong                | Check the storage account's network rules and the class parameters |
| Shared volumes mount but the pod cannot write                       | The mount options do not grant the container user access            | Use the `dir_mode` and `file_mode` options in the class above      |
| The search tier crashes on start                                    | `vm.max_map_count` is not set on the pool                           | Apply the `linux-os-config` update and let the nodes roll          |
| Pods schedule then evict                                            | The node pool is too small for the deployment                       | Add nodes, or move to a larger VM size                             |
| The database is unreachable from pods but reachable from a jump box | The private DNS zone is not linked to the cluster's virtual network | Link it, then re-test from a pod                                   |
| `kubectl` cannot reach the API server                               | The cluster is private                                              | Run from a machine inside the virtual network                      |
| Images will not pull                                                | No egress to the registry, or the pull secret is wrong              | Check outbound rules, then the credentials from your account team  |

## What this procedure does not cover

Your subscription and resource group structure, virtual network design and cluster policies. The [Solution Architecture and Design Guide](/solution-architecture/deployment/network-design.md) covers the design decisions.

## Related

* [*Prepare the cluster*](/installation/kubernetes/prepare-the-cluster.md) — do this as well
* [*Managed databases*](/installation/rpm/cloud/managed-databases.md)
* [*System requirements*](/installation/system-requirements.md)

*Applies to OpenIAM 2026.8.1.*


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.openiam.com/installation/kubernetes/azure-aks.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
