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
上記のワンライナーは以下のようなことをやっている。
crictl pods --name ${POD_NAME} --namespace ${POD_NAMESPACE} -q
- Pod の ID を取得。
crictl inspectp
- Pod の詳細情報を json で取得。
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は使えない模様。