sigs.k8s.io/external-dns@v0.14.1/docs/tutorials/azure.md (about)

     1  
     2  # Setting up ExternalDNS for Services on Azure
     3  
     4  This tutorial describes how to setup ExternalDNS for [Azure DNS](https://azure.microsoft.com/services/dns/) with [Azure Kubernetes Service](https://docs.microsoft.com/azure/aks/).
     5  
     6  Make sure to use **>=0.11.0** version of ExternalDNS for this tutorial.
     7  
     8  This tutorial uses [Azure CLI 2.0](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) for all
     9  Azure commands and assumes that the Kubernetes cluster was created via Azure Container Services and `kubectl` commands
    10  are being run on an orchestration node.
    11  
    12  ## Creating an Azure DNS zone
    13  
    14  The Azure provider for ExternalDNS will find suitable zones for domains it manages; it will not automatically create zones.
    15  
    16  For this tutorial, we will create a Azure resource group named `MyDnsResourceGroup` that can easily be deleted later:
    17  
    18  ```bash
    19  $ az group create --name "MyDnsResourceGroup" --location "eastus"
    20  ```
    21  
    22  Substitute a more suitable location for the resource group if desired.
    23  
    24  Next, create a Azure DNS zone for `example.com`:
    25  
    26  ```bash
    27  $ az network dns zone create --resource-group "MyDnsResourceGroup" --name "example.com"
    28  ```
    29  
    30  Substitute a domain you own for `example.com` if desired.
    31  
    32  If using your own domain that was registered with a third-party domain registrar, you should point your domain's name servers to the values in the `nameServers` field from the JSON data returned by the `az network dns zone create` command. Please consult your registrar's documentation on how to do that.
    33  
    34  ### Internal Load Balancer
    35  
    36  To create internal load balancers, one can set the annotation `service.beta.kubernetes.io/azure-load-balancer-internal` to `true` on the resource.
    37  **Note**: AKS cluster's control plane managed identity needs to be granted `Network Contributor` role to update the subnet. For more details refer to [Use an internal load balancer with Azure Kubernetes Service (AKS)](https://learn.microsoft.com/en-us/azure/aks/internal-lb)
    38  
    39  ## Configuration file
    40  
    41  The azure provider will reference a configuration file called `azure.json`.  The preferred way to inject the configuration file is by using a Kubernetes secret. The secret should contain an object named `azure.json` with content similar to this:
    42  
    43  ```json
    44  {
    45    "tenantId": "01234abc-de56-ff78-abc1-234567890def",
    46    "subscriptionId": "01234abc-de56-ff78-abc1-234567890def",
    47    "resourceGroup": "MyDnsResourceGroup",
    48    "aadClientId": "01234abc-de56-ff78-abc1-234567890def",
    49    "aadClientSecret": "uKiuXeiwui4jo9quae9o"
    50  }
    51  ```
    52  
    53  The following fields are used:
    54  
    55  * `tenantId` (**required**) - run `az account show --query "tenantId"` or by selecting Azure Active Directory in the Azure Portal and checking the _Directory ID_ under Properties.
    56  * `subscriptionId` (**required**) - run `az account show --query "id"` or by selecting Subscriptions in the Azure Portal.
    57  * `resourceGroup` (**required**) is the Resource Group created in a previous step that contains the Azure DNS Zone.
    58  * `aadClientID` and `aadClientSecret` are associated with the Service Principal.  This is only used with Service Principal method documented in the next section.
    59  * `useManagedIdentityExtension` - this is set to `true` if you use either AKS Kubelet Identity or AAD Pod Identities methods documented in the next section.
    60  * `userAssignedIdentityID` - this contains the client id from the Managed identitty when using the AAD Pod Identities method documented in the next setion.
    61  * `useWorkloadIdentityExtension` - this is set to `true` if you use Workload Identity method documented in the next section.
    62  
    63  The Azure DNS provider expects, by default, that the configuration file is at `/etc/kubernetes/azure.json`.  This can be overridden with the `--azure-config-file` option when starting ExternalDNS.
    64  
    65  ## Permissions to modify DNS zone
    66  
    67  ExternalDNS needs permissions to make changes to the Azure DNS zone. There are four ways configure the access needed:
    68  
    69  - [Service Principal](#service-principal)
    70  - [Managed Identity Using AKS Kubelet Identity](#managed-identity-using-aks-kubelet-identity)
    71  - [Managed Identity Using AAD Pod Identities](#managed-identity-using-aad-pod-identities)
    72  - [Managed Identity Using Workload Identity](#managed-identity-using-workload-identity)
    73  
    74  ### Service Principal
    75  
    76  These permissions are defined in a Service Principal that should be made available to ExternalDNS as a configuration file `azure.json`.
    77  
    78  #### Creating a service principal
    79  
    80  A Service Principal with a minimum access level of `DNS Zone Contributor` or `Contributor` to the DNS zone(s) and `Reader` to the resource group containing the Azure DNS zone(s) is necessary for ExternalDNS to be able to edit DNS records. However, other more permissive access levels will work too (e.g. `Contributor` to the resource group or the whole subscription).
    81  
    82  This is an Azure CLI example on how to query the Azure API for the information required for the Resource Group and DNS zone you would have already created in previous steps (requires `azure-cli` and `jq`)
    83  
    84  ```bash
    85  $ EXTERNALDNS_NEW_SP_NAME="ExternalDnsServicePrincipal" # name of the service principal
    86  $ AZURE_DNS_ZONE_RESOURCE_GROUP="MyDnsResourceGroup" # name of resource group where dns zone is hosted
    87  $ AZURE_DNS_ZONE="example.com" # DNS zone name like example.com or sub.example.com
    88  
    89  # Create the service principal
    90  $ DNS_SP=$(az ad sp create-for-rbac --name $EXTERNALDNS_NEW_SP_NAME)
    91  $ EXTERNALDNS_SP_APP_ID=$(echo $DNS_SP | jq -r '.appId')
    92  $ EXTERNALDNS_SP_PASSWORD=$(echo $DNS_SP | jq -r '.password')
    93  ```
    94  
    95  #### Assign the rights for the service principal
    96  
    97  Grant access to Azure DNS zone for the service principal.
    98  
    99  ```bash
   100  # fetch DNS id used to grant access to the service principal
   101  DNS_ID=$(az network dns zone show --name $AZURE_DNS_ZONE \
   102   --resource-group $AZURE_DNS_ZONE_RESOURCE_GROUP --query "id" --output tsv)
   103  
   104  # 1. as a reader to the resource group
   105  $ az role assignment create --role "Reader" --assignee $EXTERNALDNS_SP_APP_ID --scope $DNS_ID
   106  
   107  # 2. as a contributor to DNS Zone itself
   108  $ az role assignment create --role "Contributor" --assignee $EXTERNALDNS_SP_APP_ID --scope $DNS_ID
   109  ```
   110  
   111  #### Creating a configuration file for the service principal
   112  
   113  Create the file `azure.json` with values gather from previous steps.
   114  
   115  ```bash
   116  cat <<-EOF > /local/path/to/azure.json
   117  {
   118    "tenantId": "$(az account show --query tenantId -o tsv)",
   119    "subscriptionId": "$(az account show --query id -o tsv)",
   120    "resourceGroup": "$AZURE_DNS_ZONE_RESOURCE_GROUP",
   121    "aadClientId": "$EXTERNALDNS_SP_APP_ID",
   122    "aadClientSecret": "$EXTERNALDNS_SP_PASSWORD"
   123  }
   124  EOF
   125  ```
   126  
   127  Use this file to create a Kubernetes secret:
   128  
   129  ```bash
   130  $ kubectl create secret generic azure-config-file --namespace "default" --from-file /local/path/to/azure.json
   131  ```
   132  
   133  ### Managed identity using AKS Kubelet identity
   134  
   135  The [managed identity](https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/overview) that is assigned to the underlying node pool in the AKS cluster can be given permissions to access Azure DNS.  Managed identities are essentially a service principal whose lifecycle is managed, such as deleting the AKS cluster will also delete the service principals associated with the AKS cluster.  The managed identity assigned Kubernetes node pool, or specifically the [VMSS](https://docs.microsoft.com/azure/virtual-machine-scale-sets/overview), is called the Kubelet identity.
   136  
   137  The managed identites were previously called MSI (Managed Service Identity) and are enabled by default when creating an AKS cluster.
   138  
   139  Note that permissions granted to this identity will be accessible to all containers running inside the Kubernetes cluster, not just the ExternalDNS container(s).
   140  
   141  For the managed identity, the contents of `azure.json` should be similar to this:
   142  
   143  ```json
   144  {
   145    "tenantId": "01234abc-de56-ff78-abc1-234567890def",
   146    "subscriptionId": "01234abc-de56-ff78-abc1-234567890def",
   147    "resourceGroup": "MyDnsResourceGroup",
   148    "useManagedIdentityExtension": true,
   149    "userAssignedIdentityID": "01234abc-de56-ff78-abc1-234567890def"
   150  }
   151  ```
   152  
   153  #### Fetching the Kubelet identity
   154  
   155  For this process, you will need to get the kubelet identity:
   156  
   157  ```bash
   158  $ PRINCIPAL_ID=$(az aks show --resource-group $CLUSTER_GROUP --name $CLUSTERNAME \
   159    --query "identityProfile.kubeletidentity.objectId" --output tsv)
   160  $ IDENTITY_CLIENT_ID=$(az aks show --resource-group $CLUSTER_GROUP --name $CLUSTERNAME \
   161    --query "identityProfile.kubeletidentity.clientId" --output tsv)
   162  ```
   163  
   164  #### Assign rights for the Kubelet identity
   165  
   166  Grant access to Azure DNS zone for the kubelet identity.
   167  
   168  ```bash
   169  $ AZURE_DNS_ZONE="example.com" # DNS zone name like example.com or sub.example.com
   170  $ AZURE_DNS_ZONE_RESOURCE_GROUP="MyDnsResourceGroup" # resource group where DNS zone is hosted
   171  
   172  # fetch DNS id used to grant access to the kubelet identity
   173  $ DNS_ID=$(az network dns zone show --name $AZURE_DNS_ZONE \
   174    --resource-group $AZURE_DNS_ZONE_RESOURCE_GROUP --query "id" --output tsv)
   175  
   176  $ az role assignment create --role "DNS Zone Contributor" --assignee $PRINCIPAL_ID --scope $DNS_ID
   177  ```
   178  
   179  #### Creating a configuration file for the kubelet identity
   180  
   181  Create the file `azure.json` with values gather from previous steps.
   182  
   183  ```bash
   184  cat <<-EOF > /local/path/to/azure.json
   185  {
   186    "tenantId": "$(az account show --query tenantId -o tsv)",
   187    "subscriptionId": "$(az account show --query id -o tsv)",
   188    "resourceGroup": "$AZURE_DNS_ZONE_RESOURCE_GROUP",
   189    "useManagedIdentityExtension": true,
   190    "userAssignedIdentityID": "$IDENTITY_CLIENT_ID"
   191  }
   192  EOF
   193  ```
   194  
   195  Use the `azure.json` file to create a Kubernetes secret:
   196  
   197  ```bash
   198  $ kubectl create secret generic azure-config-file --namespace "default" --from-file /local/path/to/azure.json
   199  ```
   200  
   201  ### Managed identity using AAD Pod Identities
   202  
   203  For this process, we will create a [managed identity](https://docs.microsoft.com//azure/active-directory/managed-identities-azure-resources/overview) that will be explicitly used by the ExternalDNS container.  This process is similar to Kubelet identity except that this managed identity is not associated with the Kubernetes node pool, but rather associated with explicit ExternalDNS containers.
   204  
   205  #### Enable the AAD Pod Identities feature
   206  
   207  For this solution, [AAD Pod Identities](https://docs.microsoft.com/azure/aks/use-azure-ad-pod-identity) preview feature can be enabled.  The commands below should do the trick to enable this feature:
   208  
   209  ```bash
   210  $ az feature register --name EnablePodIdentityPreview --namespace Microsoft.ContainerService
   211  $ az feature register --name AutoUpgradePreview --namespace Microsoft.ContainerService
   212  $ az extension add --name aks-preview
   213  $ az extension update --name aks-preview
   214  $ az provider register --namespace Microsoft.ContainerService
   215  ```
   216  
   217  #### Deploy the AAD Pod Identities service
   218  
   219  Once enabled, you can update your cluster and install needed services for the [AAD Pod Identities](https://docs.microsoft.com/azure/aks/use-azure-ad-pod-identity) feature.
   220  
   221  ```bash
   222  $ AZURE_AKS_RESOURCE_GROUP="my-aks-cluster-group" # name of resource group where aks cluster was created
   223  $ AZURE_AKS_CLUSTER_NAME="my-aks-cluster" # name of aks cluster previously created
   224  
   225  $ az aks update --resource-group ${AZURE_AKS_RESOURCE_GROUP} --name ${AZURE_AKS_CLUSTER_NAME} --enable-pod-identity
   226  ```
   227  
   228  Note that, if you use the default network plugin `kubenet`, then you need to add the command line option `--enable-pod-identity-with-kubenet` to the above command.
   229  
   230  #### Creating the managed identity
   231  
   232  After this process is finished, create a managed identity.
   233  
   234  ```bash
   235  $ IDENTITY_RESOURCE_GROUP=$AZURE_AKS_RESOURCE_GROUP # custom group or reuse AKS group
   236  $ IDENTITY_NAME="example-com-identity"
   237  
   238  # create a managed identity
   239  $ az identity create --resource-group "${IDENTITY_RESOURCE_GROUP}" --name "${IDENTITY_NAME}"
   240  ```
   241  
   242  #### Assign rights for the managed identity
   243  
   244  Grant access to Azure DNS zone for the managed identity.
   245  
   246  ```bash
   247  $ AZURE_DNS_ZONE_RESOURCE_GROUP="MyDnsResourceGroup" # name of resource group where dns zone is hosted
   248  $ AZURE_DNS_ZONE="example.com" # DNS zone name like example.com or sub.example.com
   249  
   250  # fetch identity client id from managed identity created earlier
   251  $ IDENTITY_CLIENT_ID=$(az identity show --resource-group "${IDENTITY_RESOURCE_GROUP}" \
   252    --name "${IDENTITY_NAME}" --query "clientId" --output tsv)
   253  # fetch DNS id used to grant access to the managed identity
   254  $ DNS_ID=$(az network dns zone show --name "${AZURE_DNS_ZONE}" \
   255    --resource-group "${AZURE_DNS_ZONE_RESOURCE_GROUP}" --query "id" --output tsv)
   256  
   257  $ az role assignment create --role "DNS Zone Contributor" \
   258    --assignee "${IDENTITY_CLIENT_ID}" --scope "${DNS_ID}"
   259  ```
   260  
   261  #### Creating a configuration file for the managed identity
   262  
   263  Create the file `azure.json` with the values from previous steps:
   264  
   265  ```bash
   266  cat <<-EOF > /local/path/to/azure.json
   267  {
   268    "tenantId": "$(az account show --query tenantId -o tsv)",
   269    "subscriptionId": "$(az account show --query id -o tsv)",
   270    "resourceGroup": "$AZURE_DNS_ZONE_RESOURCE_GROUP",
   271    "useManagedIdentityExtension": true,
   272    "userAssignedIdentityID": "$IDENTITY_CLIENT_ID"
   273  }
   274  EOF
   275  ```
   276  
   277  Use the `azure.json` file to create a Kubernetes secret:
   278  
   279  ```bash
   280  $ kubectl create secret generic azure-config-file --namespace "default" --from-file /local/path/to/azure.json
   281  ```
   282  
   283  #### Creating an Azure identity binding
   284  
   285  A binding between the managed identity and the ExternalDNS pods needs to be setup by creating `AzureIdentity` and `AzureIdentityBinding` resources.  This will allow appropriately labeled ExternalDNS pods to authenticate using the managed identity.  When AAD Pod Identity feature is enabled from previous steps above, the `az aks pod-identity add` can be used to create these resources:
   286  
   287  ```bash
   288  $ IDENTITY_RESOURCE_ID=$(az identity show --resource-group ${IDENTITY_RESOURCE_GROUP} \
   289    --name ${IDENTITY_NAME} --query id --output tsv)
   290  
   291  $ az aks pod-identity add --resource-group ${AZURE_AKS_RESOURCE_GROUP}  \
   292    --cluster-name ${AZURE_AKS_CLUSTER_NAME} --namespace "default" \
   293    --name "external-dns" --identity-resource-id ${IDENTITY_RESOURCE_ID}
   294  ```
   295  
   296  This will add something similar to the following resources:
   297  
   298  ```yaml
   299  apiVersion: aadpodidentity.k8s.io/v1
   300  kind: AzureIdentity
   301  metadata:
   302    labels:
   303      addonmanager.kubernetes.io/mode: Reconcile
   304      kubernetes.azure.com/managedby: aks
   305    name: external-dns
   306  spec:
   307    clientID: $IDENTITY_CLIENT_ID
   308    resourceID: $IDENTITY_RESOURCE_ID
   309    type: 0
   310  ---
   311  apiVersion: aadpodidentity.k8s.io/v1
   312  kind: AzureIdentityBinding
   313  metadata:
   314    annotations:
   315    labels:
   316      addonmanager.kubernetes.io/mode: Reconcile
   317      kubernetes.azure.com/managedby: aks
   318    name: external-dns-binding
   319  spec:
   320    azureIdentity: external-dns
   321    selector: external-dns
   322  ```
   323  
   324  #### Update ExternalDNS labels
   325  
   326  When deploying ExternalDNS, you want to make sure that deployed pod(s) will have the label `aadpodidbinding: external-dns` to enable AAD Pod Identities. You can patch an existing deployment of ExternalDNS with this command:
   327  
   328  ```bash
   329  kubectl patch deployment external-dns --namespace "default" --patch \
   330   '{"spec": {"template": {"metadata": {"labels": {"aadpodidbinding": "external-dns"}}}}}'
   331  ```
   332  
   333  ### Managed identity using Workload Identity
   334  
   335  For this process, we will create a [managed identity](https://docs.microsoft.com//azure/active-directory/managed-identities-azure-resources/overview) that will be explicitly used by the ExternalDNS container. This process is somewhat similar to Pod Identity except that this managed identity is associated with a kubernetes service account.
   336  
   337  #### Deploy OIDC issuer and Workload Identity services
   338  
   339  Update your cluster to install [OIDC Issuer](https://learn.microsoft.com/en-us/azure/aks/use-oidc-issuer) and [Workload Identity](https://learn.microsoft.com/en-us/azure/aks/workload-identity-deploy-cluster):
   340  
   341  ```bash
   342  $ AZURE_AKS_RESOURCE_GROUP="my-aks-cluster-group" # name of resource group where aks cluster was created
   343  $ AZURE_AKS_CLUSTER_NAME="my-aks-cluster" # name of aks cluster previously created
   344  
   345  $ az aks update --resource-group ${AZURE_AKS_RESOURCE_GROUP} --name ${AZURE_AKS_CLUSTER_NAME} --enable-oidc-issuer --enable-workload-identity
   346  ```
   347  
   348  #### Create a managed identity
   349  
   350  Create a managed identity:
   351  
   352  ```bash
   353  $ IDENTITY_RESOURCE_GROUP=$AZURE_AKS_RESOURCE_GROUP # custom group or reuse AKS group
   354  $ IDENTITY_NAME="example-com-identity"
   355  
   356  # create a managed identity
   357  $ az identity create --resource-group "${IDENTITY_RESOURCE_GROUP}" --name "${IDENTITY_NAME}"
   358  ```
   359  
   360  #### Assign a role to the managed identity
   361  
   362  Grant access to Azure DNS zone for the managed identity:
   363  
   364  ```bash
   365  $ AZURE_DNS_ZONE_RESOURCE_GROUP="MyDnsResourceGroup" # name of resource group where dns zone is hosted
   366  $ AZURE_DNS_ZONE="example.com" # DNS zone name like example.com or sub.example.com
   367  
   368  # fetch identity client id from managed identity created earlier
   369  $ IDENTITY_CLIENT_ID=$(az identity show --resource-group "${IDENTITY_RESOURCE_GROUP}" \
   370    --name "${IDENTITY_NAME}" --query "clientId" --output tsv)
   371  # fetch DNS id used to grant access to the managed identity
   372  $ DNS_ID=$(az network dns zone show --name "${AZURE_DNS_ZONE}" \
   373    --resource-group "${AZURE_DNS_ZONE_RESOURCE_GROUP}" --query "id" --output tsv)
   374  $ RESOURCE_GROUP_ID=$(az group show --name "${AZURE_DNS_ZONE_RESOURCE_GROUP}" --query "id" --output tsv)
   375  
   376  $ az role assignment create --role "DNS Zone Contributor" \
   377    --assignee "${IDENTITY_CLIENT_ID}" --scope "${DNS_ID}"
   378  $ az role assignment create --role "Reader" \
   379    --assignee "${IDENTITY_CLIENT_ID}" --scope "${RESOURCE_GROUP_ID}"
   380  ```
   381  
   382  #### Create a federated identity credential
   383  
   384  A binding between the managed identity and the ExternalDNS service account needs to be setup by creating a federated identity resource:
   385  
   386  ```bash
   387  $ OIDC_ISSUER_URL="$(az aks show -n myAKSCluster -g myResourceGroup --query "oidcIssuerProfile.issuerUrl" -otsv)"
   388  
   389  $ az identity federated-credential create --name ${IDENTITY_NAME} --identity-name ${IDENTITY_NAME} --resource-group $AZURE_AKS_RESOURCE_GROUP} --issuer "$OIDC_ISSUER_URL" --subject "system:serviceaccount:default:external-dns"
   390  ```
   391  
   392  NOTE: make sure federated credential refers to correct namespace and service account (`system:serviceaccount:<NAMESPACE>:<SERVICE_ACCOUNT>`)
   393  
   394  #### Helm
   395  
   396  When deploying external-dns with Helm you need to create a secret to store the Azure config (see below) and create a workload identity (out of scope here) before you can install the chart.
   397  
   398  ```yaml
   399  apiVersion: v1
   400  kind: Secret
   401  metadata:
   402    name: external-dns-azure
   403  type: Opaque
   404  data:
   405    azure.json: |
   406      {
   407        "tenantId": "<TENANT_ID>",
   408        "subscriptionId": "<SUBSCRIPTION_ID>",
   409        "resourceGroup": "<AZURE_DNS_ZONE_RESOURCE_GROUP>",
   410        "useWorkloadIdentityExtension": true
   411      }
   412  ```
   413  
   414  Once you have created the secret and have a workload identity you can install the chart with the following values.
   415  
   416  ```yaml
   417  fullnameOverride: external-dns
   418  
   419  serviceAccount:
   420    labels:
   421      azure.workload.identity/use: "true"
   422    annotations:
   423      azure.workload.identity/client-id: <IDENTITY_CLIENT_ID>
   424  
   425  podLabels:
   426    azure.workload.identity/use: "true"
   427  
   428  extraVolumes:
   429    - name: azure-config-file
   430      secret:
   431        secretName: external-dns-azure
   432  
   433  extraVolumeMounts:
   434    - name: azure-config-file
   435      mountPath: /etc/kubernetes
   436      readOnly: true
   437  
   438  provider:
   439    name: azure
   440  ```
   441  
   442  NOTE: make sure the pod is restarted whenever you make a configuration change.
   443  
   444  #### kubectl (alternative)
   445  
   446  ##### Create a configuration file for the managed identity
   447  
   448  Create the file `azure.json` with the values from previous steps:
   449  
   450  ```bash
   451  cat <<-EOF > /local/path/to/azure.json
   452  {
   453    "subscriptionId": "$(az account show --query id -o tsv)",
   454    "resourceGroup": "$AZURE_DNS_ZONE_RESOURCE_GROUP",
   455    "useWorkloadIdentityExtension": true
   456  }
   457  EOF
   458  ```
   459  
   460  Use the `azure.json` file to create a Kubernetes secret:
   461  
   462  ```bash
   463  $ kubectl create secret generic azure-config-file --namespace "default" --from-file /local/path/to/azure.json
   464  ```
   465  
   466  ##### Update labels and annotations on ExternalDNS service account
   467  
   468  To instruct Workload Identity webhook to inject a projected token into the ExternalDNS pod, the pod needs to have a label `azure.workload.identity/use: "true"` (before Workload Identity 1.0.0, this label was supposed to be set on the service account instead). Also, the service account needs to have an annotation `azure.workload.identity/client-id: <IDENTITY_CLIENT_ID>`:
   469  
   470  To patch the existing serviceaccount and deployment, use the following command:
   471  
   472  ```bash
   473  $ kubectl patch serviceaccount external-dns --namespace "default" --patch \
   474   "{\"metadata\": {\"annotations\": {\"azure.workload.identity/client-id\": \"${IDENTITY_CLIENT_ID}\"}}}"
   475  $ kubectl patch deployment external-dns --namespace "default" --patch \
   476   '{"spec": {"template": {"metadata": {"labels": {\"azure.workload.identity/use\": \"true\"}}}}}'
   477  ```
   478  
   479  NOTE: it's also possible to specify (or override) ClientID through `userAssignedIdentityID` field in `azure.json`.
   480  
   481  NOTE: make sure the pod is restarted whenever you make a configuration change.
   482  
   483  ## Ingress used with ExternalDNS
   484  
   485  This deployment assumes that you will be using nginx-ingress. When using nginx-ingress do not deploy it as a Daemon Set. This causes nginx-ingress to write the Cluster IP of the backend pods in the ingress status.loadbalancer.ip property which then has external-dns write the Cluster IP(s) in DNS vs. the nginx-ingress service external IP.
   486  
   487  Ensure that your nginx-ingress deployment has the following arg: added to it:
   488  
   489  ```
   490  - --publish-service=namespace/nginx-ingress-controller-svcname
   491  ```
   492  
   493  For more details see here: [nginx-ingress external-dns](https://github.com/kubernetes-sigs/external-dns/blob/HEAD/docs/faq.md#why-is-externaldns-only-adding-a-single-ip-address-in-route-53-on-aws-when-using-the-nginx-ingress-controller-how-do-i-get-it-to-use-the-fqdn-of-the-elb-assigned-to-my-nginx-ingress-controller-service-instead)
   494  
   495  ## Deploy ExternalDNS
   496  
   497  Connect your `kubectl` client to the cluster you want to test ExternalDNS with. Then apply one of the following manifests file to deploy ExternalDNS.
   498  
   499  The deployment assumes that ExternalDNS will be installed into the `default` namespace.  If this namespace is different, the `ClusterRoleBinding` will need to be updated to reflect the desired alternative namespace, such as `external-dns`, `kube-addons`, etc.
   500  
   501  ### Manifest (for clusters without RBAC enabled)
   502  ```yaml
   503  apiVersion: apps/v1
   504  kind: Deployment
   505  metadata:
   506    name: external-dns
   507  spec:
   508    strategy:
   509      type: Recreate
   510    selector:
   511      matchLabels:
   512        app: external-dns
   513    template:
   514      metadata:
   515        labels:
   516          app: external-dns
   517      spec:
   518        containers:
   519        - name: external-dns
   520          image: registry.k8s.io/external-dns/external-dns:v0.14.0
   521          args:
   522          - --source=service
   523          - --source=ingress
   524          - --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
   525          - --provider=azure
   526          - --azure-resource-group=MyDnsResourceGroup # (optional) use the DNS zones from the tutorial's resource group
   527          volumeMounts:
   528          - name: azure-config-file
   529            mountPath: /etc/kubernetes
   530            readOnly: true
   531        volumes:
   532        - name: azure-config-file
   533          secret:
   534            secretName: azure-config-file
   535  ```
   536  
   537  ### Manifest (for clusters with RBAC enabled, cluster access)
   538  
   539  ```yaml
   540  apiVersion: v1
   541  kind: ServiceAccount
   542  metadata:
   543    name: external-dns
   544  ---
   545  apiVersion: rbac.authorization.k8s.io/v1
   546  kind: ClusterRole
   547  metadata:
   548    name: external-dns
   549  rules:
   550    - apiGroups: [""]
   551      resources: ["services","endpoints","pods", "nodes"]
   552      verbs: ["get","watch","list"]
   553    - apiGroups: ["extensions","networking.k8s.io"]
   554      resources: ["ingresses"]
   555      verbs: ["get","watch","list"]
   556  ---
   557  apiVersion: rbac.authorization.k8s.io/v1
   558  kind: ClusterRoleBinding
   559  metadata:
   560    name: external-dns-viewer
   561  roleRef:
   562    apiGroup: rbac.authorization.k8s.io
   563    kind: ClusterRole
   564    name: external-dns
   565  subjects:
   566    - kind: ServiceAccount
   567      name: external-dns
   568      namespace: default
   569  ---
   570  apiVersion: apps/v1
   571  kind: Deployment
   572  metadata:
   573    name: external-dns
   574  spec:
   575    strategy:
   576      type: Recreate
   577    selector:
   578      matchLabels:
   579        app: external-dns
   580    template:
   581      metadata:
   582        labels:
   583          app: external-dns
   584      spec:
   585        serviceAccountName: external-dns
   586        containers:
   587          - name: external-dns
   588            image: registry.k8s.io/external-dns/external-dns:v0.14.0
   589            args:
   590              - --source=service
   591              - --source=ingress
   592              - --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
   593              - --provider=azure
   594              - --azure-resource-group=MyDnsResourceGroup # (optional) use the DNS zones from the tutorial's resource group
   595              - --txt-prefix=externaldns-
   596            volumeMounts:
   597              - name: azure-config-file
   598                mountPath: /etc/kubernetes
   599                readOnly: true
   600        volumes:
   601          - name: azure-config-file
   602            secret:
   603              secretName: azure-config-file
   604  ```
   605  
   606  ### Manifest (for clusters with RBAC enabled, namespace access)
   607  This configuration is the same as above, except it only requires privileges for the current namespace, not for the whole cluster.
   608  However, access to [nodes](https://kubernetes.io/docs/concepts/architecture/nodes/) requires cluster access, so when using this manifest,
   609  services with type `NodePort` will be skipped!
   610  
   611  ```yaml
   612  apiVersion: v1
   613  kind: ServiceAccount
   614  metadata:
   615    name: external-dns
   616  ---
   617  apiVersion: rbac.authorization.k8s.io/v1
   618  kind: Role
   619  metadata:
   620    name: external-dns
   621  rules:
   622    - apiGroups: [""]
   623      resources: ["services","endpoints","pods"]
   624      verbs: ["get","watch","list"]
   625    - apiGroups: ["extensions","networking.k8s.io"]
   626      resources: ["ingresses"]
   627      verbs: ["get","watch","list"]
   628  ---
   629  apiVersion: rbac.authorization.k8s.io/v1
   630  kind: RoleBinding
   631  metadata:
   632    name: external-dns
   633  roleRef:
   634    apiGroup: rbac.authorization.k8s.io
   635    kind: Role
   636    name: external-dns
   637  subjects:
   638    - kind: ServiceAccount
   639      name: external-dns
   640  ---
   641  apiVersion: apps/v1
   642  kind: Deployment
   643  metadata:
   644    name: external-dns
   645  spec:
   646    strategy:
   647      type: Recreate
   648    selector:
   649      matchLabels:
   650        app: external-dns
   651    template:
   652      metadata:
   653        labels:
   654          app: external-dns
   655      spec:
   656        serviceAccountName: external-dns
   657        containers:
   658          - name: external-dns
   659            image: registry.k8s.io/external-dns/external-dns:v0.14.0
   660            args:
   661              - --source=service
   662              - --source=ingress
   663              - --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
   664              - --provider=azure
   665              - --azure-resource-group=MyDnsResourceGroup # (optional) use the DNS zones from the tutorial's resource group
   666            volumeMounts:
   667              - name: azure-config-file
   668                mountPath: /etc/kubernetes
   669                readOnly: true
   670        volumes:
   671          - name: azure-config-file
   672            secret:
   673              secretName: azure-config-file
   674  ```
   675  
   676  Create the deployment for ExternalDNS:
   677  
   678  ```bash
   679  $ kubectl create --namespace "default" --filename externaldns.yaml
   680  ```
   681  
   682  ## Ingress Option: Expose an nginx service with an ingress
   683  
   684  Create a file called `nginx.yaml` with the following contents:
   685  
   686  ```yaml
   687  apiVersion: apps/v1
   688  kind: Deployment
   689  metadata:
   690    name: nginx
   691  spec:
   692    selector:
   693      matchLabels:
   694        app: nginx
   695    template:
   696      metadata:
   697        labels:
   698          app: nginx
   699      spec:
   700        containers:
   701          - image: nginx
   702            name: nginx
   703            ports:
   704            - containerPort: 80
   705  ---
   706  apiVersion: v1
   707  kind: Service
   708  metadata:
   709    name: nginx-svc
   710  spec:
   711    ports:
   712      - port: 80
   713        protocol: TCP
   714        targetPort: 80
   715    selector:
   716      app: nginx
   717    type: ClusterIP
   718  
   719  ---
   720  apiVersion: networking.k8s.io/v1
   721  kind: Ingress
   722  metadata:
   723    name: nginx
   724  spec:
   725    ingressClassName: nginx
   726    rules:
   727      - host: server.example.com
   728        http:
   729          paths:
   730            - path: /
   731              pathType: Prefix
   732              backend:
   733                service:
   734                  name: nginx-svc
   735                  port:
   736                    number: 80
   737  ```
   738  
   739  When using ExternalDNS with `ingress` objects it will automatically create DNS records based on host names specified in ingress objects that match the domain-filter argument in the external-dns deployment manifest. When those host names are removed or renamed the corresponding DNS records are also altered.
   740  
   741  Create the deployment, service and ingress object:
   742  
   743  ```bash
   744  $ kubectl create --namespace "default" --filename nginx.yaml
   745  ```
   746  
   747  Since your external IP would have already been assigned to the nginx-ingress service, the DNS records pointing to the IP of the nginx-ingress service should be created within a minute.
   748  
   749  ## Azure Load Balancer option: Expose an nginx service with a load balancer
   750  
   751  Create a file called `nginx.yaml` with the following contents:
   752  
   753  ```yaml
   754  ---
   755  apiVersion: apps/v1
   756  kind: Deployment
   757  metadata:
   758    name: nginx
   759  spec:
   760    selector:
   761      matchLabels:
   762        app: nginx
   763    template:
   764      metadata:
   765        labels:
   766          app: nginx
   767      spec:
   768        containers:
   769          - image: nginx
   770            name: nginx
   771            ports:
   772            - containerPort: 80
   773  ---
   774  apiVersion: v1
   775  kind: Service
   776  annotations:
   777    external-dns.alpha.kubernetes.io/hostname: server.example.com
   778  metadata:
   779    name: nginx-svc
   780  spec:
   781    ports:
   782      - port: 80
   783        protocol: TCP
   784        targetPort: 80
   785    selector:
   786      app: nginx
   787    type: LoadBalancer
   788  ```
   789  
   790  The annotation `external-dns.alpha.kubernetes.io/hostname` is used to specify the DNS name that should be created for the service. The annotation value is a comma separated list of host names.
   791  
   792  ## Verifying Azure DNS records
   793  
   794  Run the following command to view the A records for your Azure DNS zone:
   795  
   796  ```bash
   797  $ az network dns record-set a list --resource-group "MyDnsResourceGroup" --zone-name example.com
   798  ```
   799  
   800  Substitute the zone for the one created above if a different domain was used.
   801  
   802  This should show the external IP address of the service as the A record for your domain ('@' indicates the record is for the zone itself).
   803  
   804  ## Delete Azure Resource Group
   805  
   806  Now that we have verified that ExternalDNS will automatically manage Azure DNS records, we can delete the tutorial's
   807  resource group:
   808  
   809  ```bash
   810  $ az group delete --name "MyDnsResourceGroup"
   811  ```
   812  
   813  ## More tutorials
   814  
   815  A video explanation is available here: https://www.youtube.com/watch?v=VSn6DPKIhM8&list=PLpbcUe4chE79sB7Jg7B4z3HytqUUEwcNE
   816  
   817  ![image](https://user-images.githubusercontent.com/6548359/235437721-87611869-75f2-4f32-bb35-9da585e46299.png)