azure.md 10.5 KB
Newer Older
jmo808's avatar
jmo808 committed
1

2
3
4
5
# Setting up ExternalDNS for Services on Azure

This tutorial describes how to setup ExternalDNS for usage within a Kubernetes cluster on Azure.

6
Make sure to use **>=0.4.2** version of ExternalDNS for this tutorial.
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

This tutorial uses [Azure CLI 2.0](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) for all
Azure commands and assumes that the Kubernetes cluster was created via Azure Container Services and `kubectl` commands
are being run on an orchestration master.

## Creating a Azure DNS zone

The Azure provider for ExternalDNS will find suitable zones for domains it manages; it will
not automatically create zones.

For this tutorial, we will create a Azure resource group named 'externaldns' that can easily be deleted later:

```
$ az group create -n externaldns -l eastus
```

Substitute a more suitable location for the resource group if desired.

Next, create a Azure DNS zone for "example.com":

```
$ az network dns zone create -g externaldns -n example.com
```

Substitute a domain you own for "example.com" if desired.

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.

## Creating Azure Credentials Secret
38
39
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.
40

41
42
### Azure Container Services
When your Kubernetes cluster is created by ACS, a file named `/etc/kubernetes/azure.json` is created to store
43
44
45
46
47
48
49
50
51
the Azure credentials for API access.  Kubernetes uses this file for the Azure cloud provider.

For ExternalDNS to access the Azure API, it also needs access to this file.  However, we will be deploying ExternalDNS inside of
the Kubernetes cluster so we will need to use a Kubernetes secret.

To create the secret:

```
$ kubectl create secret generic azure-config-file --from-file=/etc/kubernetes/azure.json
52
53
```

jmo808's avatar
jmo808 committed
54
55
### Azure Kubernetes Services (aka AKS)
When your cluster is created, unlike ACS there are no Azure credentials stored and you must create an azure.json object manually like with other hosting providers. In order to create the azure.json you must first create an Azure AD service principal in the Azure AD tenant linked to your Azure subscription that is hosting your DNS zone.
56

jmo808's avatar
jmo808 committed
57
58
#### Create service principal
A Service Principal with a minimum access level of contribute to the resource group containing the Azure DNS zone(s) is necessary for ExternalDNS to be able to edit DNS records. 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.
59
60
61
62

```
>az login
...
jmo808's avatar
jmo808 committed
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# find the relevant subscription and set the az context. id = subscriptionId value in the azure.json.
>az account list
{
    "cloudName": "AzureCloud",
    "id": "<subscriptionId GUID>",
    "isDefault": false,
    "name": "My Subscription",
    "state": "Enabled",
    "tenantId": "AzureAD tenant ID",
    "user": {
      "name": "name",
      "type": "user"
}
>az account set -s id
77
...
jmo808's avatar
jmo808 committed
78
>az group show --name externaldns
79
{
jmo808's avatar
jmo808 committed
80
  "id": "/subscriptions/id/resourceGroups/externaldns",
81
82
83
84
  ...
}

# use the id from the previous step in the scopes argument
jmo808's avatar
jmo808 committed
85
>az ad sp create-for-rbac --role="Contributor" --scopes="/subscriptions/id/resourceGroups/externaldns" -n ExternalDnsServicePrincipal
86
{
jmo808's avatar
jmo808 committed
87
  "appId": "appId GUID",  <-- aadClientId value
88
  ...
jmo808's avatar
jmo808 committed
89
90
  "password": "password",  <-- aadClientSecret value
  "tenant": "AzureAD Tenant Id"  <-- tenantId value
91
92
93
}
...

94
```
jmo808's avatar
jmo808 committed
95
96
97
98
99
100
101
102
103
104
105
### Other hosting providers
If the Kubernetes cluster is not hosted by Azure Container Services and you still want to use Azure DNS, you need to create the secret manually. The secret should contain an object named azure.json with content similar to this:
```
{
  "tenantId": "AzureAD tenant Id",
  "subscriptionId": "Id",
  "aadClientId": "Service Principal AppId",
  "aadClientSecret": "Service Principal Password",
  "resourceGroup": "MyDnsResourceGroup",
}
```
Roman Sokolkov's avatar
Roman Sokolkov committed
106
107
108
109
110
111
112
113
114
115
If [Azure Managed Service Identity (MSI)](https://docs.microsoft.com/en-us/azure/active-directory/managed-service-identity/overview) is enabled for virtual machines, then there is no need to create separate service principal. The contents of `azure.json` should be similar to this:
```
{
  "tenantId": "AzureAD tenant Id",
  "subscriptionId": "Id",
  "resourceGroup": "MyDnsResourceGroup",
  "useManagedIdentityExtension": true,
}
```

jmo808's avatar
jmo808 committed
116
117
118
119
120
121
122
123
If you have all the information necessary: create a file called azure.json containing the json structure above and substitute the values. Otherwise create a service principal as previously shown before creating the Kubernetes secret.

Then add the secret to the Kubernetes cluster before continuing:
```
kubectl create secret generic azure-config-file --from-file=azure.json
```


124
125
126

## Deploy ExternalDNS

jmo808's avatar
jmo808 committed
127
128
129
130
131
132
133
134
135
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.

Ensure that your nginx-ingress deployment has the following arg: added to it:

```
- --publish-service=namespace/nginx-ingress-controller-svcname
```

For more details see here: [nginx-ingress external-dns](https://github.com/kubernetes-incubator/external-dns/blob/master/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)
136

137
138
139
140
Connect your `kubectl` client to the cluster you want to test ExternalDNS with.
Then apply one of the following manifests file to deploy ExternalDNS.

### Manifest (for clusters without RBAC enabled)
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
```yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: external-dns
spec:
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: external-dns
    spec:
      containers:
      - name: external-dns
156
        image: registry.opensource.zalan.do/teapot/external-dns:v0.5.2
157
158
        args:
        - --source=service
jmo808's avatar
jmo808 committed
159
        - --source=ingress
160
161
162
163
164
165
166
167
168
169
170
171
172
        - --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
        - --provider=azure
        - --azure-resource-group=externaldns # (optional) use the DNS zones from the tutorial's resource group
        volumeMounts:
        - name: azure-config-file
          mountPath: /etc/kubernetes
          readOnly: true
      volumes:
      - name: azure-config-file
        secret:
          secretName: azure-config-file
```

173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
### Manifest (for clusters with RBAC enabled)
```yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: external-dns
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
  name: external-dns
rules:
- apiGroups: [""]
  resources: ["services"]
  verbs: ["get","watch","list"]
188
189
190
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get","watch","list"]
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
- apiGroups: ["extensions"] 
  resources: ["ingresses"] 
  verbs: ["get","watch","list"]
---
apiVersion: rbac.authorization.k8s.io/v1beta1
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: default
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: external-dns
spec:
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: external-dns
    spec:
      serviceAccountName: external-dns
      containers:
      - name: external-dns
223
        image: registry.opensource.zalan.do/teapot/external-dns:v0.5.2
224
225
        args:
        - --source=service
jmo808's avatar
jmo808 committed
226
        - --source=ingress
227
228
229
230
231
232
233
234
235
236
237
238
239
        - --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
        - --provider=azure
        - --azure-resource-group=externaldns # (optional) use the DNS zones from the tutorial's resource group
        volumeMounts:
        - name: azure-config-file
          mountPath: /etc/kubernetes
          readOnly: true
      volumes:
      - name: azure-config-file
        secret:
          secretName: azure-config-file
```

240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
Create the deployment for ExternalDNS:

```
$ kubectl create -f externaldns.yaml
```

## Deploying an Nginx Service

Create a service file called 'nginx.yaml' with the following contents:

```yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: nginx
spec:
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx
        name: nginx
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
jmo808's avatar
jmo808 committed
270
  name: nginx-svc
271
spec:
jmo808's avatar
jmo808 committed
272
273
274
275
  ports:
  - port: 80
    protocol: tcp
    targetPort: 80
276
277
  selector:
    app: nginx
jmo808's avatar
jmo808 committed
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
  type: ClusterIP
  
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: nginx
  annotations:
    kubernetes.io/ingress.class: nginx
spec:
  rules:
  - host: server.example.com
    http:
      paths:
      - backend:
          serviceName: nginx-svc
          servicePort: 80
        path: /
296
297
```

jmo808's avatar
jmo808 committed
298
When using external-dns 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.
299

jmo808's avatar
jmo808 committed
300
Create the deployment, service and ingress object:
301
302
303
304
305

```
$ kubectl create -f nginx.yaml
```

jmo808's avatar
jmo808 committed
306
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. 
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327

## Verifying Azure DNS records

Run the following command to view the A records for your Azure DNS zone:

```
$ az network dns record-set a list -g externaldns -z example.com
```

Substitute the zone for the one created above if a different domain was used.

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).

## Delete Azure Resource Group

Now that we have verified that ExternalDNS will automatically manage Azure DNS records, we can delete the tutorial's
resource group:

```
$ az group delete -n externaldns
```