Kubernetes The Hard Way におけるNodeを跨いだPod間のパケットフロー
何をするのか
Kubernetesの構築は各種クラウドベンダによるサービスによって非常に簡易化されている. 自前のサーバにKubernetesをデプロイする場合もkubeadmのようなツールによって必要なバイナリを全て手でインストールするというような手間を取る必要はなくなってきている.
しかし, 上記のリポジトリではGCEで用意したUbuntuインスタンスに全て手動でKubernetesのコンポーネントをインストールしていくというハンズオンを体験できる.
今回はそのハンズオンで構築したKubernetes上で実際にPodからPodへ向けてのパケットフローがどのように行われるかを検証した.
環境
検証の環境は以下の画像のようになる. 厳密には3台のマスタに3台のワーカがGCE上で動作しているが今回検証で使用したのは2つのノードだけである.
ストーリー
今回は以下のストーリーにおけるパケットの転送の様子を見てみる
pod 1からpod 2へpingする
pod 1からpod 2へpingする
この時, Node 1上でcnio0 ネットワークインタフェース(NI)をtcpdumpすると以下のような出力になる
03:45:45.813652 IP 10.200.0.2 > 10.200.1.2: ICMP echo request, id 2816, seq 0, length 64 03:45:45.815508 IP 10.200.1.2 > 10.200.0.2: ICMP echo reply, id 2816, seq 0, length 64 03:45:46.814361 IP 10.200.0.2 > 10.200.1.2: ICMP echo request, id 2816, seq 1, length 64 03:45:46.814807 IP 10.200.1.2 > 10.200.0.2: ICMP echo reply, id 2816, seq 1, length 64 03:45:47.814526 IP 10.200.0.2 > 10.200.1.2: ICMP echo request, id 2816, seq 2, length 64 03:45:47.814916 IP 10.200.1.2 > 10.200.0.2: ICMP echo reply, id 2816, seq 2, length 64
Podに割り当てられたvethからcnio0に対してパケットが転送されていることがわかる.
次にcnio0からNode本来のNIであるens4にパケットが転送されているかを確認したところ以下のような出力になっていた
03:49:47.934135 IP worker-0.us-west1-c.c.xxxx.internal > 10.200.1.2: ICMP echo request, id 5376, seq 0, length 64 03:49:47.935690 IP 10.200.1.2 > worker-0.us-west1-c.c.xxxx.internal: ICMP echo reply, id 5376, seq 0, length 64
ドメイン名をnslookupするとNodeのアドレスにNATされていることがわかる
$ nslookup worker-2.us-west1-c.c.xxxx.internal Server: 127.0.0.53 Address: 127.0.0.53#53 Non-authoritative answer: Name: worker-2.us-west1-c.c.xxxx.internal Address: 10.240.0.20
ここまでの状況をまとめると以下の画像のようになる
NodeのNIまでたどり着いたパケットは宛先が 10.200.0.0/24
に該当しないのでDefault Gatewayに転送する.
この時のGWルータはGCE上で作成したVPCルータとなる.
kubernetes-the-hard-wayによる手順ではVPCルータに以下のルールを追加している
NAME NETWORK DEST_RANGE NEXT_HOP PRIORITY default-route-081879136902de56 kubernetes-the-hard-way 10.240.0.0/24 kubernetes-the-hard-way 1000 default-route-55199a5aa126d7aa kubernetes-the-hard-way 0.0.0.0/0 default-internet-gateway 1000 kubernetes-route-10-200-0-0-24 kubernetes-the-hard-way 10.200.0.0/24 10.240.0.20 1000 kubernetes-route-10-200-1-0-24 kubernetes-the-hard-way 10.200.1.0/24 10.240.0.21 1000 kubernetes-route-10-200-2-0-24 kubernetes-the-hard-way 10.200.2.0/24 10.240.0.22 1000
これによって宛先10.200.1.2のパケットは 10.240.21に転送される.
ここまでの処理でパケットはNode 2のens4 NIに到達した
Node2でのrouteは以下のようになっているのでcnio0に転送される. cnio0にはvethが接続されており, あとはpodのvethに転送された後, ペアのeth0に送信され,往路のパケット転送が完了する.
route Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface default _gateway 0.0.0.0 UG 100 0 0 ens4 10.200.1.0 0.0.0.0 255.255.255.0 U 0 0 0 cnio0 _gateway 0.0.0.0 255.255.255.255 UH 100 0 0 ens4
復路もVPCルータを介してNode 1に届くまでは一緒だが, Pod 2から送出されるパケットの宛先IPは Pod 1のIPアドレスではなく, Node 1のens4のIPアドレスとなっている. これは下記のiptablesのルールによってSNATされているためである.
Chain POSTROUTING (policy ACCEPT 7 packets, 696 bytes) pkts bytes target prot opt in out source destination 47471 4663K KUBE-POSTROUTING all -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes postrouting rules */ 97 7561 CNI-be6576a86b4e6dd707f727f8 all -- * * 10.200.2.2 0.0.0.0/0 /* name: "bridge" id: "6f7cc23d7330f226cf75461157038a8718c6bb9b03a53a4bdfd7c1388d49fcfa" */ 0 0 CNI-014fd6fdf5198c5b6219910e all -- * * 10.200.2.3 0.0.0.0/0 /* name: "bridge" id: "0c54384b9da08224e9733348a2ef7da105c285ebd50c62a0c30cebf0712e5e55" */ Chain CNI-be6576a86b4e6dd707f727f8 (1 references) pkts bytes target prot opt in out source destination 0 0 ACCEPT all -- * * 0.0.0.0/0 10.200.2.0/24 /* name: "bridge" id: "6f7cc23d7330f226cf75461157038a8718c6bb9b03a53a4bdfd7c1388d49fcfa" */ 97 7561 MASQUERADE all -- * * 0.0.0.0/0 !224.0.0.0/4 /* name: "bridge" id: "6f7cc23d7330f226cf75461157038a8718c6bb9b03a53a4bdfd7c1388d49fcfa" */
VPCルータからNode 1に届けられたリプライパケットはPod1からリクエストされた際にiptablesで適用されたPOSTROUTINGテーブルのIPマスカレードのルールが icmpパケット内のシーケンスIDからPod1のIPへ逆NATする.
POSTROUTINGの返信における適用: unix.stackexchange.com
これによって宛先IPはPod1の 10.200.0.2/32
となることでcnio0にフォワーディングされ,
cnio0がもつ内部テーブルから宛先のvethへパケットが転送され, Pod 1へ正しくルーティングされる.
まとめ
今回のハンズオンで実験した環境はdockerのネットワークと同等である.
=> マルチホストでのDocker Container間通信 第1回: Dockerネットワークの基礎 - UZABASE Tech Blog
そのためflannelのようなCNIにおけるvxlanによるパケットのラップやvtepによるMAC addressの解決は起こっていない. 基本的にはNodeのiptablesにおけるNATのみでNodeをまたいだpod間の通信は行われていることがわかった.
謝辞
今回疑問の解決に協力していただいたTwitterのフォロワーの方やKubernetes Slackの方々にこの場を借りてお礼申し上げます.