こんにちは。

葬送のフリーレンのアニメが終了して途方に暮れています。 いいアニメでした。 続編に期待しています。

今回はしばらく趣味で開発している Kubernetes 用の CNI plugin とネットワークロードバランサ sart を紹介します。

リポジトリはこちらになります。 すべて Rust で実装しています。(e2e テストだけ Go 言語の Ginkgo を使っています。)

荒削りなソフトウェアですが、動かしてみたりスターをもらえると喜びます。(バグがたくさんありそう)

また、開発中のプロジェクトなので API の仕様が変更になる可能性は大いにあります。

terassyi/sart (opens new window)

# モチベーション

  • 学習目的
    • Rust と Rust の非同期プログラミング
    • BGP
    • Kubernetes Network Load Balancer
    • CNI Plugin
  • おうち Kubernetes クラスタを自分で実装したネットワークコンポーネントで構築したい

開発の動機は主に BGP や CNI、Kubernetes の LoadBalancer Service の仕組みを実装を通して知りたかったからです。

どうせ作るのであれば新規性を求めたいので Rust を選びました。

Rust も Rust の非同期プログラミングも初心者なので良くない実装も多々ありそうです。

また、おうち Kubernetes クラスタを自分で開発したソフトウェアで運用したいという密かな野望を秘めて開発しています。(クラスタ構築はこれからです。)

# Sart とは

開発している sart というソフトウェアは以下の機能を持っています。

各機能について軽く紹介します。

# BGP

ルーティングプロトコルのひとつで Kubernetes でも Pod 間の疎通性の確保などに広く利用されています。

sart では RFC 4271 - A Border Gateway Protocol 4 (BGP-4) (opens new window) に従って実装しています。

また、先行実装として GoBGP (opens new window)RustyBGP (opens new window) を参考にしました。

しかし、必要最低限の機能しか実装できておらず、使用できるユースケースは限定的です。

具体的な実装具合については以下を参照してください。

https://github.com/terassyi/sart/blob/main/docs/design.md#bgp (opens new window)

将来的には再実装、もしくは外部ソフトウェアへの切りだしをしたいと思っています。

# FIB コントローラー

FIB コントローラーは BGP デーモンやその他プログラムとカーネルの経路データベースの間で経路情報を交換して実際にカーネルに経路を登録するプログラムです。

FRR (opens new window) の zebra や Bird のルーティングテーブル機能 (opens new window)のような役割を担います。

こちらも最低限の機能しか実装できていません。

BGP と同様に将来的には再実装、もしくは外部ソフトウェアへの切りだしをしたいと思っています。

# CNI Plugin

Sart は Kubernetes 向けの CNI プラグインとして実装しています。

先行実装として Coil (opens new window)Cilium の Multi-Pool IPAM (opens new window) を参考に実装しました。

詳しくは後述しますが、Kubernetes のカスタムリソースベースの設定を与えて Pod に対して IP アドレスを割り当てることが可能となります。

また、一つのクラスタに複数の IP アドレス帯(Address Pool)を適用できるようになっています。

CNI プラグイン機能は BGP との連携を前提とした設計となっています。

# Network Load Balancer

Sart は Kubernetes 向けロードバランサ機能も有しています。

ここでのロードバランサとは、Kubernetes の LoadBalancer Service を管理するコンポーネントを指します。

先行実装として MetalLB (opens new window)Cilium BGP Control Plane + LBIPAM (opens new window) があり、これらを参考にしています。

CNI プラグイン機能と同様に Kubernetes のカスタムリソースとして定義した Address Pool を利用して LoadBalancer 用 IP アドレスをクラスタに適用し、必要に応じてそれらを割り当てて外部に広報します。

外部への広報に関して、sart の BGP 機能を Kubernetes のカスタムリソースとして抽象化して利用しています。

# 使い方

ここでは Kind と Containerlab (opens new window) を利用して BGP が使える Kubernetes クラスタを構築します。 余談ですが、Containerlab から直接 Kind クラスタを作れるようです。

この環境を動作させるためには Linux 環境が必要です。WSL2 では Containerlab でうまく環境を構築できないため動作しません。(Mac は動作未確認です。)

また、Rust が実行できる必要があります。

この実験環境は多くのコンテナを立ち上げるため、実行ホストのリソースに注意してください。

# 準備

まずは前準備でコンテナイメージのビルドと CRD マニフェストと Webhook 用証明書の生成を行います。 リポジトリの直下で以下のコマンドを実行してください。

$ make build-image # コンテナイメージのビルド
$ make certs # Kubernetes に必要な証明書の生成
$ make crd # CRD の生成
1
2
3

次に、実験用 Kubernetes クラスタを起動します。

$ cd e2e
$ make setup
$ make kubernetes MODE=cni
1
2
3

実験環境のトポロジーは以下のようになっています。

https://github.com/terassyi/sart/blob/main/e2e/README.md#cni

(CI 用に縮小版の環境も作成できます。その場合は make kubernetes MODE=cni COMPACT=trueを実行してください。)

kubernetes-cni-e2e-topology

Kubernetes クラスタが起動したら、以下のように sart の CRD をクラスタに適用します。 Sart には CNI only, LB only, Dual の三つのモードを用意しています。 今回は dual モードで動作させます。

$ make install-sart MODE=dual
1

以下のように各 Deployment, Daemonset が起動していれば準備完了です。

$ kubectl -n kube-system get deploy sart-controller
NAME              READY   UP-TO-DATE   AVAILABLE   AGE
sart-controller   1/1     1            1           3m3s
$ kubectl -n kube-system get ds sartd-agent
NAME          DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR            AGE
sartd-agent   4         4         4       4            4           kubernetes.io/os=linux   2m44s
$ kubectl -n kube-system get ds sartd-bgp
NAME        DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR            AGE
sartd-bgp   4         4         4       4            4           kubernetes.io/os=linux   2m49s
1
2
3
4
5
6
7
8
9

これで準備が整いました。 これから実際にリソースを適用して動作を確かめていきます。

# BGP 関連リソースを作成する

Sart は前述の通り、BGP を扱う機能を Kubernetes のカスタムリソースとして抽象化しています。 BGP 関連リソースは以下のような構造になっています。

kubernetes-cni-model

トップレベルのリソースとして ClusterBGP と必要な場合は BGPPeerTemplate を作成します。 ClusterBGP.spec.nodeSelector に基づいて対象ノードに対応した NodeBGP リソースを作成します。既に対象ノードに NodeBGP リソースが存在する場合は何もしません。

各対象ノードに対応する NodeBGP リソースが作成されたら、ClusterBGP リソースの .spec.peers に従って BGPPeer リソースを作成します。 .spec.peerspeerTemplateRefBGPPeerTemplate リソースが指定されていた場合はそのリソースを基に、BGPPeer の spec が直接記述されていた場合はその spec を基に作成します。

この時、BGPPeer は各対象 NodeBGP リソースに対応して作成されます。 対象 NodeBGP リソースは .spec.peers.[].nodeBGPSelector に指定されたラベルにマッチしたものが選択されます。また、NodeBGP リソースのラベルは Node リソースのラベルから引き継がれます。

まずは BGPPeerTemplate リソースと ClusterBGP リソースを作成します。

この実験環境では各 Kubernetes ノードに直接接続している spine0 と spine1 に対してそれぞれ BGPPeerTemplateClusterBGP リソースを定義しています。

apiVersion: sart.terassyi.net/v1alpha2
kind: BGPPeerTemplate
metadata:
  name: bgppeertemplate-spine0
spec:
  asn: 65001
  addr: 9.9.9.9
  groups:
    - to-spine0
---
apiVersion: sart.terassyi.net/v1alpha2
kind: BGPPeerTemplate
metadata:
  name: bgppeertemplate-spine1
spec:
  asn: 65002
  addr: 7.7.7.7
  groups:
    - to-spine1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: sart.terassyi.net/v1alpha2
kind: ClusterBGP
metadata:
  name: clusterbgp-spine0
spec:
  nodeSelector:
    bgp: a
  asnSelector:
    from: label
  routerIdSelector:
    from: internalAddress
  speaker:
    path: 127.0.0.1:5000
    multipath: true
  peers:
    - peerTemplateRef: bgppeertemplate-spine0
      nodeBGPSelector:
        bgp: a
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: sart.terassyi.net/v1alpha2
kind: ClusterBGP
metadata:
  name: clusterbgp-spine1
spec:
  nodeSelector:
    bgp: a
  asnSelector:
    from: label
  routerIdSelector:
    from: internalAddress
  speaker:
    path: 127.0.0.1:5000
    multipath: true
  peers:
    - peerTemplateRef: bgppeertemplate-spine1
      nodeBGPSelector:
        bgp: a
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

これらを順次適用すると自動的に BGPPeer リソース作成され、が各ノード上の sartd-bgpspine0spine1 の FRR と BGP セッションが確立されます。

$ kubectl apply -f ../manifests/cni/sample/peer_template.yaml
bgppeertemplate.sart.terassyi.net/bgppeertemplate-spine0 created
bgppeertemplate.sart.terassyi.net/bgppeertemplate-spine1 created
$ kubectl apply -f ../manifests/cni/sample/cluster_bgp_spine0.yaml
clusterbgp.sart.terassyi.net/clusterbgp-spine0 created
$ kubectl apply -f ../manifests/cni/sample/cluster_bgp_spine1.yaml
clusterbgp.sart.terassyi.net/clusterbgp-spine1 created
1
2
3
4
5
6
7
$ kubectl get bgppeer
NAME                                                      ASN     ADDRESS   NODEBGP              CLUSTERBGP          BACKOFF   STATUS        AGE
bgppeertemplate-spine0-sart-control-plane-65001-9.9.9.9   65001   9.9.9.9   sart-control-plane   clusterbgp-spine0   0         Established   50s
bgppeertemplate-spine0-sart-worker-65001-9.9.9.9          65001   9.9.9.9   sart-worker          clusterbgp-spine0   0         Established   50s
bgppeertemplate-spine0-sart-worker2-65001-9.9.9.9         65001   9.9.9.9   sart-worker2         clusterbgp-spine0   0         Established   50s
bgppeertemplate-spine0-sart-worker3-65001-9.9.9.9         65001   9.9.9.9   sart-worker3         clusterbgp-spine0   0         Established   50s
bgppeertemplate-spine1-sart-control-plane-65002-7.7.7.7   65002   7.7.7.7   sart-control-plane   clusterbgp-spine1   0         Established   35s
bgppeertemplate-spine1-sart-worker-65002-7.7.7.7          65002   7.7.7.7   sart-worker          clusterbgp-spine1   0         Established   25s
bgppeertemplate-spine1-sart-worker2-65002-7.7.7.7         65002   7.7.7.7   sart-worker2         clusterbgp-spine1   0         Established   15s
bgppeertemplate-spine1-sart-worker3-65002-7.7.7.7         65002   7.7.7.7   sart-worker3         clusterbgp-spine1   0         Established   5s
1
2
3
4
5
6
7
8
9
10

以上のようにすべての BGPPeer リソースのステータスが Established になっていれば完了です。

後述の AddressBlock リソースの作成や LoadBalancer Service の作成により適宜 BGPAdvertisement リソースが作成され、対象の BGPPeer リソースに対応した sartd-bgp の BGP セッションが指定された経路を広報します。

# Pod 用 Address Pool を作成する

BGP 関連リソースの適用が完了したら、Pod 用の Address Pool を作成します。

AddresssPool リソースを作成してクラスタに Address Pool を適用できます。

Sart は一つのクラスタに複数の AddressPool リソースを適用できます。

複数の AddressPool リソースが一つのクラスタに存在するとき、sart は AddressPool.spec.autoAssign をみて、true となっている Address Pool を選択してアドレスを割り当てます。(autoAssign=trueAddressPool リソースはクラスタに一つしか作成できません。)

Sart は 一つの AddressPool リソースを AddressBlock リソースという単位に分割して管理します。

.spec.blockSize はその Address Pool をどのくらいの大きさで分割するかの単位です。

実験環境では以下の2つの AddressPool を適用します。

apiVersion: sart.terassyi.net/v1alpha2
kind: AddressPool
metadata:
  name: default-pod-pool
spec:
  cidr: 10.1.0.0/24
  type: pod
  allocType: bit
  blockSize: 29
  autoAssign: true
---
apiVersion: sart.terassyi.net/v1alpha2
kind: AddressPool
metadata:
  name: non-default-pod-pool
spec:
  cidr: 10.10.0.0/29
  type: pod
  allocType: bit
  blockSize: 32
  autoAssign: false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ kubectl apply -f ../manifests/cni/sample/pool.yaml
addresspool.sart.terassyi.net/default-pod-pool created
addresspool.sart.terassyi.net/non-default-pod-pool created
1
2
3

# Pod を作成する

これまでで Pod を作成する準備ができました。 ここではテスト用に用意したマニフェストで Pod を作成します。

$ kubectl apply -f ../manifests/cni/sample/namespace.yaml
namespace/test created
$ kubectl apply -f ../manifests/cni/sample/test_pod.yaml
pod/test-cp created
pod/test-worker created
pod/test-worker2 created
pod/test-worker3 created
pod/test-worker3-2 created
1
2
3
4
5
6
7
8

この例では各ノード上に Pod を作成しています。 以下のように各 Pod に対して IP アドレスが割り当てられ、Running になっていれば CNI プラグインとして正常に動作しています。

$ kubectl -n test get pod -owide
NAME             READY   STATUS    RESTARTS   AGE   IP          NODE                 NOMINATED NODE   READINESS GATES
test-cp          1/1     Running   0          60s   10.1.0.9    sart-control-plane   <none>           <none>
test-worker      1/1     Running   0          60s   10.1.0.0    sart-worker          <none>           <none>
test-worker2     1/1     Running   0          60s   10.1.0.24   sart-worker2         <none>           <none>
test-worker3     1/1     Running   0          60s   10.1.0.16   sart-worker3         <none>           <none>
test-worker3-2   1/1     Running   0          60s   10.1.0.17   sart-worker3         <none>           <none>
1
2
3
4
5
6
7

この Pod の作成で各ノードごとに AddressBlock リソースが作成され、それに対応して BGPAdvertisement リソースが作成されます。

$ kubectl get addressblock
NAME                     CIDR            TYPE      POOLREF                NODEREF              AGE
default-lb-pool          10.0.1.0/24     service   default-lb-pool        <no value>           4m38s
default-pod-pool-0       10.1.0.0/29     pod       default-pod-pool       sart-control-plane   7m24s
default-pod-pool-1       10.1.0.8/29     pod       default-pod-pool       sart-worker          7m24s
default-pod-pool-2       10.1.0.16/29    pod       default-pod-pool       sart-worker3         7m23s
default-pod-pool-3       10.1.0.24/29    pod       default-pod-pool       sart-worker2         7m23s
1
2
3
4
5
6
7
$ kubectl get bgpadvertisement -n kube-system
NAME                     CIDR           TYPE   PROTOCOL   AGE
default-pod-pool-0       10.1.0.0/29    pod    ipv4       8m7s
default-pod-pool-1       10.1.0.8/29    pod    ipv4       8m7s
default-pod-pool-2       10.1.0.16/29   pod    ipv4       8m7s
default-pod-pool-3       10.1.0.24/29   pod    ipv4       8m7s
1
2
3
4
5
6

最後に各 Pod 間で疎通が取れることを確かめます。 適当な Pod から他の適当な Pod のアドレスに対して ping を実行します。

以下のように応答が返ってくれば問題なく経路広報やネットワークの設定が完了していることがわかります。

$ kubectl -n test exec -it test-cp -- ping -c 1 10.1.0.24
PING 10.1.0.24 (10.1.0.24) 56(84) bytes of data.
64 bytes from 10.1.0.24: icmp_seq=1 ttl=61 time=0.223 ms
--- 10.1.0.24 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.223/0.223/0.223/0.000 ms
1
2
3
4
5
6
7

# Address Pool を選択して Pod を作成する

前述の通り、sart は複数の AddressPool を作成して利用できます。

それらの AddressPool リソースの .spec.autoAssign=true (デフォルト)でない Address Pool を利用して Pod を作成したい場合は Pod のマニフェストに sart.terassyi.net/addresspool: <pool name> という annotation を付与します。

また、一つ一つの Pod に annotation をつけて作成するのは大変なので Namespace に対して同じ annotation を付与すればその Namespace に作成される Pod すべてが指定した Address Pool を利用できます。

以下のマニフェストを適用してみます。

apiVersion: v1
kind: Namespace
metadata:
  name: test-non-default
  labels:
    name: test
  annotations:
    sart.terassyi.net/addresspool: non-default-pod-pool
---
apiVersion: v1
kind: Pod
metadata:
  name: test-worker2-non-default
  namespace: test-non-default
spec:
  containers:
  - name: test-non-default
    image: ghcr.io/terassyi/test-server:0.1.2
  nodeSelector:
    kubernetes.io/hostname: sart-worker2
---
apiVersion: v1
kind: Pod
metadata:
  name: test-worker3-non-default
  namespace: test-non-default
spec:
  containers:
  - name: test
    image: ghcr.io/terassyi/test-server:0.1.2
  nodeSelector:
    kubernetes.io/hostname: sart-worker3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
$ kubectl apply -f ../manifests/cni/sample/test_pod_in_namespace.yaml
namespace/test-non-default created
pod/test-worker2-non-default created
pod/test-worker3-non-default created
1
2
3
4

新たに作成した test-non-default Namespace の Pod を取得すると non-default-pod-pool のアドレスの範囲(10.10.0.0/29)から Pod にアドレスが割り当てられています。

$ kubectl -n test-non-default get pod -owide
NAME                       READY   STATUS    RESTARTS   AGE   IP          NODE           NOMINATED NODE   READINESS GATES
test-worker2-non-default   1/1     Running   0          33s   10.10.0.0   sart-worker2   <none>           <none>
test-worker3-non-default   1/1     Running   0          33s   10.10.0.1   sart-worker3   <none>           <none>
1
2
3
4

最後に各 Pod 間の疎通性を確かめます。

以下のように test/test-cp から test-non-default/test-worker3-non-default に対して疎通が確認できます。

kubectl -n test exec -it test-cp -- ping -c 1 10.10.0.1
PING 10.10.0.1 (10.10.0.1) 56(84) bytes of data.
64 bytes from 10.10.0.1: icmp_seq=1 ttl=61 time=0.167 ms
--- 10.10.0.1 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.167/0.167/0.167/0.000 ms
1
2
3
4
5
6
7

以上が CNI プラグインとしての sart の機能となります。

# LoadBalancer Service 用 Address Pool を作成する

Sart は Network Load Balancer 機能も実装しています。

この機能を利用するためには、CNI プラグイン機能の場合と同様に AddressPool リソースを作成します。

異なるのは .spec.typeservice を指定する点です。

以下のマニフェストを適用します。

apiVersion: sart.terassyi.net/v1alpha2
kind: AddressPool
metadata:
  name: default-lb-pool
spec:
  cidr: 10.0.1.0/24
  type: service
  allocType: bit
  autoAssign: true
---
apiVersion: sart.terassyi.net/v1alpha2
kind: AddressPool
metadata:
  name: non-default-lb-pool
spec:
  cidr: 10.0.100.0/24
  type: service
  allocType: bit
  blockSize: 24
  autoAssign: false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ kubectl apply -f ../manifests/lb/sample/lb_address_pool.yaml
addresspool.sart.terassyi.net/default-lb-pool created
addresspool.sart.terassyi.net/non-default-lb-pool created
$ kubectl get addresspool
NAME                  CIDR            TYPE      BLOCKSIZE   AUTO    AGE
default-lb-pool       10.0.1.0/24     service               true    51s
non-default-lb-pool   10.0.100.0/24   service   24          false   51s
1
2
3
4
5
6
7

以上のように serviceタイプの Address Pool が作成されます。

作成された serviceタイプの Address Pool にも auto assign の設定が存在します。

後述しますが、annotation を用いて利用したい Address Pool を選択できます。

# LoadBalancer を作成する

Address Pool の作成が完了したので LoadBalancer service を作成します。

以下のマニフェストを適用します。

このマニフェストはいくつかの LoadBalancer service とバックエンドとなる Deployment を作成します。

それぞれ以下のような特徴を持っています

  • app-svc-cluster
    • externalTrafficPolicy=Cluster
    • default-lb-pool を利用(指定なし)
  • app-svc-local
    • externalTrafficPolicy=Local
    • default-lb-pool を利用(指定なし)
  • app-svc-cluster2
    • externalTrafficPolicy=Cluster
    • non-default-lb-pool を利用(annotation で指定)
    • 利用アドレスを annotation で指定
apiVersion: v1
kind: Namespace
metadata:
  name: test
  labels:
    name: test
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app-cluster
  namespace: test
spec:
  replicas: 2
  selector:
    matchLabels:
      app: app-cluster
  template:
    metadata:
      labels:
        app: app-cluster
    spec:
      containers:
        - name: app
          image: nginx:latest
          ports:
            - containerPort: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app-local
  namespace: test
spec:
  replicas: 2
  selector:
    matchLabels:
      app: app-local
  template:
    metadata:
      labels:
        app: app-local
    spec:
      containers:
        - name: app
          image: nginx:latest
          ports:
            - containerPort: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app-cluster2
  namespace: test
spec:
  replicas: 2
  selector:
    matchLabels:
      app: app-cluster2
  template:
    metadata:
      labels:
        app: app-cluster2
    spec:
      containers:
        - name: app
          image: nginx:latest
          ports:
            - containerPort: 80
---
# LoadBalancer Service
apiVersion: v1
kind: Service
metadata:
  name: app-svc-cluster
  namespace: test
  annotations:
spec:
  type: LoadBalancer
  externalTrafficPolicy: Cluster
  selector:
    app: app-cluster
  ports:
    - name: http
      port: 80
      targetPort: 80
---
# LoadBalancer Service
apiVersion: v1
kind: Service
metadata:
  name: app-svc-local
  namespace: test
  annotations:
    sart.terassyi.net/addresspool: default-lb-pool
spec:
  type: LoadBalancer
  externalTrafficPolicy: Local
  selector:
    app: app-local
  ports:
    - name: http
      port: 80
      targetPort: 80
---
# LoadBalancer Service
apiVersion: v1
kind: Service
metadata:
  name: app-svc-cluster2
  namespace: test
  annotations:
    sart.terassyi.net/addresspool: non-default-lb-pool
    sart.terassyi.net/loadBalancerIPs: "10.0.100.20"
spec:
  type: LoadBalancer
  externalTrafficPolicy: Cluster
  selector:
    app: app-cluster2
  ports:
    - name: http
      port: 80
      targetPort: 80
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
$ kubectl apply -f .../manifests/lb/sample/lb.yaml
namespace/test created
deployment.apps/app-cluster created
deployment.apps/app-local created
deployment.apps/app-cluster2 created
service/app-svc-cluster created
service/app-svc-local created
service/app-svc-cluster2 created
1
2
3
4
5
6
7
8

適用後、以下のように Service リソースが作成され、 EXTERNAL-IP フィールドが利用している Address Pool から割り当てられたアドレスとなっていることが確認できます。

app-svc-cluster2 に関してはマニフェストで指定したアドレスが割り当てられています。

kubectl -n test get svc
NAME               TYPE           CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
app-svc-cluster    LoadBalancer   10.101.145.245   10.0.1.0      80:30707/TCP   9m59s
app-svc-cluster2   LoadBalancer   10.101.138.237   10.0.100.20   80:32524/TCP   9m59s
app-svc-local      LoadBalancer   10.101.38.67     10.0.1.1      80:31691/TCP   9m59s
1
2
3
4
5

最後にこれらの LoadBalancer Service に疎通確認を行います。

以下のように clab-sart-client0 というコンテナから各アドレスに対して curl を実行して確認します。

$ docker exec -it clab-sart-client0 curl http://10.0.1.0
{"timestamp":"2024-04-08T14:17:58.127992327Z","from":"10.1.0.25","to":"172.18.0.3:18363"}
$ docker exec -it clab-sart-client0 curl http://10.0.1.1
{"timestamp":"2024-04-08T14:19:41.048009684Z","from":"10.1.0.26","to":"192.168.0.2:56018"}%
$ docker exec -it clab-sart-client0 curl http://10.0.100.20
{"timestamp":"2024-04-08T14:19:52.551894221Z","from":"10.1.0.12","to":"172.18.0.5:64839"}%
1
2
3
4
5
6

当然ではありますが、externalTrafficPolicyLocal, Cluster 両方に対応しています。 test 用サーバからのレスポンスでどの Pod から返ってきているか、externalTrafficPolicy=Cluster の場合はどのノードを経由しているかが確認できます。

# 補足

これらの動作確認は e2e テストで確認しているものとほぼ同一です。

一気に流すには以下のコマンドを実行してください。

$ make kubernetes MODE=cni # 環境作成
$ make install-sart MODE=cni # 準備
$ make cni-e2e # e2e テスト
$ make kubernetes-down MODE=cni # 環境削除
1
2
3
4

# まとめ

本記事では Rust で自作している CNI プラグインの使い方を紹介しました。

本当は設計や実装も併せて紹介したいところでしたがかなり長くなるので今回は利用方法のみにしました。

このプロジェクトのために BGP から独自実装したり CNI プラグインライブラリを自作した (opens new window)のでどこかで実装の詳細についても紹介の機会を設けたいです。

設計などのドキュメントはリポジトリの /docs (opens new window) 以下に拙い英語でまとまっています。

さて、これからですが、まずは自宅で Kubernetes クラスタを構築してそこに sart を導入して運用していきたいです。

そのうえで必要な機能拡張やログの整理などをやっていければよいかなと思っています。

この記事を読んだ方が動かして遊んでみてもらえると嬉しいなと思います。