ちょっと便利に使う CoreDNS

この記事は Retty Advent Calendar 2020 15日目の記事です。

adventar.org

どうも。エンジニアの @pikatenor です。今年は小ネタでいきます。

早速本題ですが、CoreDNS のことは皆様ご存知でしょうか。おそらく Kubernetes 絡みで聞いたことあるという方も多いと思います。

coredns.io

CoreDNS は CNCF によってホストされている OSSDNS サーバーで、昨年1月には充分成熟したとして Graduated プロジェクトに位置づけられました。開発の主体をみても分かるように、KubernetesDNS Service として利用されることが多く、Kubernetes 1.11 からは kube-dns に代わり標準の実装として採用*1されています。

とはいえこの CoreDNS、単独で使ってもなかなか便利なやつなので、今回はぼちぼちと例を挙げながら紹介していく記事になります。

インストールと使い方

github.com

CoreDNS は Go で書かれており、単一バイナリの形式で GitHub のリリースページにて配布されています。インストールはダウンロードしてお好きなところに配置するだけです。

$ ./coredns -help
Usage of ./coredns:
  -conf string
        Corefile to load (default "Corefile")
  -dns.port string
        Default port (default "53")
  -pidfile string
        Path to write pid file
  -plugins
        List installed plugins
  -quiet
        Quiet mode (no initialization output)
  -version
        Show version

デフォルトでは起動したディレクトリ内の Corefile ファイルを設定ファイルとして読み込みます。

さて、設定ファイルの書き方の前に CoreDNS の基礎的な考え方について解説します。

CoreDNS は単独ではほとんど何も行いません。与えられたクエリにどう応答するかや、ロギング・ロードバランシングなどの処理も含めて、全てはプラグインに委ねられます。CoreDNS のコアのお仕事は DNS クエリを解釈し、設定ファイルに記述されたプラグインへ受け渡すところまでです。CoreDNS という名前なのにガワしかないとはこれいかに。

しかしそんなプラガブルなアーキテクチャのおかげで柔軟な設定ができるのが CoreDNS の魅力です。設定ファイルは次のように記述します。

# <ホストゾーン>:<リッスンポート> {
#     プラグイン
#     プラグイン {
#         プラグイン個別の設定
#     }
# }

. {
    log
    whoami
}

上記では入力クエリを標準出力に吐く log プラグインと、全てのクエリに対してリクエストを送ってきたクライアント自身の IP アドレスを返す whoami プラグインを指定しています。リッスンポートは省略した場合デフォルトの UDP 53 が使われます。デフォルトポートは -dns.port オプションで上書きできます。

以降はポートとして 1053 番を利用します。

ゾーン別に設定を書き分けることもできます。例えば example.com ゾーンに対しては個別の設定を適用したい場合、

.:1053 {
    log
    forward . 1.1.1.1
}

example.com:1053 {
    log
    whoami
}

のように書けば、

$ ./coredns -conf ./Corefile
.:1053
example.com.:1053
CoreDNS-1.8.0
linux/amd64, go1.15.3, 054c9ae
[INFO] 127.0.0.1:47336 - 34552 "A IN retty.me. udp 49 false 4096" NOERROR qr,rd,ra 85 0.00843798s
[INFO] 127.0.0.1:54759 - 25644 "A IN example.com. udp 52 false 4096" NOERROR qr,aa,rd 91 0.00009736s
$ dig retty.me -p 1053 @127.0.0.1

; <<>> DiG 9.16.7 <<>> retty.me -p 1053 @127.0.0.1
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 50149
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;retty.me.                      IN      A

;; ANSWER SECTION:
retty.me.               12      IN      A       54.64.106.126
retty.me.               12      IN      A       54.65.49.141

;; Query time: 6 msec
;; SERVER: 127.0.0.1#1053(127.0.0.1)
;; WHEN: Tue Dec 15 08:40:07 JST 2020
;; MSG SIZE  rcvd: 85

$ dig example.com -p 1053 @127.0.0.1

; <<>> DiG 9.16.7 <<>> example.com -p 1053 @127.0.0.1
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 63123
;; flags: qr aa rd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 3
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
; COOKIE: b7ce4118f738e4e6 (echoed)
;; QUESTION SECTION:
;example.com.                   IN      A

;; ADDITIONAL SECTION:
example.com.            0       IN      A       127.0.0.1
_udp.example.com.       0       IN      SRV     0 0 37338 .

;; Query time: 0 msec
;; SERVER: 127.0.0.1#1053(127.0.0.1)
;; WHEN: Tue Dec 15 08:40:09 JST 2020
;; MSG SIZE  rcvd: 114

example.com に対してのみ whoami プラグインが使われ、他のゾーンのクエリは forward プラグイン により 1.1.1.1 へ転送されます。プラグインは一部の例外*2を除き上から順に適用されます。

プラグインの有効化

公式プラグインについては in-tree で CoreDNS チーム自身がメンテナンスしており、それらは標準で配布されているバイナリに含まれています。公式プラグインの一覧はこちらです。

それ以外の外部プラグインを利用する場合は、CoreDNS を手動でビルドする必要があります。とはいえ手順は単純です。

CoreDNS のビルドには Go 1.12 以上が必要です。

$ git clone https://github.com/coredns/coredns
$ cd coredns
$ make

これだけで ./coredns が完成します。プラグインを有効化してビルドする際は、plugin.cfgプラグイン名:Go パッケージ の形で追記します。

試しに CoreDNS チームがプラグイン開発の際の例として作った example プラグイン を有効化してみると

$ echo 'example:github.com/coredns/example' >> plugin.cfg
$ make
$ ./coredns -plugins

Other plugins:
...
  dns.etcd
  dns.example
  dns.file
...

example プラグインが有効になっているのが分かります。

設定例

# /etc/hosts の内容を答える
. {
    errors
    loop
    reload
    hosts {
        reload 1s
        fallthrough
    }
    forward . /etc/resolv.conf
    cache 30
}

# ファイルを個別に指定することも可能
. {
    hosts /var/tmp/hoge.hosts
}

まあ hosts プラグインの説明まんまなんですが。/etc/hosts 形式でパッと書いて答える DNS サーバーをサクッと用意できると便利なシーンは多いですよね。hosts プラグインはクエリされたホスト名が見つからない場合 fallthrough オプションがないと下流プラグインにクエリを流さず打ち切ってしまうのでそこだけ注意です。例では hosts ファイルになかった場合システムの /etc/resolv.conf に従い他の DNS サーバーに投げます。

またその他ですが、

  • errors: log プラグインは全てのクエリを標準出力に出しますが、こちらはエラーとなったクエリを出力します。

  • loop: forwarding loop を検出し、もしあれば CoreDNS を終了します。

  • reload: Corefile 設定ファイルを自動でリロードします。

  • cache: いずれかのプラグインが返した応答を一定時間キャッシュします。

などのプラグインを用いています。cache 以外はまあ大体の場合において入れておいて問題はないだろうというプラグインです。

結び

というわけで CoreDNS の簡単な紹介でした。結局いい例が浮かばず /etc/hosts というシンプルな例しか挙げられませんでしたが、dnsmasq などと比較しても設定ファイルの明快さ柔軟性、Go 製バイナリであることによる取り回しの良さなど一定の便利さを得られると思います。

明日の Retty Advent Calendar は EKS でアレをソレする話です。ご期待下さい。

*1:deprecate and remove kube-dns support in kubeadm · Issue #1943 · kubernetes/kubeadm · GitHub 1.18 より kube-dns は非推奨となり、1.21 で kubeadm による標準インストールはできなくなる見込み

*2:他のプラグインが応答を返さなかった場合に使われる(=一番最後に挿入される)forward プラグイン、他のプラグインの応答をキャッシュする cache プラグイン、そもそも DNS ではなく HTTP のエンドポイントを生やす health プラグインなど