☸️

GIP を持たない Kubernetes クラスタの external-dns 対応

2020/09/29に公開

自宅環境の Kubernetes クラスタに単純に external-dns を導入出来なかったためひと工夫加えて導入した話です。

構成

今回関係のある場所のみを単純化して示した構成図は以下です。

01.png

問題点

上記環境において、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 を用いて以下の構成を取りました。

02.png

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)
      • 運用する際はオープンリゾルバにならないよう注意して下さい

設定

それぞれのコンフィグを一部書き換えて置いておきます、参考になれば幸いです。

  • Corefile for coredns-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 for coredns-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 for etcd-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 リソースで公開したサービスのレコードをいちいち手動で登録する手間が省けたので管理がだいぶ楽になりました。
また欠点として挙げたサブドメインの委任が出来ない件も、自宅環境の場合だと対して問題にならなそうです。

脚注
  1. Service リソースでも external-dns の管理対象にできますが本筋からズレるため省略 ↩︎

  2. https://coredns.io/plugins/etcd/ より上記レコード以外の登録方法が記述されていません。また 3年前の issue によるとメンテナの方が etcd backend は全てのリソースタイプをサポートしないと明記されているように見えます。 ↩︎

GitHubで編集を提案

Discussion