AKS namespace guardrails ResourceQuota, LimitRange defaults, and a Rust census
The quota meters the namespace. The LimitRange fills in the pod. A ceiling with no default becomes the default.
<!-- hal:authoritative:yaml -->
The quota meters the namespace. The LimitRange fills in the pod. A ceiling with no default becomes the default.
§I. Frame
The last three K8s Ops ran GKE, EKS, GKE: Binary Authorization on 10-03, Fargate profiles on 09-30, surge upgrades on 09-27. AKS last appeared on 09-24 (Azure Disk CSI) and 09-12 (Pod Security Admission labels). Today returns to AKS.
The referent is the tenant namespace on a shared AKS cluster. Each tenant gets a ResourceQuota and a LimitRange. AKS managed namespaces put the quota under ARM. Nothing in that ARM object writes per-container defaults.
The problem for today: declare one managed namespace with a quota, add the LimitRange it lacks, then build a read-only kube-rs census that finds namespaces with no quota, no defaults, a ceiling standing in for a default, or usage within 10% of a hard limit.
§II. Meter, Filler, Ceiling
Meter, Filler, Ceiling (named technique). Name which object does each job before you read a rejection.
- Meter: ResourceQuota. It sums requests and limits across non-terminal pods in one namespace. If a quota lists
cpuormemorykeys, every new pod must state those values, or admission refuses it with403 Forbidden. Existing pods are untouched when the quota changes. - **Filler: LimitRange
defaultRequestanddefault.** The LimitRanger admission plugin writes these into containers that omit them, at create time only. A pod created before the LimitRange keeps its old spec. - **Ceiling: LimitRange
maxandmin.** These bound each container and reject outliers.
Ceiling Becomes Default (named technique). If a Container LimitRange sets max and leaves default empty, the API server copies max into default, then copies default into defaultRequest. On the local k3s this fire, a LimitRange written with only max: {cpu: "1", memory: 1Gi} read back with all three equal. A Deployment with no resources then asked for a full CPU per pod and its ReplicaSet logged exceeded quota. A ceiling-only LimitRange is a default you did not choose.
The AKS owner. az aks namespace add and the ARM type managedClusters/managedNamespaces create the namespace with defaultResourceQuota (cpuRequest, cpuLimit, memoryRequest, memoryLimit), a default network policy, labels, an adoptionPolicy, and a deletePolicy. Microsoft's role table says Azure Kubernetes Service RBAC Admin cannot write resource quota or the namespace itself, so a tenant admin cannot raise their own meter. The docs list no LimitRange field. Add it yourself.
Two warnings from the same page. Adopting an existing namespace with a quota below current requests blocks new pods and scale-ups. deletePolicy: Delete removes every object in the namespace with the ARM resource.
§III. The declarations
The managed namespace in Terraform through azapi (terraform validate clean on 1.14.3 with azapi v2.13.0, not applied):
resource "azapi_resource" "tenant_payments" {
type = "Microsoft.ContainerService/managedClusters/managedNamespaces@2025-09-01"
parent_id = var.cluster_id
name = "tenant-payments"
location = var.location
body = {
properties = {
defaultResourceQuota = {
cpuRequest = "2000m"
cpuLimit = "4000m"
memoryRequest = "4Gi"
memoryLimit = "8Gi"
}
defaultNetworkPolicy = { ingress = "AllowSameNamespace", egress = "AllowAll" }
adoptionPolicy = "Never"
deletePolicy = "Keep"
}
}
}
The LimitRange that every tenant needs, managed namespace or not (server dry-run accepted on local k3s v1.34.12):
apiVersion: v1
kind: LimitRange
metadata:
name: tenant-defaults
namespace: tenant-payments
spec:
limits:
- type: Container
defaultRequest: {cpu: 250m, memory: 256Mi}
default: {cpu: 500m, memory: 512Mi}
max: {cpu: "1", memory: 2Gi}
tenant-guardrails.yaml in the bundle also carries a plain ResourceQuota for clusters that do not use managed namespaces. aks-managed-namespace.tf holds the full module.
§IV. The Rust census
Automation is a cargo script with kube-rs. It lists Namespaces, ResourceQuotas, and LimitRanges once each with Api::all, groups the last two by namespace, and skips kube-* plus three add-on namespaces. Flags:
no_quota: no ResourceQuota in the namespace.no_limitrange_defaults: no Container item withdefaultRequestfor both cpu and memory.default_is_max: a storeddefaultRequestequalsmax. That is Ceiling Becomes Default caught after the fact.near_hard: any quota key withusedat or above 90% ofhard.
The quantity parser in the script is a few lines and handles the common suffixes. The paired Dev lesson builds the typed version.
Checked on the lab Mac. cargo check exit 0 (kube 4.2.0, k8s-openapi 0.28.0 feature v1_34). Real run against a throwaway local k3s, not AKS:
default ATTENTION no_quota,no_limitrange_defaults
tenant-a ok
tenant-b ATTENTION no_quota,no_limitrange_defaults
tenant-c ATTENTION near_hard=compute-quota/requests.cpu:90%,default_is_max=ceiling-only/cpu,default_is_max=ceiling-only/memory
tenant-d ATTENTION no_limitrange_defaults
summary namespaces=5 attention=4
Illustrative AKS output (no AKS cluster or Azure credentials as of this fire):
tenant-payments ok
tenant-legacy ATTENTION no_quota,no_limitrange_defaults
summary namespaces=2 attention=1
§V. How to run
cargo +nightly -Zscript ./aks-namespace-guardrail-census.rs
Point kubeconfig at the cluster first (az aks get-credentials for AKS). The script reads three lists and exits 2 when any namespace needs attention, so CI can gate on it. It never creates, patches, or deletes. Nix wrapper: aks-namespace-guardrail-census.nix (writeBashBin), built on the lab Mac this fire.
§VI. Close
Meter, Filler, Ceiling: the quota meters the namespace, LimitRange defaults fill each container at admission, and max bounds it. On AKS the meter can live in ARM through a managed namespace. The filler stays a Kubernetes object you own. The census reads all three and names the gap.
Paired Dev parses 500m and 1Gi into a typed Quantity and sums pod requests against the quota. Paired Cert walks the admission refusal and the LimitRange that admits the same pod.
Related
- Tome: Poulton, The Kubernetes Book ch5 p.55 (
kubectl describe nsquota and LimitRange lines) (grounded-in) - Tome: Poulton ch15 pp.205-206 (pods quota as DoS limit) (referenced)
- Prior K8s Ops: GKE Binary Authorization 10-03 · EKS Fargate 09-30 · GKE surge 09-27 · AKS PSA 09-12
- Web: AKS managed namespaces overview and REST reference · kubernetes.io Resource Quotas · Limit Ranges