: O. Yuanying

CNI bridge プラグインで IPv6 を試す

と、いうことでまず、k8sのネットワークといえばCNI。Container Network Interface は Bridge プラグインの IPv6 回りの挙動を確かめてみました。

TL;DR

IPv6にしたからといってIPv4と特に設定項目や挙動は変わりませんでした。(挙動が変わらないネットワーク構成にしたとも言える。)

環境

ソフトウェアバージョン

  • CNI: v0.9.1
    • https://github.com/containernetworking/plugins/releases/download/v0.9.1/cni-plugins-linux-amd64-v0.9.1.tgz

ネットワーク構成

Docker コンテナで IPv6 を利用する3つの構成パターンにもあるように、

  • パターン1 : ユニークローカルユニキャストアドレス + IPマスカレード
  • パターン2 : グローバルユニキャストアドレス + ルーティング
  • パターン3 : ホストネットワークのサブネット + NDP Proxy

という3パターンが考えられますが、最終的にk8s上で使うことだし(どうせPodはエフェメラルなんだし)、ということでパターン1を試してみることにします。

注: Pod同士の通信はルーティングで、Podから外への通信はIPマスカレード、外からPodへはServiceを介したPort fowarding、という構成でクラスタを組んでいるため。

と、いうことで以下のようなネットワーク構成を作成し、

  • Node A内のコンテナから外側への通信。
  • Node A内のコンテナ間通信。
  • Node AとNode FF内のノードを跨いだコンテナ間通信。

を試してみます。

注: 実際に環境を作って試す際には、PodのネットワークはRFC 4086に従ってランダムに生成したユニークローカルユニキャストアドレスを利用するようにしてください。

検証環境構築

兎にも角にも、検証用環境のノードを2台用意します。ノードのIPv6ネットワークも既に構築済とされていることにします。それぞれのノードにログインし、以下のコマンドでcniプラグインをインストールしてください。

sudo su
mkdir -p /opt/cni/bin
curl -L https://github.com/containernetworking/plugins/releases/download/v0.9.1/cni-plugins-linux-amd64-v0.9.1.tgz | tar zxvf - -C /opt/cni/bin

以上で検証用環境の構築は終わりです。

Node A内のコンテナから外側への通信

それでは、Node Aにログインし、container-1 (fd00:a::2)を起動します。

コンテナの作成

CNIにおけるコンテナとは、ようするにNetwork Namespaceのことです。rootになり、ネットワークネームスペースを作成します。

contid=container-1
netns=/var/run/netns/$contid
ip netns add $contid

できました。以降の作業も面倒なのでrootで作業しましょう。

CNI を実行する

CNIを実行して前項で作成したコンテナにネットワークインターフェースを挿し、IPを割り当てます。

container-1 (fd00:a::2) はサブネット fd00:a::/64 に属しています。とりあえず環境変数にその値を設定しておきます。

subnet=fd00:a::/64

また、CNIに渡す環境変数を設定します。

export CNI_PATH=/opt/cni/bin     # CNIバイナリへのパスを指定します
export PATH=$CNI_PATH:$PATH
export CNI_COMMAND=ADD           # CNIのコマンドを指定します
export CNI_IFNAME=eth0           # CNIで作成するネットワークインターフェースの名前を指定します
export CNI_CONTAINERID=$contid   # コンテナのID(コンテナを識別する任意の値)を指定します
export CNI_NETNS=$netns          # コンテナのネットワークネームスペースを指定します

それでは準備ができたので、CNI bridgeプラグインを実行しましょう。

/opt/cni/bin/bridge <<EOF
{
    "cniVersion": "0.3.1",
    "name": "bridge",
    "type": "bridge",
    "bridge": "cni0",
    "isDefaultGateway": true,
    "ipMasq": true,
    "ipam": {
        "type": "host-local",
        "ranges": [
          [{"subnet": "${subnet}"}]
        ]
    }
}
EOF

それぞれのパラメータの意味は以下のようになります。

  • name: 任意の値
  • type: 実行するプラグインを指定します、今回はbridgeを指定します。
  • bridge: ホストとコンテナを繋ぐブリッジの名前を指定します。プラグインを実行することで指定した名前のブリッジが作成されます。デフォルト値は cni0 のようです。
  • isDefaultGateway: 作成したネットワークインタフェースをデフォルトのゲートウェイにします。自動でデフォルトルートはゲートウェイに設定されます。デフォルトルートを指定したくない場合は isGatewaytrueにするか、何も指定しません。
  • ipMasq: コンテナのL2外への通信がIPマスカレードされます。

実行すると以下のような出力を得ることができます。

{
    "cniVersion": "0.3.1",
    "interfaces": [
        {
            "name": "cni0",
            "mac": "12:3e:02:9a:25:44"
        },
        {
            "name": "veth43ffea2a",
            "mac": "1e:ea:8d:24:5a:fd"
        },
        {
            "name": "eth0",
            "mac": "f2:48:95:e8:c2:26",
            "sandbox": "/var/run/netns/container-1"
        }
    ],
    "ips": [
        {
            "version": "6",
            "interface": 2,
            "address": "fd00:a::2/64",
            "gateway": "fd00:a::1"
        }
    ],
    "routes": [
        {
            "dst": "::/0",
            "gw": "fd00:a::1"
        }
    ],
    "dns": {}
}

fd00:a::2/64というアドレスがコンテナに設定されたことが確認できます。 また、ipMasqtrueに指定することで、ホストのiptablesにマスカレードの設定がされることを確認できます。

root@node-a:/home/ubuntu# ip6tables-save |grep MASQUERADE
-A CNI-cabdc08408d55627dc1f9b67 ! -d ff00::/8 -m comment --comment "name: \"bridge\" id: \"container-1\"" -j **MASQUERADE**

ゲートウェイの設定

コンテナ内に入ってネットワークインターフェースを確認してみます。

# ip addr show
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
3: eth0@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether f2:48:95:e8:c2:26 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fd00:a::2/64 scope global
       valid_lft forever preferred_lft forever
    inet6 fe80::f048:95ff:fee8:c226/64 scope link
       valid_lft forever preferred_lft forever

eth0 という名前でネットワークインターフェースがあることがわかります。

# ip -6 route
fd00:a::/64 dev eth0 proto kernel metric 256 pref medium
fe80::/64 dev eth0 proto kernel metric 256 pref medium
default via fd00:a::1 dev eth0 metric 1024 pref medium

また、デフォルトゲートウェイがfd00:a::1 dev eth0に指定されていることがわかります。isDefaultGatewayを指定せず、isGatewayフラグのみを指定するとこのルーティングが追加されていないことが確認できます。

コンテナ外への通信を確認

コンテナ内に入ってルーターに対してpingをしてみます。

# ip netns exec container-1 bash
# ping6 2008:db8::1
PING 2008:db8::1(2008:db8::1) 56 data bytes
64 bytes from 2008:db8::1: icmp_seq=1 ttl=63 time=0.927 ms
64 bytes from 2008:db8::1: icmp_seq=2 ttl=63 time=0.867 ms
64 bytes from 2008:db8::1: icmp_seq=3 ttl=63 time=1.29 ms
64 bytes from 2008:db8::1: icmp_seq=4 ttl=63 time=1.39 ms
64 bytes from 2008:db8::1: icmp_seq=5 ttl=63 time=1.36 ms
64 bytes from 2008:db8::1: icmp_seq=6 ttl=63 time=1.33 ms

ちゃんと通信できています。理由があって名前解決できませんが、IPアドレスを指定すればGoogleともやりとりできています。

# ping6 2404:6800:4004:822::2004 # www.google.com
PING 2404:6800:4004:822::2004(2404:6800:4004:822::2004) 56 data bytes
64 bytes from 2404:6800:4004:822::2004: icmp_seq=1 ttl=114 time=3.75 ms
64 bytes from 2404:6800:4004:822::2004: icmp_seq=2 ttl=114 time=4.28 ms
64 bytes from 2404:6800:4004:822::2004: icmp_seq=3 ttl=114 time=3.89 ms
64 bytes from 2404:6800:4004:822::2004: icmp_seq=4 ttl=114 time=4.12 ms
^C
--- 2404:6800:4004:822::2004 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3005ms
rtt min/avg/max/mdev = 3.747/4.010/4.281/0.205 ms

ルーターに入ってtcpdumpすることによって、ソースのアドレスがノードのIPv6アドレスになっていることも確認できました。 色々確認してみて、IPv6だろうがIPv4と挙動があまり変わらないことがわかってきて若干退屈ですね!

Node A内のコンテナ間通信

退屈で微妙になってきましたが、今度はNode A内のコンテナ間で通信してみます。 さっきと同じようにサクッと container-2 を作成しましょう。

まずは環境変数を指定し、

contid=container-2
netns=/var/run/netns/$contid
ip netns add $contid
subnet=fd00:a::/64             # Node A内のサブネット、先ほどと同じ値

export CNI_PATH=/opt/cni/bin
export CNI_COMMAND=ADD
export CNI_IFNAME=eth0
export CNI_CONTAINERID=$contid
export CNI_NETNS=$netns

CNIプラグインを実行。

/opt/cni/bin/bridge <<EOF
{
    "cniVersion": "0.3.1",
    "name": "bridge",
    "type": "bridge",
    "bridge": "cni0",
    "isDefaultGateway": true,
    "ipMasq": true,
    "ipam": {
        "type": "host-local",
        "ranges": [
          [{"subnet": "${subnet}"}]
        ]
    }
}
EOF

この手順通りに実行していた場合、container-2にはfd00:a::3/64というアドレスが設定されていたはずです。

container-1からcontainer-2にping

container-1からcontainer-2にpingしてみます。

# ping fd00:a::3
PING fd00:a::3(fd00:a::3) 56 data bytes
64 bytes from fd00:a::3: icmp_seq=1 ttl=64 time=0.126 ms
64 bytes from fd00:a::3: icmp_seq=2 ttl=64 time=0.095 ms
64 bytes from fd00:a::3: icmp_seq=3 ttl=64 time=0.065 ms
64 bytes from fd00:a::3: icmp_seq=4 ttl=64 time=0.149 ms
^C
--- fd00:a::3 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3078ms
rtt min/avg/max/mdev = 0.065/0.108/0.149/0.031 ms

通信できました。

container-2でtcpdump

# tcpdump -n -i eth0 icmp6
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
^C07:51:57.993514 IP6 fd00:a::2 > ff02::1:ff00:3: ICMP6, neighbor solicitation, who has fd00:a::3, length 32
07:51:57.993541 IP6 fd00:a::3 > fd00:a::2: ICMP6, neighbor advertisement, tgt is fd00:a::3, length 32
07:51:57.993576 IP6 fd00:a::2 > fd00:a::3: ICMP6, echo request, seq 1, length 64
07:51:57.993584 IP6 fd00:a::3 > fd00:a::2: ICMP6, echo reply, seq 1, length 64

tcpdumpからはcontainer-1からの通信がIPマスカレードされていないことが確認できました。

Node AとNode F内のノードを跨いだコンテナ間通信

そろそろめんどくさくなってきたので結論から書くと、container-1からcontainer-3への通信はNATされました。(そういう設定が入っているので当然だけど)

ちなみに、Node A内のコンテナと、Node F内のコンテナが通信できるように以下のルーティングをそれぞれのノードで設定しました。

Node A

ip -6 route add fd00:ff::/64 via 2008:db8::ff # `fd00:ff::/64`の通信は `Node F (2008:db8::ff)` へ

Node F

ip -6 route add fd00:a::/64 via 2008:db8::a # `fd00:a::/64`の通信は `Node A (2008:db8::a)` へ

まとめ

コンテナにグローバルユニキャストアドレスを振らずにユニークローカルユニキャストアドレスを振る構成の場合、IPv4と特に挙動に変わりはなく退屈。

次は「Node AとNode F内のノードを跨いだコンテナ間通信」でNATされずにルーティングだけで通信できるように FlannelのCNIプラグインを単体で試す。