Kubernetes の Nginx Ingress Controller が突然内部ドメインを解決しなくなった件
この記事は LOCAL学生部アドベントカレンダー2020 12日目の記事です。OB ですが枠が開いているのでしれっと参加します。
ことの始まり
いま勤めている会社では内部で利用するために VPS を借りて、そこに Rancher を使って Kubernetes クラスタを建てています。はじめは何の問題もなく Rancher が用意してくれた Nginx Ingress Controller が動作していたのですが、最近 Rancher 2.5 系にアップデートしたあたりから大量にこのようなログが出力されるようになりました。
Nameserver limits were exceeded, some nameservers have been omitted, the applied nameserver line is: x.x.x.x y.y.y.y z.z.z.z
しかも、Ingress で設定しているドメインにアクセスしても504しか返ってこない状況に。ログを見ればクラスタ内部のドメインが解決できないと無限にログが吐き出されています。今度はあわてて Nginx Ingress Contoller が立ち上げている Nginx Pod の /etc/resolv.conf
を見てみると、本来はクラスタ内部のドメインを解決するはずの設定が書かれているはずなのに、 nameserver
が全て外向きのアドレスを解決するDNSに設定されているではありませんか。
$ kubectl exec -n ingress-nginx nginx-ingress-controller-xxxxx -- cat /etc/resolv.conf
nameserver 10.x.x.x
search ingress.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
nameserver x.x.x.x
nameserver y.y.y.y
nameserver z.z.z.z
さてこれはどうしたことかと調査に乗り出した、今回はそんなお話です。
dnsPolicy って何?
さて、何者かによって resolv.conf
が強制的に上書きされているということは分かりましたが、何がそうさせているのかについてはこの時点で情報がありません。とりあえず数時間ググってみたところ Service / Pod に対して設定可能である dnsPolicy
の値が影響していることがなんとなく分かってきました。さて、さっそくこの値がどういうふうに影響してくるか実験してみましょう。
公式ドキュメントによると設定できる値は以下の4種類あるようです。
Default
ClusterFirst
ClusterFirstWithHostNet
None
これらのうち、とりあえず関係なさそうな None
を除いたもので、どんな結果が返ってくるか使い捨ての Pod で検証してみます。
$ kubectl run -i \
--restart=Never \
--rm test-${RANDOM} \
--image=ubuntu \
--overrides='{"kind":"Pod", "apiVersion":"v1", "spec": {"dnsPolicy":"Default"}}' -- cat /etc/resolv.conf
nameserver x.x.x.x
nameserver y.y.y.y
nameserver z.z.z.z
pod "test-7610" deleted
$ kubectl run -i \
--restart=Never \
--rm test-${RANDOM} \
--image=ubuntu \
--overrides='{"kind":"Pod", "apiVersion":"v1", "spec": {"dnsPolicy":"ClusterFirst"}}' -- cat /etc/resolv.conf
nameserver 10.x.x.x
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
pod "test-20592" deleted
$ kubectl run -i \
--restart=Never \
--rm test-${RANDOM} \
--image=ubuntu \
--overrides='{"kind":"Pod", "apiVersion":"v1", "spec": {"dnsPolicy":"ClusterFirstWithHostNet"}}' -- cat /etc/resolv.conf
nameserver 10.x.x.x
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
pod "test-402" deleted
なるほど、つまり少なくとも Nginx の Pod が "dnsPolicy":"ClusterFirst"
の状態で起動していれば良さそうです。さて、さっそく Pod の定義を確認してみましょう。
dnsPolicy: ClusterFirst
おや、おかしいですね。すでに ClusterFirst
に設定されています。この設定が有効になっていれば resolv.conf
は内部の DNS に向いているはずです。でもそうなっていないということは別の箇所に要因があるはずです。ということで調査継続。
hostNetwork の罠
ここで Pod の定義をぼんやり眺めていると hostNetwork
が true
になっていることに気づきました。VPS 上に構築している Rancher は、インターネット側からアクセス可能にするために Nginx Ingress Controller で提供している Nginx Pod をクラスタ内部のネットワークではなくホスト側のネットワークに露出させる必要があります。どうもこの設定が何かしら絡んでいる可能性があるので、またもや一時的な Pod を作って実験をしてみることにしました。
$ kubectl run -i \
--restart=Never \
--rm test-${RANDOM} \
--image=ubuntu \
--overrides='{"kind":"Pod", "apiVersion":"v1", "spec": {"dnsPolicy":"ClusterFirst", "hostNetwork": true}}' -- cat /etc/resolv.conf
nameserver x.x.x.x
nameserver y.y.y.y
nameserver z.z.z.z
pod "test-11579" deleted
$ kubectl run -i \
--restart=Never \
--rm test-${RANDOM} \
--image=ubuntu \
--overrides='{"kind":"Pod", "apiVersion":"v1", "spec": {"dnsPolicy":"ClusterFirstWithHostNet", "hostNetwork": true}}' -- cat /etc/resolv.conf
nameserver 10.x.x.x
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
pod "test-7397" deleted
はい、ビンゴです。hostNetwork
が true
になっているとき、dnsPolicy
を ClusterFirst
に設定しても、ホスト側の DNS 設定が強制的に使われてしまいます。
ここで公式ドキュメントを目を皿にして読んでみると、このように書かれています。
For Pods running with hostNetwork, you should explicitly set its DNS policy "ClusterFirstWithHostNet".
つまり、ネットワーク的にはホストに露出させたくても、DNS は内部で解決させたいユースケースでは dnsPolicy
を ClusterFirstWithHostNet
に明示的に設定してあげる必要があるということです。
で、どこから設定を変えてあげれば?
さて、ここまで分かればあとは強制的に定義を書き換えてあげればOKです。ただし、Rancher の Nginx Ingress Contoller は Pod の定義を直接書き換えることはできません。
ingress-nginx
名前空間の DaemonSets に nginx-ingress-controller
として定義されている方を書き換えてあげる必要があります(1敗)。ここの定義を書き換えると自動的に設定が反映された Pod が立ち上がってきます。
ここでようやく resolv.conf
も期待通り、内部のドメインも無事に解決できて、外からも普通にアクセスできるようになりました。
「おわりに」という名の感想
記事上では割とあっさり解決まで導かれているように見えますが、ここまでたどり着くのに丸2日かけてしまいました。そもそも最初に表示されたログ(Nameserver limits were exceeded)から、実際の解決策(dnsPolicyの変更)が想像すらできなかったというのがかなり致命的だったと思います。やってみると分かるのですが「Nameserver limits were exceeded」でググっても大した情報が見つからなくて、そこで長いこと詰んでしまっていたという経緯があります。
hostNetwork
の設定に気づいて、resolv.conf
がどう変化するかという実験をしてみてからは割とスムーズにいけたと思いますが、あまり知見のない分野だったのでエスパー力が試されていた気がします。Kubernetes という比較的巨大なシステムで、どこか不具合が起きてもなかなかドンピシャな解決策を見つけるのは難しい、という経験を得られたというところが大きかったかなと思います。
というかそもそもデフォルトで ClusterFirstWithHostNet
になっているべきだと思うのですがどうなんでしょうね。アップデート以前では不具合が起きてなかったのでそもそもどういう設定になっていたのか確認していなかったのが悔やまれます。
今回の件は環境依存の問題の可能性もあるのですが、もし同じ状況になっている方がいれば参考になると幸いです。それではまた。
Discussion