Red Hatでコンサルタントをしている織です。本記事では、KubernetesのPodに複数のNICを接続するための、MultusというCNIプラグインについてご紹介します。
Multusとは
MultusはKubernetesのCNI (Container Network Interface) プラグインのひとつです。典型的なKubernetesの構成では、PodにはひとつのNICしかアサインされませんが、Multusを使うとPodに追加のNICを生やして、複数のネットワークに接続することができるようになります。内部的には、Multusは複数のCNIプラグインを同時に稼働させるための「メタCNIプラグイン」として稼働します。

Multusは、Kubernetesの Network Plumbing Working Group で議論されている Kubernetes Network Custom Resource Definition De-facto Standard という標準にしたがっており、その参照実装として開発されてきました。
準備
CentOS7とKubernetes v1.17を使って、動作検証環境を準備します。
まず、NICを2つ(eth0, eth1)持つCentOS7の仮想マシンを3台作成し、kubeadm を使って、masterノード1台 + workerノード2台のクラスタを作成します。
Multusでは複数のCNIプラグインを併用しますが、そのうちのひとつをDefault(もしくはMaster)プラグインと呼びます。PodからAPIへの通信やLiveness/Readiness Probe等、従来のPod接続で行っていた通信は、基本的にDefaultプラグインを使います。
以下の操作では、DefaultのプラグインとしてFlannelを使います。その準備として、まず(Multusではない)普通のCNIプラグインとしてFlannelをインストールします。
kube-master $ kubectl create -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
デフォルトゲートウェイが設定されているNIC(手元の環境ではeth0)でFlannelを使うように設定されます。 他方のNIC(手元の環境ではeth1)を、Podを接続するもうひとつのネットワーク接続に使用します。
次に、Multusに必要な諸々を入れます[1]。 Multusでは、接続するネットワークをNetworkAttachmentDefinition(もしくはnet-attach-def)というCustom Resourceとして表現します。下記コマンドで、その定義(CRD, Custom Resource Definition)や、MultusのCNIデーモンをデプロイするDaemonSet等をインストールします。
kube-master $ kubectl create -f https://raw.githubusercontent.com/intel/multus-cni/master/images/multus-daemonset.yml
以上で準備は完了です。インストールが完了すると、下記のような感じになります。
kube-master $ kubectl get pod --all-namespaces -o wide NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES kube-system coredns-6955765f44-bmrln 1/1 Running 0 4d2h 10.244.1.2 kube-node-2 <none> <none> kube-system coredns-6955765f44-qh567 1/1 Running 0 4d2h 10.244.0.2 kube-master <none> <none> kube-system etcd-kube-master 1/1 Running 1 4d2h 192.168.122.125 kube-master <none> <none> kube-system kube-apiserver-kube-master 1/1 Running 1 4d2h 192.168.122.125 kube-master <none> <none> kube-system kube-controller-manager-kube-master 1/1 Running 1 4d2h 192.168.122.125 kube-master <none> <none> kube-system kube-flannel-ds-amd64-fv8s4 1/1 Running 0 4d 192.168.122.172 kube-node-1 <none> <none> kube-system kube-flannel-ds-amd64-nx85b 1/1 Running 0 4d 192.168.122.143 kube-node-2 <none> <none> kube-system kube-flannel-ds-amd64-zjt4d 1/1 Running 0 4d 192.168.122.125 kube-master <none> <none> kube-system kube-multus-ds-amd64-4fhfh 1/1 Running 0 4d 192.168.122.172 kube-node-1 <none> <none> kube-system kube-multus-ds-amd64-4n2nq 1/1 Running 0 4d 192.168.122.125 kube-master <none> <none> kube-system kube-multus-ds-amd64-gkdn5 1/1 Running 0 4d 192.168.122.143 kube-node-2 <none> <none> kube-system kube-proxy-g7wwf 1/1 Running 1 4d2h 192.168.122.172 kube-node-1 <none> <none> kube-system kube-proxy-ls8x5 1/1 Running 1 4d2h 192.168.122.125 kube-master <none> <none> kube-system kube-proxy-n9w4g 1/1 Running 1 4d2h 192.168.122.143 kube-node-2 <none> <none> kube-system kube-scheduler-kube-master 1/1 Running 1 4d2h 192.168.122.125 kube-master <none> <none>
この手順で作成されたCNIプラグインとしてのMultusの設定は、下記のようになります。
kube-master $ jq . /etc/cni/net.d/00-multus.conf
{
"cniVersion": "0.3.1",
"name": "multus-cni-network",
"type": "multus",
"logLevel": "debug",
"logFile": "/tmp/multus.log",
"binDir": "/opt/multus/bin",
"kubeconfig": "/etc/cni/net.d/multus.d/multus.kubeconfig",
"delegates": [
{
"name": "cbr0",
"cniVersion": "0.3.1",
"plugins": [
{
"type": "flannel",
"delegate": {
"hairpinMode": true,
"isDefaultGateway": true
}
},
{
"type": "portmap",
"capabilities": {
"portMappings": true
}
}
]
}
]
}
Multusインストール前のFlannelのCNI設定が、delegates 以下にそのまま入っている形になっています。
普通のPodのデプロイ
まずは従来どおり、NICをひとつだけ持つPodをデプロイします。この場合、DefaultプラグインであるFlannelのネットワークに接続します。
kube-master $ cat unmultus-pod.yaml
---
apiVersion: v1
kind: Pod
metadata:
generateName: unmultus-
labels:
app: unmultus
spec:
containers:
- name: centos-tools
image: docker.io/centos/tools:latest
command:
- /sbin/init
securityContext:
privileged: true
のようなmanifestを作ってデプロイします。
kube-master $ kubectl create -f unmultus-pod.yaml pod/unmultus-7wpr6 created $ kubectl get pod -l app=unmultus NAME READY STATUS RESTARTS AGE unmultus-7wpr6 1/1 Running 0 16s
いつもどおり、eth0 として veth のインターフェースがあることがわかります。
kube-master $ kubectl exec -it unmultus-7wpr6 -- ip -d addr show
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 promiscuity 0 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
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@if54: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default
link/ether fe:12:e9:69:80:a5 brd ff:ff:ff:ff:ff:ff link-netnsid 0 promiscuity 0
veth numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
inet 10.244.2.47/24 brd 10.244.2.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::fc12:e9ff:fe69:80a5/64 scope link
valid_lft forever preferred_lft forever
vlanで追加ネットワークに接続
Multusを使って、コンテナホストのeth1にVLANサブインターフェースを作成し、それをPodの2つ目のNICとしてアサインします。
NetworkAttachmentDefinitionの定義
まず、PodをVLANで接続する追加ネットワークをNetworkAttachmentDefinitionのCustom Resourceとして定義します。ここでは vlan-conf という名前をつけています (metadata.name: vlan-conf)。
kube-master $ cat vlan-conf.yaml
---
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
name: vlan-conf
spec:
config: '{
"cniVersion": "0.3.1",
"plugins": [
{
"type": "vlan",
"master": "eth1",
"vlanid": 100,
"capabilities": { "ips": true },
"ipam": {
"type": "static"
}
}, {
"capabilities": { "mac": true },
"type": "tuning"
} ]
}'
今回作成したKubernetes環境は、DefaultプラグインのFlannelが作るネットワーク通信に、コンテナホストの eth0 を使っています。
上記のNetworkAttachmentDefinitionは、Podが接続する(Flannelとは別の)ネットワークとして、コンテナホスト上の eth1 が接続するVLAN ID 100のtagged VLANを使うことを意味します。
kube-master $ kubectl create -f vlan-conf.yaml networkattachmentdefinition.k8s.cni.cncf.io/vlan-conf created $ kubectl get net-attach-def vlan-conf NAME AGE vlan-conf 29s
のように vlan-conf のNetworkAttachmentDefinitionオブジェクトを作成します。
Podの作成
次のようなmanifestでPodをデプロイします。
kube-master $ cat vlan-pod1.yaml
---
apiVersion: v1
kind: Pod
metadata:
# generateName: vlan-
name: vlan-172.16.1.201
labels:
app: multus-vlan
annotations:
k8s.v1.cni.cncf.io/networks: '[
{ "name": "vlan-conf",
"ips": [ "172.16.1.201/24" ]}
]'
spec:
containers:
- name: centos-tools
image: docker.io/centos/tools:latest
command:
- /sbin/init
securityContext:
privileged: true
metadata.annotations.k8s.v1.cni.cncf.io/networks で先ほど作成したnet-attach-defの名前 vlan-conf を指定しています。
kube-master $ kubectl create -f vlan-pod1.yaml pod/vlan-172.16.1.201 created
同様にして、うまく異なるworkerノードにスケジューリングされることを祈りつつ[2]、 172.16.1.202 のIPアドレスをアサインしたPodをデプロイします。
kube-master $ kubectl get pod -l app=multus-vlan -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES vlan-172.16.1.201 1/1 Running 0 3m44s 10.244.2.48 kube-node-1 <none> <none> vlan-172.16.1.202 1/1 Running 0 2m9s 10.244.1.24 kube-node-2 <none> <none>
Pod vlan-172.16.1.201 のインターフェースを確認します。
kube-master $ kubectl exec -it vlan-172.16.1.201 -- ip -d addr show
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 promiscuity 0 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
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@if61: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default
link/ether b6:1e:bd:66:b9:cd brd ff:ff:ff:ff:ff:ff link-netnsid 0 promiscuity 0
veth numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
inet 10.244.2.52/24 brd 10.244.2.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::b41e:bdff:fe66:b9cd/64 scope link
valid_lft forever preferred_lft forever
4: net1@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 52:54:00:5d:8f:e5 brd ff:ff:ff:ff:ff:ff link-netnsid 0 promiscuity 0
vlan protocol 802.1Q id 100 <REORDER_HDR> numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
inet 172.16.1.201/24 brd 172.16.1.255 scope global net1
valid_lft forever preferred_lft forever
inet6 fe80::5054:ff:fe5d:8fe5/64 scope link
valid_lft forever preferred_lft forever
eth0 はDefaultプラグインであるFlannelにつながるインターフェースです。それに加えて、net1 としてVLAN ID 100のvlanインターフェースが追加され、manifestで指定したIPアドレス(172.16.2.201)が付与されていることがわかります。
Pod vlan-172.16.1.202 の net1 も念の為確認しておきます。
kube-master $ kubectl exec -it vlan-172.16.1.202 -- ip -d addr show dev net1
4: net1@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 52:54:00:39:4d:c5 brd ff:ff:ff:ff:ff:ff link-netnsid 0 promiscuity 0
vlan protocol 802.1Q id 100 <REORDER_HDR> numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
inet 172.16.1.202/24 brd 172.16.1.255 scope global net1
valid_lft forever preferred_lft forever
inet6 fe80::5054:ff:fe39:4dc5/64 scope link
valid_lft forever preferred_lft forever
同じくVLAN ID 100のvlanインターフェースとして、172.16.1.202 がアサインされています。
このPodがスケジュールされたworkerノード kube-node-1 のインターフェースは下記のようになっています。
kube-node-1 $ ip link show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
link/ether 52:54:00:d4:9d:eb brd ff:ff:ff:ff:ff:ff
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
link/ether 52:54:00:5d:8f:e5 brd ff:ff:ff:ff:ff:ff
4: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN mode DEFAULT group default
link/ether 8e:f7:5f:9f:84:ff brd ff:ff:ff:ff:ff:ff
5: cni0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP mode DEFAULT group default qlen 1000
link/ether 8a:b9:22:00:44:9b brd ff:ff:ff:ff:ff:ff
55: veth0ce3de47@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP mode DEFAULT group default
link/ether fa:64:45:b0:ea:38 brd ff:ff:ff:ff:ff:ff link-netnsid 0
indexが3なのは eth1 なので、Pod内の net1 の親インターフェースはホストの eth1 であることがわかります。
また、今回は priviledged: true としてPodを起動しているので、Pod内で下記のようにして、ホスト上の物理インターフェースを確認することもできます。
kube-master $ kubectl exec -it vlan-172.16.1.201 -- cat /proc/net/vlan/net1
net1 VID: 100 REORDER_HDR: 1 dev->priv_flags: 1021
total frames received 15
total bytes received 976
Broadcast/Multicast Rcvd 15
total frames transmitted 13
total bytes transmitted 1046
Device: eth1
INGRESS priority mappings: 0:0 1:0 2:0 3:0 4:0 5:0 6:0 7:0
EGRESS priority mappings:
最後に、2つのPodの net1 同士で通信できることを確認します。
kube-master $ kubectl exec -it vlan-172.16.1.201 -- ping -c 3 172.16.1.202 PING 172.16.1.202 (172.16.1.202) 56(84) bytes of data. 64 bytes from 172.16.1.202: icmp_seq=1 ttl=64 time=0.995 ms 64 bytes from 172.16.1.202: icmp_seq=2 ttl=64 time=0.901 ms 64 bytes from 172.16.1.202: icmp_seq=3 ttl=64 time=0.667 ms --- 172.16.1.202 ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2002ms rtt min/avg/max/mdev = 0.667/0.854/0.995/0.139 ms
また、下記のようにmasterノードのeth1にVLAN ID 100のVLANサブインターフェースを作成すると、masterノードとPodの net1 の間で通信することができます。
kube-master $ sudo ip link add link eth1 name eth1.100 type vlan id 100 kube-master $ sudo ip addr add 172.16.1.10/24 dev eth1.100 kube-master $ sudo ip link set up eth1.100 kube-master $ ping -c 3 172.16.1.201 PING 172.16.1.201 (172.16.1.201) 56(84) bytes of data. 64 bytes from 172.16.1.201: icmp_seq=1 ttl=64 time=1.10 ms 64 bytes from 172.16.1.201: icmp_seq=2 ttl=64 time=0.542 ms 64 bytes from 172.16.1.201: icmp_seq=3 ttl=64 time=0.566 ms --- 172.16.1.201 ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2046ms rtt min/avg/max/mdev = 0.542/0.739/1.109/0.261 ms
macvlanで追加ネットワークに接続
macvlanとは
macvlanは、物理インターフェースに対して、異なるMACアドレスとIPアドレスを持つ複数のサブインターフェースを作成する仕組みです。 Linuxのbridgeインターフェースと似ている部分が多いのですが、
- bridgeはMAC Learning、STP等、いわゆるL2スイッチと同じ動きをする。macvlanでは、すべてのサブインターフェースのMACアドレスがわかるので、MAC LeariningやSTPは必要ない
- macvlanサブインターフェースと親の物理インターフェースは直接は通信できない
という違いがあります。またvlanサブインターフェースとは
- vlanサブインターフェースは親インターフェースと同じMACアドレスを持ち、VLANタグを使って異なるL2ドメインに接続するのに対して、macvlanサブインターフェースは親インターフェースとは異なるMACアドレスを持ち、外部ネットワークに直接接続する
という違いがあります。
macvlanでは、親インターフェースが複数のMACアドレス宛てのパケットを受信する必要があります。親インターフェースがUnicast filteringの機能 (IFF_UNICAST_FLT) をサポートする場合は、受信を許可する(macvlanサブインターフェースの)MACアドレスを明示的に登録します。Unicast filteringをサポートしない場合は親インターフェースをPromiscuousモードにします。検証で使った環境では親インターフェースとしてvirtio_netを使っており、virtio_netはUnicast filteringをサポートしています(ので、以下の例では親インターフェースはPromiscuousにはなっていません)。
いくつかの動作モードがありますが、ここでは bridge モードを使います。
NetworkAttachmentDefinitionの定義
Podをmacvlanで接続する追加ネットワークをNetworkAttachmentDefinitionのCustom Resourceとして定義します。ここでは macvlan-conf という名前をつけています (metadata.name: macvlan-conf)。
kube-master $ cat macvlan-conf.yaml
---
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
name: macvlan-conf
spec:
config: '{
"cniVersion": "0.3.1",
"plugins": [
{
"type": "macvlan",
"capabilities": { "ips": true },
"master": "eth1",
"mode": "bridge",
"ipam": {
"type": "static"
}
}, {
"capabilities": { "mac": true },
"type": "tuning"
} ]
}'
上記のNetworkAttachmentDefinitionでは、ホストの eth1 を親インターフェースとして、bridge modeのmacvlanインターフェースを作成し、それをPodをアサインします。
kube-master $ kubectl create -f macvlan-conf.yaml networkattachmentdefinition.k8s.cni.cncf.io/macvlan-conf created $ kubectl get net-attach-def macvlan-conf NAME AGE macvlan-conf 8s
Podの作成
さらに次のようなmanifestでPodをデプロイします。
kube-master $ cat macvlan-pod1.yaml
---
apiVersion: v1
kind: Pod
metadata:
# generateName: macvlan-
name: macvlan-10.1.1.201
labels:
app: multus-macvlan
annotations:
k8s.v1.cni.cncf.io/networks: '[
{
"name": "macvlan-conf",
"ips": [ "10.1.1.201/24" ]
}
]'
spec:
containers:
- name: centos-tools
image: docker.io/centos/tools:latest
command:
- /sbin/init
securityContext:
privileged: true
metadata.annotations.k8s.v1.cni.cncf.io/networks で、先ほど作成したnet-attach-defの名前 macvlan-conf を指定しています。
kube-master $ kubectl create -f macvlan-pod1.yaml pod/macvlan-10.1.1.201 created
同様にして、10.1.1.202 のIPアドレスをアサインしたPodをデプロイします。
kube-master $ kubectl create -f macvlan-pod2.yaml pod/macvlan-10.1.1.202 created
Podが2個デプロイされました。
kube-master $ kubectl get pod -l app=multus-macvlan -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES macvlan-10.1.1.201 1/1 Running 0 2m47s 10.244.2.49 kube-node-1 <none> <none> macvlan-10.1.1.202 1/1 Running 0 93s 10.244.1.25 kube-node-2 <none> <none>
Podのインターフェースを確認すると、bridgeモードのmacvlanインターフェースとして net1 が追加され、IPアドレス 10.1.1.201 が付与されていることがわかります。
kube-master $ kubectl exec -it macvlan-10.1.1.201 -- ip -d addr show
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 promiscuity 0 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
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@if56: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default
link/ether 9a:6e:b6:d9:e8:a3 brd ff:ff:ff:ff:ff:ff link-netnsid 0 promiscuity 0
veth numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
inet 10.244.2.49/24 brd 10.244.2.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::986e:b6ff:fed9:e8a3/64 scope link
valid_lft forever preferred_lft forever
4: net1@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether aa:2a:7c:df:12:b9 brd ff:ff:ff:ff:ff:ff link-netnsid 0 promiscuity 0
macvlan mode bridge numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
inet 10.1.1.201/24 brd 10.1.1.255 scope global net1
valid_lft forever preferred_lft forever
inet6 fe80::a82a:7cff:fedf:12b9/64 scope link
valid_lft forever preferred_lft forever
デプロイしたPod間の疎通を確認します。
kube-master $ kubectl exec -it macvlan-10.1.1.201 -- ping -c 3 10.1.1.202 PING 10.1.1.202 (10.1.1.202) 56(84) bytes of data. 64 bytes from 10.1.1.202: icmp_seq=1 ttl=64 time=0.716 ms 64 bytes from 10.1.1.202: icmp_seq=2 ttl=64 time=0.858 ms 64 bytes from 10.1.1.202: icmp_seq=3 ttl=64 time=0.686 ms --- 10.1.1.202 ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2008ms rtt min/avg/max/mdev = 0.686/0.753/0.858/0.078 ms
デプロイしたPodとmasterノードとの疎通を確認します (masterノードのeth1に 10.1.1.1 をアサインしています)。
kube-master $ kubectl exec -it macvlan-10.1.1.201 -- ping -c 3 10.1.1.1 PING 10.1.1.1 (10.1.1.1) 56(84) bytes of data. 64 bytes from 10.1.1.1: icmp_seq=1 ttl=64 time=6.25 ms 64 bytes from 10.1.1.1: icmp_seq=2 ttl=64 time=0.638 ms 64 bytes from 10.1.1.1: icmp_seq=3 ttl=64 time=0.754 ms --- 10.1.1.1 ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2002ms rtt min/avg/max/mdev = 0.638/2.548/6.252/2.619 ms
ipvlanで追加ネットワークに接続
ipvlanとは
ipvlanは、macvlanととてもよく似ています。macvlanとの違いは、サブインターフェースごとに固有のMACアドレスが付与されず、サブインターフェースは親インターフェースと同じMACアドレスを持つことです。ipvlanには、親インターフェースとMACアドレスを共有することに起因する、いくつかの考慮点があります。
- サブインターフェースのIPアドレスをDHCPで取得するには工夫が必要。具体的には、ipvlanサブインターフェースを割り当てる仮想マシンもしくはコンテナからのリクエストに対して固有のClient IDを指定し、DHCPサーバ側ではMACアドレスではなくClient IDに対してIPアドレスを割り当てるようにする。
- サブインターフェースのMACアドレスが共通であるため、MACアドレスからEUI-64フォーマットでインターフェースIDを生成するSLAACによるIPv6の自動設定は使えない
一方で、いくつかのユースケースではmacvlanよりもipvlanの方が適しているとカーネルのドキュメントに記載されています。 例として挙げられているのは下記です。
- 親インターフェースの対向装置のポートが、ひとつのMACアドレスしか許可しないポリシーになっている
- 親インターフェースで処理できるMACアドレスに上限がある、Promiscuousモードにできない、(macvlanの)パフォーマンス/スケーラビリティに懸念がある
- サブインターフェースを割り当てるコンテナや仮想マシンが信用できない (MACアドレスを書き換えたりしてほしくない)
カーネルの準備
CentOS7のカーネル(確認したバージョンは3.10.0-957.27.2.el7)は、ipvlanが有効になっていません。以下では、ELRepoのkernel-ml (mainline stableバージョンのupstreamカーネルをCentOS7用にコンパイルしたもの) を使ってipvlanの動作を確認しました。実際に使ったバージョンは5.4.10-1.el7.elrepoです。
NetworkAttachmentDefinitionの定義
Podをipvlanで接続する追加ネットワークをNetworkAttachmentDefinitionのCustom Resourceとして定義します。ここでは ipvlan-conf という名前をつけています (metadata.name: ipvlan-conf)。
kube-master $ cat ipvlan-conf.yaml
---
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
name: ipvlan-conf
spec:
config: '{
"cniVersion": "0.3.1",
"plugins": [
{
"type": "ipvlan",
"capabilities": { "ips": true },
"master": "eth1",
"ipam": {
"type": "static"
}
}, {
"capabilities": { "mac": true },
"type": "tuning"
} ]
}'
上記のNetworkAttachmentDefinitionでは、ホストの eth1 を親インターフェースとして、ipvlanインターフェースを作成し、それをPodをアサインします。
kube-master $ kubectl create -f ipvlan-conf.yaml networkattachmentdefinition.k8s.cni.cncf.io/ipvlan-conf created $ kubectl get net-attach-def ipvlan-conf NAME AGE ipvlan-conf 8s
Podの作成
次のようなmanifestでPodをデプロイします。
kube-master $ cat ipvlan-pod1.yaml
---
apiVersion: v1
kind: Pod
metadata:
# generateName: ipvlan-
name: ipvlan-10.1.1.211
labels:
app: multus-ipvlan
annotations:
k8s.v1.cni.cncf.io/networks: '[
{ "name": "ipvlan-conf",
"ips": [ "10.1.1.211/24" ] }
]'
spec:
containers:
- name: centos-tools
image: docker.io/centos/tools:latest
command:
- /sbin/init
securityContext:
privileged: true
metadata.annotations.k8s.v1.cni.cncf.io/networks で、先ほど作成したnet-attach-defの名前 ipvlan-conf を指定しています。
kube-master $ kubectl create -f ipvlan-pod1.yaml pod/ipvlan-10.1.1.211 created
同様にして、10.1.1.202 のIPアドレスをアサインしたPodをデプロイします。
kube-master $ kubectl create -f ipvlan-pod2.yaml pod/ipvlan-10.1.1.212 created
Podが2個デプロイされました。
kube-master $ kubectl get pod -l app=multus-ipvlan -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES ipvlan-10.1.1.211 1/1 Running 0 73s 10.244.2.50 kube-node-1 <none> <none> ipvlan-10.1.1.212 1/1 Running 0 17s 10.244.1.26 kube-node-2 <none> <none>
Podのインターフェースを確認すると、l2モードのipvlanインターフェースとして net1 が追加され、IPアドレス 10.1.1.211 が付与されていることがわかります。
kube-master $ kubectl exec -it ipvlan-10.1.1.211 -- ip -d addr show
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 promiscuity 0 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
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@if57: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default
link/ether 82:a6:ba:07:63:08 brd ff:ff:ff:ff:ff:ff link-netnsid 0 promiscuity 0
veth numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
inet 10.244.2.50/24 brd 10.244.2.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::80a6:baff:fe07:6308/64 scope link
valid_lft forever preferred_lft forever
4: net1@eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN group default
link/ether 52:54:00:5d:8f:e5 brd ff:ff:ff:ff:ff:ff promiscuity 0
ipvlan mode l2 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
inet 10.1.1.211/24 brd 10.1.1.255 scope global net1
valid_lft forever preferred_lft forever
inet6 fe80::5254:0:15d:8fe5/64 scope link
valid_lft forever preferred_lft forever
デプロイしたPod間の疎通を確認します。
kube-master $ kubectl exec -it ipvlan-10.1.1.211 -- ping -c 3 10.1.1.212 PING 10.1.1.212 (10.1.1.212) 56(84) bytes of data. 64 bytes from 10.1.1.212: icmp_seq=1 ttl=64 time=0.703 ms 64 bytes from 10.1.1.212: icmp_seq=2 ttl=64 time=0.781 ms 64 bytes from 10.1.1.212: icmp_seq=3 ttl=64 time=1.16 ms --- 10.1.1.212 ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2078ms rtt min/avg/max/mdev = 0.703/0.881/1.161/0.202 ms
デプロイしたPodとmasterノードとの疎通を確認します。
kube-master $ kubectl exec -it ipvlan-10.1.1.211 -- ping -c 3 10.1.1.1 PING 10.1.1.1 (10.1.1.1) 56(84) bytes of data. 64 bytes from 10.1.1.1: icmp_seq=1 ttl=64 time=0.708 ms 64 bytes from 10.1.1.1: icmp_seq=2 ttl=64 time=0.724 ms 64 bytes from 10.1.1.1: icmp_seq=3 ttl=64 time=0.341 ms --- 10.1.1.1 ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2073ms rtt min/avg/max/mdev = 0.341/0.591/0.724/0.176 ms
bridgeで追加ネットワークに接続
NetworkAttachmentDefinitionの定義
PodをLinux Bridge経由で接続する追加ネットワークをNetworkAttachmentDefinitionのCustom Resourceとして定義します。ここでは bridge-conf という名前をつけています (metadata.name: bridge-conf)。
kube-master $ cat bridge-conf.yaml
---
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
name: bridge-conf
spec:
config: '{
"cniVersion": "0.3.1",
"plugins": [
{
"type": "bridge",
"name": "br-multus",
"bridge": "br-multus0",
"capabilities": { "ips": true },
"ipam": {
"type": "static"
}
} ]
}'
上記のNetworkAttachmentDefinitionでは、ホストに br-multus0 という名前のブリッジを作成し、そこにPodのvethを接続します。
kube-master $ kubectl create -f bridge-conf.yaml networkattachmentdefinition.k8s.cni.cncf.io/bridge-conf created $ kubectl get net-attach-def bridge-conf NAME AGE bridge-conf 9s
Podの作成
次のようなmanifestでPodをデプロイします。
kube-master $ cat bridge-pod1.yaml
---
apiVersion: v1
kind: Pod
metadata:
# generateName: bridge-
name: bridge-10.1.1.221
labels:
app: multus-bridge
annotations:
k8s.v1.cni.cncf.io/networks: '[
{
"name": "bridge-conf",
"ips": [ "10.1.1.221/24" ]
}
]'
spec:
containers:
- name: centos-tools
image: docker.io/centos/tools:latest
command:
- /sbin/init
securityContext:
privileged: true
metadata.annotations.k8s.v1.cni.cncf.io/networks で、先ほど作成したnet-attach-defの名前 bridge-conf を指定しています。
kube-master $ kubectl create -f bridge-pod1.yaml pod/bridge-10.1.1.221 created
同様にして、10.1.1.222 のIPアドレスをアサインしたPodをデプロイします。
kube-master $ kubectl create -f bridge-pod2.yaml pod/bridge-10.1.1.222 created
Podが2個デプロイされました。
kube-master $ kubectl get pod -l app=multus-bridge -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES bridge-10.1.1.221 1/1 Running 0 97s 10.244.2.51 kube-node-1 <none> <none> bridge-10.1.1.222 1/1 Running 0 27s 10.244.1.27 kube-node-2 <none> <none>
Podのインターフェースを確認すると、vethとして net1 が追加され、IPアドレス 10.1.1.221 が付与されていることがわかります。
kube-master $ kubectl exec -it bridge-10.1.1.221 -- ip -d addr show
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 promiscuity 0 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
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@if63: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default
link/ether f6:a7:c8:89:40:f6 brd ff:ff:ff:ff:ff:ff link-netnsid 0 promiscuity 0
veth numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
inet 10.244.2.54/24 brd 10.244.2.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::f4a7:c8ff:fe89:40f6/64 scope link
valid_lft forever preferred_lft forever
5: net1@if65: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 92:a8:48:c2:f0:55 brd ff:ff:ff:ff:ff:ff link-netnsid 0 promiscuity 0
veth numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
inet 10.1.1.221/24 brd 10.1.1.255 scope global net1
valid_lft forever preferred_lft forever
inet6 fe80::90a8:48ff:fec2:f055/64 scope link
valid_lft forever preferred_lft forever
Pod bridge-10.1.1.221 がスケジュールされたノード kube-node-1 上で、bridgeインターフェースを確認します。
kube-node-1 $ ip link show type bridge
5: cni0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP mode DEFAULT group default qlen 1000
link/ether 8a:b9:22:00:44:9b brd ff:ff:ff:ff:ff:ff
64: br-multus0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
link/ether 1e:86:7b:f3:e8:da brd ff:ff:ff:ff:ff:ff
Flannelで使う cni0 に加えて、net-attach-defで指定した br-multus0 というブリッジが作成されています。
kube-node-1 $ brctl show
bridge name bridge id STP enabled interfaces
br-multus0 8000.1e867bf3e8da no vethc1d59074
cni0 8000.8ab92200449b no veth8911ce6e
vethdcfe4db8
により、ホスト上で br-multus0 に接続するインターフェースは vethc1d59074 であることがわかります。
さらにこのvethのifindexを確認します (65でした)。
kube-node-1 $ ip -d link show dev vethc1d59074
65: vethc1d59074@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-multus0 state UP mode DEFAULT group default
link/ether 1e:86:7b:f3:e8:da brd ff:ff:ff:ff:ff:ff link-netnsid 0 promiscuity 1
veth
bridge_slave state forwarding priority 32 cost 2 hairpin off guard off root_block off fastleave off learning on flood on port_id 0x8001 port_no 0x1 designated_port 32769 designated_cost 0 designated_bridge 8000.1e:86:7b:f3:e8:da designated_root 8000.1e:86:7b:f3:e8:da hold_timer 0.00 message_age_timer 0.00 forward_delay_timer 0.00 topology_change_ack 0 config_pending 0 proxy_arp off proxy_arp_wifi off mcast_router 1 mcast_fast_leave off mcast_flood on addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
Pod内の net1 のインターフェース情報(下記に再掲)と突き合わせると、確かに上記 vethc1d59074 とPod bridge-10.1.1.221 の net1 がveth pairになっていることが確認できます。
kube-master $ kubectl exec -it bridge-10.1.1.221 -- ip -d link show dev net1
5: net1@if65: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default
link/ether 92:a8:48:c2:f0:55 brd ff:ff:ff:ff:ff:ff link-netnsid 0 promiscuity 0
veth addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
さて、この状態ではブリッジ br-multus0 のアップリンクが設定されていないため、異なるノード上のPodとは通信できません。
各workerノードで、br-multus0 のアップリンクとして eth1 を設定します。
kube-node-1 $ sudo ip link set eth1 master br-multus0
kube-node-1 $ brctl show
bridge name bridge id STP enabled interfaces
br-multus0 8000.5254005d8fe5 no eth1
veth9b1f5832
cni0 8000.8ab92200449b no vethdcfe4db8
vethe5ca8264
こうすることで、デプロイしたPod間で疎通できるようになります。
kube-master $ kubectl exec -it bridge-10.1.1.221 -- ping -c 3 10.1.1.222 PING 10.1.1.222 (10.1.1.222) 56(84) bytes of data. 64 bytes from 10.1.1.222: icmp_seq=1 ttl=64 time=1.52 ms 64 bytes from 10.1.1.222: icmp_seq=2 ttl=64 time=0.824 ms 64 bytes from 10.1.1.222: icmp_seq=3 ttl=64 time=0.860 ms --- 10.1.1.222 ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2003ms rtt min/avg/max/mdev = 0.824/1.070/1.527/0.324 ms
デプロイしたPodとmasterノードとの疎通を確認します (masterノードのeth1に 10.1.1.1 をアサインしています)。
kube-master $ kubectl exec -it bridge-10.1.1.221 -- ping -c 3 10.1.1.1 PING 10.1.1.1 (10.1.1.1) 56(84) bytes of data. 64 bytes from 10.1.1.1: icmp_seq=1 ttl=64 time=0.601 ms 64 bytes from 10.1.1.1: icmp_seq=2 ttl=64 time=0.215 ms 64 bytes from 10.1.1.1: icmp_seq=3 ttl=64 time=0.329 ms --- 10.1.1.1 ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2057ms rtt min/avg/max/mdev = 0.215/0.381/0.601/0.163 ms
IPAM
Podに割り当てるIPアドレスの管理方法もプラグイン形式になっており、現時点では3種類から選べるようになっています。
- static
- host-local
- DHCP
本記事ではstaticを使用し、Podの net1 に付与するIPアドレスは、Pod manifestに直書きしました。
その他の方法については、また別途何か書く...かもしれません。
おまけ
余談ですが、レッドハットのMultus開発チームの人が使っているAnsible Playbookがここにあります。これを使うと、下記のような感じでKVM環境の仮想マシンとしてNICを2個持つ仮想マシンインスタンスをお手軽に構築できます。
- 仮想マシンを作成する [3]
$ ansible-playbook -i inventory/virthost.inventory -e 'network_type=none-2nics' playbooks/virthost-setup.yml
- コンテナランタイムをCRI-OにしてKubernetesをインストールする [4]
$ ansible-playbook -i inventory/vms.local.generated -e 'pod_network_type=none-2nics' -e 'container_runtime=crio' playbooks/kube-install.yml
謝辞
本記事を書くに当たり、Red HatでNFVPEをしている林さんにいろいろ教えていただきました。ありがとうございました!
開発している人が身近にいるのは最高の福利厚生ですね。
-
もしかしたら、Multusセットアップ後にPodをデプロイすると、CNIで使うバイナリのパス関連のエラー(具体的には
Failed to create pod sandbox: rpc error: code = Unknown (中略) failed to find plugin "multus" in path [/usr/libexec/cni]というメッセージ)で起動に失敗するかもしれません。その場合、このように/opt/cni/binを/usr/libexec/cniに変えて試してみてください。↩ -
vlanのNetworkAttachmentDefinitionを使う場合、VLAN IDごとにひとつVLANサブインターフェースを作成してPodのnetwork namespaceにアサインする、という仕組み上、ひとつのホストに同じvlan net-attach-defを使うPodを複数デプロイできないため↩
-
もうすぐ
ansible-playbook -i inventory/virthost.inventory -e 'network_type=2nics' playbooks/virthost-setup.ymlのように変わる予定です↩ -
もうすぐ
ansible-playbook -i inventory/vms.local.generated -e 'network_type=2nics' -e 'container_runtime=crio' playbooks/kube-install.ymlのように変わる予定です ↩