
Azure Kubernetes
- 310k installs
- 1.3k repo stars
- Updated July 26, 2026
- microsoft/azure-skills
Azure Kubernetes Service (AKS) skill guides planning, provisioning, and configuring production-ready Kubernetes clusters on Azure with best-practice networking, security, and operations.
About
Plan, provision, and configure production-ready Azure Kubernetes Service (AKS) clusters. Distinguish Day-0 decisions (networking, API server) from Day-1 features, choose cluster SKU, and implement security, observability, and upgrade strategies.
- Plan and create production-ready AKS clusters with Day-0 and Day-1 decision guidance
- Cluster SKU selection (Automatic vs Standard), networking (CNI Overlay, private API), security, and operations
- Cost optimization, upgrade strategy, and observability configuration
Azure Kubernetes by the numbers
- 310,240 all-time installs (skills.sh)
- +20,301 installs in the week ending Jul 28, 2026 (Skillselion tracking)
- Ranked #14 of 1,041 Cloud & Infrastructure skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Jul 28, 2026 (Skillselion catalog sync)
azure-kubernetes capabilities & compatibility
- Capabilities
- cluster planning · networking design · security config · cost optimization
- Works with
- azure · kubernetes · docker
What azure-kubernetes says it does
Plan, create, and configure production-ready Azure Kubernetes Service (AKS) clusters.
npx skills add https://github.com/microsoft/azure-skills --skill azure-kubernetesAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 310k |
|---|---|
| repo stars | ★ 1.3k |
| Security audit | 3 / 3 scanners passed |
| Last updated | July 26, 2026 |
| Repository | microsoft/azure-skills ↗ |
How do you fix Kubernetes YAML for AKS Automatic?
Plan and configure enterprise-ready AKS clusters with production best practices for networking, security, upgrades, and cost management.
Who is it for?
DevOps engineers, SREs, and architects designing and deploying enterprise AKS clusters.
Skip if: Developers on non-Azure clusters, local minikube-only workflows, or teams without AKS Automatic compatibility requirements.
When should I use this skill?
User wants to create AKS cluster, design networking, plan upgrades, configure security, or optimize costs.
What you get
Constraint-compliant Kubernetes YAML with resource requests, limits, and safeguard fixes applied.
- patched Kubernetes YAML
- AKS-compatible manifest
By the numbers
- Maps fixes to constraint-spec-v1.yaml constraint IDs
Files
AKS Automatic Readiness Assessment
AUTHORITATIVE GUIDANCE — MANDATORY COMPLIANCE
>
This skill assesses existing AKS clusters or local manifests for AKS Automatic compatibility.
For creating a new AKS Automatic cluster, use the azure-kubernetes skill instead.See constraint spec for all safeguard rules, common fixes for YAML patterns, migration guide for end-to-end steps, and MCP integration for tool details and fallback handling.
You are an AKS Automatic compatibility assessment agent. Your job is to evaluate whether Kubernetes workloads and cluster configurations are compatible with AKS Automatic, identify issues, and help users fix them.
AKS Automatic enforces Deployment Safeguards (21 active policies, some deny, some warn only), Pod Security Standards (Baseline mandatory, Restricted optional), 2 active webhook mutators that auto-fix certain fields at admission (resource-requests defaults and anti-affinity/topology-spread), and 23 cluster-level configuration requirements.
Quick Reference
| Property | Value |
|---|---|
| Best for | AKS Automatic migration readiness and manifest validation |
| MCP Tools | mcp_azure_mcp_aks |
| Related skills | azure-kubernetes (cluster creation), azure-diagnostics (live troubleshooting), azure-validate (readiness checks) |
When to Use This Skill
- "Can I migrate to AKS Automatic?"
- "Check my cluster readiness for Automatic"
- "Validate manifests against AKS Automatic constraints"
- "Fix my deployment for Automatic compatibility"
- "Identify AKS Automatic migration blockers"
- Any mention of AKS Automatic + (migration | readiness | compatibility | assessment | validation)
Routing Rules
Route to azure-kubernetes instead:
- "Create an AKS cluster" / "What are AKS best practices?" / "How do I deploy to AKS?"
- General cluster creation, configuration, scaling, or AKS operations
Route to azure-diagnostics instead:
- "My pod is crashing" / "Debug my AKS cluster" / "Why is my deployment failing?"
- Live troubleshooting, debugging, error diagnosis on a running cluster
Guardrails — READ FIRST
1. Read-only: NEVER modify cluster state. Assessment is read-only. Do not run kubectl apply, az aks update, or any command that changes the cluster. 2. No secrets: Do NOT transmit, display, or include in diffs: Secret data values, ConfigMap data values, environment variable values from valueFrom.secretKeyRef, service account tokens, or connection strings. 3. User approval for file changes: Present every fix as a diff. The user must explicitly accept before you write to any file. 4. Scope boundaries: Route cluster creation/deletion questions → azure-kubernetes skill. Route live troubleshooting → azure-diagnostics skill.
MCP Tools
| Tool | Purpose | Key Parameters |
|---|---|---|
mcp_azure_mcp_aks | AKS MCP entry point — call discover first, then use the assessment action name returned in the response | subscriptionId, resourceGroupName, resourceName, scope |
Workflow
Step 1: Determine Scope
Ask the user what they want to assess:
Option A — Cluster-connected assessment (via AKS MCP) Use when the user has a connected cluster context (subscription + resource group + cluster name).
Option B — Offline manifest validation Use when the user has local Kubernetes manifests, Helm charts, or Kustomize overlays in their workspace. Search for files containing apiVersion: and kind: matching Deployment, StatefulSet, DaemonSet, Job, CronJob, Pod, Service, PodDisruptionBudget, or StorageClass. For Helm charts, look for Chart.yaml and rendered templates under templates/.
Option C — Single manifest check If the user pastes or points to a single YAML manifest, validate it directly without asking for scope.
Step 2: Run Assessment
Cluster-Connected Mode
Call the AKS MCP tool — this is the preferred path. Always call discover first to get the available actions, then use the assessment action name returned in the response:
// Step 1: Discover available actions
mcp_azure_mcp_aks({ action: "discover" })
// Step 2: Use the assessment action name from the discover response
mcp_azure_mcp_aks({
action: "<action-from-discover>",
subscriptionId: "<subscription-id>",
resourceGroupName: "<resource-group>",
resourceName: "<cluster-name>",
scope: {
excludeNamespaces: ["kube-system", "gatekeeper-system"],
workloadTypes: ["Deployment", "StatefulSet", "DaemonSet", "CronJob", "Job"]
}
})Required permissions:
Microsoft.ContainerService/managedClusters/readMicrosoft.ContainerService/managedClusters/listClusterUserCredential/action
For large clusters (500+ workloads), the API may return HTTP 202 with a Location header. Poll the location URL using the Retry-After interval until a 200 response is received.
Parsing the MCP response: 1. `summary` — aggregate counts: compatible, requiresChanges, incompatible, autoFixed, totalWorkloads, clusterConfigIssues 2. `clusterConfiguration` — cluster-level issues with constraintId, severity, remediation (az CLI commands), and documentationUrl 3. `workloads[]` — per-workload array, each with name, namespace, kind, overallStatus, and issues[]
Each issue in workloads[].issues[] contains: constraintId, severity (incompatible/requiresChanges/autoFixed/informational), description, field (JSON Pointer), suggestedPatch (JSON Patch for deterministic fixes), remediationGuide (for LLM-reasoned fixes).
Fallback Chain
1. MCP tool (mcp_azure_mcp_aks) → preferred, live cluster data
↓ fails (tool not found — Azure MCP server not configured)
2. Offline validation → works on local manifests without any clusterIf mcp_azure_mcp_aks is not available, inform the user:
"The Azure MCP server is not configured in your editor. To enable live cluster assessment, follow the setup guide at aka.ms/azure-mcp-setup. For now, I can validate your local manifests offline."
Then proceed to offline mode.
Offline Mode
Load the constraint spec from references/constraint-spec-v1.yaml and evaluate each manifest. The check field tells you what to check for and what fields to check. The fix field will tell you any allowed values and possible fixes. You should evaluate each of the safeguards with each of the manifests to determine if the manifests are compatible. Suggest any fixes that are needed.
Key Checks: Per container (containers, initContainers, ephemeralContainers):
- Resource requests/limits →
safeguard-container-resource-requests - Readiness and liveness probes →
safeguard-probes-configured(warning-only — not blocked at admission; treat as informational) - Image tag not
:latest→safeguard-images-no-latest securityContext.privilegednot true →safeguard-no-privileged-containerscapabilities.addonly adds allowed capabilities →safeguard-container-capabilitiesseccompProfileis RuntimeDefault/Localhost →safeguard-allowed-seccomp-profiles- no
hostfield in any container probes and lifecycle hooks →safeguard-host-probes
Per pod spec:
hostPID/hostIPCnot true →safeguard-block-host-namespaces(incompatible)hostNetwork/hostPortnot true →safeguard-host-network-ports(incompatible)- No
hostPathvolumes →safeguard-no-host-path-volumes(incompatible)
Per workload type:
- Deployments/StatefulSets with replicas > 1: podAntiAffinity or topologySpreadConstraints →
safeguard-pod-enforce-antiaffinity - StorageClass: CSI provisioner (not in-tree) →
safeguard-csi-driver-storage-class
Severity Classification
| Severity | Meaning | Action |
|---|---|---|
incompatible | Fundamental architecture issue; cannot run on Automatic without redesign | Must fix before migration — flag prominently |
requiresChanges | Manifest changes needed; will be denied at admission | Generate fix diffs |
autoFixed | AKS Automatic will mutate this at admission; no user action needed | Informational — show what will change |
informational | No enforcement | Mention briefly |
Step 3: Present Findings
Always start with the summary:
## AKS Automatic Readiness Assessment
| Status | Count |
|--------|-------|
| ✅ Compatible | X workloads |
| ⚠️ Requires changes | Y workloads |
| ❌ Incompatible | Z workloads |
| 🔧 Auto-fixed by Automatic | W workloads |
| 🏗️ Cluster config issues | N issues |Grouping: ≤ 10 issues → list individually; > 10 → group by constraint ID. Always show incompatible first (migration blockers), then requiresChanges, then autoFixed, then cluster config.
Per-issue format:
### ❌ [constraint-id] — Short description
**Severity:** incompatible | requiresChanges
**Affected:** namespace/resource-name (Kind)
**Current:** <what the manifest has>
**Required:** <what AKS Automatic requires>
**Fix:** <remediation summary>
**Docs:** <documentation URL>Step 4: Offer Fixes
Deterministic fixes (have suggestedPatch — generate YAML diff directly):
safeguard-container-resource-requests— addresources.requestssafeguard-container-capabilities— removecapabilities.addsafeguard-allowed-seccomp-profiles— patch only whenseccompProfile.type: Unconfinedis present, or when the MCPsuggestedPatchexplicitly requires a seccomp changesafeguard-enforce-apparmor— add AppArmor annotationsafeguard-csi-driver-storage-class— replace in-tree provisioner
Use patterns in references/common-fixes.md and generate a before/after diff. Starting resource values use safe defaults — VPA (enabled on Automatic) will auto-tune after deployment.
LLM-reasoned fixes (require app context; use remediationGuide):
safeguard-images-no-latest— correct tag is user- and release-specific; ask the user: _"What specific version tag or SHA digest should I pin this image to?"_ Do not guesssafeguard-pod-enforce-antiaffinity— needs app labels for selectorsafeguard-no-host-path-volumes— replacement depends on what hostPath is used forsafeguard-block-host-namespaces— may require architecture redesignsafeguard-host-network-ports— needs alternative networking approach
For incompatible findings (e.g., hostPath volumes), explain the issue and propose alternatives. For log-collection hostPath, suggest: Azure Monitor Container Insights (recommended, auto-enabled), Azure Files CSI volume, emptyDir, or sidecar pattern.
Fix application flow: 1. Generate the fix as a YAML diff 2. Show the diff with explanation 3. Wait for explicit approval: "apply", "edit", or "skip" 4. On approval, apply the change to the file 5. Move to the next finding
If the user says "fix all" or "apply all deterministic fixes", first generate a single combined diff containing all eligible suggestedPatch-based fixes, show that combined diff with an explanation, and wait for one explicit approval before applying any writes. After approval, apply the batched changes and then suggest re-validation.
Step 5: Recommend Next Steps
All issues resolved (or only autoFixed remaining):
Your workloads are ready for AKS Automatic! Next steps:
1. Review auto-fixed items — AKS Automatic will mutate N fields at admission.
2. Apply cluster configuration changes (see cluster config issues above).
3. Perform the SKU switch — follow the migration guide.
4. Verify — after migration, check all workloads are running and healthy.See references/migration-guide-summary.md for the full migration checklist.
Incompatible findings remain: List blockers and offer three options: redesign workloads, keep on a separate AKS Standard cluster, or use Automatic for compatible + Standard for incompatible workloads.
Cluster config issues remain (Day-0 decisions): API Server VNet Integration, node pool OS SKU (requires recreating system node pools), and ephemeral OS disks require a new cluster — redirect to azure-kubernetes skill for cluster creation help.
Error Handling
| Error / Symptom | Likely Cause | Remediation |
|---|---|---|
| MCP tool call fails or times out | Invalid credentials or subscription context | Verify az login, confirm active subscription with az account show; if MCP remains unavailable, continue with offline validation using local or exported manifests and the bundled constraint spec |
| HTTP 403 on assessment action | Missing permission | Ensure caller has sufficient RBAC access to read and assess the cluster via AKS APIs |
| API returns HTTP 202 | Large cluster (500+ workloads) — async operation | Poll the Location header URL using Retry-After interval |
| Helm chart uses Go templating — cannot evaluate | Template values not resolved | Ask user for rendered output (helm template) or values files |
| Constraint spec version mismatch | Skill bundles spec v1.1.1 (2026-03-15) | Note version in output; recommend re-running after spec update |
Reference Files
| File | When to load |
|---|---|
references/constraint-spec-v1.yaml | Always load for offline validation — all constraint IDs, severities, and fix patterns |
references/common-fixes.md | When generating deterministic fixes — before/after YAML patterns |
references/migration-guide-summary.md | When user asks about migration steps or after assessment is complete |
references/mcp-integration.md | When troubleshooting MCP tool calls or debugging the fallback chain |
⚠️ Warning: This skill bundles constraint spec v1.1.1 (2026-03-15), covering 23 cluster-level constraints, 21 active Deployment Safeguards policies (9 best practices policies, 12 Pod Security Standards policies), and 2 active mutators. Always note the spec version in assessment output.
Common Fix Patterns for AKS Automatic Compatibility
Loaded on demand when generating YAML fixes during assessment. Maps to constraint IDs in constraint-spec-v1.yaml.
---
safeguard-container-resource-requests — Add resource requests/limits
Before:
containers:
- name: web
image: myapp:v1.0.0After:
containers:
- name: web
image: myapp:v1.0.0
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"💡 Tip: Use safe minimums as starting values. VPA (auto-enabled on AKS Automatic) will tune these after deployment based on actual usage.
---
safeguard-container-capabilities — Drop all capabilities
Before:
securityContext:
capabilities:
add: ["NET_ADMIN"]After:
securityContext:
capabilities:
drop: ["ALL"]⚠️ Warning: If the app genuinely requires NET_ADMIN or similar, it is incompatible with AKS Automatic. Do not silently drop — explain the incompatibility and suggest redesign.---
safeguard-allowed-seccomp-profiles — Add seccomp profile
Before:
spec:
containers:
- name: webAfter:
spec:
securityContext:
seccompProfile:
type: RuntimeDefault
containers:
- name: web---
safeguard-allowed-seccomp-profiles — Remove 'Unconfined' seccomp profile
Before:
spec:
securityContext:
seccompProfile:
type: Unconfined
containers:
- name: webAfter:
spec:
containers:
- name: web---
safeguard-enforce-apparmor — Add AppArmor annotation
Before:
metadata:
name: my-deploymentAfter:
metadata:
name: my-deployment
annotations:
container.apparmor.security.beta.kubernetes.io/web: runtime/default💡 Tip: Replace web with the actual container name. Add one annotation per container.---
safeguard-images-no-latest — Pin image tag (LLM-reasoned — ask user)
Before:
image: myapp:latestAfter:
image: myapp:v1.2.3 # ← version confirmed with user⚠️ Warning: Do not guess the version. Ask the user: _"What specific version tag or SHA digest should I pin this image to?"_ If from a public registry, suggest checking Docker Hub or the registry for the latest stable tag.
---
safeguard-probes-configured — Add probes (best-practice recommendation — warning-only, not blocked at admission)
HTTP app (most common):
readinessProbe:
httpGet:
path: /healthz # ← ask user for their health endpoint
port: 8080 # ← ask user for port
initialDelaySeconds: 5
periodSeconds: 10
failureThreshold: 3
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 15
periodSeconds: 20
failureThreshold: 3TCP-only app (databases, Redis, etc.):
readinessProbe:
tcpSocket:
port: 6379 # ← service port
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
tcpSocket:
port: 6379
initialDelaySeconds: 15
periodSeconds: 20gRPC app:
readinessProbe:
grpc:
port: 50051
initialDelaySeconds: 5
periodSeconds: 10---
safeguard-host-probes — Remove host field in probes and lifecycle hooks
Before:
spec:
containers:
- name: my-container
image: nginx:v1.2.3
livenessProbe:
httpGet:
host: "my-host"
path: /healthz
port: 8080
initialDelaySeconds: 15
periodSeconds: 20
failureThreshold: 3After: Remove the host field Example:
spec:
containers:
- name: my-container
image: nginx:v1.2.3
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 15
periodSeconds: 20
failureThreshold: 3---
safeguard-pod-enforce-antiaffinity — Add topology spread (LLM-reasoned — ask user for label)
Ask user: _"What label key/value identifies your workload's pods?"_
spec:
template:
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: <app-label> # ← from user
containers:
- name: web---
safeguard-csi-driver-storage-class — Migrate in-tree to CSI
Before (Azure Disk in-tree):
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-storage
provisioner: kubernetes.io/azure-disk
parameters:
skuName: Premium_LRS
reclaimPolicy: Delete
volumeBindingMode: ImmediateAfter (Azure Disk CSI):
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-storage
provisioner: disk.csi.azure.com
parameters:
skuName: Premium_LRS
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer # ← preferred for zonal disks| In-tree provisioner | CSI replacement |
|---|---|
kubernetes.io/azure-disk | disk.csi.azure.com |
kubernetes.io/azure-file | file.csi.azure.com |
---
PodDisruptionBudget — Add missing PDB
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: <app-name>-pdb
namespace: <namespace>
spec:
maxUnavailable: 1
selector:
matchLabels:
app: <app-label>PodDisruptionBudget — Fix blocking maxUnavailable: 0
Before:
spec:
maxUnavailable: 0After:
spec:
maxUnavailable: 1⚠️ Warning: maxUnavailable: 0 completely blocks node drain during AKS Automatic upgrades. At least 1 pod must be allowed unavailable for upgrades to proceed.---
safeguard-no-host-path-volumes — Replace hostPath (incompatible — suggest alternatives)
| hostPath use case | Recommended replacement |
|---|---|
Log collection (/var/log) | Azure Monitor Container Insights (auto-enabled on AKS Automatic) |
Container runtime socket (/var/run/docker.sock) | Use the AKS Automatic node observability features — direct socket access not supported |
| Shared config files | configMap volume |
| Secrets / credentials | Kubernetes secret volume or Azure Key Vault CSI Driver |
| Ephemeral scratch space | emptyDir volume |
| Persistent app data | Azure Disk CSI via PVC (disk.csi.azure.com) |
| Shared file storage across pods | Azure Files CSI via PVC (file.csi.azure.com) |
emptyDir example:
volumes:
- name: scratch
emptyDir: {}Azure Files CSI PVC example:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: logs-pvc
spec:
accessModes:
- ReadWriteMany
storageClassName: azurefile-csi
resources:
requests:
storage: 10Gi# AKS Automatic Compatibility Constraint Spec — Condensed Reference
# Version: 1.1.1 | AKS: 2026-03-15
# This condensed version is optimized for LLM context.
apiVersion: aks-automatic.azure.com/v1
kind: ConstraintSpecReference
metadata:
version: "1.1.1"
aksVersion: "2026-03-15"
policyInitiatives:
deploymentSafeguards: c047ea8e-9c78-49b2-958b-37e56d291a44
podSecurityBaseline: a8640138-9b0a-4a28-b8cb-1666c838647d
podSecurityRestricted: 42b8ef37-b724-4e24-bbc8-7a7708edfe00
# =============================================================================
# CLUSTER CONSTRAINTS (23 total)
# =============================================================================
clusterConstraints:
# -- Addons --
- id: cluster-azure-policy-addon
severity: requiresChanges
field: addonProfiles.azurepolicy.enabled
required: true
fix: "az aks addon enable --addon azure-policy"
- id: cluster-keyvault-secrets-provider
severity: requiresChanges
field: addonProfiles.azureKeyvaultSecretsProvider.enabled
required:
enabled: true
enableSecretRotation: true
fix: "az aks addon enable --addon azure-keyvault-secrets-provider --enable-secret-rotation"
# -- Networking --
- id: cluster-api-server-vnet-integration
severity: requiresChanges
field: privateConnectProfile.enabled
required: true
fix: "az aks update --enable-apiserver-vnet-integration --apiserver-subnet-id <subnet-id>"
- id: cluster-azure-cni-overlay-cilium
severity: requiresChanges
field: networkPlugin/networkPluginMode/networkPolicy/ebpfDataplane
required: azure/overlay/cilium/cilium
fix: |
Step 1: az aks update --network-plugin-mode overlay --pod-cidr 192.168.0.0/16
Step 2: az aks update --network-dataplane cilium
Note: Irreversible. Disable NAP before Cilium update.
- id: cluster-standard-load-balancer
severity: requiresChanges
field: loadBalancerSku
required: standard
fix: "az aks update --load-balancer-sku standard (in-place upgrade from Basic supported)"
- id: cluster-nat-gateway-managed-vnet
severity: requiresChanges
condition: AKS-managed VNet only
field: outboundType
required: managedNATGateway
fix: "az aks update --outbound-type managedNATGateway"
# -- Upgrades --
- id: cluster-auto-upgrade
severity: requiresChanges
field: autoUpgradeProfile
required: upgradeChannel=stable, nodeOSUpgradeChannel=NodeImage
fix: "az aks update --auto-upgrade-channel stable --node-os-upgrade-channel NodeImage"
# -- Ingress --
- id: cluster-web-app-routing
severity: requiresChanges
field: ingressProfile.webAppRouting.enabled
required: true
fix: "az aks addon enable --addon web_application_routing"
# -- Identity --
- id: cluster-workload-identity-oidc
severity: requiresChanges
field: workloadIdentity.enabled + oidcProfile.enabled
required: true
fix: "az aks update --enable-oidc-issuer --enable-workload-identity"
- id: cluster-azure-rbac
severity: requiresChanges
field: aadProfile (managed + enableAzureRBAC)
required: true
fix: "az aks update --enable-aad --enable-azure-rbac"
- id: cluster-disable-local-accounts
severity: requiresChanges
field: disableLocalAccounts
required: true
fix: "az aks update --disable-local-accounts"
- id: cluster-system-assigned-managed-identity
severity: requiresChanges
condition: AKS-managed VNet only
field: identity.type
required: SystemAssigned
fix: "Day-0 decision for managed VNet clusters."
# -- Security --
- id: cluster-image-cleaner
severity: requiresChanges
field: securityProfile.imageCleaner.enabled
required: true
fix: "az aks update --enable-image-cleaner"
# -- Autoscaling --
- id: cluster-vpa
severity: requiresChanges
field: verticalPodAutoscaler
required: enabled=true, updateMode=Off
fix: "az aks update --enable-vpa"
- id: cluster-keda
severity: requiresChanges
field: keda.enabled
required: true
fix: "az aks update --enable-keda"
- id: cluster-node-auto-provisioning
severity: requiresChanges
field: nodeProvisioningProfile.mode
required: Auto
fix: "az aks update --node-provisioning-mode Auto"
# -- Governance --
- id: cluster-node-rg-readonly
severity: requiresChanges
field: nodeResourceGroupProfile.restrictionLevel
required: ReadOnly
fix: "Day-0 setting. May require new cluster."
# -- Node Pool (system pools) --
- id: pool-ephemeral-os-disk
severity: incompatible
appliesTo: system pools
field: storageProfile
required: Ephemeral
fix: "Day-0. Recreate system node pool."
- id: pool-availability-zones
severity: incompatible
appliesTo: system pools only
field: availabilityZones
required: "[1, 2, 3]"
fix: "Day-0. Recreate system pool in 3-AZ region. User pools not affected."
- id: pool-critical-addons-taint
severity: requiresChanges
appliesTo: system pools
field: taints
required: CriticalAddonsOnly=true:NoSchedule
fix: "az aks nodepool update --node-taints CriticalAddonsOnly=true:NoSchedule"
- id: pool-vmss-type
severity: incompatible
appliesTo: system pools
field: type
required: VirtualMachineScaleSets
fix: "Day-0. Recreate as VMSS."
- id: pool-azure-linux-os
severity: incompatible
appliesTo: system pools only
field: osSKU
required: AzureLinux
fix: "Day-0. Recreate system pool with --os-sku AzureLinux. User pools can use any OS."
- id: pool-ssh-disabled
severity: requiresChanges
appliesTo: all pools
field: agentPoolProfiles[*].securityProfile.sshAccess
required: Disabled
fix: "az aks nodepool update --cluster-name CLUSTER --name POOL_NAME --ssh-access disabled"
# =============================================================================
# WORKLOAD CONSTRAINTS — Deployment Safeguards (21 active policies)
# Initiative: c047ea8e | Effect: Mixed(Deny/Warn/Mutate) on Automatic
# =============================================================================
safeguards:
# -- AKS Best Practices (9 policies) --
- id: safeguard-restricted-node-edits
policyId: 53a4a537
severity: requiresChanges
category: nodeProtection
check: Check if a rolebinding for a service account references a role with node edit permissions. The application might try to edit node objects directly
fix: Manage node pools through the AKS API (az aks nodepool) instead of direct Node object edits
- id: safeguard-container-resource-requests
policyId: 03a4ecdb
severity: autoFixed
category: resources
check: Every container must have cpu + memory requests and limits
effect: "ResourceRequestsWorkloadMutator sets defaults cpu=500m, memory=2Gi for requests+limits; enforces minimums cpu=100m, memory=100Mi; fixes QoS if requests > limits"
- id: safeguard-pod-enforce-antiaffinity
policyId: 34c88cd4
severity: autoFixed
category: availability
check: Replicated workloads with >1 replica should have podAntiAffinity or topologySpreadConstraints
effect: "AntiAffinityTopologySpreadWorkloadMutator adds preferred anti-affinity (weight=100, hostname) + topology spread (maxSkew=1, hostname, ScheduleAnyway) if neither exists"
- id: safeguard-restricted-labels
policyId: a22123bd
severity: requiresChanges
category: labeling
check: AKS-reserved label prefixes blocked
fix: Remove/rename labels with kubernetes.azure.com/ prefix
- id: safeguard-restricted-taints
policyId: 48940d92
severity: requiresChanges
category: nodeProtection
check: AKS-reserved taint CriticalAddonsOnly key blocked for users
fix: Remove reserved taints, use custom taint keys
- id: safeguard-probes-configured
policyId: b1a9997f
severity: informational
enforcement: warn # Warning-only — deployments are admitted with a kubectl warning, not denied
category: reliability
check: Every container should have readinessProbe + livenessProbe (recommended best practice)
fix: Add probes (app-specific — HTTP/TCP/exec/gRPC) — recommended, not required for migration
- id: safeguard-csi-driver-storage-class
policyId: 4f3823b6
severity: requiresChanges
category: storage
check: StorageClass must use CSI provisioner (not in-tree)
fix: "Replace kubernetes.io/azure-disk → disk.csi.azure.com, also replace kubernetes.io/azure-file with file.csi.azure.com"
- id: safeguard-unique-service-selectors
policyId: b0fdedee
severity: requiresChanges
category: networking
check: Services must have unique selectors per namespace
fix: Deduplicate Service selectors
- id: safeguard-images-no-latest
policyId: 021f8078
severity: requiresChanges
category: imagePolicy
check: Image tag must not be :latest or untagged (no colon)
patch: "replace image tag with specific version or sha256 digest"
# -- PSS-related policies in Safeguards (12 policies) --
- id: safeguard-block-host-namespaces
policyId: 47a1ee2f
severity: incompatible
category: podSecurity
check: |
Sharing the host PID or IPC namespaces is disallowed in the Baseline policy.
Check the following fields:
- spec.hostPID
- spec.hostIPC
fix: |
The allowed values are:
- undefined/nil
- false
Remove hostPID and hostIPC; incompatible if required.
- id: safeguard-host-network-ports
policyId: 82985f06
severity: incompatible
category: podSecurity
check: |
Sharing the host network namespace is disallowed, and host ports should not be used.
Check the following fields:
- spec.hostNetwork
- spec.containers[*].ports[*].hostPort
- spec.initContainers[*].ports[*].hostPort
- spec.ephemeralContainers[*].ports[*].hostPort
fix: |
The allowed values are:
- spec.hostNetwork: undefined/nil or false
- hostPort fields: undefined/nil or 0
Use ClusterIP Services, Ingress, or internal Pod networking instead of host networking or host ports.
- id: safeguard-allowed-sysctls
policyId: 5e5a0673
severity: requiresChanges
category: podSecurity
check: |
Sysctls are limited to the Baseline safe subset.
Check the following field:
- spec.securityContext.sysctls[*].name
fix: |
The allowed values are:
- undefined/nil
- kernel.shm_rmid_forced
- net.ipv4.ip_local_port_range
- net.ipv4.ip_unprivileged_port_start
- net.ipv4.tcp_syncookies
- net.ipv4.ping_group_range
- net.ipv4.ip_local_reserved_ports
- net.ipv4.tcp_keepalive_time
- net.ipv4.tcp_fin_timeout
- net.ipv4.tcp_keepalive_intvl
- net.ipv4.tcp_keepalive_probes
Remove any sysctl not in this list.
- id: safeguard-no-host-path-volumes
policyId: 098fc59e
severity: incompatible
category: podSecurity
check: |
HostPath volumes are forbidden in the Baseline policy.
Check the following field:
- spec.volumes[*].hostPath
fix: |
The allowed values are:
- undefined/nil
Replace hostPath volumes with PVCs, ConfigMaps, Secrets, CSI-backed storage, or another non-hostPath volume type.
- id: safeguard-enforce-apparmor
policyId: 511f5417
severity: requiresChanges
category: podSecurity
check: |
On supported hosts, the Baseline policy does not allow disabling the default AppArmor profile.
Check the following fields:
- spec.securityContext.appArmorProfile.type
- spec.containers[*].securityContext.appArmorProfile.type
- spec.initContainers[*].securityContext.appArmorProfile.type
- spec.ephemeralContainers[*].securityContext.appArmorProfile.type
- metadata.annotations["container.apparmor.security.beta.kubernetes.io/*"]
fix: |
The allowed values are:
- appArmorProfile.type: undefined/nil, RuntimeDefault, or Localhost
- AppArmor annotation: undefined/nil, runtime/default, or localhost/*
Set RuntimeDefault, or use an allowed Localhost profile.
- id: safeguard-enforce-selinux
policyId: e1e6c427
severity: informational
category: podSecurity
check: |
SELinux settings are restricted to specific types, and custom user or role values are forbidden.
Check the following fields:
- spec.securityContext.seLinuxOptions.type
- spec.containers[*].securityContext.seLinuxOptions.type
- spec.initContainers[*].securityContext.seLinuxOptions.type
- spec.ephemeralContainers[*].securityContext.seLinuxOptions.type
- spec.securityContext.seLinuxOptions.user
- spec.containers[*].securityContext.seLinuxOptions.user
- spec.initContainers[*].securityContext.seLinuxOptions.user
- spec.ephemeralContainers[*].securityContext.seLinuxOptions.user
- spec.securityContext.seLinuxOptions.role
- spec.containers[*].securityContext.seLinuxOptions.role
- spec.initContainers[*].securityContext.seLinuxOptions.role
- spec.ephemeralContainers[*].securityContext.seLinuxOptions.role
fix: |
The allowed values are:
- seLinuxOptions.type: undefined/"", container_t, container_init_t, container_kvm_t, or container_engine_t
- seLinuxOptions.user: undefined/""
- seLinuxOptions.role: undefined/""
Optional hardening only: remove custom seLinuxOptions or use one of the allowed types.
- id: safeguard-windows-block-host-process
policyId: 077f0ce1
severity: incompatible
category: podSecurity
check: |
Windows Pods offer the ability to run HostProcess containers which enables privileged access to the Windows host machine. Privileged access to the host is disallowed in the Baseline policy.
Check the following fields:
- spec.securityContext.windowsOptions.hostProcess
- spec.containers[*].securityContext.windowsOptions.hostProcess
- spec.initContainers[*].securityContext.windowsOptions.hostProcess
- spec.ephemeralContainers[*].securityContext.windowsOptions.hostProcess
fix: |
The allowed values are:
- undefined/nil
- false
Remove hostProcess; incompatible if required.
- id: safeguard-no-privileged-containers
policyId: 95edb821
severity: incompatible
category: podSecurity
check: |
Privileged containers are disallowed in the Baseline policy.
Check the following fields:
- spec.containers[*].securityContext.privileged
- spec.initContainers[*].securityContext.privileged
- spec.ephemeralContainers[*].securityContext.privileged
fix: |
The allowed values are:
- undefined/nil
- false
Remove privileged mode or set privileged to false; incompatible if privileged access is required.
- id: safeguard-no-custom-proc-mount
policyId: f85eb0dd
severity: requiresChanges
category: podSecurity
check: |
Custom /proc mount types are disallowed.
Check the following fields:
- spec.containers[*].securityContext.procMount
- spec.initContainers[*].securityContext.procMount
- spec.ephemeralContainers[*].securityContext.procMount
fix: |
The allowed values are:
- undefined/nil
- Default
Remove custom procMount values or set procMount to Default.
- id: safeguard-container-capabilities
policyId: c26596ff
severity: requiresChanges
category: podSecurity
check: |
Adding capabilities is limited to the Baseline allowlist.
Check the following fields:
- spec.containers[*].securityContext.capabilities.add
- spec.initContainers[*].securityContext.capabilities.add
- spec.ephemeralContainers[*].securityContext.capabilities.add
fix: |
The allowed values are:
- undefined/nil
- AUDIT_WRITE
- CHOWN
- DAC_OVERRIDE
- FOWNER
- FSETID
- KILL
- MKNOD
- NET_BIND_SERVICE
- SETFCAP
- SETGID
- SETPCAP
- SETUID
- SYS_CHROOT
Remove any added capability outside this list.
- id: safeguard-host-probes
policyId: acdf8909
severity: requiresChanges
category: podSecurity
check: |
The host field in probes and lifecycle hooks is disallowed
Restricted fields:
- spec.containers[*].livenessProbe.httpGet.host
- spec.containers[*].readinessProbe.httpGet.host
- spec.containers[*].startupProbe.httpGet.host
- spec.containers[*].livenessProbe.tcpSocket.host
- spec.containers[*].readinessProbe.tcpSocket.host
- spec.containers[*].startupProbe.tcpSocket.host
- spec.containers[*].lifecycle.postStart.tcpSocket.host
- spec.containers[*].lifecycle.preStop.tcpSocket.host
- spec.containers[*].lifecycle.postStart.httpGet.host
- spec.containers[*].lifecycle.preStop.httpGet.host
- spec.initContainers[*].livenessProbe.httpGet.host
- spec.initContainers[*].readinessProbe.httpGet.host
- spec.initContainers[*].startupProbe.httpGet.host
- spec.initContainers[*].livenessProbe.tcpSocket.host
- spec.initContainers[*].readinessProbe.tcpSocket.host
- spec.initContainers[*].startupProbe.tcpSocket.host
- spec.initContainers[*].lifecycle.postStart.tcpSocket.host
- spec.initContainers[*].lifecycle.preStop.tcpSocket.host
- spec.initContainers[*].lifecycle.postStart.httpGet.host
- spec.initContainers[*].lifecycle.preStop.httpGet.host
fix: |
The allowed values are:
- undefined/nil
- ""
Remove the `host` field from probes and lifecycle hooks; the kubelet uses the pod IP by default.
- id: safeguard-allowed-seccomp-profiles
policyId: 975ce327
severity: requiresChanges
category: podSecurity
check: |
Seccomp must not be explicitly set to Unconfined.
Check the following fields:
- spec.securityContext.seccompProfile.type
- spec.containers[*].securityContext.seccompProfile.type
- spec.initContainers[*].securityContext.seccompProfile.type
- spec.ephemeralContainers[*].securityContext.seccompProfile.type
fix: |
The allowed values are:
- undefined/nil
- RuntimeDefault
- Localhost
Remove Unconfined, or set seccompProfile.type to RuntimeDefault or Localhost.
# =============================================================================
# WEBHOOK MUTATIONS (2 active mutators) — auto-applied at admission
# =============================================================================
mutations:
- id: mutation-anti-affinity-topology-spread
policyId: implicit
target: [Deployment, StatefulSet, ReplicaSet]
effect: "Adds preferred pod anti-affinity (weight=100, kubernetes.io/hostname) + topology spread (maxSkew=1, kubernetes.io/hostname, ScheduleAnyway). Skips if any existing anti-affinity or topology spread. Label priority: app > app.kubernetes.io/name > default-antiaffinity-applabel."
- id: mutation-resource-requests-default
policyId: implicit
target: containers
effect: "Sets resources.requests+limits defaults cpu=500m, memory=2Gi. Minimums cpu=100m, memory=100Mi. If only limits set, requests=limits. If requests > limits, requests capped at limits (QoS fix)."
MCP Integration Reference
Loaded when troubleshooting MCP tool calls, debugging the fallback chain, or understanding the API response format.
---
Tool Discovery
Always call mcp_azure_mcp_aks first to discover the current available tool surface. Do not assume a fixed action name — the available actions depend on the MCP server version deployed to the client.
mcp_azure_mcp_aks({ action: "discover" })The response lists available actions and their parameter schemas. Use the returned schema — do not hardcode parameter names.
---
Assessment Call
After calling discover, use the assessment action name returned in the response. Pass parameters according to the discovered schema — do not hardcode action names or API versions.
Typical parameters include:
subscriptionId— Azure subscription IDresourceGroupName— resource group containing the clusterresourceName— AKS cluster namescope(optional) — filter by namespaces or workload types
Example shape (use actual action name and schema from discover output):
mcp_azure_mcp_aks({
action: "<action-from-discover>",
subscriptionId: "<subscription-id>",
resourceGroupName: "<resource-group>",
resourceName: "<cluster-name>",
scope: {
excludeNamespaces: ["kube-system", "gatekeeper-system", "azure-arc"],
workloadTypes: ["Deployment", "StatefulSet", "DaemonSet", "CronJob", "Job"]
}
})All scope parameters are optional. If omitted, the API assesses all workloads excluding kube-system and gatekeeper-system.
---
Required Permissions
# Check current role assignments
az role assignment list \
--assignee $(az ad signed-in-user show --query id -o tsv) \
--scope /subscriptions/<subscription-id>/resourceGroups/<rg>/providers/Microsoft.ContainerService/managedClusters/<cluster>
# Minimum permissions required:
# - Microsoft.ContainerService/managedClusters/read
# - Microsoft.ContainerService/managedClusters/listClusterUserCredential/action
# Assign if missing (requires Owner or User Access Administrator)
az role assignment create \
--assignee <principal-id> \
--role "Azure Kubernetes Service Cluster User Role" \
--scope /subscriptions/<subscription-id>/resourceGroups/<rg>/providers/Microsoft.ContainerService/managedClusters/<cluster>---
Response Schema
The API returns three top-level sections:
summary
{
"summary": {
"totalWorkloads": 42,
"compatible": 27,
"requiresChanges": 12,
"incompatible": 3,
"autoFixed": 8,
"clusterConfigIssues": 4
}
}clusterConfiguration
{
"clusterConfiguration": [
{
"constraintId": "cluster-oidc-issuer",
"severity": "requiresChanges",
"description": "OIDC issuer not enabled",
"remediation": "az aks update --enable-oidc-issuer --resource-group <rg> --name <cluster>",
"documentationUrl": "https://learn.microsoft.com/azure/aks/..."
}
]
}workloads[]
{
"workloads": [
{
"name": "sample-app",
"namespace": "default",
"kind": "Deployment",
"overallStatus": "requiresChanges",
"issues": [
{
"constraintId": "safeguard-images-no-latest",
"severity": "requiresChanges",
"description": "Container 'web' uses :latest image tag",
"field": "/spec/containers/0/image",
"suggestedPatch": null,
"remediationGuide": "Pin the image to a specific version or SHA digest"
}
]
}
]
}---
Async Response Handling (HTTP 202 — Large Clusters)
For clusters with 500+ workloads, the API returns HTTP 202 Accepted with a Location header. Poll until complete:
// Initial call returns: { status: 202, headers: { Location: "...", "Retry-After": "30" } }
async function pollAssessment(locationUrl, retryAfterSeconds) {
while (true) {
await new Promise(r => setTimeout(r, retryAfterSeconds * 1000));
const response = await mcp_azure_mcp_aks({
action: "pollOperation",
locationUrl: locationUrl
});
if (response.status === "Succeeded") return response.result;
if (response.status === "Failed") throw new Error(response.error.message);
retryAfterSeconds = response.retryAfter ?? retryAfterSeconds;
}
}---
Fallback Chain
Attempt each step in order. Do not ask the user which is available — just try:
1. mcp_azure_mcp_aks → discover, then call the assessment action returned
↓ fails (tool not found — Azure MCP server not configured)
2. Inform user to install Azure MCP, then fall back to offline validation
kubectl get deployment,statefulset,daemonset,job,cronjob -A -o yaml > /tmp/workloads.yaml
kubectl get pdb,storageclass -A -o yaml > /tmp/policies.yamlIf mcp_azure_mcp_aks is not available, say:
"The Azure MCP server is not configured. To enable live cluster assessment, install it following aka.ms/azure-mcp-setup. For now, I can validate your local manifests offline — export them with kubectl get ... -o yaml or share your manifest files."Then proceed to offline manifest validation against constraint-spec-v1.yaml.
---
Prerequisites Verification
Run these before attempting MCP or CLI assessment:
# 1. Verify Azure login
az account show --query "{name:name, id:id, state:state}" -o table
# 2. Verify cluster exists and is accessible
az aks show \
--resource-group <rg> \
--name <cluster> \
--query "{name:name, provisioningState:provisioningState, sku:sku.name}" \
-o table
# 3. Verify kubectl context
kubectl config current-context
kubectl cluster-info// 4. Verify MCP server is reachable (Azure MCP)
// If this returns available actions, MCP is configured
mcp_azure_mcp_aks({ action: "discover" })---
Common MCP Errors
| Error | Cause | Fix |
|---|---|---|
tool not found: mcp_azure_mcp_aks | Azure MCP server not configured | Guide user to install: aka.ms/azure-mcp-setup, then fall back to offline |
HTTP 401 Unauthorized | Not logged in | az login |
HTTP 403 Forbidden | Insufficient RBAC permissions | Ensure caller has read access to the cluster via AKS APIs |
HTTP 404 Not Found | Wrong subscription, RG, or cluster name | Verify with az aks list -o table |
HTTP 202 with no Location header | API version mismatch | Ensure the MCP server version supports async polling; retry with the latest server |
| Timeout after 30s | Cluster too large (500+ workloads) | Implement async polling — see section above |
AKS Automatic Migration Guide
Loaded when user asks about migration steps or after assessment is complete.
---
Migration Checklist
Phase 1 — Assessment (this skill)
- [ ] Run the AKS Automatic compatibility assessment (via
mcp_azure_mcp_aks({ action: "discover" })then the assessment action returned, or the offline manifest scan) - [ ] Resolve all
incompatiblefindings — these are hard blockers - [ ] Apply all
requiresChangesfixes — these will be denied at admission - [ ] Review
autoFixeditems — understand what AKS Automatic will mutate at runtime - [ ] Address cluster-level Day-0 config issues (see below)
Phase 2 — Create AKS Automatic Cluster (use azure-kubernetes skill)
az aks create \
--resource-group <resource-group> \
--name <new-cluster-name> \
--sku automatic \
--location <location> \
--generate-ssh-keys💡 Tip: AKS Automatic auto-enables: OIDC issuer, workload identity, Azure CNI Overlay, NAP, VPA, Azure Monitor Container Insights, Deployment Safeguards, and Pod Security Standards (Baseline). No manual configuration needed for these.
Phase 3 — Validate on New Cluster
# Get credentials
az aks get-credentials \
--resource-group <resource-group> \
--name <new-cluster-name>
# Dry-run server-side apply — catches admission policy rejections
kubectl apply --dry-run=server -f <manifests-directory>/
# Deploy to a staging namespace first
kubectl create namespace staging
kubectl apply -f <manifests-directory>/ -n staging
# Watch pod startup
kubectl get pods -n staging -w
# Check events for admission rejections
kubectl get events -n staging --sort-by=.lastTimestamp | grep -i "denied\|error\|failed"⚠️ Keep the old cluster running for a rollback window (recommended: 48 hours minimum) while you validate workloads on the new AKS Automatic cluster.
Phase 4 — Decommission Old Cluster
# Only after confirming workloads are stable on AKS Automatic
az aks delete \
--resource-group <resource-group> \
--name <old-cluster-name> \
--yes --no-wait---
Day-0 Decisions — Cluster-Level Configuration Requirements
Some settings require creating a new cluster; others can be enabled on existing clusters. Route to azure-kubernetes skill for cluster creation.
| Requirement | AKS Automatic default | What to do |
|---|---|---|
| API Server VNet Integration | Required, auto-enabled | Requires a new cluster |
| Network plugin | Azure CNI Overlay | Requires a new cluster if currently on kubenet |
| System node pool OS | Azure Linux | Recreate system node pool (user pools unaffected) |
| OIDC Issuer | Auto-enabled | Can be enabled on existing: az aks update --enable-oidc-issuer |
| Workload Identity | Auto-enabled | Can be enabled on existing: az aks update --enable-workload-identity |
---
What AKS Automatic Auto-Enables
No manual setup needed for these — show this list when user asks "what do I get for free":
| Feature | Benefit |
|---|---|
| Node Auto Provisioning (NAP) | Replaces cluster autoscaler; right-sizes node pools automatically |
| Vertical Pod Autoscaler (VPA) | Auto-tunes resource requests after deployment |
| Azure Monitor Container Insights | Logs, metrics, and dashboards out of the box |
| Deployment Safeguards | 25 active deny policies + 2 webhook mutators at admission (resource-requests defaults + anti-affinity/topology-spread) |
| Pod Security Standards (Baseline) | Enforced cluster-wide; Restricted available opt-in |
| Managed OIDC Issuer | Required for workload identity |
| Azure Key Vault CSI Driver | Secret injection without static credentials |
| Ephemeral OS disks | Faster node provisioning by default |
| Azure Linux node OS | Smaller footprint, faster boot times |
---
Post-Migration Verification Commands
# Verify all pods running
kubectl get pods -A | grep -v Running | grep -v Completed
# Check for pods stuck in Pending (may indicate resource quota or node issues)
kubectl get pods -A --field-selector status.phase=Pending
# Check Deployment Safeguards are active
kubectl get constrainttemplate -A
# Verify VPA is running
kubectl get vpa -A
# Check NAP node pools
az aks nodepool list \
--resource-group <resource-group> \
--cluster-name <cluster-name> \
--query "[].{name:name, mode:mode, osType:osType, count:count}" \
-o table
# View Container Insights metrics
az aks show \
--resource-group <resource-group> \
--name <cluster-name> \
--query addonProfiles.omsagent.enabledAKS Cluster Autoscaler (CAS)
Enable and tune the Cluster Autoscaler to automatically scale down idle nodes.
Check CAS Status
az aks show \
--name "<CLUSTER_NAME>" --resource-group "<RESOURCE_GROUP>" \
--query "agentPoolProfiles[].{name:name, casEnabled:enableAutoScaling, min:minCount, max:maxCount, count:count}" \
-o table
az aks show \
--name "<CLUSTER_NAME>" --resource-group "<RESOURCE_GROUP>" \
--query "autoScalerProfile" -o jsonCheck Node Utilization (7 days)
Follow the metrics discovery steps in azure-aks-rightsizing.md to list available metric definitions and query node CPU utilization. Use metric names such as node_cpu_usage_percentage or cpuUsagePercentage depending on what's available on the cluster.
Enable CAS
# Cluster-level
az aks update \
--name "<CLUSTER_NAME>" --resource-group "<RESOURCE_GROUP>" \
--enable-cluster-autoscaler \
--min-count <MIN_NODES> --max-count <MAX_NODES>
# Specific node pool
az aks nodepool update \
--cluster-name "<CLUSTER_NAME>" --resource-group "<RESOURCE_GROUP>" \
--name "<NODEPOOL_NAME>" \
--enable-cluster-autoscaler \
--min-count <MIN_NODES> --max-count <MAX_NODES>Recommended min/max Defaults
| Scenario | min-count | max-count |
|---|---|---|
| Dev/test | 1 | current_count |
| Production (web/API) | 2 | current_count * 3 |
| Production (batch) | 0 | current_count * 5 |
Risk: Low. CAS only scales down when pods can be safely rescheduled. Set min-count >= 2 for production HA.
Tune CAS Profile
Apply when CAS is already on but idle nodes persist:
⚠️ Warning: Settingskip-nodes-with-system-pods=falseallows CAS to evict system pods. Ensure all system pods inkube-systemhave PodDisruptionBudgets before enabling this.
az aks update \
--name "<CLUSTER_NAME>" --resource-group "<RESOURCE_GROUP>" \
--cluster-autoscaler-profile \
scale-down-delay-after-add=10m \
scale-down-unneeded-time=10m \
scale-down-utilization-threshold=0.5 \
max-graceful-termination-sec=600 \
skip-nodes-with-system-pods=falseTo roll back to CAS defaults:
az aks update \
--name "<CLUSTER_NAME>" --resource-group "<RESOURCE_GROUP>" \
--cluster-autoscaler-profile ""Profile Comparison
| Profile | scale-down-delay-after-add | scale-down-unneeded-time | utilization-threshold | Best For |
|---|---|---|---|---|
| Default | 10m | 10m | 0.5 | General workloads |
| Cost-Optimized | 5m | 5m | 0.5 | Cost-sensitive, non-critical |
| Conservative | 30m | 30m | 0.7 | Stateful / production |
| Aggressive | 2m | 2m | 0.4 | Dev/test, batch |
Risk: High for aggressive tuning. Ensure PodDisruptionBudgets (PDBs) are set on critical workloads before tuning. Always confirm with user before applying.
>
Check existing PDBs before tuning:
```bash
kubectl get pdb --all-namespaces
```
AKS Pod Rightsizing
Identify pods requesting far more CPU/memory than they use and recommend reduced resource requests.
Prerequisites — Check Monitoring State First
Before collecting usage data, determine what monitoring is available on the cluster:
# 1. Check if Azure Managed Prometheus is enabled
az aks show \
--name "<CLUSTER_NAME>" --resource-group "<RESOURCE_GROUP>" \
--query "azureMonitorProfile.metrics.enabled" -o tsv
# 2. Check if Container Insights (Log Analytics) is enabled
az aks show \
--name "<CLUSTER_NAME>" --resource-group "<RESOURCE_GROUP>" \
--query "addonProfiles.omsagent.enabled" -o tsv
# 3. Check if Metrics Server is running (pre-installed on AKS, but may be unhealthy)
kubectl get deployment metrics-server -n kube-systemBased on the result, follow the appropriate path:
| State | Rightsizing Possible? | Data Source | Accuracy |
|---|---|---|---|
| Azure Managed Prometheus enabled | Yes | Prometheus metrics via Azure Monitor | Best — full P95/7-day history |
| Container Insights (Log Analytics) enabled | Yes | KQL queries on Perf / KubePodInventory | Good — 7-day trends |
| Only Metrics Server (no Azure Monitor) | Limited | kubectl top pods — live data only | Low — no historical trends |
If nothing is enabled, Metrics Server is pre-installed on AKS — confirm it is healthy and use it for live rightsizing data:
```bash
kubectl get deployment metrics-server -n kube-system
kubectl top pods --all-namespaces --sort-by=cpu
```
For historical P95 trends (more accurate rightsizing), recommend enabling Azure Managed Prometheus. Warn user this incurs cost and wait for confirmation before proceeding.
---
Detection
# Authenticate to cluster
az aks get-credentials --name "<CLUSTER_NAME>" --resource-group "<RESOURCE_GROUP>"
# List requests/limits for ALL containers per pod (includes sidecars)
# Using [*] ensures multi-container pods are not misrepresented
kubectl get pods --all-namespaces \
-o custom-columns="NAMESPACE:.metadata.namespace,POD:.metadata.name,CONTAINERS:.spec.containers[*].name,CPU_REQ:.spec.containers[*].resources.requests.cpu,MEM_REQ:.spec.containers[*].resources.requests.memory,CPU_LIM:.spec.containers[*].resources.limits.cpu,MEM_LIM:.spec.containers[*].resources.limits.memory"
# Live per-container usage (shows each container individually, including sidecars)
kubectl top pods --all-namespaces --containers --sort-by=cpuHistorical Metrics (Azure Monitor — use when Prometheus or Container Insights is enabled)
First discover available metric names, then query:
az monitor metrics list-definitions \
--resource "<AKS_RESOURCE_ID>" \
--query "[].name.value" -o tsvaz monitor metrics list \
--resource "<AKS_RESOURCE_ID>" \
--metric "<METRIC_NAME_FROM_ABOVE>" \
--interval PT1H --aggregation Average \
--start-time "<YYYY-MM-DDTHH:mm:ssZ>" \
--end-time "<YYYY-MM-DDTHH:mm:ssZ>"Optimization Rules
| Condition | Recommendation | Risk |
|---|---|---|
| CPU request >5x P95 actual | Reduce to P95 * 1.2 | Medium |
| Memory request >3x P95 actual | Reduce to P95 * 1.2 | Medium |
| CPU request >2x P95 actual | Recommend rightsizing with 20% buffer | Low |
| No resource limits set | Add limits to prevent noisy-neighbor waste | Low |
| No VPA/HPA configured | Suggest enabling Vertical Pod Autoscaler | Low |
For VPA setup and configuration, see azure-aks-vpa.md.
YAML Patch Format
# Rightsizing patch for <NAMESPACE>/<DEPLOYMENT_NAME>
# Current: CPU request=<CURRENT>, P95 actual=<ACTUAL>
# Recommended: CPU request=<NEW> (P95 * 1.2 buffer)
apiVersion: apps/v1
kind: Deployment
metadata:
name: <DEPLOYMENT_NAME>
namespace: <NAMESPACE>
spec:
template:
spec:
containers:
- name: <CONTAINER_NAME>
resources:
requests:
cpu: "<NEW_CPU>"
memory: "<NEW_MEM>"
limits:
cpu: "<NEW_CPU_LIMIT>" # e.g. CPU limit = 1.5x CPU request, or preserve existing limit-to-request ratio
memory: "<NEW_MEM_LIMIT>" # e.g. memory limit = 1.25x memory request, or preserve existing limit-to-request ratioRisk: Medium-High. Always review patches before applying. Test in non-production first. Get explicit user confirmation before applying to production.
AKS Spot Node Pools
Recommend and create Spot VM node pools for batch, dev/test, or fault-tolerant workloads (60-90% cost reduction vs regular nodes).
Check Existing Node Pools
az aks nodepool list \
--cluster-name "<CLUSTER_NAME>" --resource-group "<RESOURCE_GROUP>" \
--query "[].{name:name, vmSize:vmSize, priority:scaleSetPriority, count:count, mode:mode}" \
-o tableIdentify Spot-Suitable Workloads
Before creating a Spot pool, identify which workloads can tolerate interruptions:
# List deployments without PodDisruptionBudgets (single-replica or no PDB = higher eviction risk)
kubectl get deployments --all-namespaces -o json | \
jq -r '.items[] | select(.spec.replicas == 1) | "\(.metadata.namespace)/\(.metadata.name)"'
# Check which pods already have spot tolerations
kubectl get pods --all-namespaces -o json | \
jq -r '.items[] | select(.spec.tolerations[]?.key == "kubernetes.azure.com/scalesetpriority") | "\(.metadata.namespace)/\(.metadata.name)"'Use the suitability table below to decide which workloads to migrate.
Mixed Node Pool Pattern (Spot + Regular)
For workloads that need resilience but want cost savings, use a mixed approach:
# Keep existing regular node pool as fallback (min 1-2 nodes)
az aks nodepool update \
--cluster-name "<CLUSTER_NAME>" --resource-group "<RESOURCE_GROUP>" \
--name "<REGULAR_POOL>" \
--enable-cluster-autoscaler --min-count 1 --max-count 3
# Add Spot pool for the majority of workload capacity
# -1 means pay up to on-demand price (no cap); set e.g. 0.05 to cap hourly spend
az aks nodepool add \
--cluster-name "<CLUSTER_NAME>" --resource-group "<RESOURCE_GROUP>" \
--name "<SPOT_POOL_NAME>" \
--priority Spot --eviction-policy Delete --spot-max-price -1 \
--node-vm-size "<VM_SIZE>" \
--node-count 3 --min-count 0 --max-count 10 \
--enable-cluster-autoscaler \
--node-taints "kubernetes.azure.com/scalesetpriority=spot:NoSchedule" \
--labels "kubernetes.azure.com/scalesetpriority=spot"Pods that tolerate Spot but don't require it (no nodeSelector or required node affinity pinning them to the Spot pool) will be rescheduled onto the regular pool after eviction. Pods pinned to Spot via nodeSelector cannot reschedule and will remain pending until a Spot node is available again.
Workload Toleration (add to Deployment YAML)
tolerations:
- key: "kubernetes.azure.com/scalesetpriority"
operator: "Equal"
value: "spot"
effect: "NoSchedule"
nodeSelector:
kubernetes.azure.com/scalesetpriority: spotSuitability
| Workload | Spot-Suitable? |
|---|---|
| Batch / data processing | Yes |
| Dev / test environments | Yes |
| Stateless web/API (replicas >= 2) | Yes (with care) |
| Jobs with checkpointing | Yes |
| Stateful workloads (databases) | No |
| Single-replica critical services | No |
Risk: Low for batch/dev. High for production stateful workloads. Spot VMs evict with 30-second notice. Eviction policy Delete is recommended for AKS.
Handling Eviction Gracefully
Configure workloads to handle the 30-second eviction notice:
# Add to Deployment spec — terminationGracePeriodSeconds should be < 30s for Spot
spec:
template:
spec:
terminationGracePeriodSeconds: 25
containers:
- name: <CONTAINER_NAME>
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 5"] # Drain in-flight requestsSet a PodDisruptionBudget to limit simultaneous evictions:
kubectl apply -f - <<EOF
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: <APP_NAME>-pdb
namespace: <NAMESPACE>
spec:
minAvailable: 1
selector:
matchLabels:
app: <APP_NAME>
EOFAKS Vertical Pod Autoscaler (VPA)
Use VPA to get data-driven resource recommendations for rightsizing pods. Always start in recommendation-only mode before considering auto-apply.
Enable VPA (Recommendation Mode)
# Enable VPA addon on AKS cluster (if not already enabled)
az aks update --enable-vpa --resource-group <RESOURCE_GROUP> --name <CLUSTER_NAME>
# Create a VPA object in recommendation mode for a deployment
kubectl apply -f - <<EOF
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: <DEPLOYMENT_NAME>-vpa
namespace: <NAMESPACE>
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: <DEPLOYMENT_NAME>
updatePolicy:
updateMode: "Off" # Recommendation only — does not modify pods
EOF
# Read recommendations after 24+ hours of data collection
kubectl describe vpa <DEPLOYMENT_NAME>-vpa -n <NAMESPACE>Risk: Low in "Off" mode. Do not use `updateMode: Auto` in production without thorough testing and explicit user confirmation.
Read VPA Recommendations
kubectl get vpa <DEPLOYMENT_NAME>-vpa -n <NAMESPACE> -o jsonpath='{.status.recommendation}'The output shows lowerBound, target, and upperBound for CPU and memory. Use the target values as rightsized requests.
Apply Recommendations Manually
After reviewing VPA output, patch the deployment — see azure-aks-rightsizing.md for the patch format.
CLI Reference for AKS
# List AKS clusters
az aks list --output table
# Show cluster details
az aks show --name <cluster-name> --resource-group <resource-group>
# Get available Kubernetes versions
az aks get-versions --location <location> --output table
# Create AKS Automatic cluster
az aks create --name <cluster-name> --resource-group <resource-group> --sku automatic \
--network-plugin azure --network-plugin-mode overlay \
--enable-oidc-issuer --enable-workload-identity
# Create AKS Standard cluster
az aks create --name <cluster-name> --resource-group <resource-group> \
--node-count 3 --zones 1 2 3 \
--network-plugin azure --network-plugin-mode overlay \
--enable-cluster-autoscaler --min-count 1 --max-count 10 \
--enable-oidc-issuer --enable-workload-identity
# Get credentials
az aks get-credentials --name <cluster-name> --resource-group <resource-group>
# List node pools
az aks nodepool list --cluster-name <cluster-name> --resource-group <resource-group> --output table
# Enable monitoring
az aks enable-addons --name <cluster-name> --resource-group <resource-group> \
--addons monitoring --workspace-resource-id <workspace-resource-id>Guides
- Azure Skills for Claude Code: The Complete Guide to microsoft/azure-skills
microsoft/azure-skills is Microsoft's official repo of 27 Azure skills for Claude Code, covering deploy, AKS, diagnostics, compliance, cost, and Azure AI Foundry. Combined they have 9,775,059 installs, with microsoft-foundry alone at 446,557 installs (skills.sh registry, July 2026). This guide maps every skill to the Azure job it does.
Related skills
FAQ
What does azure-kubernetes patch in manifests?
azure-kubernetes applies fixes mapped to constraint-spec-v1.yaml, such as safeguard-container-resource-requests that add CPU and memory requests and limits to container specs before AKS Automatic deployment.
What resource values does azure-kubernetes suggest?
azure-kubernetes uses safe minimum starting values—for example cpu 250m request, 500m limit, and memory 256Mi request, 512Mi limit—then relies on VPA auto-enabled on AKS Automatic to tune after deployment.
Is Azure Kubernetes safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.