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

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

Pod CIDR を切り分けてプラグイン連鎖を設計する

TT Labで続きを見る

目標

Podのアドレスがどこから出てくるのかを、ノードオブジェクトとプラグイン設定の両方で確認し、アドレス範囲を重ならないように切る計算を、手で行います。

なぜ重要なのか

クラスターを新しく立てるとき、最初に決める必要があり、最も元に戻しにくいものが、アドレスの範囲です。Pod・Service・ノードの3種類を、それぞれ別の主体が割り当てますが、互いに相談しないので、重ならないように決めることは、設計者の担当です。また、hostPortや帯域制限のように、マニフェストには書けるのに、プラグインがないと静かに無視される機能があるので、何を誰が担当するかを一度切り分けておかないと、「設定は合っているのに動かない」で長く迷うことになります。

ステップ

  1. クラスターのPodアドレス範囲10.244.0.0/16を/24に分けて、lab-node-0・lab-node-1・lab-node-2に、前から順番に固定してください(spec.podCIDRとspec.podCIDRsの両方)。そのあと、/root/k8nd-cni/01-podcidr.txtに노드이름 대역の3行を、ノード名の順に書いてください(プレースホルダーはノード名とアドレス範囲です)。
  2. /root/k8nd-cni/02-capacity.txtに5行を書いてください。subnets=(10.244.0.0/16を/24で切ったときにできる断片の数)、addresses_per_subnet=(1断片のアドレスの総数)、usable_per_subnet=(ネットワークアドレスとゲートウェイを除いた数)、node_pod_limit=(ノードオブジェクトが示すstatus.allocatable.podsの値)、binding_limit=(2つのうち先に限界に達するほうを、subnetまたはnodeで)です。
  3. /root/k8nd-cni/10-k8nd.conflistを作成してください。cniVersionは1.0.0、nameはk8nd-pod-network、pluginsはちょうど3つで、順番にbridge・portmap・bandwidthです。bridgeのipamはhost-localで、subnetはlab-node-0のPodアドレス範囲、portmapにはcapabilities.portMappingsが真、bandwidthにはcapabilities.bandwidthが真である必要があります。
  4. /root/k8nd-cni/shaped.yamlにPodマニフェストを書いて適用してください。ネームスペースk8nd-cni、名前shaped、アノテーションkubernetes.io/ingress-bandwidth: 1Mとkubernetes.io/egress-bandwidth: 1M、コンテナweb(nginx:1.27-alpine)にcontainerPort 8080・hostPort 8080です。そして/root/k8nd-cni/04-chain.txtに、hostport_plugin=・bandwidth_plugin=・ingress_annotation=・egress_annotation=の4行を書いてください(プラグインは、ステップ3のconflistでその仕事を担当したプラグインのtype、アノテーションは実際に付けた値です)。
  5. /root/k8nd-cni/edge-node.yamlに、ノードk8nd-cni-edgeを書いて適用してください。spec.unschedulableを真にして、spec.taintsに、自分で決めたキーk8nd.example.com/cni-missing(effect NoSchedule)を1つ付けます。適用したあと、/root/k8nd-cni/05-notready.txtに、node=・custom_taint=(自分が付けたキー)・controller_taint=(コントローラーが追加で付けた組み込みテイントのキー)・unschedulable=の4行を書いてください。
  6. /root/k8nd-cni/06-node1.conflistと/root/k8nd-cni/06-node2.conflistを作成してください。それぞれ、lab-node-1・lab-node-2に固定したアドレス範囲をipam.subnetに使い、ipam.gatewayはそのアドレス範囲の最初の使用可能なアドレスで、ipam.routesには0.0.0.0/0とクラスターのPodアドレス範囲10.244.0.0/16の両方が入っている必要があります。ipam.typeはhost-localです。
  7. 4通りのアドレス範囲の配置を判定してください。p1はPodが10.244.0.0/16、Serviceが10.96.0.0/16、ノードが192.168.10.0/24、p2はPodが10.96.0.0/12、Serviceが10.96.0.0/16、ノードが192.168.10.0/24、p3はPodが10.244.0.0/16、Serviceが10.245.0.0/16、ノードが10.244.3.0/24、p4はPodが172.16.0.0/16、Serviceが10.96.0.0/16、ノードが192.168.10.0/24です。/root/k8nd-cni/07-overlap.txtに、p1=からp4=までの4行(値はokまたはoverlap)と、rule=pod,service,nodeの1行、合わせて5行を書いてください。
  8. /root/k8nd-cni/08-report.mdに、cluster_pod_cidr=・per_node_prefix=・nodes_addressable=(そのプレフィックスで収められるノード数)・notready_node=(ステップ5で作成したノード名)・chained_plugins=(ステップ3のconflistで、bridgeのあとにつなげたプラグインをカンマで)の5行を書き、その下に、- で始まる行を4行以上書いてください。

参考

ノードごとにPodのアドレス範囲を切って渡す

クラスターのPodアドレス範囲10.244.0.0/16を/24に分けて、lab-node-0・lab-node-1・lab-node-2に、前から順番に固定してください(spec.podCIDRとspec.podCIDRsの両方)。そのあと、/root/k8nd-cni/01-podcidr.txtに노드이름 대역の3行を、ノード名の順に書いてください(プレースホルダーはノード名とアドレス範囲です)。

Podのアドレスは、APIサーバーが与えません。ノードごとに自分の分のアドレス範囲を受け取り、その中で、ネットワークプラグインのIPAMがPodに1つずつ切り出します。そのため、ノードが増えても、アドレスの管理がノードの中だけで完結します。kubectl patch node <이름> --type=merge -p '{"spec":{"podCIDR":"...","podCIDRs":["..."]}}'で固定し(プレースホルダーはノード名です)、kubectl get node <이름> -o jsonpath='{.spec.podCIDR}'で読み戻してください(プレースホルダーはノード名です)。一度値が入ったあとは、別の値に変更できません。

そのアドレス範囲で、Podを何個収められるか

/root/k8nd-cni/02-capacity.txtに5行を書いてください。subnets=(10.244.0.0/16を/24で切ったときにできる断片の数)、addresses_per_subnet=(1断片のアドレスの総数)、usable_per_subnet=(ネットワークアドレスとゲートウェイを除いた数)、node_pod_limit=(ノードオブジェクトが示すstatus.allocatable.podsの値)、binding_limit=(2つのうち先に限界に達するほうを、subnetまたはnodeで)です。

アドレスを数えることと、Podを数えることは違います。/241断片には、アドレスが256個入っていますが、ネットワークアドレスとゲートウェイが、それぞれ1つずつ引かれます。ところが、ノードが受け付けるPodの数は、kubelet側の上限で別に決まっていて、普通は、アドレスではなく、そちらが先に限界に達します。ノードの上限は、kubectl get node lab-node-0 -o jsonpath='{.status.allocatable.pods}'で読みます。

プラグインの設定をつなげる

/root/k8nd-cni/10-k8nd.conflistを作成してください。cniVersionは1.0.0、nameはk8nd-pod-network、pluginsはちょうど3つで、順番にbridge・portmap・bandwidthです。bridgeのipamはhost-localで、subnetはlab-node-0のPodアドレス範囲、portmapにはcapabilities.portMappingsが真、bandwidthにはcapabilities.bandwidthが真である必要があります。

設定ファイル1つに、プラグインを複数書く形式がconflistです。前から順番に呼ばれ、前のプラグインが作った結果を、後ろのプラグインが受け取って手を入れます。そのため、アドレスを付ける仕事と、hostPortを開ける仕事と、帯域を絞る仕事が、別々のプラグインに分かれています。既定の設定ディレクトリは/etc/cni/net.d、バイナリのディレクトリは/opt/cni/binです。書き終えたら、jq . <파일>で構文を確認してください(プレースホルダーはファイルです)。

hostPortと帯域制限は、誰が行うのか

/root/k8nd-cni/shaped.yamlにPodマニフェストを書いて適用してください。ネームスペースk8nd-cni、名前shaped、アノテーションkubernetes.io/ingress-bandwidth: 1Mとkubernetes.io/egress-bandwidth: 1M、コンテナweb(nginx:1.27-alpine)にcontainerPort 8080・hostPort 8080です。そして/root/k8nd-cni/04-chain.txtに、hostport_plugin=・bandwidth_plugin=・ingress_annotation=・egress_annotation=の4行を書いてください(プラグインは、ステップ3のconflistでその仕事を担当したプラグインのtype、アノテーションは実際に付けた値です)。

Podの仕様のhostPortと帯域のアノテーションは、APIサーバーが行うことではありません。どちらも、チェーンにつなげたプラグインが読んで実行します。そのため、そのプラグインがノードにインストールされていないと、マニフェストは通るのに、何も起きません。静かに無視される種類の故障です。Podはkubectl apply -fで作成し、アノテーションはkubectl -n <ns> get pod shaped -o jsonpath='{.metadata.annotations}'で読み戻してください。

プラグインがないと、ノードが丸ごと止まる

/root/k8nd-cni/edge-node.yamlに、ノードk8nd-cni-edgeを書いて適用してください。spec.unschedulableを真にして、spec.taintsに、自分で決めたキーk8nd.example.com/cni-missing(effect NoSchedule)を1つ付けます。適用したあと、/root/k8nd-cni/05-notready.txtに、node=・custom_taint=(自分が付けたキー)・controller_taint=(コントローラーが追加で付けた組み込みテイントのキー)・unschedulable=の4行を書いてください。

ネットワークプラグインが設定されていないノードは、Podを1つも受け付けられません。その隔離を実際に作り出すのがテイントで、公式ドキュメントの組み込みテイントの一覧に、node.kubernetes.io/network-unavailable(ネットワークを使えない)とnode.kubernetes.io/unschedulableが並んで入っています。重要な違いが1つあります。条件から派生する組み込みテイントは、コントローラーが管理します。手で付けても、条件がその状態でなければ、すぐに削除され、逆に、spec.unschedulableを真にすると、コントローラーが自動でテイントを1つ付けます。そのため、人が付けられるのは、自分のキーを使うテイントだけです。一覧の最初が自分で書いたものだと仮定せずに、kubectl get node <이름> -o json | jq -r '.spec.taints[].key'で、すべて確認してください(プレースホルダーはノード名です)。

ノードごとに異なるIPAM設定を作る

/root/k8nd-cni/06-node1.conflistと/root/k8nd-cni/06-node2.conflistを作成してください。それぞれ、lab-node-1・lab-node-2に固定したアドレス範囲をipam.subnetに使い、ipam.gatewayはそのアドレス範囲の最初の使用可能なアドレスで、ipam.routesには0.0.0.0/0とクラスターのPodアドレス範囲10.244.0.0/16の両方が入っている必要があります。ipam.typeはhost-localです。

host-localは、名前のとおり、そのノードの中でだけアドレスを管理します。ノード同士が、互いに何を配ったかを尋ねません。そのため、アドレス範囲を重ねて渡すと、2つのノードが同じアドレスを平然と配ります。重ならないように切ることは、人かコントロールプレーンの担当です。クラスターのアドレス範囲へ向かう経路を別に書く理由は、デフォルトの経路に出すとノードの外に出てしまう、Pod間のトラフィックを、ノードの中で処理するためです。

重なると、どこへ行くかが決まらない

4通りのアドレス範囲の配置を判定してください。p1はPodが10.244.0.0/16、Serviceが10.96.0.0/16、ノードが192.168.10.0/24、p2はPodが10.96.0.0/12、Serviceが10.96.0.0/16、ノードが192.168.10.0/24、p3はPodが10.244.0.0/16、Serviceが10.245.0.0/16、ノードが10.244.3.0/24、p4はPodが172.16.0.0/16、Serviceが10.96.0.0/16、ノードが192.168.10.0/24です。/root/k8nd-cni/07-overlap.txtに、p1=からp4=までの4行(値はokまたはoverlap)と、rule=pod,service,nodeの1行、合わせて5行を書いてください。

Kubernetesの公式ドキュメントは、Pod・Service・ノードの3種類のアドレスが互いに重なってはならないと固定しています。重なると、どちらの規則が先に適用されるかが実装によって異なり、その違いは、「あるノードでだけ動かない」という形で現れます。判定は、目ではなく計算で行ってください。python3 -c "import ipaddress; print(ipaddress.ip_network('10.96.0.0/12').overlaps(ipaddress.ip_network('10.96.0.0/16')))"の1行で済みます。プレフィックスが短いほど広いアドレス範囲だという点を、見落としやすいです。

Podネットワークの設計メモを残す

/root/k8nd-cni/08-report.mdに、cluster_pod_cidr=・per_node_prefix=・nodes_addressable=(そのプレフィックスで収められるノード数)・notready_node=(ステップ5で作成したノード名)・chained_plugins=(ステップ3のconflistで、bridgeのあとにつなげたプラグインをカンマで)の5行を書き、その下に、- で始まる行を4行以上書いてください。

値は、記憶ではなく、前のステップで作成したファイルから取ってください。説明の行には、次にクラスターを設計するときに、実際に取り出して使える文を書きます。「アドレスを与える主体は誰か」「何が静かに無視されるか」のようなものです。