Using External DNS in AKS
External DNS is a tool that synchronizes exposed Kubernetes Services and Ingresses with DNS providers, such as Azure DNS. It allows you to control DNS records dynamically via Kubernetes resources in a DNS-provider-agnostic way.
This eliminates the need to manually manage DNS records every time you deploy an application, streamlining operations and automation.
Architecture
External DNS runs as a Pod within your Kubernetes cluster. It watches for changes in Service or Ingress resources via the API Server. When a change is detected, it automatically creates, updates, or deletes the corresponding DNS records in the configured Azure DNS Zone using the Azure API.
Prerequisites
- An AKS cluster with OIDC Issuer and Workload Identity enabled
- An Azure DNS Zone (delegated domain recommended)
kubectlandazCLI tools installed
Following recommended security practices, this document explains how to configure authentication using Azure Workload Identity. For the legacy method using Service Principal, please refer to the Legacy: Using Service Principal section at the end of this document.
Setup Steps
The setup flow using Workload Identity is as follows:
1. Create Azure DNS Zone
First, create an Azure DNS Zone. You can skip this if you already have one.
DNS_ZONE_NAME="example.com"
DNS_ZONE_RG="rg-azure-dns"
az group create -n $DNS_ZONE_RG -l japaneast
az network dns zone create -g $DNS_ZONE_RG -n $DNS_ZONE_NAME
2. Enable Workload Identity on AKS
If Workload Identity is not enabled on your existing cluster, enable it with the following command:
AKS_CLUSTER_NAME="my-aks-cluster"
AKS_RG="my-aks-rg"
az aks update -g $AKS_RG -n $AKS_CLUSTER_NAME --enable-oidc-issuer --enable-workload-identity
3. Create Managed Identity and Assign Roles
Create a User Assigned Managed Identity for External DNS and assign permissions to access Azure DNS.
IDENTITY_NAME="id-external-dns"
IDENTITY_RG=$AKS_RG # Any RG you prefer
# Create Managed Identity
az identity create -g $IDENTITY_RG -n $IDENTITY_NAME
IDENTITY_CLIENT_ID=$(az identity show -g $IDENTITY_RG -n $IDENTITY_NAME --query clientId -o tsv)
IDENTITY_RESOURCE_ID=$(az identity show -g $IDENTITY_RG -n $IDENTITY_NAME --query id -o tsv)
# Get Resource IDs for DNS Zone
DNS_ZONE_ID=$(az network dns zone show -n $DNS_ZONE_NAME -g $DNS_ZONE_RG --query id -o tsv)
DNS_ZONE_RG_ID=$(az group show -g $DNS_ZONE_RG --query id -o tsv)
# Assign Roles (DNS Zone Contributor for record ops, Reader for RG read)
az role assignment create --role "Reader" --assignee $IDENTITY_CLIENT_ID --scope $DNS_ZONE_RG_ID
az role assignment create --role "DNS Zone Contributor" --assignee $IDENTITY_CLIENT_ID --scope $DNS_ZONE_ID
4. Create Federated Identity Credential
Create a federation setting to link the Kubernetes ServiceAccount with the created Managed Identity.
# Get AKS OIDC Issuer URL
AKS_OIDC_ISSUER=$(az aks show -n $AKS_CLUSTER_NAME -g $AKS_RG --query "oidcIssuerProfile.issuerUrl" -o tsv)
# Create Federation
# Assumes namespace: external-dns, ServiceAccount name: external-dns
az identity federated-credential create \
--name "fed-external-dns" \
--identity-name $IDENTITY_NAME \
--resource-group $IDENTITY_RG \
--issuer $AKS_OIDC_ISSUER \
--subject "system:serviceaccount:external-dns:external-dns"
5. Deploy External DNS
Deploy External DNS with configurations for using Workload Identity.
First, create a Secret for the configuration file.
azure.json:
{
"tenantId": "YOUR_TENANT_ID",
"subscriptionId": "YOUR_SUBSCRIPTION_ID",
"resourceGroup": "rg-azure-dns",
"useWorkloadIdentityExtension": true
}
kubectl create namespace external-dns
kubectl create secret generic azure-config-file -n external-dns --from-file=azure.json
Next, apply the manifest. The annotations and labels on the ServiceAccount are crucial.
apiVersion: v1
kind: ServiceAccount
metadata:
name: external-dns
namespace: external-dns
annotations:
# Specify Client ID of Managed Identity
azure.workload.identity/client-id: "YOUR_IDENTITY_CLIENT_ID"
labels:
azure.workload.identity/use: "true"
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: external-dns
rules:
- apiGroups: [""]
resources: ["services","endpoints","pods","nodes"]
verbs: ["get","watch","list"]
- apiGroups: ["extensions","networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get","watch","list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: external-dns-viewer
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: external-dns
subjects:
- kind: ServiceAccount
name: external-dns
namespace: external-dns
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: external-dns
namespace: external-dns
spec:
strategy:
type: Recreate
selector:
matchLabels:
app: external-dns
template:
metadata:
labels:
app: external-dns
# Label to enable Workload Identity projection for the Pod
azure.workload.identity/use: "true"
spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service
- --source=ingress
- --domain-filter=example.com
- --provider=azure
- --azure-resource-group=rg-azure-dns
volumeMounts:
- name: azure-config-file
mountPath: /etc/kubernetes
readOnly: true
volumes:
- name: azure-config-file
secret:
secretName: azure-config-file
Usage
Usage with Gateway API
To manage DNS records using Gateway API (Standard), you need to update the RBAC permissions and Deployment arguments of External DNS.
Configuration Changes
Update the ClusterRole and Deployment arguments to allow monitoring of Gateway API resources.
1. Add permissions to ClusterRole:
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["gateways", "httproutes", "grpcroutes", "tlsroutes"]
verbs: ["get","watch","list"]
2. Add sources to Deployment args:
args:
- --source=gateway-httproute
- --source=gateway-grpcroute
- --source=gateway-tlsroute
# Existing sources
- --source=service
- --source=ingress
Resource Example
DNS records are automatically created based on the domain names specified in the hostnames field of the HTTPRoute resource. No special annotations are required.
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: my-http-route
namespace: default
spec:
parentRefs:
- name: my-gateway
hostnames:
- "gateway-app.example.com"
rules:
- backendRefs:
- name: my-service
port: 80
Usage with Service (LoadBalancer)
Annotate your Service resource with external-dns.alpha.kubernetes.io/hostname. External DNS will create an A record pointing to the Service's External IP.
apiVersion: v1
kind: Service
metadata:
name: nginx-svc
annotations:
external-dns.alpha.kubernetes.io/hostname: nginx.example.com
spec:
type: LoadBalancer
ports:
- port: 80
targetPort: 80
selector:
app: nginx
Usage with Ingress
For Ingress, simply specifying the host in the rules section is enough. External DNS will detect the host and create a record pointing to the Ingress Controller's Public IP.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx-ingress
spec:
ingressClassName: nginx
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nginx-svc
port:
number: 80
Legacy: Using Service Principal
Previously, using a Service Principal was the standard way to authenticate, but Workload Identity (Federated Identity) is now recommended. If you must use a Service Principal, you need to manage the ID/Password within a Kubernetes Secret (aadClientId, aadClientSecret). Workload Identity is more secure as it does not require storing secrets within the cluster.