AKS Platform: Architecture¶
This page describes the high level design: the layers, the environments, the resource groups and how the Azure and Azure Local variants share one structure.
Reference design
This is the layout of the reference configuration. Your platform may use other names, CIDR ranges, sizes or switches.
Layers¶
The solution is built in layers. Each layer is a Terraform module with a deploy_* switch (the networking layer is always
evaluated). A layer consumes the outputs of the layers below it, so the order matters on a first deployment.
flowchart TB
subgraph tf["Terraform (per platform and environment)"]
net["Networking<br/>spoke, subnets, DNS, network profile"]
idn["Identity<br/>Entra ID groups, managed identities,<br/>workload and CI/CD identities"]
sec["Security policy<br/>Azure Policy, Defender, diagnostics, alerts"]
cls["Cluster baseline<br/>AKS, node pools, maintenance, add-ons"]
kv["Secrets<br/>Key Vault, private endpoint"]
end
subgraph gitops["GitOps repositories (Argo CD)"]
ns["Namespaces, priority classes, RBAC"]
pol["Kyverno policies, network policies, quotas"]
svc["Platform services:<br/>ingress gateway, monitoring, certificates"]
app["Applications"]
end
net --> cls
idn --> cls
net --> kv
net --> sec
cls --> gitops
ns --> app
pol --> app
svc --> app
| Layer | Terraform module (Azure) | What it creates | Switch |
|---|---|---|---|
| Networking | modules/azure/networking |
Resource group, VNet, four subnets, NSG, route table, Private DNS zone, ingress public IP (standalone); or data lookups of an existing spoke (hub-integrated). Also computes the AKS network profile. | network_mode |
| Identity | modules/azure/identity |
Resource group, Entra ID groups, break-glass group, kubelet managed identity, workload identities, CI/CD identities with federated credentials, optional role assignments | deploy_identity |
| Security policy | modules/azure/security-policy |
Azure Policy assignments, Defender for Cloud plans, diagnostic settings, security alerts, image cleaner interval | deploy_security_policy |
| Cluster baseline | modules/azure/cluster-baseline |
Resource group, AKS cluster, user node pools, role assignments of the cluster identities, optional diagnostic settings | deploy_cluster_baseline |
| Secrets | modules/azure/secrets |
Resource group, Key Vault, private endpoint, optional diagnostic settings | deploy_key_vault |
The Azure Local variant has a matching module per layer under modules/azure-local/, with a smaller scope
(AKS on Azure Local). Shared building blocks (Entra ID groups, break-glass, identities, role assignments,
Key Vault) sit in modules/identity/ and modules/secrets/ and are reused by both variants.
Environments and clusters¶
The design runs two clusters. Production workloads run only on the production cluster. Development, test and acceptance run together on the non-production cluster, separated by namespaces.
flowchart LR
subgraph prod["Production cluster (aks-prod)"]
pns["Namespaces per team:<br/>team-a-prod"]
end
subgraph nonprod["Non-production cluster (aks-nonprod)"]
dns["team-a-dev"]
tns["team-a-test"]
ans["team-a-accp"]
end
pvnet["Production spoke"] --- prod
nvnet["Non-production spoke"] --- nonprod
| Environment value | Platform | Network mode | Cluster name in the reference configuration |
|---|---|---|---|
prod |
azure |
standalone | aks-prod |
nonprod |
azure |
standalone | aks-nonprod |
nonprod-integrated |
azure |
hub-integrated | aks-nonprod-integrated |
prod-integrated |
azure |
hub-integrated | Selector exists in the pipeline; no configuration file in the sources |
nonprod, prod |
azure-local |
Azure Local logical network | Set by the pipeline variables of the environment |
The reasons for two clusters: blast radius, independent upgrade lifecycle (non-production first), different access rules (developers get broader access outside production), cost, and network separation. The spokes are not peered to each other.
Each platform and environment has its own configuration file (config/<environment>.tfvars), its own backend file
(config/backend-<environment>.hcl) and therefore its own Terraform state.
Resource groups¶
| Resource group (Azure) | Content |
|---|---|
rg-<env>-network |
Spoke VNet, subnets, route table, Private DNS zone, ingress public IP (standalone only) |
rg-<env>-identity |
Managed identities (kubelet, workload, CI/CD) |
rg-<env>-aks |
The AKS cluster and its user node pools |
rg-<env>-aks-nodes |
Node resource group managed by AKS |
rg-<env>-secrets |
Key Vault and its private endpoint |
rg-<env>-security |
Security alerts, when enabled |
On Azure Local the identity, secrets and security groups get an -arc suffix (rg-<env>-identity-arc and so on). The cluster
resource group is created by the bootstrap and named in the pipeline variables.
Policy scope
In the Azure variant the security policy layer is given the ID of the network resource group as the scope of its
Azure Policy assignments (module.networking.spoke_resource_group_id), while the cluster lives in rg-<env>-aks. Azure
Policy for Kubernetes applies to clusters inside the assigned scope. Confirm with the platform team which scope is
effective in your deployment.
Naming and tags¶
Resource names follow <type>-<purpose>-<env>: vnet-<env>, snet-aks-nodes-<env>, mi-kubelet-<env>, mi-cicd-<name>,
fc-github-<name>, kv-<name>. The modules add a layer tag. The environment root adds environment, managed_by
(terraform) and platform (azure or azure-local), next to the project and owner tags from the configuration.
See also Naming and tags for the platform-wide conventions.
Relation to the MCP hub and spokes¶
| Mode | Relation |
|---|---|
| Standalone | The solution does not need the hub. It creates its own spoke and peering is present in the code but disabled. |
| Hub-integrated | The solution uses a spoke from the landing zone, hub-owned Private DNS zones and the hub egress path. The hub must provide the prerequisites in Networking. See Architecture reference for the hub and spoke model. |