Kubernetes with CALICOでコントロールノードとルータを兼用させたい

## 結論、何ができて何ができなかったか

- ルータ（特にNAT機能）とCALICOは共存できない
  今回はルータ内に仮想サーバをたて、そっちをノード化することで対応できた。
  NAT機能をCalico側で対応することも可能らしいが、今回は未検証。
- 複数IPを持つことが原因のトラブルは解決可能
  ネットワークが２つ推奨なceph osdとの共存は可能と思われる（が、今回の構成では結果としてやっていない）

## 経緯

かれこれ5年ほど自宅サーバを運用している。もっぱらファイルサーバ用途だったのだが、素人の場当たり的な運用によりしばしば設定ミスによる障害が発生し、可用性はかなり低かった。

可用性の低さ自体は自分専用なので許容していたのだが、とはいえ高いに越したことはない。

ということで、学習も兼ねて、自作ルータ２台のマルチホーミング[^gateway]でゲートウェイを、cephでストレージを、kubernetesでサービスをそれぞれ冗長化するという計画をここ1年ほどちまちま進めていた。

で、自作ルータもcephクラスタの構築も済み、満をじしてkubernetesを導入しようとしたところ、色々うまく行かなかったので、それについて書く。


## やりたいこと

- サービスの冗長化のためkubernetesの導入
- すでに稼働中のcephクラスター（自作ルータ含む）に同居させ、ceph RBDをストレージとして利用する。
- プライベート用のサービスに加え、将来的に公開用のサービス[^public]をネットワーク的に分離するため、cniとしてネットワークポリシーを設定可能なcalicoを使用する。

## 起きたトラブル（本記事で扱うこと）
- ノードが複数のIPを持っていた場合に、Internal IPとしてクラスタ外のIPが割り振られる
  ⇒Internal IPの手動指定
- ルータ兼用ノードでBGPサーバとCALICOのポートが衝突する。
  => CALICO側でリッスンするIPを指定したり、ポートを変更する
- ルータ兼用ノードでファイヤーウォール,NATとCALICOが共存できない
  => ルータ内に仮想サーバを建て、そちらをノード化する。

## 本記事で扱わないこと

- Cephクラスタの構築
- 自作ルータの構築
- Calicoの基本設定
- 仮想サーバの構築

## 構成
なお、IPアドレスやホスト名は実際のものからわかりやすいものに変えてある。
~~~
$ kubectl get node -o wide               
NAME        STATUS   ROLES           AGE    VERSION   INTERNAL-IP     EXTERNAL-IP   OS-IMAGE                           KERNEL-VERSION           CONTAINER-RUNTIME
aiserver    Ready    control-plane   5d3h   v1.28.1   10.10.0.1       <none>        Fedora Linux 38 (Server Edition)   6.4.12-200.fc38.x86_64   cri-o://1.26.1
router1     Ready    control-plane   5d3h   v1.28.0   10.10.0.2       <none>        Debian GNU/Linux 12 (bookworm)     6.1.0-11-amd64           containerd://1.6.20
router2     Ready    control-plane   5d3h   v1.28.1   10.10.0.3       <none>        Debian GNU/Linux 12 (bookworm)     6.1.0-11-amd64           containerd://1.6.20
worker1     Ready    <none>          5d1h   v1.28.1   10.10.0.4       <none>        Debian GNU/Linux 12 (bookworm)     6.1.0-11-arm64           containerd://1.6.20
worker2     Ready    <none>          5d1h   v1.28.1   10.10.0.5       <none>        Debian GNU/Linux 12 (bookworm)     6.1.0-11-arm64           containerd://1.6.20
worker3     Ready    <none>          29h    v1.28.1   10.10.0.6       <none>        Debian GNU/Linux 12 (bookworm)     6.1.0-10-arm64           containerd://1.6.20
~~~
### ルータ２台

- アーキテクチャ: amd64
- OS: Debian Bookworm
- VPNゲートウェイ
- 中継サーバとの経路の冗長化にBGPによる経路交換をしている
- 自宅クラスタ側の経路の冗長化に、ゲートウェイをkeepalivedで切り替えられるようにしている。
- CEPH 管理ノード
- CEPH OSD [^osd]

### AI用サーバ１台

- アーキテクチャ: amd64
- OS: Fedora
  日進月歩のAI用ということもあり、新しい機能をすぐ使えるようにこいつだけDebianではなくFedoraを採用している。[^ubuntu]
  幸い今の所cephもkubernetesもfedoraが混じっていること自体は問題にはなっていない。
- 元はゲーミングPCだったが、サーバ趣味がゲームよりも優先された結果今年とうとうヘッドレスになった。
- CEPH 管理ノード
- CEPH OSD

### ワーカーノード3台

- アーキテクチャ: arm64
  （いずれもRaspberry Pi 4 8GBRAMモデル。）
- OS: Debian bookwarm
- 以降とくに今回の記事では触れない
  （驚いたことに、アーキテクチャ混在でも問題が起きなかったので）



## Internal IPがクラスター外のものが振られる

参考: [kubelet - How to change the internal IP of Kubernetes worker nodes? - Stack Overflow](https://stackoverflow.com/questions/54942488/how-to-change-the-internal-ip-of-kubernetes-worker-nodes)
ホスト側でkubeletの設定をすることで指定が可能。

Debianの場合
~~~:/etc/default/kubelet 
KUBELET_EXTRA_ARGS='--node-ip 10.10.0.1'
~~~

Fedoraの場合は`/etc/sysconfig/kubelet`に置く。

## CalicoのIPの指定

参考: [Configure IP autodetection | Calico Documentation](https://docs.tigera.io/calico/latest/networking/ipam/ip-autodetection)

CalicoのIPはデフォルトでは上述のInternalIPとなって**いない**ので、InternalIPを参照するよう`custom-resources.yaml`に指定する必要がある。

```diff_yaml:custom-resources.yaml
apiVersion: operator.tigera.io/v1
kind: Installation
metadata:
  name: default
spec:
  # Configures Calico networking.
  calicoNetwork:
    # Note: The ipPools section cannot be modified post-install.
    ipPools:
    - blockSize: 26
      cidr: 10.20.0.0/16
      encapsulation: VXLANCrossSubnet
      natOutgoing: Enabled
      nodeSelector: all()
+   nodeAddressAutodetectionV4:
+     kubernetes: NodeInternalIP

#<後略>
```

## CalicoとFRRのBGPの併用

それぞれデフォルトでは0.0.0.0をリッスンしているようなので、それぞれ別のIPに紐づける必要がある。

### FRR

```diff:/etc/frr/daemons
bgpd=yes
#<略>
- bgpd_options=" -A 127.0.0.1"
+ bgpd_options=" -A 127.0.0.1 -l 10.30.0.1/24"
#<略>
```

参考: [BGP — FRR latest documentation](http://docs.frrouting.org/en/latest/bgp.html)

### CALICO
先ほどのInternalIPの設定はBGPのPEERの設定らしく、Listenする自分のIPはデフォルトでは`bindMode: None`となっており、0.0.0.0をリッスンしている模様。

[BGP configuration | Calico Documentation](https://docs.tigera.io/calico/latest/reference/resources/bgpconfig)

~~~yml:bgp-config.yaml
apiVersion: projectcalico.org/v3
kind: BGPConfiguration
metadata:
  name: default
spec:
  bindMode: NodeIP
~~~

設定を読み込んで、ノードを作り直す
~~~
$ kubectl apply -f bgp-config
$ kubectl delete pod --all -n calico-system
~~~

これでInternal Node IPのみをリッスンするようになるはず

## ファイヤーウォールとCalicoの衝突

動的なFirewalldを停止し、
```
$ sudo systemctl disable --now firewalld
```

nftablesで最低限のNATのみ静的に設定する
```:/etc/nftables.conf
flush ruleset

table bridge bridge_filter {
	chain input {
		type filter hook input priority 0;
	}
	chain forward {
		type filter hook forward priority 0;
	}
	chain output {
		type filter hook output priority 0;
	}
}

table inet filter {
	chain input {
		type filter hook input priority 0;
	}
	chain forward {
		type filter hook forward priority 0;
	}
	chain output {
		type filter hook output priority 0;
	}
}
table ip nat {
  chain prerouting {
    type nat hook prerouting priority 0;
  }
  chain postrouting {
    type nat hook postrouting priority 0;
    oifname "wan0" iifname {"lan0"} masquerade
  }
}
```

```
$ sudo systemctl enable --now nftables
```

ここまでやった後のpodの状況が以下

~~~
$ kubectl get pod --all-namespaces        
NAMESPACE          NAME                                         READY   STATUS    RESTARTS        AGE
calico-apiserver   calico-apiserver-7b4bf88c5b-449ll            1/1     Running   0               107m
calico-apiserver   calico-apiserver-7b4bf88c5b-wtf4s            1/1     Running   1 (34m ago)     107m
calico-system      calico-kube-controllers-5f94576855-gzjzm     1/1     Running   0               42m
calico-system      calico-node-7thnm                            1/1     Running   0               42m
calico-system      calico-node-l4fs5                            1/1     Running   0               42m
calico-system      calico-node-n2dlk                            1/1     Running   0               42m
calico-system      calico-node-pqd98                            1/1     Running   0               42m
calico-system      calico-node-t966m                            1/1     Running   1 (34m ago)     42m
calico-system      calico-node-xtmr2                            1/1     Running   0               42m
calico-system      calico-typha-847799db4-hg8x7                 1/1     Running   2 (33m ago)     42m
calico-system      calico-typha-847799db4-rw7rt                 1/1     Running   0               42m
calico-system      calico-typha-847799db4-zcrth                 1/1     Running   0               42m
calico-system      csi-node-driver-8swwz                        2/2     Running   0               42m
calico-system      csi-node-driver-b92vj                        2/2     Running   0               42m
calico-system      csi-node-driver-cg4dm                        2/2     Running   0               42m
calico-system      csi-node-driver-jnzlr                        2/2     Running   0               42m
calico-system      csi-node-driver-vr5ck                        2/2     Running   2 (34m ago)     42m
calico-system      csi-node-driver-wggsc                        2/2     Running   0               42m
kube-system        coredns-5dd5756b68-6lnlt                     1/1     Running   0               29h
kube-system        coredns-5dd5756b68-cfsnj                     1/1     Running   0               29h
kube-system        etcd-hedgehog                                1/1     Running   3 (34m ago)     29h
kube-system        etcd-rabbit                                  1/1     Running   4 (3h1m ago)    29h
kube-system        etcd-wolf                                    1/1     Running   18              29h
kube-system        kube-apiserver-hedgehog                      1/1     Running   11 (34m ago)    29h
kube-system        kube-apiserver-rabbit                        1/1     Running   11 (3h1m ago)   29h
kube-system        kube-apiserver-wolf                          1/1     Running   18              29h
kube-system        kube-controller-manager-hedgehog             1/1     Running   4 (34m ago)     29h
kube-system        kube-controller-manager-rabbit               1/1     Running   5 (3h1m ago)    29h
kube-system        kube-controller-manager-wolf                 1/1     Running   3               29h
kube-system        kube-proxy-9b2tr                             1/1     Running   0               29h
kube-system        kube-proxy-hdzp8                             1/1     Running   1 (3h1m ago)    29h
kube-system        kube-proxy-j2xfb                             1/1     Running   1 (34m ago)     29h
kube-system        kube-proxy-n2fdc                             1/1     Running   14 (27h ago)    29h
kube-system        kube-proxy-pmk2q                             1/1     Running   0               29h
kube-system        kube-proxy-zwbj8                             1/1     Running   6 (27h ago)     28h
kube-system        kube-scheduler-hedgehog                      1/1     Running   10 (34m ago)    29h
kube-system        kube-scheduler-rabbit                        1/1     Running   6 (3h1m ago)    29h
kube-system        kube-scheduler-wolf                          1/1     Running   7               29h
tigera-operator    tigera-operator-7c8ff84d4f-bhq5h             1/1     Running   2 (33m ago)     29h
~~~
これで解決、かと思いきや…

### ノードの仮想サーバへの移行 

実際に運用していると何かおかしい。

結論から言うと、natを有するルータでcalicoがうまく動いていなかった。このエラーではcalicoのコンテナがしておらず、上記のように`running`ステータスとして一見正常に動いているように見え、その上ai用サーバのコントロールノードが正常だったため、一見するとちゃんと機能して言うように見えた。が、実際はルータ圏コントロールノードとワーカーノードが正常に通信出来ておらず、断続的に通信エラーが発生していた模様。

この段階でルータとCalicoの兼用は諦め、ルータ内にコントロールノード用の仮想サーバを建て、クラスターのネットワークにブリッジ接続することにした。

~~最初からそうしておけばよかった。~~

## その後

Calico側でNATを設定することができるらしいことを知った。正直NATの問題が解消したところで他のトラブルがないとも限らないので、仮想サーバ化の方が無難だとは思うが。
そのうち仮想化による不都合が発生したら試すかもしれない。
参考: [Configure outgoing NAT | Calico Documentation](https://docs.tigera.io/calico/latest/networking/configuring/workloads-outside-cluster)



[^gateway]: ここでいう擬似マルチホーミングは、あくまでも自宅クラスタのゲートウェイがマルチなのであって、インターネットへの経路自体は1つのまま。障害の原因が基本的に人為的なミスのため、ほとんど触らないレンタルルータの可用性については不満がないのと単純に２回線契約する金がもったいないので。
[^public]: 具体的には現在Lightsail上で動かしているアクセス解析ソフトのplausibleや、将来的に公開予定のmastodon互換のakkomaおひとり様インスタンスを自宅サーバに統合する予定。

[^osd]: ルータやAIサーバがosdを兼用しているのは、単純にGPUやNICの増築のため（SBCではなく）自作PC互換のハードウェアが都合が良かったため。とは言えコスト的にはなるべく安価なSBCを使い、自作PCは最小限にしたい。ということで、現在のルータ２台AI用サーバ1台の計３台体制に落ち着いた。３台というと、クラスタの最小限の数としてもちょうどいいため、コントロールノードなどもやらせている。将来的には[Orange Pi Plus](http://www.orangepi.org/html/hardWare/computerAndMicrocontrollers/details/Orange-Pi-5-plus.html)のような、NIC二つにSSDも詰めるSBCをOSD兼ワーカーノードとして追加するかもしれない

[^ubuntu]: Ubuntuではないのは、過去に2度LTSでないUbuntuでトラブルを経験し、以来LTSでないUbuntuを信用していないため。LTS使うならDebianでいいし。