Skip to content

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.