Kubernetes with CALICOでノードとルータを兼用させる

[以前Kubernetes with CALICOでコントロールノードとルータを兼用させたいといっていて、当時は結局兼用できずに仮想的に別サーバに分けることで解決した](https://qiita.com/fluo10/items/4c992105ad1cade1db00)わけだが、先日再挑戦して無事できたので設定について晒す。

## 初めに
### 本記事の目的
- 稼働中の自作ルータに直接kubernetesを導入し、（マスター）ノード化する

### 本記事で扱うもの
- ルータとしての以下の機能をCalicoに移譲するための設定
    - デフォルトゲートウェイ向けのNAT/IPマスカレード[^Firewalld]
    - BGPによる中継サーバとのルーティング[^frr]

### 本記事で扱わないもの
- CalicoやKubernetesのインストール  
  公式のドキュメント通りでできたので
- Calicoの設定の詳細な説明  
  現時点で期待通り動いているだけで、なぜこの設定で動いているのか細かいところはよくわかっていないので
- ファイアーウォールとしてのCalicoの設定  
  今回はとにかくNATとルーティングをしたかった（がなかなかうまくいかなかった）都合上、ネットワークポリシーは全部通すザル設定

## 構成

### Nodeバージョン一覧

```
$ kubectl get node -o wide
NAME         STATUS   ROLES           AGE    VERSION   INTERNAL-IP     EXTERNAL-IP   OS-IMAGE                           KERNEL-VERSION          CONTAINER-RUNTIME
router       Ready    control-plane   21d    v1.28.8   10.0.0.1        <none>        Debian GNU/Linux 12 (bookworm)     6.1.0-18-amd64          containerd://1.6.20
worker1      Ready    <none>          206d   v1.28.8   10.0.0.2        <none>        Debian GNU/Linux 12 (bookworm)     6.1.0-18-arm64          containerd://1.6.20
worker2      Ready    <none>          206d   v1.28.8   10.0.0.3        <none>        Debian GNU/Linux 12 (bookworm)     6.1.0-18-arm64          containerd://1.6.20

```

### ネットワーク図
```
Internet                                                           
─┬────────────────────┬──────────────────────────┬─────────┬─        
 │                    │                          │         │         
┌┴────────────────┐ ┌─┴───────────────────┐    ┌─┴─────┐ ┌─┴─────┐ 
│Rental ONU/Router│ │Jump Server          │    │Client1│ │Client2│ 
└──────────────┬──┘ └─┬──────────────┬────┘    └────┬──┘ └┬──────┘ 
               │      │              │              │     │        
Home Network   │      │ Router VPN   │ Client VPN   │     │        
192.168.0.0/24 │      │ 10.1.0.0/24  │ 10.1.1.0/24  │     │        
───────────────┼───  ─┴────────┬─── ─┴──────────────┴─────┴────────
               │               │                                   
┌──────────────┼───────────────┼──────────────────────────┐        
│ Home build   │               │                          │        
│ Router     ┌─┴────────────┐ ┌┴──────────┐ ┌───────────┐ │        
│            │wan0          │ │wg0        │ │lan0       │ │        
│            │192.168.0.2/24│ │10.1.0.2/24│ │10.0.0.1/et│ │        
│            └──────────────┘ └───────────┘ └────┬──────┘ │        
│                                                │        │        
└────────────────────────────────────────────────┼────────┘        
                                                 │                 
Clusteer Network                                 │                 
10.0.0.0/24                                      │                 
───┬────────────┬────────────────────────────────┴─────────        
   │            │                                                  
┌──┴─────┐    ┌─┴──────┐                                           
│Worker1 │    │Worker 2│                                           
└────────┘    └────────┘                                           
```

以下ノードやネットワークの解説

### レンタルルータ

- 普通のレンタルルータ兼ONU
- Home NetworkにDHCPやDNSを提供している

### 自宅ネットワーク
- 普通の家庭のネットワーク
- 自作ルータもこの配下に位置している
### クラスタネットワーク
- 自作ルータの下流に位置するネットワーク
- kubernetesクラスタを構築するホスト間の物理ネットワーク

### 自作ルータ（ルータ兼管理ノード）
- 自宅ネットワークおよび中継サーバとクラスターネットワークの中間に位置するルータ
- デフォルトゲートウェイはレンタルルータ側で、中継ネットワークはクライアントからのアクセスのみで使う
- BGPで中継サーバと中継ネットワークとクラスターネットワークのルートの交換をしている
- クラスターネットワークにDHCPやDNSを提供している
- 実際には微妙に異なる構成の管理ノードが3台あり、keepalivedでクラスター内のデフォルトゲートウェイを冗長化したりしているが、本記事では省略して1台として扱っている
### 自宅ノード（ワーカーノード）
- 主にラズパイが担当

### 中継サーバ
- AWS Lightsail上で稼働
- 自作ルータおよび各クライアントをVPNで橋渡ししている

## BGP

中継サーバとBGPでルートの共有をし、中継サーバから自宅サーバへアクセスできるようにしていた。
今回、中継サーバの構成はそのままに、自作ルータ側をCalicoを使ってクラスタネットワークのアドレス（`10.0.0.0/24`)を共有する


### BGP Configuration
```yaml
apiVersion: projectcalico.org/v3
kind: BGPConfiguration
metadata:                                        
  name: default
spec:
  bindMode: None
  communities:
    - name: bgp-large-community
      value: 64512:000
  prefixAdvertisements:
    - cidr: 10.0.0.0/24
      communities:
        - bgp-large-community
```
クラスタネットワークネットワーク`10.0.0.0/24`をPrefixAdvertisementsに設定することで、中継サーバにこれを広告している。

### BGP Filter

クラスタネットワーク`10.0.0.0/24`をエクスポート、中継ネットワーク`10.1.0.0/16`をインポートするようBGPフィルターを設定する。

```
apiVersion: projectcalico.org/v3
kind: BGPFilter [130]
metadata:n: projectcalico.org/v3
  name: bgp-filter
spec:
  exportV4:
    - action: Accept
      matchOperator: In
      cidr: 10.0.0.0/24
  importV4:
    - action: Accept
      matchOperator: In
      cidr: 10.1.0.0/16
```



### BGP Peer

中継サーバとVPN上で隣接するノードは自作ルータのみなので、自作ルータが中継サーバとピアを組むよう設定する
```yaml
apiVersion: projectcalico.org/v3
kind: BGPPeer
metadata:
  name: router
spec:
  peerIP: 10.1.0.1
  asNumber: 65048
  node: router
  filters:
    - bgp-filter
  sourceAddress: None
  keepOriginalNextHop: true
```


## NAT

### Host Endpoints

どうもCalicoではフォワーディングをホワイトリスト形式で行っているらしく、インターフェース間での転送にはそれぞれのインターフェースを設定する必要がある模様。

```yaml
apiVersion: projectcalico.org/v3
kind: HostEndpoint
metadata:
  name: router-wan
spec:
  interfaceName: wan0
  node: router
  expectedIPs:
    - 192.168.0.2
---
apiVersion: projectcalico.org/v3
kind: HostEndpoint
metadata:
  name: router-vpn
  labels: defaultgateway
spec:
  interfaceName: wg0
  node: router
  expectedIPs:
    - 10.1.0.2
---
apiVersion: projectcalico.org/v3
kind: HostEndpoint
metadata:
  name: router-cluster
  labels: defaultgateway
spec:
  interfaceName: lan0
  node: router
  expectedIPs:
    - 10.0.0.1
```
### IP Pool

IP Poolは`disabled`にすることで、kubernetes内部で使わないIPアドレスを宣言できる。ここで`natOutgoing: true`とすることで、クラスタネットワークから外部へのNATを有効にできる。

```yaml
apiVersion: projectcalico.org/v3
kind: IPPool
metadata:
  name: border-ipv4-ippool
spec:
  cidr: 10.0.0.0/24
  disabled: true
  natOutgoing: true
```

### GlobalNetworkPolicy

クラスター全体のネットワークポリシー

本来ならファイアーウォールとして余計な通信を除くよう設定すべきだが、今回はとりあえず通信させたいのでIngressもEgressもすべて許可している。
注意点として、DNSやDHCPといったルータとしてのサービスもクラスター内に含まれる都合上、許可する必要がある。とくにApiserverのエンドポイントをドメインにしている状態でDNSをシャットダウンすると、kubectlでドメインが見つからず詰むので要注意。

```
apiVersion: projectcalico.org/v3
kind: IPPool
metadata:
  name: border-ipv4-ippool
spec:
apiVersion: projectcalico.org/v3
kind: GlobalNetworkPolicy
metadata:
  name: allow-forward
spec:
  applyOnForward: true
  types:
    - Ingress
    - Egress
  ingress:
    - action: Allow
  egress:
    - action: Allow
```

## 結果

中継サーバでクラスタネットワークへのルートが確認できる。
```
$ ip route | grep "10.0.0.0/24"
10.0.0.0/24 nhid 56 via 10.1.0.1 dev wg0 proto bgp metric 20 
```

ワーカーノードが自作ルータ経由で外部にアクセスできることが確認できる。
```
$ traceroute google.com
traceroute to google.com (172.217.26.238), 30 hops max, 60 byte packets
 1  10.0.0.1 (10.0.0.1)  0.288 ms  0.162 ms  0.118 ms
 2  * * *
 3  * * *
 4  * * *
 5  * * *
 6  * * *
 7  bom05s09-in-f14.1e100.net (172.217.26.238)  40.357 ms 108.170.242.129 (108.170.242.129)  40.114 ms nrt12s51-in-f14.1e100.net (172.217.26.238)  40.437 ms
```

[^firewalld]: kubernetes導入前はfirewalldで行っていた。
[^frr]: kubernetes導入前はfrrで行っていた。