MAQPNADocs

Install on Azure AKS

Install MAQPNA on Azure Kubernetes Service step by step, with Pod Sandboxing node pools, Azure CNI powered by Cilium, Entra Workload ID, immutable Blob audit storage and a passing doctor check.

This page installs MAQPNA on Azure Kubernetes Service (AKS). You create the cluster with Azure CNI powered by Cilium and Entra Workload ID, add a Pod Sandboxing node pool for the sandboxes, federate an identity with the gateway, and install the chart with the values-aks.yaml profile on top of the production profile.

The generic steps (Secrets, values, OIDC, the PostgreSQL state backend) are in Production install with Helm. This page adds what is specific to AKS.

What you get#

Trust tier Runtime on AKS Node pool RuntimeClass
tier-0 Kata VM isolation (Pod Sandboxing, Cloud Hypervisor) Azure Linux pool with --workload-runtime KataVmIsolation kata-vm-isolation (created by AKS)
tier-1 (microVM) the same the same kata-vm-isolation
tier-2 (confidential VM) not created — —

gVisor is not a supported AKS runtime, so tier-0 also runs in a Kata VM: stronger isolation, a little more overhead. The profile turns off all three of the chart's own RuntimeClasses because AKS creates kata-vm-isolation itself.

Plan for the Pod Sandboxing limits: each pod VM gets memory equal to the pod's memory limit and whole vCPUs, so set limits in Agent.spec.resources. Arm64, FIPS and Trusted Launch pools cannot run Kata. Defender for Containers does not assess Kata pods; the MAQPNA audit ledger still records every call they make.

flowchart LR
  subgraph AKS["AKS cluster (one region)"]
    subgraph SYS["System pool"]
      OP[operator]
      GW["gateway x3"]
      ID[identity broker]
    end
    subgraph SB["Pod Sandboxing pool"]
      S0["tier-0 / tier-1 sandboxes<br/>kata-vm-isolation"]
    end
  end
  S0 -->|"Cilium default-deny"| GW
  GW -->|"Entra Workload ID"| BLOB[("Blob immutable storage<br/>audit ledger")]
  GW -->|"Entra Workload ID"| KV["Key Vault / Managed HSM<br/>checkpoint key"]
  GW --> PG[("Azure Database<br/>for PostgreSQL")]
  GW --> SEN["Microsoft Sentinel<br/>(optional SIEM)"]
  ACR[("ACR mirror")] -.-> SYS

Before you begin#

  • Azure CLI 2.60 or later (add aks-preview with az extension add --name aks-preview if your region still has the features in preview), kubectl, helm 3.14 or later.
  • The maqpna CLI and its maqpna-install plugin, version 2.1.0: curl -fsSL https://maqpna.com/install.sh | sh.
  • REGISTRY set to the release registry listed on maqpna.com/download.
RG=maqpna-eu LOC=germanywestcentral CLUSTER=maqpna V=2.1.0
export REGISTRY=<release-registry-from-maqpna.com/download>
az group create -n "$RG" -l "$LOC"

Step 1: Create the cluster#

Azure CNI powered by Cilium enforces NetworkPolicy; Advanced Container Networking Services (ACNS) adds FQDN policies. The OIDC issuer and workload identity let the gateway get Entra tokens without a secret.

az aks create -g "$RG" -n "$CLUSTER" -l "$LOC" \
  --os-sku AzureLinux \
  --network-plugin azure --network-plugin-mode overlay --network-dataplane cilium \
  --enable-acns \
  --node-count 3 --node-vm-size Standard_D4ds_v5 \
  --enable-oidc-issuer --enable-workload-identity \
  --generate-ssh-keys

The system pool runs the MAQPNA control plane: operator, gateway, identity broker and, if enabled, the attestation service.

Step 2: Add the Pod Sandboxing node pool#

Use a Gen2 size with nested virtualization, such as Dsv5, Ddsv5 or Dadsv7:

az aks nodepool add -g "$RG" --cluster-name "$CLUSTER" -n sandbox \
  --os-sku AzureLinux --workload-runtime KataVmIsolation \
  --node-vm-size Standard_D8ds_v5 --node-count 2 \
  --labels maqpna.com/pool=sandbox

az aks get-credentials -g "$RG" -n "$CLUSTER"
kubectl get runtimeclass      # expect kata-vm-isolation

Step 3: Federate an identity with the gateway#

Create a user-assigned managed identity and a federated credential for the gateway's ServiceAccount:

az identity create -g "$RG" -n maqpna-gateway
az identity federated-credential create --name maqpna-gateway \
  --identity-name maqpna-gateway --resource-group "$RG" \
  --issuer "$(az aks show -g "$RG" -n "$CLUSTER" --query oidcIssuerProfile.issuerUrl -o tsv)" \
  --subject system:serviceaccount:maqpna-system:maqpna-gateway \
  --audiences api://AzureADTokenExchange
CLIENT_ID=$(az identity show -g "$RG" -n maqpna-gateway --query clientId -o tsv)

Grant it Key Vault Crypto User on the vault that holds the checkpoint key, and Storage Blob Data Contributor on the audit container. AKS identity bindings and managed identity through IMDS are covered in Cloud identity & keys.

Step 4: Create the immutable audit container#

The gateway requires version-level immutability support on the container and checks it at startup:

az storage container-rm create --storage-account acmeaudit -n maqpna-audit --enable-vlw

Step 5: Mirror the images into ACR (optional)#

For an air-gapped or sovereignty-restricted cluster, mirror everything into a Premium ACR in the same region and verify each image first:

ACR=maqpnaeu$RANDOM
az acr create -g "$RG" -n "$ACR" --sku Premium --public-network-enabled false
az aks update -g "$RG" -n "$CLUSTER" --attach-acr "$ACR"
for img in maqpna-operator maqpna-gateway maqpna-identity maqpna-attest maqpna-attest-agent \
           maqpna-exec maqpna-egress-relay maqpna-browser maqpna-code-interpreter; do
  maqpna verify image "$REGISTRY/$img:$V"
  az acr import -n "$ACR" --source "$REGISTRY/$img:$V" --image "maqpna/$img:$V"
done

Fully disconnected: see Air-gapped install.

Step 6: Install the agent-sandbox controller#

kubectl apply -f https://github.com/kubernetes-sigs/agent-sandbox/releases/download/v1.0.6/sandbox-with-extensions.yaml

Step 7: Create the Secrets and your values file#

helm pull "oci://$REGISTRY/charts/maqpna" --version "$V" --untar
grep -n CHANGE-ME maqpna/values-production.yaml maqpna/values-aks.yaml

Create the Secrets as in Production install, step 1. The identity signing key is Ed25519, which Key Vault cannot hold, so it stays a Secret you sync from your key store. Then write my-values.yaml. The client-id annotation and the pod label always go together:

gateway:
  podLabels:
    azure.workload.identity/use: "true"
  serviceAccount:
    annotations:
      azure.workload.identity/client-id: "<CLIENT_ID>"
audit:
  sink: worm
  worm:
    provider: azure
    endpoint: https://acmeaudit.blob.core.windows.net
    bucket: maqpna-audit                  # the container
    credentials: azure                    # keyless, no Secret
  checkpointSigning:
    enabled: true
    keyURI: kms://azure/<vault>/maqpna-audit   # EC-HSM P-256 key, signs ES256
networkPolicy:
  agentNamespaces:
    names: [team-a]
# With an ACR mirror:
# image:
#   registry: <ACR>.azurecr.io/maqpna

Key Vault and Managed HSM have no Ed25519, so checkpoints, evidence packs and usage reports are signed ES256; every verifier accepts it. For a sovereign installation, use --profile sovereign-eu in step 8 and set airgap.imageRegistry and sovereignty.allowedRegistries to the ACR.

Step 8: Validate, preflight and install#

maqpna values validate --chart ./maqpna --profile production \
  -f maqpna/values-production.yaml -f maqpna/values-aks.yaml -f my-values.yaml

maqpna preflight --chart ./maqpna --profile prod -f maqpna/values-aks.yaml -f my-values.yaml --strict

maqpna install --chart ./maqpna --profile prod -f maqpna/values-aks.yaml -f my-values.yaml \
  --license maqpna-license.jws --wait --timeout 15m

Preflight finds the cilium DaemonSet in kube-system, so cni-networkpolicy passes. Because the profile turns off the chart's RuntimeClasses, preflight has no runtimeclass-* check here; maqpna doctor checks runtimeclass-per-tier after the install.

Step 9: Expose the gateway#

The chart creates a ClusterIP Service and no Ingress. Keep it, and add your own Service for an internal Azure load balancer (with gateway.service.type: LoadBalancer the chart drops the admin port from its Service):

apiVersion: v1
kind: Service
metadata:
  name: maqpna-gateway-lb
  namespace: maqpna-system
  annotations:
    service.beta.kubernetes.io/azure-load-balancer-internal: "true"
spec:
  type: LoadBalancer
  selector:
    app.kubernetes.io/name: maqpna-gateway
  ports:
    - {name: http, port: 443, targetPort: 8080, protocol: TCP}

Add a NetworkPolicy that admits the load balancer's traffic on port 8080, and pick a TLS option from Ingress and TLS. For FQDN egress filtering of the gateway with ACNS, set networkPolicy.fqdnEgress.enabled: true (provider cilium) after listing your hosts in sovereignty.allowedEgressHosts (FQDN egress policies).

Step 10: Verify#

kubectl -n maqpna-system get pods
kubectl get trusttiers
helm test maqpna -n maqpna-system
kubectl -n maqpna-system port-forward svc/maqpna-gateway 9090:9090 &
maqpna doctor -n maqpna-system --gateway http://127.0.0.1:9090 --oidc-token-file admin.tok --strict

Start a tier-0 session and confirm the pod landed on the sandbox pool with the Kata runtime:

kubectl get pods -A -o custom-columns=NS:.metadata.namespace,POD:.metadata.name,RC:.spec.runtimeClassName,NODE:.spec.nodeName

To prove keyless credentials, the immutability check and egress blocking end to end, run hack/cloud-smoke.sh aks from the MAQPNA source.

Troubleshooting#

Symptom Cause Fix
kubectl get runtimeclass shows no kata-vm-isolation The sandbox pool was created without --workload-runtime KataVmIsolation, or on an unsupported size Recreate the pool on Azure Linux with a Gen2 size that supports nested virtualization.
Agent process OOMKilled in the sandbox Pod VMs get exactly the pod's memory limit Set realistic limits in Agent.spec.resources.
tier-0 or tier-1 sessions Pending No node in the Kata pool, or the pool is full kubectl get nodes -l maqpna.com/pool=sandbox; scale the pool.
Gateway fails at startup naming immutability The container has no version-level immutability support Create it with --enable-vlw (or the account with --enable-alw).
Blob or Key Vault calls return 401 or 403 Pod label or annotation missing, or the federated subject does not match Set both azure.workload.identity/use and client-id; the subject must be system:serviceaccount:maqpna-system:maqpna-gateway.
doctor: runtimeclass-per-tier fails A TrustTier names a RuntimeClass that does not exist Check kubectl get trusttiers against kubectl get runtimeclass.
Images fail to pull from ACR ACR not attached or private endpoint not reachable az aks check-acr -g "$RG" -n "$CLUSTER" --acr "$ACR.azurecr.io".

More: Troubleshooting.

Next steps#