AWSハイブリッド構成にてRoute 53 Private Hosted Zoneでサブドメインを管理する

# 前提となる環境

* オンプレミス環境に既に権威サーバがある(例: `example.org`)
* AWS Direct ConnectやVPNでオンプレミス環境とAmazon VPCを接続している

# やりたいこと

* Route 53 Private Hosted Zoneでサブドメインを管理したい(例: `sub.example.org`) 
* サブドメインをオンプレミス側からでも解決できるようにしたい

# 課題

* Route 53 Private Hosted Zoneがサブドメインの委任に対応していない
* Amazon DNSがVPC専用のため、オンプレミスからのリクエストに直接応答できない

# モチベーション

クラスメソッドさんがまとめられた[AWSハイブリッド構成のDNS設計レシピ](https://dev.classmethod.jp/cloud/aws-hybrid-cloud-dns-designs/)の **設計レシピ5 : オンプレミスとAWSの権威サーバーを併用する** では、上記課題があるためにRoute 53は使わずに、DNSキャッシュサーバと権威サーバをVPC内に構築するという設計パターンが紹介されています。

しかし、APIで色々とコントロールできるRoute 53はやっぱり魅力的なわけです。例えば弊社ではAWS上にKubernetesを構築して開発環境用の各種サーバを運用しているのですが、そうするとDNSレコードの登録をKubernetesへのデプロイと連動させて自動化したい、というモチベーションがありました。

また、バックアップ運用など、耐障害を考慮したDNSサーバを自前で構築・運用したくないというのもモチベーションのひとつです。Route 53のSLAは100%であり魅力的です。

というわけで本記事では、VPC内に権威サーバとして振る舞いつつバックエンドにRoute 53 Private Hosted Zoneを使用するDNSサーバを配置し、上記課題を解決する方式について説明したいと思います。

# 実現方式

前述のとおり、

* 権威サーバをVPC内に構築する
* その権威サーバでは直接サブドメイン管理をせずに、Amazon DNSにリクエストを委譲してRoute 53 Private Hosted Zoneにて名前解決を行う

ということを実現すればよさそうです。今回実現にあたり、[CoreDNS](https://coredns.io/) に自作プラグイン([Amazon DNS plugin](https://github.com/wadahiro/coredns-amazondns))を追加することで対応しています。構成図としては以下のようになります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/35233/4e3c6efa-5110-d703-ca6d-0f60dbf79ae7.png)

※BINDなどのよく使われるDNSサーバはフォワーダーとしては設定できますが、権威サーバとして振る舞うように設定ができませんでした。もし設定できる/他のDNSサーバでできるよ、という情報がありましたら是非教えていただきたいです :bow:

# CoreDNSとは

CoreDNSについて簡単に説明しておきます。

CoreDNSはGolangで実装されたDNSサーバで、[KubernetesのデフォルトDNSとしてkube-dnsの代わりとなることが予定されています](https://coredns.io/2018/01/29/deploying-kubernetes-with-coredns-using-kubeadm/)。

> Currently, CoreDNS is Alpha in Kubernetes 1.9. We have a roadmap which will make CoreDNS Beta in version 1.10 and eventually be the default DNS, replacing kube-dns.

CoreDNSの特徴として、プラグインでDNSのリクエスト・レスポンス処理の拡張が簡単に行えるという点があります。

今回作成したAmazon DNS pluginでは、リクエストを受け付けるとVPC内のAmazon DNSに対してそのままリクエストをフォワードし、レスポンスを返す際に権威サーバの情報を付加して返す、ということをやっています。

CoreDNSそのものについては、Qiitaにも[CoreDNS入門](https://qiita.com/kanga/items/e74038f25d1f53ca6ade)記事がありますのでそちらを参照してみてください。

# CoreDNS(+Amazon DNS plugin)の設定

CoreDNSの設定はとてもシンプルです。ゾーンごとに適用したいプラグインの設定を並べる形となります。今回作成したAmazon DNS pluginの設定としては、

* `soa`: SOAレコード設定
* `ns`: NSレコード設定(複数設定可)
* `nsa`: NSレコードで設定したFQDNを解決するためのAレコード設定(複数設定可)

を行うようにしています。例えば、サブドメインとして`sub.example.org`を管理する設定としては、以下のような設定になります。

```
. {
    amazondns sub.example.org {
        soa "sub.example.org 3600 IN SOA ns.sub.example.org hostmaster.sub.example.org (2018030619 3600 900 1209600 900)"
        ns "sub.example.org 3600 IN NS ns.sub.example.org"
        nsa "ns.sub.example.org 3600 IN A 192.168.0.10"
    }
}
```

また、ネームサーバが複数台構成としたい場合(例えば、AWSなのでマルチAZ構成でAZ単位にネームサーバを配置したい場合)があるかと思いますが、その場合は下記のように複数設定ができるようにしています。

```
. {
    amazondns sub.example.org {
        soa "sub.example.org 3600 IN SOA ns1.sub.example.org hostmaster.sub.example.org (2018030619 3600 900 1209600 900)"
        ns "sub.example.org 3600 IN NS ns1.sub.example.org"
        ns "sub.example.org 3600 IN NS ns2.sub.example.org"
        nsa "ns1.sub.example.org 3600 IN A 192.168.0.10"
        nsa "ns2.sub.example.org 3600 IN A 192.168.0.130"
    }
}
```

# 動作確認

## 事前設定

上記CoreDNSの設定でCoreDNSをVPC内で起動しつつ、AWSの環境を

* Route 53 Private Hosted Zoneを`sub.example.org`というFQDNで作成し、VPCにアタッチ
* 上記ゾーンに`test.sub.example.org`のAレコードを登録
* ELBを作成し、上記ゾーンに`lb.sub.example.org`でELBのCNAMEレコードを登録

のように設定しておきます。

## 名前解決

CoreDNSを起動しているサーバにて、`dig`コマンドを使って動作を確認してみます。権威サーバとしての挙動を確認したいので、`+norecurse`をつけています。

`test.sub.example.org`のAレコードが返るだけでなく、**AUTHORITY SECTION**、**ADDITIONAL SECTION**が合わせて返ってきているところがポイントです。

```
> dig @localhost test.sub.example.org +norecurse

; <<>> DiG 9.11.1 <<>> @localhost test.sub.example.org +norecurse
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 28681
;; flags: qr aa ra; QUERY: 1, ANSWER: 1, AUTHORITY: 2, ADDITIONAL: 3

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
; COOKIE: 23246de45b4a3601 (echoed)
;; QUESTION SECTION:
;test.sub.example.org.        IN  A

;; ANSWER SECTION:
test.sub.example.org.   3600  IN  A  10.0.0.10

;; AUTHORITY SECTION:
sub.example.org.        3600  IN  NS  ns1.sub.example.org.
sub.example.org.        3600  IN  NS  ns2.sub.example.org.

;; ADDITIONAL SECTION:
ns1.sub.example.org.    3600  IN  A   192.168.0.10
ns2.sub.example.org.    3600  IN  A   192.168.0.130

;; Query time: 12 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Tue Feb 20 15:11:55 JST 2018
;; MSG SIZE  rcvd: 146
```

NSレコードも確認してみます。こちらはCoreDNSの設定ファイルに記述したNSレコードの設定が静的に返ってきます。

```
> dig @localhost sub.example.org ns

; <<>> DiG 9.11.1 <<>> @localhost sub.example.org ns +norecurse
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 2719
;; flags: qr aa ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 3

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
; COOKIE: c1c3332966dba8fd (echoed)
;; QUESTION SECTION:
;sub.example.org.            IN  NS

;; ANSWER SECTION:
sub.example.org.       3600  IN  NS  ns1.sub.example.org.
sub.example.org.       3600  IN  NS  ns2.sub.example.org.

;; ADDITIONAL SECTION:
ns1.sub.example.org.   3600  IN  A   192.168.0.10
ns2.sub.example.org.   3600  IN  A   192.168.0.130

;; Query time: 1 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Tue Feb 20 15:08:27 JST 2018
;; MSG SIZE  rcvd: 125
```

最後に、作成しておいたELBのCNAMEレコードを解決してみます。通常、CNAMEレコードが返ってくるところなのですが、Amazon DNS pluginの内部処理でAレコードに敢えて変換して返しています(Route 53のAliasのような挙動になります)。こうするとこで、問い合わせ元がAmazon DNSに到達できない場所、つまりVPC外でも名前解決できるようにしています。

```
> dig @localhost lb.sub.example.org

; <<>> DiG 9.11.1 <<>> @localhost test.sub.example.org +norecurse
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 63630
;; flags: qr aa ra; QUERY: 1, ANSWER: 2, AUTHORITY: 2, ADDITIONAL: 3

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
; COOKIE: 89a840e16b4d3fc7 (echoed)
;; QUESTION SECTION:
;lb.sub.example.org.         IN  A

;; ANSWER SECTION:
lb.sub.example.org.    54    IN  A   10.0.0.16
lb.sub.example.org.    54    IN  A   10.0.0.132

;; AUTHORITY SECTION:
sub.example.org.       3600  IN  NS  ns1.sub.example.org.
sub.example.org.       3600  IN  NS  ns2.sub.example.org.

;; ADDITIONAL SECTION:
ns1.sub.example.org.   3600  IN  A   192.168.0.10
ns2.sub.example.org.   3600  IN  A   192.168.0.130

;; Query time: 11 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Tue Feb 27 11:20:08 JST 2018
;; MSG SIZE  rcvd: 174
```

# 上位の委任元へのフォワード設定

CoreDNSの設定に下記のように上位ドメインに対するフォワード設定を追記しておきます。これにはCoreDNSに付属の[proxyプラグイン](https://coredns.io/plugins/proxy/)を利用しています。こうすることで、VPC内からこのDNSサーバに対して上位ドメインの名前解決がリクエストされた場合は、オンプレミス側のDNSサーバに問い合わせがフォワードされ名前解決が可能になります。

```
example.org {
    proxy . /etc/resolv.conf
}
```

`/etc/resolv.conf` にはオンプレミス側のネームサーバを設定しておきます。

注意点として、VPC内ではデフォルトではAmazon DNSがDNSサーバとして設定されるため、VPC内のDNSクライアント側では明示的にCoreDNSのサーバをDNSサーバとして設定するか、VPCのデフォルトのDNSサーバを設定変更する必要があります。

# 上位の委任元のDNSサーバの設定

上位のドメイン(例: `example.org`)を管理しているDNSサーバにはサブドメイン(例: `sub.example.org`)への委任を示すグルーレコードの登録をお忘れなく。この登録が完了すれば、オンプレ側のネットワークからサブドメイン配下のFQDNの名前解決ができるようになります。

# その他考慮点

せっかくRoute 53を利用しても、CoreDNSサーバの障害対策ができていなければSLA100%の意味がありません。今運用している環境では、AZ障害も考慮して、

* AZを2つ利用
* AZ単位にCoreDNSサーバを構築
* 事前にENIを作成して固定プライベートIP化しておき、EC2起動時にLambdaで自動アタッチさせる
    * [How do I automatically attach a second elastic network interface (ENI) to an instance launched through Auto Scaling?](https://aws.amazon.com/jp/premiumsupport/knowledge-center/attach-second-eni-auto-scaling/) が参考になります。
* AutoScalingGroupをAZ単位に作成してMaxとMinを1台としておき、EC2障害時に自動復旧させる
* CoreDNSのログはCloudWatch Logsで外部に保存

という構成にしています。また、上記環境の構築をCloudFormationで自動化もしており、

* cfn-initでEC2起動時にCloudWatch LogsのエージェントのインストールやCoreDNSのインストール・設定させる

ということもしております。これにより、他のVPCにも横展開が容易にできるようにもしています。


# まとめ
AWSハイブリッド構成にて、Route 53 Private Hosted Zoneでサブドメインを管理する方式の一つとして、CoreDNSを活用した方式を紹介しました。同じようにオンプレ-AWSのハイブリッド環境でRoute 53を活用したくて悩んでいらっしゃる方の助けになれば幸いです。

# 参考

* [AWSハイブリッド構成のDNS設計レシピ](https://dev.classmethod.jp/cloud/aws-hybrid-cloud-dns-designs/)
* [オンプレからAWSのDNSを引く高可用性な構成](https://dev.classmethod.jp/cloud/aws/direct-connect-dns/)
* [Expose internal route53 DNS over VPN to on-premise ActiveDirectory](https://serverfault.com/questions/875323/expose-internal-route53-dns-over-vpn-to-on-premise-activedirectory) ← 同じことをやりたいという質問ですが、良い回答は得られていませんでしたので、今回作成したCoreDNS + Amazon DNS pluginを紹介しています。