Back to Blog
6 min read

Azure Arc-Enabled Kubernetes: Manage Kubernetes Clusters Anywhere

Azure Arc-enabled Kubernetes extends Azure’s management plane to any CNCF-conformant Kubernetes cluster. At Ignite 2021, Microsoft announced GA for several Arc-enabled Kubernetes features and new capabilities for GitOps and policy enforcement.

What is Arc-Enabled Kubernetes?

Arc-enabled Kubernetes provides:

  • Inventory and grouping: Organize clusters in Azure Resource Manager
  • GitOps configurations: Deploy applications using Git repositories
  • Azure Policy: Enforce security and configuration standards
  • Azure Monitor: Unified monitoring for all clusters
  • Azure Defender: Security posture management

Connecting a Cluster

Prerequisites

# Install required CLI extensions
az extension add --name connectedk8s
az extension add --name k8s-configuration
az extension add --name k8s-extension

# Register providers
az provider register --namespace Microsoft.Kubernetes
az provider register --namespace Microsoft.KubernetesConfiguration
az provider register --namespace Microsoft.ExtendedLocation

Connect the Cluster

# Connect an existing cluster
az connectedk8s connect \
    --name my-cluster \
    --resource-group rg-arc-k8s \
    --location eastus \
    --tags Environment=Production Tier=Gold

# Verify connection
az connectedk8s show \
    --name my-cluster \
    --resource-group rg-arc-k8s \
    --query "connectivityStatus"

# View Arc agents
kubectl get pods -n azure-arc

For air-gapped environments:

# Export agent images
az connectedk8s connect \
    --name my-cluster \
    --resource-group rg-arc-k8s \
    --proxy-https https://proxy.example.com:3128 \
    --proxy-http http://proxy.example.com:3128 \
    --proxy-skip-range 10.0.0.0/8,kubernetes.default.svc

GitOps with Flux

Create GitOps Configuration

# Create a Flux configuration
az k8s-configuration flux create \
    --name app-config \
    --cluster-name my-cluster \
    --resource-group rg-arc-k8s \
    --cluster-type connectedClusters \
    --namespace flux-system \
    --scope cluster \
    --url https://github.com/myorg/kubernetes-configs \
    --branch main \
    --kustomization name=infrastructure path=./infrastructure prune=true \
    --kustomization name=apps path=./apps prune=true dependsOn=["infrastructure"]

Repository Structure

kubernetes-configs/
├── infrastructure/
│   ├── namespaces.yaml
│   ├── network-policies.yaml
│   └── kustomization.yaml
├── apps/
│   ├── frontend/
│   │   ├── deployment.yaml
│   │   ├── service.yaml
│   │   └── kustomization.yaml
│   ├── backend/
│   │   ├── deployment.yaml
│   │   ├── service.yaml
│   │   └── kustomization.yaml
│   └── kustomization.yaml
└── clusters/
    ├── production/
    │   └── kustomization.yaml
    └── staging/
        └── kustomization.yaml

Kustomization Files

# apps/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: default
resources:
  - frontend/
  - backend/
# apps/frontend/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend
spec:
  replicas: 3
  selector:
    matchLabels:
      app: frontend
  template:
    metadata:
      labels:
        app: frontend
    spec:
      containers:
      - name: frontend
        image: myregistry.azurecr.io/frontend:v1.2.3
        ports:
        - containerPort: 80
        resources:
          requests:
            cpu: 100m
            memory: 128Mi
          limits:
            cpu: 500m
            memory: 256Mi

Monitor GitOps Status

# Check configuration status
az k8s-configuration flux show \
    --name app-config \
    --cluster-name my-cluster \
    --resource-group rg-arc-k8s \
    --cluster-type connectedClusters

# List kustomizations
az k8s-configuration flux kustomization list \
    --name app-config \
    --cluster-name my-cluster \
    --resource-group rg-arc-k8s \
    --cluster-type connectedClusters

# View in cluster
kubectl get gitrepositories -n flux-system
kubectl get kustomizations -n flux-system

Azure Policy for Kubernetes

Built-in Policies

# List available Kubernetes policies
az policy definition list \
    --query "[?contains(displayName, 'Kubernetes')]" \
    --output table

# Assign built-in policy
az policy assignment create \
    --name "k8s-https-ingress" \
    --display-name "Kubernetes cluster should only use HTTPS ingress" \
    --policy "1a5b4dca-0b6f-4cf5-907c-56316bc1bf3d" \
    --scope "/subscriptions/your-sub/resourceGroups/rg-arc-k8s"

Custom Policy Definition

{
  "mode": "Microsoft.Kubernetes.Data",
  "policyRule": {
    "if": {
      "field": "type",
      "in": [
        "Microsoft.Kubernetes/connectedClusters",
        "Microsoft.ContainerService/managedClusters"
      ]
    },
    "then": {
      "effect": "[parameters('effect')]",
      "details": {
        "templateInfo": {
          "sourceType": "Base64Encoded",
          "content": "[base64(concat('apiVersion: templates.gatekeeper.sh/v1beta1\nkind: ConstraintTemplate\nmetadata:\n  name: k8srequiredlabels\nspec:\n  crd:\n    spec:\n      names:\n        kind: K8sRequiredLabels\n      validation:\n        openAPIV3Schema:\n          properties:\n            labels:\n              type: array\n              items:\n                type: string\n  targets:\n    - target: admission.k8s.gatekeeper.sh\n      rego: |\n        package k8srequiredlabels\n        violation[{\"msg\": msg}] {\n          provided := {label | input.review.object.metadata.labels[label]}\n          required := {label | label := input.parameters.labels[_]}\n          missing := required - provided\n          count(missing) > 0\n          msg := sprintf(\"Missing required labels: %v\", [missing])\n        }'))]"
        },
        "constraint": {
          "apiVersion": "constraints.gatekeeper.sh/v1beta1",
          "kind": "K8sRequiredLabels",
          "spec": {
            "match": {
              "kinds": [
                {
                  "apiGroups": [""],
                  "kinds": ["Namespace"]
                }
              ]
            },
            "parameters": {
              "labels": "[parameters('requiredLabels')]"
            }
          }
        }
      }
    }
  },
  "parameters": {
    "effect": {
      "type": "String",
      "metadata": {
        "displayName": "Effect",
        "description": "Audit or Deny"
      },
      "allowedValues": ["Audit", "Deny"],
      "defaultValue": "Audit"
    },
    "requiredLabels": {
      "type": "Array",
      "metadata": {
        "displayName": "Required Labels",
        "description": "Labels that must be present on namespaces"
      },
      "defaultValue": ["costcenter", "environment"]
    }
  }
}

Cluster Extensions

Deploy Azure Monitor Extension

# Install monitoring extension
az k8s-extension create \
    --name azuremonitor-containers \
    --extension-type Microsoft.AzureMonitor.Containers \
    --cluster-name my-cluster \
    --resource-group rg-arc-k8s \
    --cluster-type connectedClusters \
    --configuration-settings \
        logAnalyticsWorkspaceResourceID="/subscriptions/.../workspaces/my-workspace"

Deploy Azure Key Vault Secrets Provider

# Install secrets store CSI driver
az k8s-extension create \
    --name azure-secrets-store \
    --extension-type Microsoft.AzureKeyVaultSecretsProvider \
    --cluster-name my-cluster \
    --resource-group rg-arc-k8s \
    --cluster-type connectedClusters \
    --configuration-settings \
        secrets-store-csi-driver.enableSecretRotation=true \
        secrets-store-csi-driver.rotationPollInterval=2m

Use secrets in pods:

apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
  name: azure-kvname
spec:
  provider: azure
  parameters:
    usePodIdentity: "false"
    useVMManagedIdentity: "true"
    userAssignedIdentityID: ""
    keyvaultName: "my-keyvault"
    objects: |
      array:
        - |
          objectName: db-password
          objectType: secret
        - |
          objectName: api-key
          objectType: secret
    tenantId: "your-tenant-id"
---
apiVersion: v1
kind: Pod
metadata:
  name: app-with-secrets
spec:
  containers:
  - name: app
    image: myapp:latest
    volumeMounts:
    - name: secrets-store
      mountPath: "/mnt/secrets"
      readOnly: true
  volumes:
  - name: secrets-store
    csi:
      driver: secrets-store.csi.k8s.io
      readOnly: true
      volumeAttributes:
        secretProviderClass: "azure-kvname"

Multi-Cluster Management

Resource Graph Queries

// All Arc-enabled clusters
Resources
| where type == "microsoft.kubernetes/connectedclusters"
| project name, resourceGroup, location,
          agentVersion = properties.agentVersion,
          kubernetesVersion = properties.kubernetesVersion,
          status = properties.connectivityStatus

// Clusters by Kubernetes version
Resources
| where type == "microsoft.kubernetes/connectedclusters"
| summarize count() by tostring(properties.kubernetesVersion)

// Clusters needing upgrades
Resources
| where type == "microsoft.kubernetes/connectedclusters"
| where properties.kubernetesVersion !startswith "1.22"
| project name, resourceGroup,
          currentVersion = properties.kubernetesVersion

Bicep Template for Fleet Management

param clusterName string
param location string = resourceGroup().location
param tags object = {}

resource connectedCluster 'Microsoft.Kubernetes/connectedClusters@2021-10-01' existing = {
  name: clusterName
}

resource fluxConfig 'Microsoft.KubernetesConfiguration/fluxConfigurations@2022-03-01' = {
  name: 'app-config'
  scope: connectedCluster
  properties: {
    scope: 'cluster'
    namespace: 'flux-system'
    sourceKind: 'GitRepository'
    gitRepository: {
      url: 'https://github.com/myorg/configs'
      repositoryRef: {
        branch: 'main'
      }
      syncIntervalInSeconds: 60
    }
    kustomizations: {
      infrastructure: {
        path: './infrastructure'
        prune: true
        syncIntervalInSeconds: 120
      }
      apps: {
        path: './apps'
        prune: true
        dependsOn: ['infrastructure']
        syncIntervalInSeconds: 120
      }
    }
  }
}

resource monitorExtension 'Microsoft.KubernetesConfiguration/extensions@2022-03-01' = {
  name: 'azuremonitor-containers'
  scope: connectedCluster
  properties: {
    extensionType: 'Microsoft.AzureMonitor.Containers'
    autoUpgradeMinorVersion: true
    configurationSettings: {
      logAnalyticsWorkspaceResourceID: '/subscriptions/.../workspaces/shared-workspace'
    }
  }
}

Arc-enabled Kubernetes provides a unified control plane for managing Kubernetes clusters across any infrastructure, enabling consistent operations and security policies across your entire container platform.

Resources

Michael John Pena

Michael John Pena

Senior Data Engineer based in Sydney. Writing about data, cloud, and technology.