GIP を持たない Kubernetes クラスタの external-dns 対応
自宅環境の Kubernetes クラスタに単純に external-dns を導入出来なかったためひと工夫加えて導入した話です。
構成
今回関係のある場所のみを単純化して示した構成図は以下です。
問題点
上記環境において、Kubernetes クラスタに external-dns を単純に導入しても以下の理由よりうまく動作しません。
- external-dns は Ingress オブジェクト[1] の
spec.rules[].host
の値 (例:example.com
) をstatus.loadBalancer.ingress[].ip
の値 (例:192.168.0.51
) に解決するようなレコードを、指定した対象の DNS プロバイダ に設定する。 - ここで、上記環境だと Ingress Controller から払い出される IPv4 アドレスは 内部アドレス であるため、external-dns によって登録される A レコードは外部から参照すると不正な値となってしまう。
解決法
上記問題を解決するために、external-dns の provider に CoreDNS を用いて以下の構成を取りました。
CoreDNS etcd Plugin
CoreDNS の etcd Plugin のドキュメントは以下です。
CoreDNS を雑に説明すると、A レコードの操作は以下のようになります。
- etcd の
/${base_key}/com/example/xxx/${random}
key に{"host":"1.1.1.1"}
という value を登録することで、CoreDNS にてxxx.example.com
を A レコードで1.1.1.1
に名前解決可能となる
これにより CoreDNS はプロセスを再起動せずとも簡単に外部からレコードの更新が可能になっています。
external-dns CoreDNS Provider
external-dns の etcd Provider は以下に利用例があります。
external-dns は上記で述べたとおり、 Ingress オブジェクトを検知し自動でレコードを生成します。
external-dns の Provider に CoreDNS を指定した際は、 CoreDNS の参照先 etcd に対して external-dns がレコードの登録・削除するいった動作をします。
etcd-injector
上記構成を取るために実装した etcd-injector のソースコードは以下です。
当プログラムは one-shot で以下のように動作します。
- 取得: src に指定した etcd の key-value を取得
- key がディレクトリの場合、そのディレクトリ以下全てのキーがコピー対象
- 変換: 上記の key に対応する JSON format の value に対し、 ルールに基づいて JSON key-value (ここで言う JSON key は、 JSON format で格納された etcd の value) を挿入
- 既に JSON key が存在する場合は JSON value を置換
- 登録: 変換済みの etcd key-value を dst に指定した etcd へコピー
これを Kubernetes の CronJob で動作させることにより、定期的に external-dns が挿入した /int/com/example
以下のレコードの解決先アドレスを Global Addr に置換して /ext/com/example
以下にコピーします。これにより、coredns-internal
, coredns-external
とそれぞれ CoreDNS を動作させることで以下が実現できます。
-
coredns-internal
は etcd の/int/com/example
を参照することで、xxx.example.com
は内部アドレスに名前解決される- 自宅環境の各マシンはこちらの DNS サーバが参照されることを想定 (キャッシュ DNS)
-
coredns-internal
は etcd の/int/com/example
を参照することで、xxx.example.com
は外部アドレスに名前解決される- 外部からはこちらの DNS サーバが参照されることを想定 (権威 DNS)
- 運用する際はオープンリゾルバにならないよう注意して下さい
- 外部からはこちらの DNS サーバが参照されることを想定 (権威 DNS)
設定
それぞれのコンフィグを一部書き換えて置いておきます、参考になれば幸いです。
-
Corefile
forcoredns-internal
. {
errors
health {
lameduck 5s
}
ready
prometheus :9153
cache 30
loop
reload
loadbalance
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
ttl 30
}
etcd $DOMAIN {
path /skydns
endpoint $ETCD_ENDPOINT
}
forward . 8.8.8.8 8.8.4.4
}
-
Corefile
forcoredns-external
$DOMAIN {
errors
health {
lameduck 5s
}
ready
prometheus :9153
cache 30
reload
loadbalance
etcd $DOMAIN {
path /external
endpoint $ETCD_ENDPOINT
}
}
-
external-dns
cammand options
external-dns --source=ingress --provider=coredns --log-level=debug
-
etcd-injector
command options
etcd-injector \
--src-endpoints=$ETCD_ENDPOINT \
--dst-endpoints=$ETCD_ENDPOINT \
--src-directory=/skydns/com/example \
--ignore=/skydns/com/example/local \
--dst-directory=/external/com/example \
--rules-filepath=rules.yaml \
--delete
-
rules.yml
foretcd-injector
- jsonpath: ".host"
repl: "${GLOBAL_ADDR}"
- jsonpath: ".ttl"
repl: 900
考察
上記構成にすることで以下の利点があります。
- Kubernetes の Ingress オブジェクトに紐付いたレコードが自動で作成/削除される
- キャッシュ DNS サーバと権威 DNS サーバが分かれた構成となっている
- 一般的な DNS キャッシュポイズニングの対処法
対して、coredns の etcd plugin は A
, AAAA
, SRV
, TXT
, PTR
レコードしか登録できないといった制約 [2] があるため、以下の欠点があると認識しています。
- NS レコードを用いてサブドメインを別のネームサーバに委任することが出来ない
まとめ
上記方法で自宅環境の Kubernetes に external-dns を導入することが出来ました。これにより Ingress リソースで公開したサービスのレコードをいちいち手動で登録する手間が省けたので管理がだいぶ楽になりました。
また欠点として挙げたサブドメインの委任が出来ない件も、自宅環境の場合だと対して問題にならなそうです。
-
Service リソースでも external-dns の管理対象にできますが本筋からズレるため省略 ↩︎
-
https://coredns.io/plugins/etcd/ より上記レコード以外の登録方法が記述されていません。また 3年前の issue によるとメンテナの方が etcd backend は全てのリソースタイプをサポートしないと明記されているように見えます。 ↩︎
Discussion