kubectl は OpenVPN 経由でプライベートクラスタの API を解決できない
トンネルが機能していてもプライベートゾーンが解決可能になるわけではない。VNet リゾルバーを push する方法と hosts やフォワーダーの登録を比較し、API エンドポイントを検証する。
トンネルが上がっていて API のプライベートアドレスがまだ ping に応答しているのに、kubectl は名前解決で失敗する。経路は問題ない。クライアントは、クラスタのプライベートゾーンを一度も知らないリゾルバーに問い合わせている。よく使われる修正は二つある。仮想ネットワークのリゾルバーをすべての VPN クライアントに push するか、hosts エントリや条件付きフォワーダーで名前をローカルに固定するかだ。どちらも同じ症状を別の責任範囲で解決するもので、両者を混同すると、エンドポイントが作り直されるたびに hosts ファイルを編集することになる。
名前解決と到達性は別の層で失敗する
何かを変える前に検索を自分で実行する。Linux では getent hosts、Windows では nslookup が、クライアントがそもそも応答を得られるかを示す。タイムアウトや NXDOMAIN はリゾルバーを指し、誤ったアドレスの応答は古いレコードかパブリック側への漏れを指す。
次に kubectl が実際にどのサーバーへ接続しているかを確認する。kubeconfig のコンテキストは URL を保存しており、VPN の経路がそれを書き換えることはない。コンテキストがパブリックなエンドポイントを指していれば、DNS が完璧でもコマンドは失敗する。
getent hosts api.internal.example
kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}'
kubectl -v=8 get nodes 2>&1 | head -40
三つ目のシグナルは、検索後の失敗の形だ。プライベートアドレスに解決された後に TCP のタイムアウトになるなら、ファイアウォールか許可ネットワークの問題である。パブリックアドレスに解決されるなら、問い合わせがプライベートゾーンから漏れている。この三つの結果を切り分けて考える。それぞれ担当者が違う。
案 A: 仮想ネットワークのリゾルバーをトンネルに push する
VPN サーバーが仮想ネットワーク内で動いているなら、クライアントはサーバーのホストと同じリゾルバーを使える。OpenVPN Community ではサーバーが DNS オプションを push する。Access Server も同じ設定を管理画面で公開しており、DNS ディレクティブがプロファイル内を移動するため、クライアントはプロファイルを再ダウンロードする必要がある。
# server.conf
push "dhcp-option DNS 10.0.0.4"
push "route 10.0.0.4 255.255.255.255"
ルート行は split tunnel のプロファイルで重要になる。リゾルバーのアドレスを push することは、そこへ到達できるようにすることとは違う。クラスタのサブネットだけがトンネルを通る場合、10.0.0.4 への問い合わせはローカル LAN から出て消える。リゾルバー用のホストルートを push するか、そのサブネットをトンネルに含める。
受け側にもポリシーがある。クライアントのアドレス範囲から到達できても、その範囲からの問い合わせを拒否するリゾルバーはあるし、仮想ネットワークにリンクされたプライベートゾーンは、そのネットワークの DNS 経路で来たクライアントにしか応答しない。push 後に検索がタイムアウトするなら、クライアントに触る前にリゾルバー自身のログを見る。
クライアント側で何が適用されたかを確認する。systemd-resolved の Linux では resolvectl status、macOS では scutil --dns、Windows では ipconfig /all を使う。push された DNS サーバーが現れないなら、プロファイルが再読み込みされていない。
案 B: hosts エントリと条件付きフォワーディング
hosts エントリは最小の修正であり、最も壊れやすい。一つの名前を一つのマシン上の一つのアドレスに固定するので、デモには向くが、プライベートなエンドポイントやその IP が作り直されると壊れる。チームの他のメンバーから本当の欠落を隠してしまう点も問題だ。
条件付きフォワーダーは、すべてのクライアントに DNS を push せずに拡張できる。dnsmasq なら一つのゾーンを VNet のリゾルバーへ送り、それ以外はそのままにできる。設定は server=/cluster.internal/10.0.0.4 だ。Windows DNS にも条件付きフォワーダーがあり、Windows クライアントは同じ分割に NRPT ルールを使える。フォワーダーには VNet リゾルバーへの経路が依然として必要だ。
フォワーディングは通常の閲覧をローカルに保つ。これは split tunnel では重要だ。プライベートゾーンの問い合わせを記録する一箇所も得られるし、フォワーダーのホストが移ると壊れる一箇所も得られる。
利便性ではなく責任範囲で選ぶ
VPN プロファイルを管理していて、API だけでなくすべてのプライベート名を動かしたいなら、リゾルバーを push する。クライアントがそれ以外のためにローカル DNS を保持しなければならない場合や、VPN がクラスタとは別チームの管理下にある場合は、フォワーダーを選ぶ。
hosts エントリは診断であって配備ではない。API に到達できることを証明するために一つ使い、その後で削除してリゾルバーの経路を直す。残したままにすると、後で経路の疑問が宝探しに変わる。どの案を選んでも、ゾーン、リゾルバーのアドレス、問い合わせを運ぶ経路を書き留める。
API エンドポイントを検証する、名前だけでは足りない
検索が成功したら、クラスタの CA を使って API のバージョンエンドポイントを取得する。curl --cacert ca.crt https://api.internal.example:6443/version は TLS と認可の失敗を DNS から切り分ける。403 は名前と接続が機能し、識別情報が機能していないことを意味する。
クライアントのアドレス範囲が、クラスタの許可ネットワークやファイアウォールで許可されているかも確認する。プライベートクラスタは API トラフィックを VNet や peered 範囲からしか受け付けないことが多く、別のプールからアドレスを割り当てるトンネルはネットワーク層では接続できても API で落とされる。
エスカレーションするときは四つの事実を記録する。クライアントが使ったリゾルバー、返ってきたアドレス、選択中の kubeconfig コンテキストのサーバー URL、最初に失敗した操作だ。この記録は次のエンジニアに DNS、経路、ファイアウォールポリシーのどれを変えるべきか伝え、次に hosts ファイルを編集するのを止める。
まず検索から始め、ゾーンの所有者に合うリゾルバーの経路を選び、それからエンドポイントを検証する。三つとも通れば、残る問題は認可であって名前解決ではない。