TT Lab
はじめる
学ぶ 学習パス コース

Kubernetesネットワーク — 本物のクラスタで

API サーバーは Pod のアドレスを知らない

TT Labで続きを見る

一言でいうと

Kubernetesは、Podにアドレスを直接与えません。APIサーバーはServiceのアドレスだけを管理し、Podのアドレスは、ノード上のネットワークプラグインが与えます。そのため、「Podが起動しない」と「Pod同士が届かない」は、別々の層の故障です。

なぜ必要なのか

コンテナを複数、1台のマシンに載せると、最初にぶつかるのがポートです。3つのチームがそれぞれ8080を使いたがれば、誰かが譲る必要があり、譲った側は設定でポートを変数にする必要があり、そうするとサービスディスカバリーもポートを知る必要があります。公式ドキュメントは、この道を「規模が大きくなると調整が非常に難しくなり、ユーザーが制御できないクラスターレベルの問題を生む」とまとめています。

Kubernetesは、その道を選びませんでした。代わりに、Podごとにアドレスを1つずつ与えます。Podの中では、コンテナ同士がlocalhostで会い、Podの外では、アドレスで会います。ポート調整という問題そのものがなくなります。

問題は、そのアドレスを、誰がどのように与えるかです。クラウドごとに違い、オンプレミスはまた違い、ある場所はルーティングで、ある場所はトンネルで解決します。Kubernetesは、この場所を空けておき、規格だけを決めました。それがCNI(Container Network Interface)です。

どう動くのか

公式ドキュメントの表現は明確です。「Kubernetesのネットワークモデルを実装するには、CNIプラグインが必要である」。そして、プラグインはCNI仕様v0.4.0以上と互換である必要があり、プロジェクトはv1.0.0互換を推奨しています。

誰が呼ぶのか。1.24より前は、kubeletがcni-bin-dirとnetwork-pluginオプションで、プラグインを管理していました。その2つのオプションは、1.24で削除されました。今、CNIを呼ぶのは、kubeletではなく、コンテナランタイム(containerd、CRI-O)です。そのため、「CNIの設定を変えたのに反映されない」という報告の半分は、kubeletを再起動した場合です。

どこを読むのか。設定は既定値が/etc/cni/net.d、バイナリは既定値が/opt/cni/binです。設定ファイル1つに、プラグインを複数書く形式がconflistで、前から順番に呼ばれます。

なぜ複数なのか。1つのプラグインがすべてを行うわけではないからです。公式ドキュメントが自分で挙げている例が2つあります。

機能 担当するプラグイン 有効にする方法
hostPort portmap capabilitiesにportMappings: trueを置きます
帯域制限 bandwidth capabilitiesにbandwidth: trueを置き、Podにkubernetes.io/ingress-bandwidthアノテーションを付けます
ループバックlo loopback ランタイムがサンドボックスごとに提供する必要があります

ここから、運用上重要な事実が出てきます。hostPortを書いたのに、何も起きないことがあります。マニフェストはAPI検証を通り、PodはRunningになるのに、ポートは開きません。portmapがチェーンにないと、そうなります。静かに無視される種類の故障です。

アドレスはIPAMが与えます。bridgeのようなメインのプラグインの中のipam項目が、その仕事を担当します。最も一般的なhost-localは、名前のとおり、そのノードの中でだけアドレスを管理します。ノード同士が、お互いに何を配ったかを尋ねません。そのため、ノードごとに重ならない範囲を、あらかじめ切って渡す必要があり、その範囲がノードオブジェクトのspec.podCIDRです。公式の例が"subnet": "usePodCidr"と書かれているのは、そういう意味です。

3種類のアドレス。公式ドキュメントは、クラスターがPod・Service・ノードの3種類のアドレスを、重ならないように割り当てる必要があると固定し、それぞれを決める主体まで分けて書いています。Podはネットワークプラグイン、Serviceはkube-apiserver、ノードはkubeletまたはクラウドコントローラーです。3つの主体が互いに相談しないので、重なりを防ぐのは、設計者の担当です。

現場での姿

「ノード1台だけがPodを受け付けません」。Kubernetesは、この状況をテイントで表現します。公式ドキュメントの組み込みテイントの一覧に、node.kubernetes.io/network-unavailableが、「ノードのネットワークを使えない」という意味で入っています。そのため、報告は「PodがPending」として来ますが、直す場所はスケジューラーではなく、そのノードのプラグイン設定です。プラグインの設定ファイルがそのノードにだけない、バイナリのディレクトリが違う、ランタイムが別のパスを見ている、といった原因です。

「PodのIPが2つのノードで同じです」。host-localは、ノードの外を知らないので、2つのノードに同じ範囲を渡すと、平然と同じアドレスを配ります。症状はランダムな接続失敗として現れるので、原因を見つけるまでに時間がかかります。

「範囲を間違えたので、変更します」。ノードのspec.podCIDRは、一度値が入ると、別の値に変更できません。クラスター設計で、元に戻すことが最も難しい決定が、アドレスの範囲です。

公式ドキュメント: Network Plugins・Cluster Networking

次のラボですること

ノード3台に、/24ずつPodのアドレス範囲を固定し、その範囲で収められる数と、ノードが受け付けるPodの数のどちらが先に限界に達するかを計算します。次に、bridge・portmap・bandwidthの3つのプラグインをつなげたconflistを書き、hostPortと帯域のアノテーションを付けたPodを作って、どのプラグインがその仕事を担当するかを書きます。最後に、プラグインがないノードを模擬してNotReady条件を作ってみて、4通りのアドレス範囲の配置のうち、どれが重なるかを、計算で判定します。