CNI Migration と題しまして、今回は、FlannelがインストールされているクラスターのCNIをCiliumに移行させてみたいと思います。思ったより大したことがなかったのでエントリにするのは躊躇われたのだけれども、他にアドベントカレンダーに書くこともなかったのでまあいいか。
ちなみにこの記事は、Kubernetes Advent Calender 21 日目の記事となります。
注意
- クラウドのKubernetesクラスターを使ってるなど、クラスターの移行が簡単な場合は、クラスターの移行によるCNIの変更がオススメです。
- K8s@Home#2 にて同内容のLTをしました。
環境
テストした環境は以下の通り。
- Kubernetes: v1.24
- Flannel
- Version
- flannelcni/flannel: v0.20.2
- flannelcni/flannel-cni-plugin: v1.2.0
- Tunnel: VXLAN
- IPAM: Kubernetes
- Pod CIDR:
10.243.0.0/16
- Version
- Cilium
- Version
- Chart: 1.12.2
- CLI: v0.12.10
- Tunnel: geneve
- IPAM: cluster-pool
- Pod CIDR:
172.20.0.0/16
- Version
Flannel to Cilium
手順
- Cilium Install
- Flannel Uninstall
移行
実のところ、こんな "CNI Migration" というタイトルでこのエントリを書いていたりはするのですが、CNIの、特にCiliumへの移行は大体の場合においてそんな難しい話ではなかったりします。というのも単に cilium install
を実行するだけで古いCNIからCiliumへの移行が大体完了するからです。
例えば事前に、以下のようなFlannelによりIPアドレスがアサインされたPodがあるとします。
$ k get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
testapp-0 1/1 Running 0 3m32s 10.243.4.3 node-01.staging.vms.svc.fraction.cluster <none> <none>
testapp-1 1/1 Running 0 3m1s 10.243.6.4 node-02.staging.vms.svc.fraction.cluster <none> <none>
testapp-2 1/1 Running 0 2m34s 10.243.0.4 node-03.staging.vms.svc.fraction.cluster <none> <none>
そこに、以下のような設定で Cilium をインストールします。
$ cat <<EOF > values.yaml
tunnel: geneve
ipam:
mode: cluster-pool
operator:
clusterPoolIPv4PodCIDRList:
- "172.20.0.0/16"
hubble:
enabled: false
EOF
$ cilium install --helm-values values.yaml
ℹ️ Using Ciliumversion 1.12.2
🔮 Auto-detected cluster name: unstable
🔮 Auto-detected datapath mode: tunnel
🔮 Auto-detected kube-proxy has been installed
ℹ️ helm temlate --namespace kube-system ciliumr cilium/cilium --version 1.12.2 --set cluster.id=0,cluster.name=unstable,encryption.nodeEncryption=false,hubble.enabled=false,ipam.mode=cluster-pool,ipam.operator.clusterPoolIPv4PodCIDRList[0]=172.20.0.0/16,kubeProxyReplacement=disabled,operator.replicas=1,serviceAccounts.cilium.name=cilium,serviceAccounts.operator.name=cilium-operator,tunnel=geneve
ℹ️ Storng hvelm values file in kube-system/cilium-cli-helm-values Secret
🔑 Created CA in secret cilium-ca
🔑 Generating certificates for Hubble...
🚀 Creating Service accounts...
🚀 Creating Cluster roles...
🚀 Creating ConfigMap for Cilium version 1.12.2...
🚀 Creating Agent DaemonSet...
🚀 Creating Operator Deployment...
⌛ Waiting for Cilium to be installed and ready...
♻️ Restrting unmanaged pods...
♻️ Restrtned unmanaged pod default/testapp-0
♻️ Restrtned unmanaged pod default/testapp-1
♻️ Restarted unmanagedpod default/testapp-2
...
(snip)
...
♻️ Restarted unmanaged podkube-system/trigger-76c66678c4-8funbr
Cilium was su78ccessfully installed! Run 'cilium status' to view installation health
そうすると、なんと不思議、コマンドによっていい感じにCiliumがインストールされて、Flannelによってネットワークが設定されていたPodが一旦削除されて再起動されます。
コマンド実行後に再起動された Pod の状態を確認すると、CiliumによってIPアドレスが付与されていることが確認されます。
$ k get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
testapp-0 1/1 Running 0 12h 172.20.3.216 node-03.staging.vms.svc.fraction.cluster <none> <none>
testapp-1 1/1 Running 0 19s 172.20.2.7 node-02.staging.vms.svc.fraction.cluster <none> <none>
testapp-2 1/1 Running 0 18s 172.20.1.189 node-01.staging.vms.svc.fraction.cluster <none> <none>
いや、待て、まだFlannelがインストールされているぞ?それはどうなっているんだ?と思う方がいるかもしれません。しかしCiliumはそもそもこのようなCNIの移行を最初から考慮しているのか、ノードの /etc/cni/net.d
を確認すると Flannel の設定ファイルが無効化されていることがわかります。
$ ssh ubuntu@node-01.staging.vms.svc.fraction.cluster sudo ls /etc/cni/net.d
05-cilium.conf
10-flannel.conflist.cilium_bak
素晴らしい。
と、いうことで、Ciliumをインストールした後にやることは古いCNIを削除するだけ、ということになります。
$ kubectl delete ds -n kube-system kube-flannel-ds
daemonset.apps "kube-flannel-ds" deleted
ネットワーク接続性
さて、CNIの移行が簡単にできることはわかったものの、Podが再起動している最中、CNIのMigrationが行われている最中はどうなっているんだろう?というのが気になりますよね!願わくば上品で気品がある感じで移行されているといいですよね。
と、いうわけで途中の過程を見るために、また一度、Flannel CNI がインストールされているクラスターにロールバックしまして、Ciliumを手作業(Helm)でインストールしてみます。(コマンドでインストールすると勝手にPodが再起動されてしまうので)
$ helm template --namespace kube-system cilium cilium/cilium --version 1.12.2 -f values.yaml | k apply -f -
はい、上記のコマンドで Cilium をインストールした状態のクラスタです。PodはまだFlannelが割り当てたIPアドレスを持っていますね。
$ k get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
testapp-0 1/1 Running 0 7m54s 10.243.4.3 node-01.staging.vms.svc.fraction.cluster <none> <none>
testapp-1 1/1 Running 0 7m23s 10.243.6.4 node-02.staging.vms.svc.fraction.cluster <none> <none>
testapp-2 1/1 Running 0 6m56s 10.243.0.4 node-03.staging.vms.svc.fraction.cluster <none> <none>
しかしこの状態はすでにノードの /etc/cni/net.d/10-flannel.conflist
は無効化されているため、Flannelは動作しません。この状態で、testapp-0のみ kubectl delete pod testapp-0
で削除して再起動させてみると、再起動したPodにFlannelの代わりにCiliumが172.20.0.0/16
のアドレス帯からIPアドレスを割り当ててくれているのがわかります。
$ k get pod -o wide testapp-0
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
testapp-0 1/1 Running 0 7s 172.20.2.137 node-01.staging.vms.svc.fraction.cluster <none> <none>
CiliumにIPアドレスを割り当ててもらったPodとFlannelにIPアドレスを割り当ててもらったPod間のネットワーク疎通を確認してみましょう。
# testapp-0(Cilium:172.20.2.137) -> testapp-1(Flannel:10.243.6.4)
$ k exec -ti testapp-0 -- ping 10.243.6.4
PING 10.243.6.4 (10.243.6.4) 56(84) bytes of data.
64 bytes from 10.243.6.4: icmp_seq=1 ttl=61 time=1.28 ms
64 bytes from 10.243.6.4: icmp_seq=2 ttl=61 time=1.79 ms
# testapp-1(Flannel:10.243.6.4) -> testapp-0(Cilium:172.20.2.137)
$ k exec -ti testapp-1 -- ping 172.20.2.137
PING 172.20.2.137 (172.20.2.137) 56(84) bytes of data.
64 bytes from 172.20.2.137: icmp_seq=1 ttl=62 time=0.662 ms
64 bytes from 172.20.2.137: icmp_seq=2 ttl=62 time=1.21 ms
ping がちゃんと通ってることがわかります。この感じだとCiliumをインストールした後、クラスターが壊れないようにPodのローリングアップデートをしながらグレースフルにCNIの移行を進めることができそうですね!
ネットワーク接続性のための条件
実は、このように新旧のCNIによりIPアドレスが割り当てられたPod同士が通信できるようになるためにはいくつか条件があります。上記の"環境"セクションで挙げられていた環境は適当な感じで無邪気に決めたテスト条件ではなかったんですね。
以下がその条件となります。
- IPAM が異なること
- 割り当てられるネットワークアドレスが異なること
- ノードがゲートウェイになりそれぞれのPodに接続可能であること
- VXLAN を使うならば、どちらか一方の CNI に限ること
IPAM が異なること
これはまあ、仕方がないですね。FlannelでKubernetesのIPAMを利用しているのにCiliumでもKubernetesのIPAMを利用したら、割り当てるネットワークアドレスが被ってしまいます。
幸運なことに、CiliumはKubernetesのIPAM以外にもいくつか別の方式をサポートしています。
これが Cilium(IPAM: kubernetes)からFlannelへの移行だったらちょっと困ったことになっていましたね。
割り当てられるネットワークアドレスが異なること
これもまあ、仕方がない。ルーティングどうするんだって話ですし、IPアドレスが重複してしまうかもしれないですし。
ノードがゲートウェイになりそれぞれのPodに接続可能であること
自分は基本的に、Node -> Pod が NAT なしに疎通可能な CNI しか使ったことがないので良くわからないのだけど、少なくともこれができないと今回の構成では異なるCNI間で接続はできないです。
これもまた幸運なことに Flannel も Cilium もホストのNICをゲートウェイとして別のネットワークと通信するモデルでした。
VXLAN を使うならば、どちらか一方の CNI に限ること
これは知らなかったんですが、VXLANってひとつのホストに二つ以上設定することができなかったんですね。
さらに幸運なことに Flannel も Cilium もノード間通信の方式で VXLAN 以外の方式もサポートしているCNIでしたので、今回は Cilium で geneve
を利用することにしました。
ネットワーク構成
さて、いくつかの幸運により、Flannel -> Cilium の移行は比較的簡単にグレースフルに行えることができることがわかりました。
ところで一体全体、これらのPodがどうやってやり取りしてるのか気になりますよね?
うまい具合にここにデプロイしているtestappはSinatra製のアプリで、以下の情報を表示してくれるようになっています。
- HttpのHostヘッダ (Requested Host)
- 自身に割り当てられているIPアドレス (Host IP Addresses)
- クライアントのIPアドレス (Client IP Address)
ping と同じように testapp-0 <-> testapp-1 間で curl してみます。
# testapp-0(Cilium:172.20.2.137) -> testapp-1(Flannel:10.243.6.4)
$ k exec -ti testapp-0 -- curl 10.243.6.4:4567
## Requested Host
- 10.243.6.4
## Host IP Addresses
- 10.243.6.4
## Client IP Address
- 10.243.4.0
# testapp-1(Flannel:10.243.6.4) -> testapp-0(Cilium:172.20.2.137)
$ k exec -ti testapp-1 -- curl 172.20.2.137:4567
## Requested Host
- 172.20.2.137
## Host IP Addresses
- 172.20.2.137
## Client IP Address
- 172.20.1.84
おや、クライアントのIPアドレスが自身のアドレスと異なるものが使われていますね。どうやらホストのIPアドレスでSNATされているようです。
ノードに潜って調べたところ、Flannel/Cilium の混在環境下においては大体こんな感じのネットワーク構成になっていることがわかりました。
testapp-0(Cilium:172.20.2.137) -> testapp-1(Flannel:10.243.6.4)
testapp-0 から testapp-1 への通信の際には、ノード内の flannel.1 を通る際に SNAT され、ソースのIPアドレスが flannel.1 のものに変更。
testapp-1(Flannel:10.243.6.4) -> testapp-0(Cilium:172.20.2.137)
testapp-1 から testapp-0 への通信の際には、ノード内の cilium_host を通る際に SNAT され、ソースのIPアドレスが cilium_host のものに変更。
ということのようです。
まあ、特段SNATされることで問題になるようなアプリケーションを使っていなければ問題はないのだけれども、一応、Kubernetesのネットワークモデルは以下のように説明されているようです。
- pods can communicate with all other pods on any other node without NAT
- agents on a node (e.g. system daemons, kubelet) can communicate with all pods on that node
Note: For those platforms that support Pods running in the host network (e.g. Linux), when pods are attached to the host network of a node they can still communicate with all pods on all nodes without NAT.
- Podは全ての他のノードにいるPod含めてNAT無しで接続できなければならない。
回避策はいくつか思いつくのだけれども、CNIの移行中の一時的な状態におけるネットワークの問題なので、無視できる人は無視しても良かろうと思う。その方法については時間があったら別途書きたいと思う。
これらのNATはどこからやってきたのか?
標準的なCNIプラグインは、自分が割り当てられたPodネットワーク以外へのパケット送信時にマスカレードを行う設定が含まれている。Podネットワークは大抵プライベートアドレスを持っているので、外側のネットワークに行く際にSNATしないとパケットが戻って来れなくなるからです。
Flannel
通常のFlannelのインストーレション時には以下のオプションが有効化されているはずです。
--ip-masq
その際に、PodCIDRはマスカレードから除外されるようです。
Cilium
Helmインストール時で下記の設定で有効化します。デフォルト値は true
のようです。
enableIPv4Masquerade
また、clusterPoolIPv4PodCIDRList
の値は自動的にマスカレードから除外されるようです。それとは別に、ipv4NativeRoutingCIDR
の値でも追加でネットワークを指定できるようですが未検証です。
その他注意点
- Service間通信で Pod Network に属するIPアドレスをNATしたくない場合は、kube-proxy の
--cluster-cidr
を事前に調整しておいた方が良いでしょう。
まとめ
Ciliumへのマイグレーションは簡単だった。