CiliumでPodをPacket Forwardingに使おうとすると何が問題になるか?
![YutaroHayakawa](https://res.cloudinary.com/zenn/image/fetch/s--ekfikv-B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/8f0c2f2567.jpeg)
Ciliumのインストールされたk8sクラスタでPodを何かしらのforwarding function (VPN gatewayとかLBとかFWとかNATとか) として使うのを真剣にやろうとしてみて、どんな問題にぶつかるか試す。
![YutaroHayakawa](https://res.cloudinary.com/zenn/image/fetch/s--ekfikv-B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/8f0c2f2567.jpeg)
途中でCiliumに変更が必要になったら変更するので、とりあえずブランチ切ってここで作業することにする。
![YutaroHayakawa](https://res.cloudinary.com/zenn/image/fetch/s--ekfikv-B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/8f0c2f2567.jpeg)
このレポジトリにコードとかは置く。
![YutaroHayakawa](https://res.cloudinary.com/zenn/image/fetch/s--ekfikv-B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/8f0c2f2567.jpeg)
まずもって気づくのはCiliumはPod間のL2疎通性を担保しないからL2 reachabilityが必要なものは基本動かない。VRRPとか使って冗長化してるNetwork Functionはこの時点でもうhackyなことしないといけない (Pod間でL2トンネル張るとか)。
![YutaroHayakawa](https://res.cloudinary.com/zenn/image/fetch/s--ekfikv-B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/8f0c2f2567.jpeg)
とりあえずAll L3でやる想定にしよう。
![YutaroHayakawa](https://res.cloudinary.com/zenn/image/fetch/s--ekfikv-B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/8f0c2f2567.jpeg)
あと、Pod間でブロードキャストとかマルチキャストは通らない。マルチキャストは頑張ってCiliumの方変えれば通せるかも。
![YutaroHayakawa](https://res.cloudinary.com/zenn/image/fetch/s--ekfikv-B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/8f0c2f2567.jpeg)
Podに外からのトラフィックを直接引き込めないとあまりにもNetwork Functionとして不便だから、Native Routing Modeを使っていく。PodCIDRはBGPで広報する。
![YutaroHayakawa](https://res.cloudinary.com/zenn/image/fetch/s--ekfikv-B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/8f0c2f2567.jpeg)
トポロジは単純なやつで。
![YutaroHayakawa](https://res.cloudinary.com/zenn/image/fetch/s--ekfikv-B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/8f0c2f2567.jpeg)
ひとまず基本的なトポロジとBGPの広報はOK
![YutaroHayakawa](https://res.cloudinary.com/zenn/image/fetch/s--ekfikv-B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/8f0c2f2567.jpeg)
さて、ここからが面白いところ。まずはLBとして使うことを考えてみる。
![YutaroHayakawa](https://res.cloudinary.com/zenn/image/fetch/s--ekfikv-B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/8f0c2f2567.jpeg)
単純なL4LBとして使うとしてまずはVIPをどうやって外の世界に伝えるかという問題がある。L3ネットワークだとBGPで伝えるのがよくあるやり方だけれども、それをやるためにはPodから外の世界にBGPでVIPを広報しないといけない。
![YutaroHayakawa](https://res.cloudinary.com/zenn/image/fetch/s--ekfikv-B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/8f0c2f2567.jpeg)
ここで気づく問題としてはBGPのピアを張る時のIPアドレスがk8sのPodだとPodを再起動するたびにコロコロ変わるので、それに合わせて外側のルータの設定を変えないといけない。
![YutaroHayakawa](https://res.cloudinary.com/zenn/image/fetch/s--ekfikv-B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/8f0c2f2567.jpeg)
Node上のBGP SpeakerとUnnumberedでビアを張るにしてもインターフェースの名前は解決してやらないといけない。これを実現するためにはまずk8sサーバから情報を取ってきてCiliumのBGPスピーカーを設定しないといけない。
![YutaroHayakawa](https://res.cloudinary.com/zenn/image/fetch/s--ekfikv-B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/8f0c2f2567.jpeg)
ユーザインターフェースの設定もどうするか考える必要がある。今のCiliumの設定はPodとピアを張るのを全然想定していないので、こんな感じでIP直書きするようになっている。
apiVersion: "cilium.io/v2alpha1"
kind: CiliumBGPPeeringPolicy
metadata:
name: server0
spec:
nodeSelector:
matchLabels:
kubernetes.io/hostname: clab-k8s-pod-as-a-forwarding-function-control-plane
virtualRouters:
- localASN: 65010
exportPodCIDR: true
neighbors:
- peerAddress: 10.0.0.1/32
peerASN: 65000
これをPodSelectorか何かで書けるようにするとIP意識せずにやれるので、良さそう。
virtualRouters:
- localASN: 65010
exportPodCIDR: true
neighbors:
- podSelector:
labelSelector:
app: loadbalancer
![YutaroHayakawa](https://res.cloudinary.com/zenn/image/fetch/s--ekfikv-B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/8f0c2f2567.jpeg)
Cilium側のCRDを変えるのは面倒なので、今回はlabelベースでPodをwatchしてくれてこのIPの部分を書き換えてくれるコントローラを手抜きで書く。本当はCiliumの側で対応するのが正しい。
![YutaroHayakawa](https://res.cloudinary.com/zenn/image/fetch/s--ekfikv-B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/8f0c2f2567.jpeg)
その前にCiliumのBGPってPodとピア張れるんだったかな??それを確認してみる。
![YutaroHayakawa](https://res.cloudinary.com/zenn/image/fetch/s--ekfikv-B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/8f0c2f2567.jpeg)
とりあえずPodのIPを手動でBGPの設定に入れてみる。Podの方にもNode側のIPを手動入力。
![YutaroHayakawa](https://res.cloudinary.com/zenn/image/fetch/s--ekfikv-B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/8f0c2f2567.jpeg)
おお、なんかピア張れないな。デバッグしてみる。
![YutaroHayakawa](https://res.cloudinary.com/zenn/image/fetch/s--ekfikv-B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/8f0c2f2567.jpeg)
CiliumはCalicoとかL2 Isolateと違ってノードにアクセスするためのIPが固定じゃないからやりづらいな。
![YutaroHayakawa](https://res.cloudinary.com/zenn/image/fetch/s--ekfikv-B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/8f0c2f2567.jpeg)
まず、Pod側のFRRの設定はneighborがデフォゲ経由でしか見つからないからこの設定がいる。まあ、まあ、これはFRRの想定の範囲内だってことで良しとする。これできないBGPスピーカーとかいそうだけど。
! router bgpの中
neighbor 10.244.2.214 ebgp-multihop 255
! グローバルスコープ
ip nht resolve-via-default
![YutaroHayakawa](https://res.cloudinary.com/zenn/image/fetch/s--ekfikv-B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/8f0c2f2567.jpeg)
GoBGPの方にebgp multihopの設定入れたら動くようになった。
![YutaroHayakawa](https://res.cloudinary.com/zenn/image/fetch/s--ekfikv-B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/8f0c2f2567.jpeg)
が、CiliumのBGPスピーカーがCiliumのCRDにしか書いてないIPアドレスでピア張りにくるからそれを解決してPod側のFRRに反映するやつを書かないといけない。これは面倒臭いポイントだ。
![YutaroHayakawa](https://res.cloudinary.com/zenn/image/fetch/s--ekfikv-B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/8f0c2f2567.jpeg)
もうこの時点では普通のユーザには対応不可能だ。Ciliumのソース書き換えちゃったし。
![YutaroHayakawa](https://res.cloudinary.com/zenn/image/fetch/s--ekfikv-B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/8f0c2f2567.jpeg)
とりあえず雑に自動でNeighbor注入してくれるやつを作った。
これをinit contianerの中で実行してFRRの設定ファイルをレンダリングする
![YutaroHayakawa](https://res.cloudinary.com/zenn/image/fetch/s--ekfikv-B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/8f0c2f2567.jpeg)
ひとまずPodから広報した経路を受け取れるところまではOK。192.168.0.1/32 がPodから広報している経路。
Pod側
loadbalancer# show bgp ipv4 neighbors 10.244.2.69 advertised-routes
BGP table version is 5, local router ID is 10.255.1.0, vrf id 0
Default local pref 100, local AS 65100
Status codes: s suppressed, d damped, h history, * valid, > best, = multipath,
i internal, r RIB-failure, S Stale, R Removed
Nexthop codes: @NNN nexthop's vrf id, < announce-nh-self
Origin codes: i - IGP, e - EGP, ? - incomplete
RPKI validation codes: V valid, I invalid, N Not found
Network Next Hop Metric LocPrf Weight Path
*> 10.244.0.0/24 0.0.0.0 0 65012 65000 65010 i
*> 10.244.1.0/24 0.0.0.0 0 65012 65000 65011 i
*> 10.244.2.0/24 0.0.0.0 0 65012 i
*> 10.244.2.216/32 0.0.0.0 0 32768 ?
*> 192.168.0.1/32 0.0.0.0 0 32768 ?
Node側
root@clab-k8s-pod-as-a-forwarding-function-worker2:/home/cilium# gpbgp --target unix:///var/run/cilium/gobgp.65012.sock global rib
Network Next Hop AS_PATH Age Attrs
*> 10.244.0.0/24 10.0.2.1 65000 65010 05:13:55 [{Origin: i}]
*> 10.244.1.0/24 10.0.2.1 65000 65011 05:13:55 [{Origin: i}]
*> 10.244.2.0/24 0.0.0.0 05:14:03 [{Origin: i}]
*> 10.244.2.216/32 10.244.2.216 65100 00:09:25 [{Origin: ?} {Med: 0}]
*> 192.168.0.1/32 10.244.2.216 65100 00:08:46 [{Origin: ?} {Med: 0}]
Router側
router0# show bgp ipv4r
% Unknown command: show bgp ipv4r
router0# show bgp ipv4
BGP table version is 161, local router ID is 10.0.0.0, vrf id 0
Default local pref 100, local AS 65000
Status codes: s suppressed, d damped, h history, * valid, > best, = multipath,
i internal, r RIB-failure, S Stale, R Removed
Nexthop codes: @NNN nexthop's vrf id, < announce-nh-self
Origin codes: i - IGP, e - EGP, ? - incomplete
RPKI validation codes: V valid, I invalid, N Not found
Network Next Hop Metric LocPrf Weight Path
*> 10.244.0.0/24 10.0.0.2(clab-k8s-pod-as-a-forwarding-function-control-plane)
0 65010 i
*> 10.244.1.0/24 10.0.1.2(clab-k8s-pod-as-a-forwarding-function-worker)
0 65011 i
*> 10.244.2.0/24 10.0.2.2(clab-k8s-pod-as-a-forwarding-function-worker2)
0 65012 i
*> 10.244.2.216/32 10.0.2.2(clab-k8s-pod-as-a-forwarding-function-worker2)
0 65012 65100 ?
*> 192.168.0.1/32 10.0.2.2(clab-k8s-pod-as-a-forwarding-function-worker2)
0 65012 65100 ?
![YutaroHayakawa](https://res.cloudinary.com/zenn/image/fetch/s--ekfikv-B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/8f0c2f2567.jpeg)
ここまでのつまづきポイント
Pod内からNodeのピアリングに使っているIPとASNを知る方法
- IPはCiliumNodeCRDから取ってくるかPodに入っているデフォルトの経路に書いてあるので、これを取ってきてPod側のBGPスピーカーのコンフィグに反映させる必要がある。FRR式のBGP Unnumberedを使ってピアリングできるようにするか、固定のIPでピアリングできるのが多分理想。
- ASNはFRRだと
remote-as external
でASNを省略できるので、FRR使ってる人は楽。そうでなければ CiliumBGPPeeringPolicy から取ってくる必要があってかなり面倒。
NodeからPodのピアリングに使っているIPとASNを知る方法
- IPは繋ぎに行くPodがわかってるならPodのリソースからPod IP取って来れば大丈夫。
- ASNはユーザが CiliumBGPPeeringPolicy に直接書くか、GoBGPをFRRみたいに remote-as external してASN省略できるようにするのがいいか?
- 同じPodSelectorで選択されているPodは全部同じASNじゃないといけない?多分そうじゃない方が嬉しいケースも多そう。
PodのIPが変化するのに追従する方法
- CiliumBGPPeeringPolicy のneighborのところにPodSelectorを書けるようにするのが多分理想。実装はちょっとやってみた感じかなり面倒だけど、テクニカルには可能なので、大丈夫。
- ノードローカルのコントローラでノードにスケジュールされている + PodSelectorで選択されているPodのIPをGoBGPにNeighborとして突っ込むという設定が必要。
- あるいはPodからのAPIコール契機でノードのローカルに穴をあけるっていうやり方もある。
Podから経路ハイジャックされるのを防ぐ方法
- 経路フィルタをGoBGP側で設定する必要あり、設定の口はどうしよう?これは別にPodとピアするのに限った話ではなく、経路を受け取るようになったら必ず必要になる話なので、要検討。
- 経路フィルタを設定する口はあるとして、クラスタ内で使われているIPは広報できないようにするとかはした方がいいかも。「ユーザが広報したいPrefix」と「管理者が広報させたくないPrefix」は分けて管理できるようなインターフェースが必要。クラスタ内で使ってるIPはIPAMをCiliumが握っていれば簡単にリストアップできる。
![YutaroHayakawa](https://res.cloudinary.com/zenn/image/fetch/s--ekfikv-B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/8f0c2f2567.jpeg)
データプレーンは今日は手を出せなかったけれども、検討ポイントとしては
- Podから広報した経路向けにパケットが飛んできた時にCiliumはそれをハンドルできる?
- 多分Native Routingモードであれば普通に動く、ただPod宛の通信とそうじゃない通信でConntrackは使い分ける必要がありそう。Conntrackはsrcとdstが入れ替わった状態でエントリが入るからフォワーディングされると帰りのトラフィックがConntrackに引っかからない。パケットが同じタプルのまま戻ってくる保障もないので、Conntrack溢れする可能性がある。
- PodのIP宛なのかPodの広報している経路で引き込まれているのかは区別する必要があるかもしれない。
Podからフォワードされて出てきたトラフィックをCiliumはハンドルできるか?
- CiliumはPodから出てきたパケットのソースIPをチェックしてPodIPじゃなければ落としてしまうので、多分動かない。フォワーディングをするPodとそうじゃないPodは区別して扱う必要がある。
フォワーディングをするPodだけソースIPチェックを省くとか。 - 結局forwarding functionをPodで動かしてる時にはCiliumはただのルータとして振る舞ってくれるのが一番楽で、Conntrackとか余計なことしない方がいい。
![YutaroHayakawa](https://res.cloudinary.com/zenn/image/fetch/s--ekfikv-B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/8f0c2f2567.jpeg)
ここまで、L3オンリーなNFが上に乗る前提だったけれどもそんなに都合よく動くとは思えない。インターフェースが複数ついてるのが前提のNFとか L2疎通性を求めるNFとかそういうのをサポートしてくれって言われたら結構面倒。パケットに足に相当する情報入れてくれて、1本足にしてくれればまだいけるかも。こうしてみんなPodの中でVLANトランクしだすんだな。
![YutaroHayakawa](https://res.cloudinary.com/zenn/image/fetch/s--ekfikv-B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/8f0c2f2567.jpeg)
ラベル指定でPodとピアが張れる機能ができた。ラベルで選択されているPodが変わってもReconcilerが反応しないので、不完全だけれどもそれは後でなおす。こんな感じで指定できる。