Kubernetes と OpenVPN サイドカーの間で DNS を分割する方法
サイドカー Pod には二つの名前空間が必要です。一つのループバックリゾルバーをクラスターとトンネルに向け、編集した resolv.conf への依存をやめましょう。
OpenVPN サイドカーを動かす Pod は、名前解決でよく知られたパターンで失敗します。クラスター名はぶら下がり、リモート名は Pod が再起動するまで応答し、編集したリゾルバーファイルは元に戻ります。この流れのどこにもトンネルの障害はありません。Pod にはリゾルバーファイルが一つと二つの名前空間があり、ファイルはサーバーを順番に並べられてもゾーンを表現できません。恒久的な解決策は、Pod の内部で動き分割を所有するリゾルバーであり、ファイルは単純なままで、ポリシーが書き換える理由もなくなります。
あなたが加えた編集が消え続ける理由
そのファイルは kubelet のものであり、あなたのものではありません。dnsPolicy: ClusterFirst の下では、kubelet がクラスター DNS を指すようにファイルを生成し、コンテナーが起動するたびに再生成します。kubectl exec で変更したものはコンテナーの書き込み層に残るため、再起動、退去、ロールアウトで破棄されます。Pod の仕様は同じポリシーを保持したままで、だから同じファイルが戻ってくるのです。
dnsPolicy: None を設定すると再生成が止まり、クラスターの解決も失われます。制御できるファイルを得る代わりに、必要だった名前を失います。この交換が問題のすべてです。resolv.conf にはゾーンの概念がなく、順番に試すサーバーしかないため、cluster.local を一方のアドレスへ、プライベートゾーンを別のアドレスへ送ることはできません。
Pod の中で、ループバック上に一つのリゾルバーを動かす
Pod をループバック上の単一のネームサーバーに向け、Pod のネットワーク名前空間を共有する CoreDNS コンテナーにポート 53 で応答させ、ポリシーを None に設定し、その一つのアドレスを Pod に与え、VPN とアプリケーションの隣に DNS コンテナーを追加します。
spec:
dnsPolicy: None
dnsConfig:
nameservers:
- 127.0.0.1
options:
- name: ndots
value: "5"
containers:
- name: dns
image: coredns/coredns
args: ["-conf", "/etc/coredns/Corefile"]
volumeMounts:
- {name: corefile, mountPath: /etc/coredns, readOnly: true}
- name: vpn
image: your-openvpn-client-image
- name: app
image: your-app-image
volumes:
- name: corefile
configMap:
name: split-dns-corefile三つのコンテナーは一つのネットワーク名前空間を共有するため、アプリケーションコンテナーからの 127.0.0.1:53 は、サービスも port-forward もクラスターネットワーク経由の回り道もなしに CoreDNS に届きます。ConfigMap から Corefile をマウントすれば、その場で編集するファイルではなく版管理された入力になります。生成された resolv.conf はその後、一つのネームサーバーだけを持ち、それについて推論できる状態になります。
ここで二つの点が効いてきます。公式イメージはポート 53 を root で束縛するため、クラスターのポリシーによっては明示的に書く必要があります。Pod 内のすべてのコンテナーがその一つのコンテナーの稼働に依存するので、無害な名前に対する readiness プローブを追加し、アプリケーションに起動時の失敗を許容させてください。
各プライベートゾーンに専用の upstream を与える
Corefile はゾーンの一覧で、それぞれ応答できるサーバーを指定します。一つのブロックがトンネルのゾーンを覆い、すべてを捕まえるブロックが最後に来て、明示的なブロックが主張しなかったすべてを受け取ります。すべてを捕まえるブロックは実際の upstream を指さなければならず、決して Pod 自身に戻してはいけません。.:53 { forward . 10.100.0.10 } はクラスターのリゾルバーを再利用しますが、それはそのリゾルバーがすでに公開名をノード自身のリゾルバーへ転送しているからです。Pod 内の他の何も、名前がどのネットワークに属するかを知る必要はありません。
.:53 {
errors
cache 30
forward . 10.100.0.10
}
svc.acme.internal:53 {
errors
cache 30
forward . 192.168.240.1
}クラスターのアドレスは、記事からコピーするのではなく、kube-system の kube-dns サービスから読み取ってください。サービスの CIDR は環境ごとに異なるからです。トンネルのアドレスは OpenVPN サーバーが送る dhcp-option DNS の値、またはそのゾーンの権威サーバーから得ます。クラスターのリゾルバーが公開名に応答しない場合は、ノードのリゾルバーのアドレスをすべてを捕まえるブロックに置き、Pod の中ではなくノード上で読んでください。
すべてを捕まえるブロックを /etc/resolv.conf に向けないでください。それは魅力的な一行であり、まさにこの構成での罠です。dnsPolicy: None の下では Pod 自身のファイルが唯一のネームサーバー 127.0.0.1 を含むため、そのターゲットは CoreDNS 自身であり、一致しないクエリーはタイムアウトまで再帰し続けます。svc.acme.internal という名前のブロックはその名前とその下のすべての名前に一致し、db.svc.acme.internal を覆い svc.acme.internal.example.net を除外します。末尾のドットは見た目だけのもので、ゾーン名は完全修飾形式に正規化されます。クエリーがどこに落ちるかを決めるのは接尾辞の境界です。
転送をクラスターのリゾルバーに置くべきか決める
名前空間のすべてのワークロードがトンネルのゾーンを必要とするなら、kube-system の下の CoreDNS ConfigMap に転送ブロックを置く方が、Pod ごとのサイドカーより安上がりです。その変更はクラスター規模で、管理者権限と全員が依存するデプロイメントの再起動が必要です。その一つのゾーンに限定し、決してルートに向けてはいけません。一つのワークロードのトンネルアドレスが、そのゾーンに対するすべての Pod のリゾルバーになり、トンネルが落ちた日にはクラスター全体が名前を失います。
サイドカーは爆発半径を一つの Pod 内に保ち、リモートネットワークを必要とするサービスが少数のときのより良い既定値です。どちらを選ぶにせよ、どのリゾルバーがゾーンを所有するかを書き留めてください。次にタイムアウトを調べる人が必ず尋ねます。
接続がまだ失敗するのに解決する名前
解決はトンネルが運ぶべき二つのうちの一つで、もう一つは 戻り経路であり、Pod のネットワークはそれをうまく隠します。OpenVPN サーバー自体が Pod として動く場合、そのコンテナー内に追加したルールはノードを通って出るトラフィックを制御しません。その失敗は狭く、サーバーも動かしていたノード上の Pod だけが応答を得て、他はすべてタイムアウトしました。
サーバーを hostNetwork: true で動かすとノードの名前空間に戻り、そこでは FORWARD ルールと送信元変換が効きます。クライアントプールを出るときに変換しないと、10.8.0.6 のようなトンネルアドレスへ戻る経路をどのリモートノードも持ちません。Corefile に再び触る前に両方向を確認してください。クエリーを運んで応答を運ばないトンネルは、クエリーを一度も受け取らなかったリゾルバーとまったく同じタイムアウトを生みます。
修正したセッションではなく、新しい Pod から検証する
両方のゾーンに明示的に問い合わせ、Pod が置き換わった後にクエリーを繰り返し、実行中のコンテナーにしか存在しない構成は構成ではありません。
kubectl exec -it deploy/app -- dig @127.0.0.1 kubernetes.default.svc.cluster.local +short
kubectl exec -it deploy/app -- dig @127.0.0.1 db.svc.acme.internal +short
kubectl delete pod -l app=your-app
kubectl wait --for=condition=Ready pod -l app=your-app --timeout=90s
kubectl exec -it deploy/app -- dig @127.0.0.1 db.svc.acme.internal +short最初の組が応答し、置き換え後の組が応答しないなら、Corefile は正しく Pod の仕様が誤りです。両方が応答するのにアプリケーションが接続できないなら、DNS のデバッグをやめ、コンテナー内から nc -vz db.svc.acme.internal 5432 で TCP の経路を試してください。dig の出力をチケットに残してください。それが名前の問題とルーティングの問題を分ける成果物です。