: O. Yuanying

KubernetesのPodに二枚目のNICでmacvlanを試す

kubevirt の環境を作って いくつか気になる点があるのだけれども、 その一つがネットワークである。

VMをPod Networkに繋ぐことができるのは良いのだが、 今までvirshを使って手作業でVMを利用していたときは bridge を作ってホストのネットワークに参加させていたので、 できれば同じようにkubevirtのVMもホストのネットワークに繋げたい。

幸いなことに、kubevirt は multus に対応している。

そこで二枚目のNICをVMに刺して、それをノードのネットワークに参加させれば良いのではないかと思い付いた。

macvlan

VLAN対応のスイッチを買って自宅のクラスターのノードを参加させるか、などいくつか考えたのだが、残念なことにNUCにNICは二つない。 色々思案した後に macvlan がちょうどいいんじゃないかと思い付いた。

CNI の macvlan プラグイン は Pod を直接ホストのネットワークに参加させるにはちょうど良いプラグインに見えるが、 macvlan はその、「ホストとの通信ができなくなる」という Pod network として利用するには致命的な制限があるためあまり利用することはないのだが、 今回の要件は Pod network とは別に二枚目のNICをPodに刺す、というユースケースなので、その制限も問題にならない。

と、いうことで以下のような構成を組む。

CNI

Pod の起動

試す、と言ってもいきなり multus をインストールして multus 越しにNICを刺すのも面倒だったので、直接手作業で NIC を Pod に刺して試すことにした。

  • https://qiita.com/yuanying/items/68b2a32b4d217e679955

まずは適当にPodを作る。

$ cat <<EOF | k apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  namespace: default
spec:
  containers:
  - name: nginx
    image: nginx:1.14.2
    ports:
    - containerPort: 80
  nodeSelector:
    hostname: pablo
EOF

Pod の状態を確認。指定したノードに起動している。

➜ k get pod -o wide
NAME    READY   STATUS    RESTARTS   AGE   IP            NODE    NOMINATED NODE   READINESS GATES
nginx   1/1     Running   0          4s    10.244.4.99   pablo   <none>           <none>

ノードでPodの確認

ノードにsshし、crictl を使ってPodを確認する。

$ sudo su -
# crictl pods --name nginx --namespace default
POD ID              CREATED             STATE               NAME                NAMESPACE           ATTEMPT             RUNTIME
bbdc2c603ffb9       2 minutes ago       Ready               nginx               default             0                   (default)

このPodが利用しているNework Namespaceは以下のコマンドで確認できる。

# crictl inspectp $(crictl pods --name ${POD_NAME} --namespace ${POD_NAMESPACE} -q) | gojq '.info.runtimeSpec.linux.namespaces.[]|select(.type=="network")|.path' -r
/var/run/netns/cni-5bcada6f-4722-477c-0e55-b7339fb28fc6

上記のワンライナーは以下のようなことをやっている。

  1. crictl pods --name ${POD_NAME} --namespace ${POD_NAMESPACE} -q
    • Pod の ID を取得。
  2. crictl inspectp
    • Pod の詳細情報を json で取得。
  3. gojq '.info.runtimeSpec.linux.namespaces.[]|select(.type=="network")|.path'
    • Pod の詳細情報から Network namespace のパスを取得。

上記の Network namespace に入って状態を確認してみる。

# ip netns exec cni-5bcada6f-4722-477c-0e55-b7339fb28fc6 bash
# ip addr 
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: eth0@if248: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 16:26:ab:97:65:e1 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 10.244.4.99/24 brd 10.244.4.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fde7:4ad6:425d:8::248/64 scope global
       valid_lft forever preferred_lft forever
    inet6 fe80::1426:abff:fe97:65e1/64 scope link
       valid_lft forever preferred_lft forever

# exit

ローカルインターフェースと、デフォルトのPod networkのNICが刺さっていることが確認できる。

CNI macvlan

ということで、上記の network namespace に CNI の macvlan プラグインで二枚目のNICを刺してみる。

IPAM に dhcp プラグインを利用するので事前に CNI の dhcp プラグインを daemon モードで起動しておく。

# /opt/cni/bin/dhcp daemon

まずはCNI実行に必要な環境変数をセットアップ。

POD_NAME=nginx
POD_NAMESPACE=default

export CNI_PATH=/opt/cni/bin
export PATH=$CNI_PATH:$PATH

# NIC を追加するので ADD
export CNI_COMMAND=ADD

# 上記のワンライナーで Network namespace のパスを取得
export CNI_NETNS=$(crictl inspectp $(crictl pods --name ${POD_NAME} --namespace ${POD_NAMESPACE} -q) | gojq '.info.runtimeSpec.linux.namespaces.[]|select(.type=="network")|.path' -r)

# Container ID は Network namespace の名前を利用することが多そう
export CNI_CONTAINERID=$(echo ${CNI_NETNS} | cut -f 5 -d "/")

# すでに一枚刺さってるので二枚目なので `eth1` を指定
export CNI_IFNAME=eth1

そして macvlan プラグインをキック!

master に指定しているNICはホストのネットワークにつなげたいNICを指定すること。

/opt/cni/bin/macvlan <<EOF
{
    "name": "mynet",
    "type": "macvlan",
    "master": "eno1",
    "ipam": {
        "type": "dhcp"
    }
}
EOF

うまくいくと以下のような出力を得ることができる。

{
    "cniVersion": "0.1.0",
    "ip4": {
        "ip": "192.168.2.11/16",
        "gateway": "192.168.0.1",
        "routes": [
            {
                "dst": "0.0.0.0/0",
                "gw": "192.168.0.1"
            }
        ]
    },
    "dns": {}
}

もう一回、先ほどのNetwork namespaceに入って、NICが刺さっているか確認する。

# ip netns exec $CNI_CONTAINERID ip addr
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: eth0@if248: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 16:26:ab:97:65:e1 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 10.244.4.99/24 brd 10.244.4.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fde7:4ad6:425d:8::248/64 scope global
       valid_lft forever preferred_lft forever
    inet6 fe80::1426:abff:fe97:65e1/64 scope link
       valid_lft forever preferred_lft forever
3: eth1@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 1e:52:59:8f:14:7f brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 192.168.2.11/16 brd 192.168.255.255 scope global eth1
       valid_lft forever preferred_lft forever
    inet6 XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX/64 scope global dynamic mngtmpaddr
       valid_lft 86308sec preferred_lft 14308sec
    inet6 fe80::1c52:59ff:fe8f:147f/64 scope link
       valid_lft forever preferred_lft forever

DHCPで取得したアドレスのついた二枚目のNICが刺さってることが確認できますね!(ついでにグローバルのIPv6アドレスもついてきた。

別のノードからpingも通る。素晴らしい。

➜ ping 192.168.2.11
PING 192.168.2.11 (192.168.2.11) 56(84) bytes of data.
64 bytes from 192.168.2.11: icmp_seq=1 ttl=63 time=0.604 ms
64 bytes from 192.168.2.11: icmp_seq=2 ttl=63 time=0.290 ms
64 bytes from 192.168.2.11: icmp_seq=3 ttl=63 time=0.487 ms

注意点

macvlan の注意点として、ホスト <-> コンテナ間 の通信ができない、というものがあるが、 その制限により、使用しているデフォルトに使用している CNI プラグインによっては同一ノード上の Pod 間通信ができないので注意が必要。

つまり同一ホスト上の以下のような通信ができない場合がある。

  • x: Pod A (default interface) -> Pod B (macvlan interface)
  • x: Pod A (macvlan interface) -> Pod B (default interface)

ただ、Service 越しの通信はできるので、Pod間通信をしなければならない特別な事情がない限りは問題なさそう。

まとめ

自宅クラスタなのでノードにNICが1枚しか刺さってないので、Pod networkの二つ目のネットワークどうしようかと悩んでいたが、 なんのことはない、単にノードのネットワークにつなげたいだけなら二つ目のNICで macvlan を使えば良いことがわかった。

追記 2022-09-29

ドキュメントを読んでいたら、二つ目のNICとしてmacvlanは使えない模様。