aws.md 13.3 KB
Newer Older
1
2
# Setting up ExternalDNS for Services on AWS

3
This tutorial describes how to setup ExternalDNS for usage within a Kubernetes cluster on AWS. Make sure to use **>=0.4** version of ExternalDNS for this tutorial
4

5
6
7
8
9
10
11
12
13
## IAM Policy

The following IAM Policy document allows ExternalDNS to update Route53 Resource
Record Sets and Hosted Zones. You'll want to create this Policy in IAM first. In
our example, we'll call the policy AllowExternalDNSUpdates (but you can call
it whatever you prefer).

If you prefer, you may fine-tune the policy to permit updates only to explicit
Hosted Zone IDs.
14
15
16

```json
{
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "route53:ChangeResourceRecordSets"
      ],
      "Resource": [
        "arn:aws:route53:::hostedzone/*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "route53:ListHostedZones",
        "route53:ListResourceRecordSets"
      ],
      "Resource": [
        "*"
      ]
    }
  ]
39
40
41
}
```

42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
## Create IAM Role

You'll need to create an IAM Role that can be assumed by the ExternalDNS Pod.
Note the role name; you'll need to refer to it in the K8S manifest below.

Attach the AllowExternalDNSUpdates IAM Policy (above) to the role.

The trust relationship associated with the IAM Role will vary depending on how
you've configured your Kubernetes cluster:

### Amazon EKS

If your EKS-managed cluster is >= 1.13 and was created after 2019-09-04, refer
to the [Amazon EKS
documentation](https://docs.aws.amazon.com/eks/latest/userguide/create-service-account-iam-policy-and-role.html)
for instructions on how to create the IAM Role. Otherwise, you will need to use
kiam or kube2iam.

### kiam

If you're using [kiam](https://github.com/uswitch/kiam), follow the
[instructions](https://github.com/uswitch/kiam/blob/master/docs/IAM.md) for
creating the IAM role.

### kube2iam

If you're using [kube2iam](https://github.com/jtblin/kube2iam), follow the
instructions for creating the IAM Role.

### EC2 Instance Role (not recommended)

**:warning: WARNING: This will grant all pods on the node the ability to
manipulate Route 53 Resource Record Sets. If exploited by an attacker, this
could lead to a serious security and/or availability incident. For this reason,
it is not recommended.**

Create an IAM Role for your EC2 instances as described in the [Amazon EC2
documentation](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html).
Then, attach the associated Instance Profile to the EC2 instances that comprise
your K8S cluster.

For this method to work, you must permit your pods the ability to access the EC2
instance metadata service (169.254.169.254). This is allowed by default.
85

86
87
88
## Set up a hosted zone

*If you prefer to try-out ExternalDNS in one of the existing hosted-zones you can skip this step*
89
90
91
92

Create a DNS zone which will contain the managed DNS records.

```console
93
$ aws route53 create-hosted-zone --name "external-dns-test.my-org.com." --caller-reference "external-dns-test-$(date +%s)"
94
95
```

96
Make a note of the ID of the hosted zone you just created, which will serve as the value for my-hostedzone-identifier.
97
98

```console
99
$ aws route53 list-hosted-zones-by-name --output json --dns-name "external-dns-test.my-org.com." | jq -r '.HostedZones[0].Id'
100
/hostedzone/ZEWFWZ4R16P7IB
101
102
103
104
105
```

Make a note of the nameservers that were assigned to your new zone.

```console
106
$ aws route53 list-resource-record-sets --output json --hosted-zone-id "/hostedzone/ZEWFWZ4R16P7IB" \
107
    --query "ResourceRecordSets[?Type == 'NS']" | jq -r '.[0].ResourceRecords[].Value'
108
109
ns-5514.awsdns-53.org.
...
110
111
112
113
```

In this case it's the ones shown above but your's will differ.

114
## Deploy ExternalDNS
115
116

Connect your `kubectl` client to the cluster you want to test ExternalDNS with.
117
Then apply one of the following manifests file to deploy ExternalDNS. You can check if your cluster has RBAC by `kubectl api-versions | grep rbac.authorization.k8s.io`.
118

119
120
For clusters with RBAC enabled, be sure to choose the correct `namespace`.

121
### Manifest (for clusters without RBAC enabled)
122
```yaml
123
apiVersion: apps/v1
124
125
126
127
128
129
kind: Deployment
metadata:
  name: external-dns
spec:
  strategy:
    type: Recreate
130
131
132
  selector:
    matchLabels:
      app: external-dns
133
134
135
136
  template:
    metadata:
      labels:
        app: external-dns
137
138
139
140
      # If you're using kiam or kube2iam, specify the following annotation.
      # Otherwise, you may safely omit it.
      annotations:
        iam.amazonaws.com/role: arn:aws:iam::ACCOUNT-ID:role/IAM-SERVICE-ROLE-NAME
141
142
143
    spec:
      containers:
      - name: external-dns
144
        image: registry.opensource.zalan.do/teapot/external-dns:latest
145
146
        args:
        - --source=service
147
        - --source=ingress
148
        - --domain-filter=external-dns-test.my-org.com # will make ExternalDNS see only the hosted zones matching provided domain, omit to process all available hosted zones
149
        - --provider=aws
150
        - --policy=upsert-only # would prevent ExternalDNS from deleting any records, omit to enable full synchronization
151
        - --aws-zone-type=public # only look at public hosted zones (valid values are public, private or no value for both)
152
        - --registry=txt
153
        - --txt-owner-id=my-hostedzone-identifier
154
155
```

156
157
158
159
160
161
162
### Manifest (for clusters with RBAC enabled)

```yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: external-dns
163
164
165
166
167
  # If you're using Amazon EKS with IAM Roles for Service Accounts, specify the following annotation.
  # Otherwise, you may safely omit it.
  annotations:
    # Substitute your account ID and IAM service role name below.
    eks.amazonaws.com/role-arn: arn:aws:iam::ACCOUNT-ID:role/IAM-SERVICE-ROLE-NAME
168
169
170
171
172
173
174
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
  name: external-dns
rules:
- apiGroups: [""]
Alfred Krohmer's avatar
Alfred Krohmer committed
175
  resources: ["services","endpoints","pods"]
176
  verbs: ["get","watch","list"]
177
178
- apiGroups: ["extensions"]
  resources: ["ingresses"]
179
  verbs: ["get","watch","list"]
180
181
- apiGroups: [""]
  resources: ["nodes"]
Paweł Prażak's avatar
Paweł Prażak committed
182
  verbs: ["list","watch"]
183
184
185
186
187
188
189
190
191
192
193
194
195
196
---
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
---
197
apiVersion: apps/v1
198
199
200
201
202
203
kind: Deployment
metadata:
  name: external-dns
spec:
  strategy:
    type: Recreate
204
205
206
  selector:
    matchLabels:
      app: external-dns
207
208
209
210
  template:
    metadata:
      labels:
        app: external-dns
211
212
213
214
      # If you're using kiam or kube2iam, specify the following annotation.
      # Otherwise, you may safely omit it.
      annotations:
        iam.amazonaws.com/role: arn:aws:iam::ACCOUNT-ID:role/IAM-SERVICE-ROLE-NAME
215
216
217
218
    spec:
      serviceAccountName: external-dns
      containers:
      - name: external-dns
219
        image: registry.opensource.zalan.do/teapot/external-dns:latest
220
221
222
223
224
225
226
227
        args:
        - --source=service
        - --source=ingress
        - --domain-filter=external-dns-test.my-org.com # will make ExternalDNS see only the hosted zones matching provided domain, omit to process all available hosted zones
        - --provider=aws
        - --policy=upsert-only # would prevent ExternalDNS from deleting any records, omit to enable full synchronization
        - --aws-zone-type=public # only look at public hosted zones (valid values are public, private or no value for both)
        - --registry=txt
228
        - --txt-owner-id=my-hostedzone-identifier
229
230
      securityContext:
        fsGroup: 65534 # For ExternalDNS to be able to read Kubernetes and AWS token files
231
232
```

233
234
235
236
237
238
239
240
## Arguments

This list is not the full list, but a few arguments that where chosen.

### aws-zone-type

`aws-zone-type` allows filtering for private and public zones

vaegt's avatar
vaegt committed
241
242
243
244
245
246
## Annotations

Annotations which are specific to AWS.

### alias

Piotr Jander's avatar
Piotr Jander committed
247
`external-dns.alpha.kubernetes.io/alias` if set to `true` on an ingress, it will create an ALIAS record when the target is an ALIAS as well. To make the target an alias, the ingress needs to be configured correctly as described in [the docs](./nginx-ingress.md#with-a-separate-tcp-load-balancer). In particular, the argument `--publish-service=default/nginx-ingress-controller` has to be set on the `nginx-ingress-controller` container. If one uses the `nginx-ingress` Helm chart, this flag can be set with the `controller.publishService.enabled` configuration option.
248
249
250
251
252
253
254
255

## Verify ExternalDNS works (Ingress example)

Create an ingress resource manifest file.

> For ingress objects ExternalDNS will create a DNS record based on the host specified for the ingress object.

```yaml
256
apiVersion: networking.k8s.io/v1beta1
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
kind: Ingress
metadata:
  name: foo
  annotations:
    kubernetes.io/ingress.class: "nginx" # use the one that corresponds to your ingress controller.
spec:
  rules:
  - host: foo.bar.com
    http:
      paths:
      - backend:
          serviceName: foo
          servicePort: 80
```

## Verify ExternalDNS works (Service example)
273

274
275
Create the following sample application to test that ExternalDNS works.

276
277
> For services ExternalDNS will look for the annotation `external-dns.alpha.kubernetes.io/hostname` on the service and use the corresponding value.

278
279
> If you want to give multiple names to service, you can set it to external-dns.alpha.kubernetes.io/hostname with a comma separator.

280
281
282
283
284
285
```yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx
  annotations:
286
    external-dns.alpha.kubernetes.io/hostname: nginx.external-dns-test.my-org.com
287
288
289
290
spec:
  type: LoadBalancer
  ports:
  - port: 80
291
    name: http
292
293
294
295
296
297
    targetPort: 80
  selector:
    app: nginx

---

298
apiVersion: apps/v1
299
300
301
302
kind: Deployment
metadata:
  name: nginx
spec:
303
304
305
  selector:
    matchLabels:
      app: nginx
306
307
308
309
310
311
312
313
314
315
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx
        name: nginx
        ports:
        - containerPort: 80
316
          name: http
317
318
319
320
321
```

After roughly two minutes check that a corresponding DNS record for your service was created.

```console
322
$ aws route53 list-resource-record-sets --output json --hosted-zone-id "/hostedzone/ZEWFWZ4R16P7IB" \
323
    --query "ResourceRecordSets[?Name == 'nginx.external-dns-test.my-org.com.']|[?Type == 'A']"
324
325
[
    {
326
327
328
329
330
331
332
333
334
335
336
337
338
      "AliasTarget": {
          "HostedZoneId": "ZEWFWZ4R16P7IB",
          "DNSName": "ae11c2360188411e7951602725593fd1-1224345803.eu-central-1.elb.amazonaws.com.",
          "EvaluateTargetHealth": true
      },
      "Name": "external-dns-test.my-org.com.",
      "Type": "A"
    },
    {
      "Name": "external-dns-test.my-org.com",
      "TTL": 300,
      "ResourceRecords": [
          {
339
              "Value": "\"heritage=external-dns,external-dns/owner=my-hostedzone-identifier\""
340
341
342
          }
      ],
      "Type": "TXT"
343
344
345
346
    }
]
```

347
348
Note created TXT record alongside ALIAS record. TXT record signifies that the corresponding ALIAS record is managed by ExternalDNS. This makes ExternalDNS safe for running in environments where there are other records managed via other means.

349
350
351
Let's check that we can resolve this DNS name. We'll ask the nameservers assigned to your zone first.

```console
352
$ dig +short @ns-5514.awsdns-53.org. nginx.external-dns-test.my-org.com.
353
354
355
356
357
358
ae11c2360188411e7951602725593fd1-1224345803.eu-central-1.elb.amazonaws.com.
```

If you hooked up your DNS zone with its parent zone correctly you can use `curl` to access your site.

```console
359
$ curl nginx.external-dns-test.my-org.com.
360
361
362
363
364
365
366
367
368
369
370
371
372
373
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
</head>
<body>
...
</body>
</html>
```

Ingress objects on AWS require a separately deployed Ingress controller which we'll describe in another tutorial.

374
375
376
377
378
379
380
381
382
383
384
## Custom TTL

The default DNS record TTL (Time-To-Live) is 300 seconds. You can customize this value by setting the annotation `external-dns.alpha.kubernetes.io/ttl`.
e.g., modify the service manifest YAML file above:

```yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx
  annotations:
385
    external-dns.alpha.kubernetes.io/hostname: nginx.external-dns-test.my-org.com
386
387
388
389
390
391
392
    external-dns.alpha.kubernetes.io/ttl: 60
spec:
    ...
```

This will set the DNS record's TTL to 60 seconds.

393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
## Routing policies

Route53 offers [different routing policies](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/routing-policy.html). The routing policy for a record can be controlled with the following annotations:

* `external-dns.alpha.kubernetes.io/set-identifier`: this **needs** to be set to use any of the following routing policies

For any given DNS name, only **one** of the following routing policies can be used:

* Weighted records: `external-dns.alpha.kubernetes.io/aws-weight`
* Latency-based routing: `external-dns.alpha.kubernetes.io/aws-region`
* Failover:`external-dns.alpha.kubernetes.io/aws-failover`
* Geolocation-based routing:
  * `external-dns.alpha.kubernetes.io/aws-geolocation-continent-code`
  * `external-dns.alpha.kubernetes.io/aws-geolocation-country-code`
  * `external-dns.alpha.kubernetes.io/aws-geolocation-subdivision-code`
* Multi-value answer:`external-dns.alpha.kubernetes.io/aws-multi-value-answer`

410
411
412
413
414
415
416
417
## Clean up

Make sure to delete all Service objects before terminating the cluster so all load balancers get cleaned up correctly.

```console
$ kubectl delete service nginx
```

418
Give ExternalDNS some time to clean up the DNS records for you. Then delete the hosted zone if you created one for the testing purpose.
419
420

```console
421
$ aws route53 delete-hosted-zone --id /hostedzone/ZEWFWZ4R16P7IB
422
```