- 1. はじめに
- 2. Kubernetes や CNI の IPv6 サポート
- 3. デュアルスタック構成のクラスタ構築
- 4. IPv6 ネットワークの確認
- 5. Service を用いた IPv6 ネットワークの確認
- 6. まとめ
- 7. 補足
執筆者 : 山下雅喜
1. はじめに
前回の記事では、Docker コンテナで IPv6 を利用する方法を整理しました。
これに引き続き、本記事では、Kubernetes クラスタを IPv4-IPv6 デュアルスタック構成で構築する方法や、IPv6 通信がどのように行われているかを解説します。
2. Kubernetes や CNI の IPv6 サポート
Kubernetes
Kubernetes では、v1.9 で IPv6 シングルスタック構成の利用が、v1.16 で IPv4-IPv6 デュアルスタック構成の利用がサポートされました。
IPv6 オンリーのネットワークでシステムを利用しているケースは少ないでしょうから、デュアルスタック構成で IPv6 を利用するのが現実的な選択肢でしょう。
Kubernetes は複数のコンポーネントが互いに通信し合うことでクラスタの基盤部分が構成されていますが、今回はクラスタ上にデプロイするアプリケーション部分 (Pod, Service など) の通信を IPv6 化することを目指します。
Calico
Pod で IPv6 を利用するには、CNI Plugin も IPv6 をサポートしている必要があります。
IPv4-IPv6 デュアルスタック構成で利用できる CNI Plugin の中で、人気のある Calico を今回は利用します。
(※本記事執筆時点で、Weave Net や Flannel は IPv6 の利用をサポートしていません。)
Calico では、IPIP トンネルや VXLAN オーバーレイネットワークを用いてマルチノードの Pod 間の通信を行う仕組みがあり、標準設定では IPIP トンネルを利用するようになっています。
ただし、Calico は IPv6 の通信で IPIP や VXLAN の利用をがサポートしていないため、ノード同士が Pod 間のパケットをルーティングして通信を行う方式を取ることになります。
https://docs.projectcalico.org/archive/v3.15/networking/vxlan-ipip
なお、Calico の仕組みについては過去に以下の記事でも解説しています。
3. デュアルスタック構成のクラスタ構築
前提構成
利用したソフトウェアおよびバージョンは次の通りです。
- OS : Ubuntu 18.04.4 LTS
- Linux Kernel : 4.15.0-111-generic
- Docker : 19.03.12
- Kubernetes : 1.18.5
- Calico : 3.15.1
また、サーバは2台用意し、Master ノードと Worker ノードをそれぞれ1台ずつとしました。
ノードのネットワークは次のように IPv4-IPv6 デュアルスタック構成とし、ルーターをデフォルトゲートウェイとしています。
- ノードネットワーク CIDR
- IPv4 : 192.168.1.0/24
- IPv6 : 2001:db8::/64
Pod や Service の CIDR は、ノードのネットワークと重複しないよう次のようにします。
- Pod CIDR
- IPv4 : 10.244.0.0/16
- IPv6 : fd00:1::/48
- Service CIDR :
- IPv4 : 10.96.0.0/12
- IPv6 : fd00::/108
- Service CIDR のビット数は 20 ビットまでと制約があるため、IPv6 は /108 としている。
https://github.com/kubernetes/kubernetes/blob/v1.18.5/cmd/kube-apiserver/app/options/validation.go
- Service CIDR のビット数は 20 ビットまでと制約があるため、IPv6 は /108 としている。
IPv4 はプライベートアドレスを利用することが一般的であるため、IPv6 も同様にユニークローカルユニキャストアドレスを利用するようにしています。
なお、本記事を参考に環境を構築する際は、RFC 4086 に従ってランダムに生成したユニークローカルユニキャストアドレスを利用するようにしてください。
Kubernetes クラスタの構築
次の公式ドキュメントにならってクラスタを構築し、IPv6 を利用するために必要な部分についてのみ記載します。
- https://v1-18.docs.kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/
- https://v1-18.docs.kubernetes.io/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/
まず、ノードが IPv6 パケットを転送できるようにするため、全ノードで次のようにカーネルパラメータを設定します。
$ sudo sysctl -w net.ipv6.conf.all.forwarding=1
次に、kubeadm を用いて Master ノードをセットアップします。
$ sudo kubeadm init \ --feature-gates="IPv6DualStack=true" \ --pod-network-cidr="10.244.0.0/16,fd00:1::/48" \ --service-cidr="10.96.0.0/12,fd00::/108"
Kubernetes v1.18 では IPv6 デュアルスタック機能は標準では無効となっているため、パラメータで有効化します。
Pod CIDR および Service CIDR のパラメータには、IPv4 および IPv6 の CIDR をカンマ区切りで繋いで指定します。
今回は Kubernetes クラスタの基盤部分の IPv6 利用を行わないため、--apiserver-advertise-address パラメータで IPv6 アドレスの指定は行っていません。
その後、kubeadm を用いて Worker ノードをセットアップします。
$ sudo kubeadm join 192.168.1.11:6443 \ --token XXXXXXXXXX \ --discovery-token-ca-cert-hash sha256:XXXXXXXXXX
kubeadm init と join が成功すると、ノードの状態は次のようになります。
ノードの IPv4 アドレスが認識されています。
$ kubectl get node -o wide NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME master NotReady master 77s v1.18.5 192.168.1.11 <none> Ubuntu 18.04.4 LTS 4.15.0-111-generic docker://19.3.12 worker NotReady <none> 44s v1.18.5 192.168.1.12 <none> Ubuntu 18.04.4 LTS 4.15.0-111-generic docker://19.3.12
Calico のインストール
次の公式ドキュメントにならい、Calico をインストールします。
- https://docs.projectcalico.org/archive/v3.15/getting-started/kubernetes/self-managed-onprem/onpremises
- https://docs.projectcalico.org/archive/v3.15/networking/ipv6#enabling-ipv6-support-in-calico
$ curl https://docs.projectcalico.org/archive/v3.15/manifests/calico.yaml -O $ cp -p calico.yaml calico.yaml.org $ vim calico.yaml # 下記のように変更する $ kubectl apply -f calico.yaml
calico.yaml は次のように変更しました。
$ diff -u calico.yaml.org calico.yaml --- calico.yaml.org 2020-07-14 06:03:42.481570654 +0000 +++ calico.yaml 2020-07-14 06:08:11.183994061 +0000 @@ -16,7 +16,7 @@ # - Otherwise, if VXLAN or BPF mode is enabled, set to your network MTU - 50 # - Otherwise, if IPIP is enabled, set to your network MTU - 20 # - Otherwise, if not using any encapsulation, set to your network MTU. - veth_mtu: "1440" + veth_mtu: "1500" # The CNI network configuration to install on each node. The special # values in this config will be automatically populated. @@ -32,7 +32,9 @@ "nodename": "__KUBERNETES_NODE_NAME__", "mtu": __CNI_MTU__, "ipam": { - "type": "calico-ipam" + "type": "calico-ipam", + "assign_ipv4": "true", + "assign_ipv6": "true" }, "policy": { "type": "k8s" @@ -3551,7 +3553,7 @@ value: "autodetect" # Enable IPIP - name: CALICO_IPV4POOL_IPIP - value: "Always" + value: "Never" # Enable or Disable VXLAN on the default IP pool. - name: CALICO_IPV4POOL_VXLAN value: "Never" @@ -3586,7 +3588,11 @@ value: "ACCEPT" # Disable IPv6 on Kubernetes. - name: FELIX_IPV6SUPPORT - value: "false" + value: "true" + - name: IP6 + value: "autodetect" + - name: CALICO_IPV6POOL_NAT_OUTGOING + value: "true" # Set Felix logging to "info" - name: FELIX_LOGSEVERITYSCREEN value: "info"
IPIP、VXLAN、Wireguard (Calico v3.15 から導入された Pod 間通信の暗号化機能) を利用しないため、MTU はノードの NIC と同じ値にしました。
CNI の ipam 設定では、Pod に IPv4 と IPv6 の両方のアドレスを割り当てるようにしています。
calico-node の環境変数は次のように変更しました。
環境変数名 | デフォルト値 | 変更後の値 | 備考 |
---|---|---|---|
CALICO_IPV4POOL_IPIP | Always | Never | マルチノードの Pod 間の IPv4 通信で IPIP トンネルの利用を無効化する (IPv6 通信と合わせるため) |
FELIX_IPV6SUPPORT | false | true | IPv6 のサポートを有効にする |
IP6 | なし | autodetect | ノードの IPv6 アドレスの取得方法を自動検出モードにする |
CALICO_IPV6POOL_NAT_OUTGOING | なし | true | Pod からノードの外に IPv6 通信する際のIPマスカレードを有効化する |
Calico のインストールが成功して正常に動作すると、Pod の状態は次のようになります。
$ kubectl get pod -A -o wide NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES kube-system calico-kube-controllers-578894d4cd-lhcxq 1/1 Running 0 30s 10.244.219.64 master <none> <none> kube-system calico-node-rsl5t 1/1 Running 0 30s 192.168.1.11 master <none> <none> kube-system calico-node-zphpn 1/1 Running 0 30s 192.168.1.12 worker <none> <none> kube-system coredns-66bff467f8-2vrh4 1/1 Running 0 2m36s 10.244.219.65 master <none> <none> kube-system coredns-66bff467f8-55mqb 1/1 Running 0 2m36s 10.244.171.64 worker <none> <none> kube-system etcd-master 1/1 Running 0 2m44s 192.168.1.11 master <none> <none> kube-system kube-apiserver-master 1/1 Running 0 2m44s 192.168.1.11 master <none> <none> kube-system kube-controller-manager-master 1/1 Running 0 2m44s 192.168.1.11 master <none> <none> kube-system kube-proxy-5mbb5 1/1 Running 0 2m21s 192.168.1.12 worker <none> <none> kube-system kube-proxy-pmkct 1/1 Running 0 2m36s 192.168.1.11 master <none> <none> kube-system kube-scheduler-master 1/1 Running 0 2m44s 192.168.1.11 master <none> <none>
これで、デュアルスタック構成のクラスタ構築が完了し、任意に Pod や Service を作成し、IPv4 と IPv6 のどちらでも利用できるようになりました。
4. IPv6 ネットワークの確認
クラスタ構築直後の状態で IPv6 ネットワークの設定や動作を確認していきます。
Master ノードのネットワーク設定
$ ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 2: ens32: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000 link/ether 00:0c:29:2b:fd:72 brd ff:ff:ff:ff:ff:ff inet 192.168.1.11/24 brd 192.168.1.255 scope global ens32 valid_lft forever preferred_lft forever inet6 2001:db8::11/64 scope global valid_lft forever preferred_lft forever inet6 fe80::20c:29ff:fe2b:fd72/64 scope link valid_lft forever preferred_lft forever 5: calib8c5667857f@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet6 fe80::ecee:eeff:feee:eeee/64 scope link valid_lft forever preferred_lft forever 6: cali99739419c54@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff link-netnsid 1 inet6 fe80::ecee:eeff:feee:eeee/64 scope link valid_lft forever preferred_lft forever $ ip -6 route 2001:db8::/64 dev ens32 proto kernel metric 256 pref medium fd00:1:0:ab7e:b4b6:dc9b:3ca1:a780/122 via 2001:db8::12 dev ens32 proto bird metric 1024 pref medium fd00:1:0:db4d:f2f2:402d:3b99:a180 dev calib8c5667857f metric 1024 pref medium fd00:1:0:db4d:f2f2:402d:3b99:a181 dev cali99739419c54 metric 1024 pref medium blackhole fd00:1:0:db4d:f2f2:402d:3b99:a180/122 dev lo proto bird metric 1024 error -22 pref medium fe80::/64 dev ens32 proto kernel metric 256 pref medium fe80::/64 dev calib8c5667857f proto kernel metric 256 pref medium fe80::/64 dev cali99739419c54 proto kernel metric 256 pref medium default via 2001:db8::1 dev ens32 proto static metric 1024 pref medium
Worker ノードのネットワーク設定
$ ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 2: ens32: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000 link/ether 00:0c:29:47:ea:f2 brd ff:ff:ff:ff:ff:ff inet 192.168.1.12/24 brd 192.168.1.255 scope global ens32 valid_lft forever preferred_lft forever inet6 2001:db8::12/64 scope global valid_lft forever preferred_lft forever inet6 fe80::20c:29ff:fe47:eaf2/64 scope link valid_lft forever preferred_lft forever 5: cali33c691eb8a3@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet6 fe80::ecee:eeff:feee:eeee/64 scope link valid_lft forever preferred_lft forever $ ip -6 route 2001:db8::/64 dev ens32 proto kernel metric 256 pref medium fd00:1:0:ab7e:b4b6:dc9b:3ca1:a780 dev cali33c691eb8a3 metric 1024 pref medium blackhole fd00:1:0:ab7e:b4b6:dc9b:3ca1:a780/122 dev lo proto bird metric 1024 error -22 pref medium fd00:1:0:db4d:f2f2:402d:3b99:a180/122 via 2001:db8::11 dev ens32 proto bird metric 1024 pref medium fe80::/64 dev ens32 proto kernel metric 256 pref medium fe80::/64 dev cali33c691eb8a3 proto kernel metric 256 pref medium default via 2001:db8::1 dev ens32 proto static metric 1024 pref medium
Calico の IPAM ブロック
$ kubectl get ipamblocks -o custom-columns=NAME:.metadata.name,AFFINITY:.spec.affinity,CIDR:.spec.cidr NAME AFFINITY CIDR 10-244-171-64-26 host:worker 10.244.171.64/26 10-244-219-64-26 host:master 10.244.219.64/26 fd00-1-0-ab7e-b4b6-dc9b-3ca1-a780-122 host:worker fd00:1:0:ab7e:b4b6:dc9b:3ca1:a780/122 fd00-1-0-db4d-f2f2-402d-3b99-a180-122 host:master fd00:1:0:db4d:f2f2:402d:3b99:a180/122
Pod のネットワーク設定
Master ノード上の coredns Pod
$ sudo nsenter -t $(pidof coredns) -n ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 3: eth0@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether f6:10:0b:04:6f:52 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 10.244.219.65/32 scope global eth0 valid_lft forever preferred_lft forever inet6 fd00:1:0:db4d:f2f2:402d:3b99:a181/128 scope global valid_lft forever preferred_lft forever inet6 fe80::f410:bff:fe04:6f52/64 scope link valid_lft forever preferred_lft forever $ sudo nsenter -t $(pidof coredns) -n ip -6 route fd00:1:0:db4d:f2f2:402d:3b99:a181 dev eth0 proto kernel metric 256 pref medium fe80::/64 dev eth0 proto kernel metric 256 pref medium default via fe80::ecee:eeff:feee:eeee dev eth0 metric 1024 pref medium
Worker ノード上の coredns Pod
$ sudo nsenter -t $(pidof coredns) -n ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 3: eth0@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether d6:47:0b:3d:90:9c brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 10.244.171.64/32 scope global eth0 valid_lft forever preferred_lft forever inet6 fd00:1:0:ab7e:b4b6:dc9b:3ca1:a780/128 scope global valid_lft forever preferred_lft forever inet6 fe80::d447:bff:fe3d:909c/64 scope link valid_lft forever preferred_lft forever $ sudo nsenter -t $(pidof coredns) -n ip -6 route fd00:1:0:ab7e:b4b6:dc9b:3ca1:a780 dev eth0 proto kernel metric 256 pref medium fe80::/64 dev eth0 proto kernel metric 256 pref medium default via fe80::ecee:eeff:feee:eeee dev eth0 metric 1024 pref medium
Master ノード上の calico-kube-controllers Pod
$ sudo nsenter -t $(pidof kube-controllers) -n ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 3: eth0@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether aa:da:6c:54:47:a7 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 10.244.219.64/32 scope global eth0 valid_lft forever preferred_lft forever inet6 fd00:1:0:db4d:f2f2:402d:3b99:a180/128 scope global valid_lft forever preferred_lft forever inet6 fe80::a8da:6cff:fe54:47a7/64 scope link valid_lft forever preferred_lft forever $ sudo nsenter -t $(pidof kube-controllers) -n ip -6 route fd00:1:0:db4d:f2f2:402d:3b99:a180 dev eth0 proto kernel metric 256 pref medium fe80::/64 dev eth0 proto kernel metric 256 pref medium default via fe80::ecee:eeff:feee:eeee dev eth0 metric 1024 pref medium
ノードの NAT テーブルの設定
Master ノードおよび Worker ノードの NAT テーブルは、次の内容となっていました。
$ sudo ip6tables -L -n -t nat Chain PREROUTING (policy ACCEPT) target prot opt source destination cali-PREROUTING all ::/0 ::/0 /* cali:6gwbT8clXdHdC1b1 */ KUBE-SERVICES all ::/0 ::/0 /* kubernetes service portals */ Chain INPUT (policy ACCEPT) target prot opt source destination Chain OUTPUT (policy ACCEPT) target prot opt source destination cali-OUTPUT all ::/0 ::/0 /* cali:tVnHkvAo15HuiPy0 */ KUBE-SERVICES all ::/0 ::/0 /* kubernetes service portals */ Chain POSTROUTING (policy ACCEPT) target prot opt source destination cali-POSTROUTING all ::/0 ::/0 /* cali:O3lYWMrLQYEMJtB5 */ KUBE-POSTROUTING all ::/0 ::/0 /* kubernetes postrouting rules */ Chain KUBE-MARK-MASQ (0 references) target prot opt source destination MARK all ::/0 ::/0 MARK or 0x4000 Chain KUBE-NODEPORTS (1 references) target prot opt source destination Chain KUBE-POSTROUTING (1 references) target prot opt source destination MASQUERADE all ::/0 ::/0 /* kubernetes service traffic requiring SNAT */ mark match 0x4000/0x4000 Chain KUBE-PROXY-CANARY (0 references) target prot opt source destination Chain KUBE-SERVICES (2 references) target prot opt source destination KUBE-NODEPORTS all ::/0 ::/0 /* kubernetes service nodeports; NOTE: this must be the last rule in this chain */ ADDRTYPE match dst-type LOCAL Chain cali-OUTPUT (1 references) target prot opt source destination cali-fip-dnat all ::/0 ::/0 /* cali:GBTAv2p5CwevEyJm */ Chain cali-POSTROUTING (1 references) target prot opt source destination cali-fip-snat all ::/0 ::/0 /* cali:Z-c7XtVd2Bq7s_hA */ cali-nat-outgoing all ::/0 ::/0 /* cali:nYKhEzDlr11Jccal */ Chain cali-PREROUTING (1 references) target prot opt source destination cali-fip-dnat all ::/0 ::/0 /* cali:r6XmIziWUJsdOK6Z */ Chain cali-fip-dnat (2 references) target prot opt source destination Chain cali-fip-snat (1 references) target prot opt source destination Chain cali-nat-outgoing (1 references) target prot opt source destination MASQUERADE all ::/0 ::/0 /* cali:Ir_z2t1P6-CxTDof */ match-set cali60masq-ipam-pools src ! match-set cali60all-ipam-pools dst $ sudo ipset list cali60masq-ipam-pools Name: cali60masq-ipam-pools Type: hash:net Revision: 6 Header: family inet6 hashsize 1024 maxelem 1048576 Size in memory: 1240 References: 1 Number of entries: 1 Members: fd00:1::/48 $ sudo ipset list cali60all-ipam-pools Name: cali60all-ipam-pools Type: hash:net Revision: 6 Header: family inet6 hashsize 1024 maxelem 1048576 Size in memory: 1240 References: 1 Number of entries: 1 Members: fd00:1::/48
確認結果の整理
上記の確認結果から、次のようなネットワーク構成が作られていることがわかります。
Pod やノードのネットワーク設定および Calico の IPAM ブロックの内容から、通信は次のように行われることがわかります。
- Pod
- 任意の宛先の通信は、eth0 NIC から "fe80::ecee:eeff:feee:eeee" (ノード上の接頭辞 cali から始まる仮想 NIC) 宛にパケットを送信する。
- ノード
- 自ノード上の Pod 宛の通信は、Pod に接続された仮想 NIC のリンク上で NDP で宛先を見つけて、イーサネットフレームを送信する。
- 自ノードに割り当てられた IPAM ブロックのうち、Pod に割り当てられていない IP アドレス宛の通信のパケットは捨てる。
- 他ノードに割り当てられた IPAM ブロック宛の通信は、ノード間のネットワークを通して、そのノードにパケットを送信する。
- ノードネットワーク宛の通信は、ens32 NIC のリンク上で NDP で宛先を見つけて、イーサネットフレームを送信する。
- 上記以外宛の通信は、ens32 NIC からルーター宛にパケットを送信する。
また、各ノードの NAT テーブルの内容から、次のように NAT が行われることがわかります。
- cali-nat-outgoing チェーンによって、送信元が cali60masq-ipam-pools ("fd00:1::/48")、宛先が cali60all-ipam-pools ("fd00:1::/48") 以外のパケットは、IPマスカレードする。
IPv6 通信の動作確認
異なるノード上の Pod 間の通信
Master ノード上の Pod から、Worker ノード上の Pod への通信を確認します。
Master ノード上で次のように ping を実行すると、問題なく成功しました。
$ sudo nsenter -t $(pidof coredns) -n ping6 fd00:1:0:ab7e:b4b6:dc9b:3ca1:a780 -c 4 PING fd00:1:0:ab7e:b4b6:dc9b:3ca1:a780(fd00:1:0:ab7e:b4b6:dc9b:3ca1:a780) 56 data bytes 64 bytes from fd00:1:0:ab7e:b4b6:dc9b:3ca1:a780: icmp_seq=1 ttl=62 time=0.446 ms 64 bytes from fd00:1:0:ab7e:b4b6:dc9b:3ca1:a780: icmp_seq=2 ttl=62 time=0.737 ms 64 bytes from fd00:1:0:ab7e:b4b6:dc9b:3ca1:a780: icmp_seq=3 ttl=62 time=0.782 ms 64 bytes from fd00:1:0:ab7e:b4b6:dc9b:3ca1:a780: icmp_seq=4 ttl=62 time=0.777 ms --- fd00:1:0:ab7e:b4b6:dc9b:3ca1:a780 ping statistics --- 4 packets transmitted, 4 received, 0% packet loss, time 3054ms rtt min/avg/max/mdev = 0.446/0.685/0.782/0.141 ms
このとき、Worker ノード上での tcpdump の結果は次のようになっており、Pod CIDR の IP アドレス同士で通信できていることが確認できます。
$ sudo nsenter -t $(pidof coredns) -n tcpdump -n icmp6 tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes 01:24:50.428052 IP6 fd00:1:0:db4d:f2f2:402d:3b99:a181 > fd00:1:0:ab7e:b4b6:dc9b:3ca1:a780: ICMP6, echo request, seq 1, length 64 01:24:50.428106 IP6 fd00:1:0:ab7e:b4b6:dc9b:3ca1:a780 > fd00:1:0:db4d:f2f2:402d:3b99:a181: ICMP6, echo reply, seq 1, length 64 01:24:51.434440 IP6 fd00:1:0:db4d:f2f2:402d:3b99:a181 > fd00:1:0:ab7e:b4b6:dc9b:3ca1:a780: ICMP6, echo request, seq 2, length 64 01:24:51.434518 IP6 fd00:1:0:ab7e:b4b6:dc9b:3ca1:a780 > fd00:1:0:db4d:f2f2:402d:3b99:a181: ICMP6, echo reply, seq 2, length 64 01:24:52.458973 IP6 fd00:1:0:db4d:f2f2:402d:3b99:a181 > fd00:1:0:ab7e:b4b6:dc9b:3ca1:a780: ICMP6, echo request, seq 3, length 64 01:24:52.459059 IP6 fd00:1:0:ab7e:b4b6:dc9b:3ca1:a780 > fd00:1:0:db4d:f2f2:402d:3b99:a181: ICMP6, echo reply, seq 3, length 64 01:24:53.482470 IP6 fd00:1:0:db4d:f2f2:402d:3b99:a181 > fd00:1:0:ab7e:b4b6:dc9b:3ca1:a780: ICMP6, echo request, seq 4, length 64 01:24:53.482544 IP6 fd00:1:0:ab7e:b4b6:dc9b:3ca1:a780 > fd00:1:0:db4d:f2f2:402d:3b99:a181: ICMP6, echo reply, seq 4, length 64
また、traceroute の結果は次のようになっており、Master ノードと Worker ノードをホップとして経由していることが確認できます。
$ sudo nsenter -t $(pidof coredns) -n traceroute6 fd00:1:0:ab7e:b4b6:dc9b:3ca1:a780 traceroute to fd00:1:0:ab7e:b4b6:dc9b:3ca1:a780 (fd00:1:0:ab7e:b4b6:dc9b:3ca1:a780) from fd00:1:0:db4d:f2f2:402d:3b99:a181, 30 hops max, 24 byte packets 1 2001:db8::11 (2001:db8::11) 0.126 ms 0.054 ms 0.031 ms 2 2001:db8::12 (2001:db8::12) 0.513 ms 0.311 ms 0.427 ms 3 fd00:1:0:ab7e:b4b6:dc9b:3ca1:a780 (fd00:1:0:ab7e:b4b6:dc9b:3ca1:a780) 0.366 ms 0.577 ms 0.449 ms
ノードから異なるノード上の Pod への通信
Master ノードから、Worker ノード上の Pod への通信を確認します。
Master ノード上で次のように ping を実行すると、問題なく成功しました。
$ ping6 fd00:1:0:ab7e:b4b6:dc9b:3ca1:a780 -c 4 PING fd00:1:0:ab7e:b4b6:dc9b:3ca1:a780(fd00:1:0:ab7e:b4b6:dc9b:3ca1:a780) 56 data bytes 64 bytes from fd00:1:0:ab7e:b4b6:dc9b:3ca1:a780: icmp_seq=1 ttl=63 time=0.393 ms 64 bytes from fd00:1:0:ab7e:b4b6:dc9b:3ca1:a780: icmp_seq=2 ttl=63 time=0.867 ms 64 bytes from fd00:1:0:ab7e:b4b6:dc9b:3ca1:a780: icmp_seq=3 ttl=63 time=0.750 ms 64 bytes from fd00:1:0:ab7e:b4b6:dc9b:3ca1:a780: icmp_seq=4 ttl=63 time=0.440 ms --- fd00:1:0:ab7e:b4b6:dc9b:3ca1:a780 ping statistics --- 4 packets transmitted, 4 received, 0% packet loss, time 3072ms rtt min/avg/max/mdev = 0.393/0.612/0.867/0.202 ms
このとき、Worker ノード上での tcpdump の結果は次のようになっており、NAT されることなく通信できていることが確認できます。
$ sudo nsenter -t $(pidof coredns) -n tcpdump -n icmp6 tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes 04:18:18.357217 IP6 2001:db8::11 > fd00:1:0:ab7e:b4b6:dc9b:3ca1:a780: ICMP6, echo request, seq 1, length 64 04:18:18.357248 IP6 fd00:1:0:ab7e:b4b6:dc9b:3ca1:a780 > 2001:db8::11: ICMP6, echo reply, seq 1, length 64 04:18:19.382617 IP6 2001:db8::11 > fd00:1:0:ab7e:b4b6:dc9b:3ca1:a780: ICMP6, echo request, seq 2, length 64 04:18:19.382662 IP6 fd00:1:0:ab7e:b4b6:dc9b:3ca1:a780 > 2001:db8::11: ICMP6, echo reply, seq 2, length 64 04:18:20.406083 IP6 2001:db8::11 > fd00:1:0:ab7e:b4b6:dc9b:3ca1:a780: ICMP6, echo request, seq 3, length 64 04:18:20.406126 IP6 fd00:1:0:ab7e:b4b6:dc9b:3ca1:a780 > 2001:db8::11: ICMP6, echo reply, seq 3, length 64 04:18:21.429982 IP6 2001:db8::11 > fd00:1:0:ab7e:b4b6:dc9b:3ca1:a780: ICMP6, echo request, seq 4, length 64 04:18:21.430011 IP6 fd00:1:0:ab7e:b4b6:dc9b:3ca1:a780 > 2001:db8::11: ICMP6, echo reply, seq 4, length 64
Pod から異なるノードへの通信
Master ノード上の Pod から、Worker ノードへの通信を確認します。
Master ノード上で次のように ping を実行すると、問題なく成功しました。
$ sudo nsenter -t $(pidof coredns) -n ping6 2001:db8::12 -c 4 PING 2001:db8::12(2001:db8::12) 56 data bytes 64 bytes from 2001:db8::12: icmp_seq=1 ttl=63 time=0.629 ms 64 bytes from 2001:db8::12: icmp_seq=2 ttl=63 time=0.431 ms 64 bytes from 2001:db8::12: icmp_seq=3 ttl=63 time=0.743 ms 64 bytes from 2001:db8::12: icmp_seq=4 ttl=63 time=0.791 ms --- 2001:db8::12 ping statistics --- 4 packets transmitted, 4 received, 0% packet loss, time 3067ms rtt min/avg/max/mdev = 0.431/0.648/0.791/0.140 ms
このとき、Worker ノード上での tcpdump の結果は次のようになっており、送信元アドレスが Master ノードのIPアドレスに変換されていることが確認できます。
$ sudo tcpdump -i ens32 -n icmp6 tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on ens32, link-type EN10MB (Ethernet), capture size 262144 bytes 01:42:19.970799 IP6 2001:db8::11 > 2001:db8::12: ICMP6, echo request, seq 1, length 64 01:42:19.970876 IP6 2001:db8::12 > 2001:db8::11: ICMP6, echo reply, seq 1, length 64 01:42:20.991366 IP6 2001:db8::11 > 2001:db8::12: ICMP6, echo request, seq 2, length 64 01:42:20.991437 IP6 2001:db8::12 > 2001:db8::11: ICMP6, echo reply, seq 2, length 64 01:42:22.016677 IP6 2001:db8::11 > 2001:db8::12: ICMP6, echo request, seq 3, length 64 01:42:22.016780 IP6 2001:db8::12 > 2001:db8::11: ICMP6, echo reply, seq 3, length 64 01:42:23.041453 IP6 2001:db8::11 > 2001:db8::12: ICMP6, echo request, seq 4, length 64 01:42:23.041561 IP6 2001:db8::12 > 2001:db8::11: ICMP6, echo reply, seq 4, length 64
5. Service を用いた IPv6 ネットワークの確認
Service を用いる場合 IPv6 ネットワークの設定や動作についても確認していきます。
サンプルアプリケーションのデプロイ
Web アプリケーションを想定し、Apache HTTP Server のコンテナイメージを使って次の内容の Deployment を作成しました。
Master ノードと Worker ノードで1つずつ Pod が作成されるようにしています。
kind: Deployment apiVersion: apps/v1 metadata: name: httpd spec: replicas: 2 selector: matchLabels: app: httpd template: metadata: labels: app: httpd spec: tolerations: - key: node-role.kubernetes.io/master operator: Exists topologySpreadConstraints: - topologyKey: kubernetes.io/hostname maxSkew: 1 whenUnsatisfiable: DoNotSchedule labelSelector: matchLabels: app: httpd containers: - name: httpd image: httpd:latest ports: - containerPort: 80
また、次の内容の Service も作成し、Service 経由で Pod にアクセスできるようにしました。
1つの Service では1つのIPスタックしか扱えないため、デュアルスタック構成の場合は .spec.ipFamily に "IPv4" または "IPv6" を指定する必要があります。
kind: Service apiVersion: v1 metadata: name: httpd spec: type: NodePort ipFamily: IPv6 ports: - port: 80 targetPort: 80 selector: app: httpd
作成した Kubernetes リソースの状態
作成した Pod や Service には、期待通りに IPv6 アドレスが割り当てられました。
$ kubectl get deploy -o wide NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR httpd 2/2 2 2 13m httpd httpd:latest app=httpd $ kubectl get pod -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES httpd-9bdf85554-n2f55 1/1 Running 0 13m 10.244.171.65 worker <none> <none> httpd-9bdf85554-nqnr4 1/1 Running 0 13m 10.244.219.66 master <none> <none> $ kubectl get pod -o custom-columns=NAME:.metadata.name,IPv4:.status.podIPs[0].ip,IPv6:.status.podIPs[1].ip NAME IPv4 IPv6 httpd-9bdf85554-n2f55 10.244.171.65 fd00:1:0:ab7e:b4b6:dc9b:3ca1:a781 httpd-9bdf85554-nqnr4 10.244.219.66 fd00:1:0:db4d:f2f2:402d:3b99:a182 $ kubectl get service -o wide NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR httpd NodePort fd00::9bc <none> 80:31837/TCP 9s app=httpd kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 115m <none>
NAT テーブルの設定
Master ノードおよび Worker ノードの NAT テーブルは、次の内容に変更されていました。
$ sudo ip6tables -L -n -t nat Chain PREROUTING (policy ACCEPT) target prot opt source destination cali-PREROUTING all ::/0 ::/0 /* cali:6gwbT8clXdHdC1b1 */ KUBE-SERVICES all ::/0 ::/0 /* kubernetes service portals */ Chain INPUT (policy ACCEPT) target prot opt source destination Chain OUTPUT (policy ACCEPT) target prot opt source destination cali-OUTPUT all ::/0 ::/0 /* cali:tVnHkvAo15HuiPy0 */ KUBE-SERVICES all ::/0 ::/0 /* kubernetes service portals */ Chain POSTROUTING (policy ACCEPT) target prot opt source destination cali-POSTROUTING all ::/0 ::/0 /* cali:O3lYWMrLQYEMJtB5 */ KUBE-POSTROUTING all ::/0 ::/0 /* kubernetes postrouting rules */ Chain KUBE-MARK-MASQ (4 references) target prot opt source destination MARK all ::/0 ::/0 MARK or 0x4000 Chain KUBE-NODEPORTS (1 references) target prot opt source destination KUBE-MARK-MASQ tcp ::/0 ::/0 /* default/httpd: */ tcp dpt:31837 KUBE-SVC-6SDXOZXL2M4BHO2Q tcp ::/0 ::/0 /* default/httpd: */ tcp dpt:31837 Chain KUBE-POSTROUTING (1 references) target prot opt source destination MASQUERADE all ::/0 ::/0 /* kubernetes service traffic requiring SNAT */ mark match 0x4000/0x4000 Chain KUBE-PROXY-CANARY (0 references) target prot opt source destination Chain KUBE-SEP-JYKFFQKKRMMGYAWD (1 references) target prot opt source destination KUBE-MARK-MASQ all fd00:1:0:ab7e:b4b6:dc9b:3ca1:a781 ::/0 /* default/httpd: */ DNAT tcp ::/0 ::/0 /* default/httpd: */ tcp to:[fd00:1:0:ab7e:b4b6:dc9b:3ca1:a781]:80 Chain KUBE-SEP-VATU5ZPQYFRGI5HR (1 references) target prot opt source destination KUBE-MARK-MASQ all fd00:1:0:db4d:f2f2:402d:3b99:a182 ::/0 /* default/httpd: */ DNAT tcp ::/0 ::/0 /* default/httpd: */ tcp to:[fd00:1:0:db4d:f2f2:402d:3b99:a182]:80 Chain KUBE-SERVICES (2 references) target prot opt source destination KUBE-MARK-MASQ tcp !fd00:1::/48 fd00::9bc /* default/httpd: cluster IP */ tcp dpt:80 KUBE-SVC-6SDXOZXL2M4BHO2Q tcp ::/0 fd00::9bc /* default/httpd: cluster IP */ tcp dpt:80 KUBE-NODEPORTS all ::/0 ::/0 /* kubernetes service nodeports; NOTE: this must be the last rule in this chain */ ADDRTYPE match dst-type LOCAL Chain KUBE-SVC-6SDXOZXL2M4BHO2Q (2 references) target prot opt source destination KUBE-SEP-JYKFFQKKRMMGYAWD all ::/0 ::/0 /* default/httpd: */ statistic mode random probability 0.50000000000 KUBE-SEP-VATU5ZPQYFRGI5HR all ::/0 ::/0 /* default/httpd: */ Chain cali-OUTPUT (1 references) target prot opt source destination cali-fip-dnat all ::/0 ::/0 /* cali:GBTAv2p5CwevEyJm */ Chain cali-POSTROUTING (1 references) target prot opt source destination cali-fip-snat all ::/0 ::/0 /* cali:Z-c7XtVd2Bq7s_hA */ cali-nat-outgoing all ::/0 ::/0 /* cali:nYKhEzDlr11Jccal */ Chain cali-PREROUTING (1 references) target prot opt source destination cali-fip-dnat all ::/0 ::/0 /* cali:r6XmIziWUJsdOK6Z */ Chain cali-fip-dnat (2 references) target prot opt source destination Chain cali-fip-snat (1 references) target prot opt source destination Chain cali-nat-outgoing (1 references) target prot opt source destination MASQUERADE all ::/0 ::/0 /* cali:Ir_z2t1P6-CxTDof */ match-set cali60masq-ipam-pools src ! match-set cali60all-ipam-pools dst
確認結果の整理
サンプルアプリケーションをデプロイする前と比較して、NAT テーブルには次の3つのチェーンが増えています。
- KUBE-SEP-JYKFFQKKRMMGYAWD
- KUBE-SEP-VATU5ZPQYFRGI5HR
- KUBE-SVC-6SDXOZXL2M4BHO2Q
NAT テーブルの設定を読み解くと、ClusterIP "fd00::9bc" の TCP 80 番ポートへの通信では、次のように NAT が行われることがわかります。
- 50% ずつの確率で KUBE-SEP-JYKFFQKKRMMGYAWD または KUBE-SEP-VATU5ZPQYFRGI5HR チェーンを実行する。
- KUBE-SERVICES チェーン2行目
- KUBE-SVC-6SDXOZXL2M4BHO2Q チェーン
- KUBE-SEP-JYKFFQKKRMMGYAWD チェーンが実行されると、宛先を Worker ノードの Pod "fd00:1:0:ab7e:b4b6:dc9b:3ca1:a781" の TCP 80 番ポートに変換する。
- KUBE-SEP-JYKFFQKKRMMGYAWD チェーン2行目
- KUBE-SEP-VATU5ZPQYFRGI5HR チェーンが実行されると、宛先を Master ノードの Pod "fd00:1:0:db4d:f2f2:402d:3b99:a182" の TCP 80 番ポートに変換する。
- KUBE-SEP-VATU5ZPQYFRGI5HR チェーン2行目
- Pod CIDR "fd00:1::/48" 以外から ClusterIP への通信では、送信元をIPマスカレードする。
- KUBE-SERVICES チェーン1行目
- KUBE-MARK-MASQ チェーン
- KUBE-POSTROUTING チェーン
- Pod が送信元で、宛先が自 Pod に変換される場合、送信元をIPマスカレードする。
- KUBE-SEP-JYKFFQKKRMMGYAWD チェーン1行目または KUBE-SEP-VATU5ZPQYFRGI5HR チェーン1行目
- KUBE-MARK-MASQ チェーン
- KUBE-POSTROUTING チェーン
このうち、最後の動作はいわゆるヘアピン NAT であり、Pod が ClusterIP 経由でのアクセスに対する応答を適切に返せるようにするために必要なものです。
また、NodePort への通信では、次のように NAT が行われることもわかります。
- 送信元をIPマスカレードする。
- KUBE-NODEPORTS チェーン1行目
- KUBE-MARK-MASQ チェーン
- KUBE-POSTROUTING チェーン
- ClusterIP への通信と同じ NAT を行う。
- KUBE-NODEPORTS チェーン2行目
なお、IPv4 の NAT テーブルには、サンプルアプリケーションに関する NAT 設定は追加されていませんでした。
Service の .spec.ipFamily で IPv6 を指定すると、IPv6 に関する NAT 設定のみ追加されることが確認できました。
6. まとめ
本記事では、IPv4-IPv6 デュアルスタック構成の Kubernetes クラスタを構築する方法と、IPv6 通信がどのように行われるようになっているかを解説しました。
本記事執筆時点では、Kubernetes クラスタをいったん IPv4 シングルスタック構成で構築すると、デュアルスタック構成に変更することはできません。
クラスタを新しく構築する際には、初めからデュアルスタック構成を設計して構築しておくと、後から困ることが少なくて良いでしょう。
7. 補足
ユニークローカルユニキャストアドレスの利用について
本記事では Pod CIDR にユニークローカルユニキャストアドレスを利用し、Pod から Pod CIDR 外に通信する際は、ノードでIPマスカレードを行うようにしました。
(calico-node の環境変数 CALICO_IPV6POOL_NAT_OUTGOING の指定を追加しました。)
ノードで NAT することで、ユニークローカルユニキャストアドレスが外部ネットワークに出ていかないようにするだけでなく、Pod CIDR に関する経路情報をルーターなどクラスタ外の機器に持たせなくても済むようにしています。
しかし、IPv6 ではアドレス空間が非常に広いため、内部ネットワークでもグローバルユニキャストアドレスを利用し、NAT せずに利用することが一般的です。
本記事では試していませんが、BGP ルーターを用意し、Pod CIDR に関する経路情報をルーターに渡して適切にルーティングされるようにすれば、NAT が不要になります。
NAT を行わない分だけ通信が高速化する効果も得られると考えられるため、興味がある方はお試しください。
https://docs.projectcalico.org/reference/resources/bgppeer
Kubernetes のクラスタ基盤部分での IPv6 の利用について
Kubernetes は多数のコンポーネント (etcd, kube-apiserver, kube-controller-manager, kube-scheduler, kubelet, kube-proxy など) で構成されており、それらは互いに通信して協調して動作しています。
本記事ではこの部分の通信は IPv4 で行われるようにしましたが、IPv6 で行うようにすることもできます。
具体的には、kubeadm を用いて Master ノードをセットアップする際、コマンドを次のように変更すると良いです。
$ sudo kubeadm init \ --feature-gates="IPv6DualStack=true" \ --pod-network-cidr="fd00:1::/48,10.244.0.0/16" \ --service-cidr="fd00::/108,10.96.0.0/12" \ --apiserver-advertise-address="2001:db8::11"
- --apiserver-advertise-address パラメータを追加し、ノードの IPv6 アドレスを指定する。
- --pod-network-cidr, --service-cidr パラメータは、IPv6 アドレスを先に記載する。
- IPv6 をプライマリ、IPv4 をセカンダリとする。
これにより、etcd や kube-apiserver の通信は IPv6 で行われるようになります。
ただし、kubeadm は kube-controller-manager や kube-scheduler を IPv4 アドレスにバインドして実行するような構成でクラスタを構築するようになっており、その動作を変更するパラメータは用意されていません。
そのため、kubeadm でクラスタを構築したのち、それらの静的 Pod のマニフェストを手動で変更し、IPv6 が利用されるようにする必要があります。
https://github.com/kubernetes/kubernetes/blob/v1.18.5/cmd/kubeadm/app/phases/controlplane/manifests.go#L306