8
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

KubernetesのPodに複数のNICを刺す

Last updated at Posted at 2022-12-08

通常、KubernetesのPodに複数のPodを構成する場合は Multus-CNI の利用が一般的ですが、CRIにcontainerdを利用している場合には、単純に複数のNICを刺すためのもっと簡単な方法がありますよ、というエントリです。

このエントリは、Z Lab Advent Calendar 2022 の七日目の記事です。

TL;DR

config.toml における max_conf の設定を変更する。

[plugins."io.containerd.grpc.v1.cri".cni]
  max_conf_num = 2

環境

この記事は、以下のソフトウェアを利用してテストしています。

  • Kubernetes: 1.24
  • containerd: 1.6.8

CRI が CNI をキックする

この設定項目を知ったのは、ふと、CRI が CNI を呼び出す際の仕様はどうなっているのか気になったからでした。

CNI の仕様的には、CNI のバイナリに対して環境変数と標準入力に設定ファイルを渡す だけなのだが、CRIはどのCNIをどういう優先度で決定して呼び出しているのだろうか?

CRIに設定したCNIの設定ファイルディレクトリ(デフォルト: /etc/cni/net.d) にある設定ファイルのうち、辞書順でソートして1番目の設定ファイルが利用されることは知っていたのだけれども、その仕様はどこに明記されているのやら。

とりあえず、ちょっと探しただけでは見つからなかったので、知っている人がいたら教えてください。

containerd の実装

と言うことで探検隊は真実を知るために、containerd のジャングルの奥地へと向かったのでした。

// loadFromConfDir detects network config files from the
// configured cni config directory and load them. max is
// the maximum network config to load (max i<= 0 means no limit).
func loadFromConfDir(c *libcni, max int) error {
	files, err := cnilibrary.ConfFiles(c.pluginConfDir, []string{".conf", ".conflist", ".json"})
	switch {
	case err != nil:
		return fmt.Errorf("failed to read config file: %v: %w", err, ErrRead)
	case len(files) == 0:
		return fmt.Errorf("no network config found in %s: %w", c.pluginConfDir, ErrCNINotInitialized)
	}

	// files contains the network config files associated with cni network.
	// Use lexicographical way as a defined order for network config files.
	sort.Strings(files)

早速辿り着いたのは、containerd が内部で CNI をキックする時に利用しているライブラリは go-cniloadFromConfDir と言う関数でした。

コメントを読むと分かる通り、設定ファイルディレクトリ内のファイルを辞書順でソートしています。

		networks = append(networks, &Network{
			cni:    c.cniConfig,
			config: confList,
			ifName: getIfName(c.prefix, i),
		})
		i++
		if i == max {
			break
		}

そして順番にネットワークを追加し、max値まで追加したらbreak してますね。

ここで思ったのは、「待て、KubernetesのNICってデフォルト一枚じゃなかったっけ?」でした。

その昔、CNIの記事を書いた際に、戯れにKubernetesが管理するPodに2枚目のNICを刺した ことがあったのですが、containerd 公式にもその方法があったとは。

ソースコードをcontainerd に戻り、このmax値を制御している設定が、config.tomlmax_conf_num と言うことがわかりました。と、言うことで冒頭の "TL;DR" に戻るわけですね!

Pod に複数のNICを刺す

そんなわけでせっかくなので、各ノードに上記の設定を施し、実際にPodにネットワークが複数設定されるか試して見ましょう。今回は、以下のCNIの組み合わせを試してみます。

  • Flannel + Cilium
  • Flannel + Bridge

各CNIの設定は以下の通りです。

  • Flannel
    • Version
      • flannelcni/flannel: v0.20.2
      • flannelcni/flannel-cni-plugin: v1.2.0
    • Tunnel: none (host-gw)
    • IPAM: Kubernetes
    • Pod CIDR: 10.242.0.0/16
  • Cilium
    • Version
      • Chart: 1.12.2
      • CLI: v0.12.10
    • Tunnel: none (host-gw)
    • IPAM: cluster-pool
    • Pod CIDR: 172.20.0.0/16
  • Bridge
    • name: br0
    • CIDR: 192.168.0.0/16
    • 備考: ホストネットワーク直結

Flannel + Cilium

まずは、以下のように三つのPodがFlannelによりIPアドレスが割り当てられているとします。

$ kubectl get pod -o=custom-columns=NAME:.metadata.name,IP:.status.podIP
NAME        IP
testapp-0   10.242.4.2
testapp-1   10.242.1.2
testapp-2   10.242.3.2

コンテナの中を見てみると、eth0 に上記のアドレスがついていることが確認できます。

$ kubectl exec -ti testapp-0 -- 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
2: eth0@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 32:27:88:90:7f:90 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 10.242.4.2/24 brd 10.242.4.255 scope global eth0
       valid_lft forever preferred_lft forever

さて、そこに Cilium をインストールしてみましょう。

以下の kustomization.yaml を用意します。

$ cat <<EOF > kustomization.yaml
helmCharts:
- name: cilium
  includeCRDs: true
  valuesInline:
    tunnel: disabled
    autoDirectNodeRoutes: true
    ipv4NativeRoutingCIDR: "172.20.0.0/16"

    cni:
      exclusive: false

    ipam:
      mode: cluster-pool
      operator:
        clusterPoolIPv4PodCIDRList:
        - "172.20.0.0/16"

    hubble:
      enabled: false
  releaseName: cilium
  version: 1.12.2
  repo: https://helm.cilium.io/

patchesStrategicMerge:
- |-
  apiVersion: apps/v1
  kind: DaemonSet
  metadata:
    name: cilium
  spec:
    template:
      spec:
        containers:
        - name: cilium-agent
          env:
          - name: CNI_CONF_NAME
            value: 20-cilium.conf
EOF

Cilium には古いCNIを /etc/cni/net.d から削除してしまう勝手機能があるのですが、cni.exclusive=false にしてそれを無効化してやります。

また、デフォルト Cilium がインストールする CNI の設定ファイルのファイル名は 05-cilium.conf なのですが、事前にインストールされている Flannel の設定ファイル名が 10-flannel.conflist のため、そのままCNIを適用すると Cilium が優先されてしまい、一番目のNICが Cilium になってしまいます。

今回は、一番目のNICにFlannelを、二番目のNICにCiliumを利用したかったため、CNI_CONF_NAME を変更して 20-cilium.conf としています。

さて、早速 Cilium を kustomize build してインストールしてみましょう。

$ kustomize build --enable-helm --helm-command=helm . | k apply -f -

この状態で testapp-0testapp-1 を再起動します。

$ kubectl delete pod testapp-0 testapp-1
pod "testapp-0" deleted
pod "testapp-1" deleted

$ kubectl get pod -o=custom-columns=NAME:.metadata.name,IP:.status.podIP
NAME        IP
testapp-0   10.242.4.5
testapp-1   10.242.1.5
testapp-2   10.242.3.2

その後、PodのIPアドレスを確認してみると、、変わらずFlannelがIPアドレスを割り当てていますね。

Podの中に入ってアドレスを確認してみます。

$ kubectl exec -ti testapp-0 -- 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@if17: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether ba:35:96:c8:e5:49 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 10.242.4.5/24 brd 10.242.4.255 scope global eth0
       valid_lft forever preferred_lft forever
15: eth1@if16: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 76:5e:de:13:57:a5 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.20.1.3/32 scope global eth1
       valid_lft forever preferred_lft forever

おお、eth1 が追加で設定されているようです。

ルーティングは以下のようになっていました。

$ kubectl exec -ti testapp-0 -- ip route
default via 10.242.4.1 dev eth0
default via 172.20.1.247 dev eth1 mtu 1500
10.242.0.0/16 via 10.242.4.1 dev eth0
10.242.4.0/24 dev eth0 proto kernel scope link src 10.242.4.5
172.20.1.247 dev eth1 scope link

以下の二つのルーティングはFlannelの設定ファイル由来ですね。

  • default via 10.242.4.1 dev eth0
  • 10.242.0.0/16 via 10.242.4.1 dev eth0

Flannel の設定ファイルをノードで確認してみます。

$ cat /etc/cni/net.d/10-flannel.conflist
{
  "name": "cbr0",
  "cniVersion": "0.3.1",
  "plugins": [
    {
      "type": "flannel",
      "delegate": {
        "hairpinMode": true,
        "isDefaultGateway": true,
        "isGateway": true
      }
    }
  ]
}

ここで、isDefaultGatewayisGatewayfalse になっていると上記のルーティングがなくなっていることが確認できるはずです。

FlannelのネットワークへのルーティングはFlannelが設定したNIC越しに、CiliumへのネットワークへのルーティングはCiliumが設定したNIC越しに利用する、ということがやりたければこのあたりの値を調整する必要がありそうです。

Flannel + Bridge

TBD (自宅のネットワークを調整したらあとで書きたい)

ユースケース

と、まあ、簡単にNICを2枚刺すことはできたのですが、ユースケースは限られそうです。

というのも、NICを2枚刺したいケースというのは、Kubevirt を利用して Kubernetes上のVMにKubernetesのPod Network外のNICを刺したい場合、とか、Longhornでストレージのネットワークを分けたい場合、とかが考えられるのですが、どちらもネイティブに対応しているのは Multus-CNI だったりします。

苦肉の策で思いついたユースケースが CNI の Migration に使えそう、というものだったのですが、これはまた"移行期間中にPod間の接続でNATを許し"さえすれば、このように二つのCNIを共存させる必要はなさそうでした。

CNI の Migration については Kubernetes Advent Calender 2022 の21日目に書く予定です。

まとめ

KubernetesのPodに複数のNICを構成するのは、Multusを使わずとも簡単にできることがわかったが、ユースケースが見当たらない。

以上。

8
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?