Open29

CiliumでPodをPacket Forwardingに使おうとすると何が問題になるか?

YutaroHayakawaYutaroHayakawa

Ciliumのインストールされたk8sクラスタでPodを何かしらのforwarding function (VPN gatewayとかLBとかFWとかNATとか) として使うのを真剣にやろうとしてみて、どんな問題にぶつかるか試す。

YutaroHayakawaYutaroHayakawa

まずもって気づくのはCiliumはPod間のL2疎通性を担保しないからL2 reachabilityが必要なものは基本動かない。VRRPとか使って冗長化してるNetwork Functionはこの時点でもうhackyなことしないといけない (Pod間でL2トンネル張るとか)。

YutaroHayakawaYutaroHayakawa

あと、Pod間でブロードキャストとかマルチキャストは通らない。マルチキャストは頑張ってCiliumの方変えれば通せるかも。

YutaroHayakawaYutaroHayakawa

Podに外からのトラフィックを直接引き込めないとあまりにもNetwork Functionとして不便だから、Native Routing Modeを使っていく。PodCIDRはBGPで広報する。

YutaroHayakawaYutaroHayakawa

さて、ここからが面白いところ。まずはLBとして使うことを考えてみる。

YutaroHayakawaYutaroHayakawa

単純なL4LBとして使うとしてまずはVIPをどうやって外の世界に伝えるかという問題がある。L3ネットワークだとBGPで伝えるのがよくあるやり方だけれども、それをやるためにはPodから外の世界にBGPでVIPを広報しないといけない。

YutaroHayakawaYutaroHayakawa

ここで気づく問題としてはBGPのピアを張る時のIPアドレスがk8sのPodだとPodを再起動するたびにコロコロ変わるので、それに合わせて外側のルータの設定を変えないといけない。

YutaroHayakawaYutaroHayakawa

Node上のBGP SpeakerとUnnumberedでビアを張るにしてもインターフェースの名前は解決してやらないといけない。これを実現するためにはまずk8sサーバから情報を取ってきてCiliumのBGPスピーカーを設定しないといけない。

YutaroHayakawaYutaroHayakawa

ユーザインターフェースの設定もどうするか考える必要がある。今の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
YutaroHayakawaYutaroHayakawa

Cilium側のCRDを変えるのは面倒なので、今回はlabelベースでPodをwatchしてくれてこのIPの部分を書き換えてくれるコントローラを手抜きで書く。本当はCiliumの側で対応するのが正しい。

YutaroHayakawaYutaroHayakawa

その前にCiliumのBGPってPodとピア張れるんだったかな??それを確認してみる。

YutaroHayakawaYutaroHayakawa

とりあえずPodのIPを手動でBGPの設定に入れてみる。Podの方にもNode側のIPを手動入力。

YutaroHayakawaYutaroHayakawa

CiliumはCalicoとかL2 Isolateと違ってノードにアクセスするためのIPが固定じゃないからやりづらいな。

YutaroHayakawaYutaroHayakawa

まず、Pod側のFRRの設定はneighborがデフォゲ経由でしか見つからないからこの設定がいる。まあ、まあ、これはFRRの想定の範囲内だってことで良しとする。これできないBGPスピーカーとかいそうだけど。

! router bgpの中
neighbor 10.244.2.214 ebgp-multihop 255

! グローバルスコープ
ip nht resolve-via-default
YutaroHayakawaYutaroHayakawa

が、CiliumのBGPスピーカーがCiliumのCRDにしか書いてないIPアドレスでピア張りにくるからそれを解決してPod側のFRRに反映するやつを書かないといけない。これは面倒臭いポイントだ。

YutaroHayakawaYutaroHayakawa

もうこの時点では普通のユーザには対応不可能だ。Ciliumのソース書き換えちゃったし。

YutaroHayakawaYutaroHayakawa

ひとまず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 ?
YutaroHayakawaYutaroHayakawa

ここまでのつまづきポイント

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が握っていれば簡単にリストアップできる。
YutaroHayakawaYutaroHayakawa

データプレーンは今日は手を出せなかったけれども、検討ポイントとしては

  • 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とか余計なことしない方がいい。
YutaroHayakawaYutaroHayakawa

ここまで、L3オンリーなNFが上に乗る前提だったけれどもそんなに都合よく動くとは思えない。インターフェースが複数ついてるのが前提のNFとか L2疎通性を求めるNFとかそういうのをサポートしてくれって言われたら結構面倒。パケットに足に相当する情報入れてくれて、1本足にしてくれればまだいけるかも。こうしてみんなPodの中でVLANトランクしだすんだな。