ExternalDNSをさくらのクラウドに対応させたので使ってみる

本稿は、さくらのアドベントカレンダー2022 24日目の投稿です。

https://qiita.com/advent-calendar/2022/sakura

さくらインターネットの相馬です。
今回は、さくらのクラウド DNSアプライアンスに対応させたExternalDNSの使い方を紹介させていただきます。

**ExternalDNSはDNSゾーンを操作するため、環境に影響が出ないことを保証できません。**
**実際にお試しになる場合は自己責任でお願いします。**



# ExternalDNSとは

[ExternalDNS](https://github.com/kubernetes-sigs/external-dns)は、Kubernetesにおいてサービス公開のために `Ingress` リソースや `Service` リソースを作成/更新した際にDNSレコードを動的に設定してくれるアドオンです。

例えば、以下のような `Ingress` リソースを作成することで、ドメイン名 `example.com` に対するAレコードを登録することができます。

```
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: example
  namespace: example
spec:
  ingressClassName: traefik
  rules:
  - host: "example.com"
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: example
            port:
              number: 80
```

&nbsp;



現状のExternalDNS(v0.13.1現在)は、DNSプロバイダとしてさくらのクラウドは対応しておりません。
したがって今回、さくらのクラウド DNSアプライアンスをExternalDNSに対応させて使ってみました。

ExternalDNSにおけるプロバイダの開発方法につきましては下記の記事で紹介しておりますのでご興味がありましたら参照してください。

https://zenn.dev/sosomasox/articles/cf2919cdcc9578



# デプロイ

まず、下記マニフェストをKubernetesクラスタに適用し、ネームスペースの作成、RBACの設定を行います。

```
---
apiVersion: v1
kind: Namespace
metadata:
  name: external-dns
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: external-dns
  namespace: external-dns
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: external-dns
rules:
- apiGroups: [""]
  resources: ["services","endpoints","pods"]
  verbs: ["get","watch","list"]
- apiGroups: ["networking.k8s.io"]
  resources: ["ingresses"]
  verbs: ["get","watch","list"]
- apiGroups: [""]
  resources: ["nodes"]
  verbs: ["list","watch"]
---
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
---
```

&nbsp;

次に、さくらのクラウドのAPIキー情報を持った `Secret` リソースとさくらのクラウドに対応したExternalDNSをデプロイします。
(実際にAPIキーなどの機密情報を保管する際は[Sealed Secrets](https://github.com/bitnami-labs/sealed-secrets)といった `Secret` リソースをセキュアに管理する方法を利用してください。)


`Secret` リソースにおいて、`SAKURACLOUD_ACCESS_TOKEN` にはAPIキーのアクセストークンを、`SAKURACLOUD_ACCESS_TOKEN_SECRET` にはAPIキーのアクセストークンシークレットを指定します。

```
---
apiVersion: v1
kind: Secret
metadata:
  name: sakuracloud
  namespace: external-dns
type: Opaque
data:
  SAKURACLOUD_ACCESS_TOKEN: "<your sakuracloud access token>"
  SAKURACLOUD_ACCESS_TOKEN_SECRET: "<your sakuracloud access token secret>"
---
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
    spec:
      serviceAccountName: external-dns
      containers:
      - name: external-dns
        image: sosomasox/external-dns:latest
        args:
        - --source=service
        - --source=ingress
        - --domain-filter=example.com
        - --provider=sakuracloud
        #- --log-level=debug # debug only
        env:
        - name: SAKURACLOUD_ACCESS_TOKEN
          valueFrom:
            secretKeyRef:
              name: sakuracloud
              key: SAKURACLOUD_ACCESS_TOKEN
        - name: SAKURACLOUD_ACCESS_TOKEN_SECRET
          valueFrom:
            secretKeyRef:
              name: sakuracloud
              key: SAKURACLOUD_ACCESS_TOKEN_SECRET
---
```

&nbsp;

APIキー作成時には適切なアクセスレベル(`作成・削除`の権限が必要)を設定する必要があります。
APIキーの作成方法につきましては下記のドキュメントを参照ください。

https://manual.sakura.ad.jp/cloud/api/apikey.html

&nbsp;

下記はさくらのクラウドに対応したExternalDNSを稼働させる`Deployment` リソースの `spec` の部分を抜粋したものです。

`spec.containers.args` の `--domain-filter` にはさくらのクラウド DNSアプライアンスで作成したDNSゾーンを指定してください。
また、`--provider` にはDNSプロバイダとしてさくらのクラウドを利用することを表す `sakuracloud` を指定します。
(ログにおいてデバッグメッセージが不必要な場合は `--log-level` をコメントアウト、またはその他のログレベルにしてください。)

```
    spec:
      serviceAccountName: external-dns
      containers:
      - name: external-dns
        image: sosomasox/external-dns:latest
        args:
        - --source=service
        - --source=ingress
        - --domain-filter=example.com
        - --provider=sakuracloud
        - --log-level=debug # debug only
        env:
        - name: SAKURACLOUD_ACCESS_TOKEN
          valueFrom:
            secretKeyRef:
              name: sakuracloud
              key: SAKURACLOUD_ACCESS_TOKEN
        - name: SAKURACLOUD_ACCESS_TOKEN_SECRET
          valueFrom:
            secretKeyRef:
              name: sakuracloud
              key: SAKURACLOUD_ACCESS_TOKEN_SECRET
```



# 動作確認

まず、下記マニフェストをKubernetesクラスタに適用し、Nginxのコンテナを稼働させ、`Ingress` リソースでサービス公開します。
(本環境では[Ingressコントローラー](https://kubernetes.io/ja/docs/concepts/services-networking/ingress-controllers/)として[traefik](https://doc.traefik.io/traefik/)を利用しています。)

```
---
apiVersion: v1
kind: Namespace
metadata:
  name: test-web
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: test-web
  namespace: test-web
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: test-web
  template:
    metadata:
      labels:
        app.kubernetes.io/name: test-web
    spec:
      containers:
      - name: test-web
        image: nginx:1.23
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: test-web
  namespace: test-web
spec:
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
  selector:
    app.kubernetes.io/name: test-web
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: test-web
  namespace: test-web
spec:
  ingressClassName: traefik
  rules:
  - host: test.sosomasox.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: test-web
            port:
              number: 80
---
```

&nbsp;

次に、Kuberneteクラスタ上にリソースが作成されたことを確認します。

```
$ kubectl -n test-web get all,ingress
NAME                            READY   STATUS    RESTARTS   AGE
pod/test-web-586cc45d97-njrvw   1/1     Running   0          5s

NAME               TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
service/test-web   ClusterIP   10.96.207.16   <none>        80/TCP    5s

NAME                       READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/test-web   1/1     1            1           5s

NAME                                  DESIRED   CURRENT   READY   AGE
replicaset.apps/test-web-586cc45d97   1         1         1       5s

NAME                                 CLASS     HOSTS                ADDRESS        PORTS   AGE
ingress.networking.k8s.io/test-web   traefik   test.sosomasox.com   172.18.255.1   80      5s
```

&nbsp;

ExternalDNSによって、`Ingress` リソースの情報に対応したDNSレコードがさくらのクラウド DNSアプライアンス上に設定されているか、クラウドのコントロールパネルから確認してみます。

↓ちゃんと設定されている！
![DNSゾーン.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3075712/c9bcf9f1-74c4-3713-c012-c1aba602a69b.png)


また、ExternalDNSのコンテナログにもDNSレコードの作成が行われていることが確認できました。

```
time="2022-12-22T07:06:07Z" level=debug msg="No endpoints could be generated from service test-web/test-web"
time="2022-12-22T07:06:07Z" level=debug msg="Endpoints generated from ingress: test-web/test-web: [test.sosomasox.com 0 IN A  172.18.255.1 []]"
time="2022-12-22T07:06:07Z" level=debug msg="change.Create: iaas.DNSRecord{Name:\"test\", Type:\"A\", RData:\"172.18.255.1\", TTL:300}"
time="2022-12-22T07:06:07Z" level=debug msg="change.Create: iaas.DNSRecord{Name:\"test\", Type:\"TXT\", RData:\"heritage=external-dns,external-dns/owner=default,external-dns/resource=ingress/test-web/test-web\", TTL:300}"
time="2022-12-22T07:06:07Z" level=debug msg="change.Create: iaas.DNSRecord{Name:\"a-test\", Type:\"TXT\", RData:\"heritage=external-dns,external-dns/owner=default,external-dns/resource=ingress/test-web/test-web\", TTL:300}"
```



# 最後に

本稿では、さくらのクラウド DNSアプライアンスに対応させたExternalDNSの使い方を紹介させていただきました。

現状、さくらのクラウドに対応させたExternalDNSは私個人でメンテナンスしております。
もし、さくらのクラウド対応ExternalDNSに需要がありそうでしたら、さくらインターネットでメンテナンスし、Helmチャート化とリポジトリの公開、ひいては[ExternalDNSのupstream](https://github.com/kubernetes-sigs/external-dns)へのマージなどできればと思います。