Calicoを使用したnamespaceでアドレスレンジの分割

## 環境
- ubuntu：22.0.4.1 LTS
- kubernetes：1.28.2
- calico 3.26.3


## 検証環境
既存の検証環境は以下の通りです。

![kube_calico.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2604097/9d296690-ba2a-4ae6-b366-45c0d2195438.png)


ここまでの構築方法、詳細は、以下をご参照ください。

https://qiita.com/ktog/items/73c9af74980592ce757f

https://qiita.com/ktog/items/c8092b02f978b43577c0


## ipPoolの導入
### 既存のipPoolの確認

まずは、ipPoolについて`calicoctl`を使って、確認してみます。

```cdm
$ calicoctl get ipPool
NAME                  CIDR             SELECTOR
default-ipv4-ippool   192.168.0.0/16   all()
```

現状、`192.168.0.0/16`のアドレスレンジとなっており、新規でPodを作成すると、以下のように該当のアドレスレンジが使用されます。

```
# kubectl get pods -owide
NAME       READY   STATUS    RESTARTS   AGE   IP              NODE                    NOMINATED NODE   READINESS GATES
test-1     1/1     Running   0          8s    192.168.170.5   kube-controlplane-001   <none>           <none>
test-2-1   1/1     Running   0          8s    192.168.181.7   kube-worker-001         <none>           <none>
test-2-2   1/1     Running   0          7s    192.168.181.8   kube-worker-001         <none>           <none>
```

### 既存のipPoolの削除

まずは、ipPoolについて`calicoctl`を使って、デフォルトのipPoolを削除します。

```cmd
# calicoctl delete ipPool default-ipv4-ippool
Successfully deleted 1 'IPPool' resource(s)
```

```cmd
# calicoctl get ipPool
NAME   CIDR   SELECTOR

```

### 新規ipPoolの作成

次に新規のipPoolを作成していきます。今回は、以下の要件とします。

特定のnamespace（ここれは「dev」ネームスペース）でPodを立ち上げた場合、以下のアドレスレンジを割り当てる。
`192.168.129.0/24`

上記以外のnamespaceでPodを立ち上げた場合、以下のアドレスレンジを割り当てる。
`192.168.0.0/17`

それぞれのipPoolを作成していきます。
デフォルトのipPoolのyamlは以下の通りです。

```yaml: ippool_default.yaml
- apiVersion: projectcalico.org/v3
  kind: IPPool
  metadata:
    name: default-ipv4-ippool
  spec:
    allowedUses:
    - Workload
    - Tunnel
    blockSize: 24
    cidr: 192.168.0.0/17
    ipipMode: Never
    nodeSelector: all()
    vxlanMode: Never
kind: IPPoolList
```

では、`kubectl`で'apply'してみます。

```cmd
# kubectl apply -f ippool_default.yaml
ippool.projectcalico.org/default-ipv4-ippool created
```

`calicoctl`を使って、確認します。

```cmd
# calicoctl get ipPool -owide
NAME                  CIDR             NAT     IPIPMODE   VXLANMODE   DISABLED   DISABLEBGPEXPORT   SELECTOR
default-ipv4-ippool   192.168.0.0/17   false   Never      Never       false      false              all()
```

同様に特定のdevネームスペース用のipPoolを作成します。
`label`は、なくても問題ないです。

```yaml:ippool_namespace_dev.yaml
apiVersion: projectcalico.org/v3
items:
- apiVersion: projectcalico.org/v3
  kind: IPPool
  metadata:
    name: namespace-dev-ippool
    labels:
      environment: dev
  spec:
    allowedUses:
    - Workload
    - Tunnel
    blockSize: 24
    cidr: 192.168.129.0/24
    ipipMode: Never
    vxlanMode: Never
kind: IPPoolList
```

では、再度ipPoolを確認してみます。

```cmd
# calicoctl get ipPool -owide
NAME                   CIDR               NAT     IPIPMODE   VXLANMODE   DISABLED   DISABLEBGPEXPORT   SELECTOR
default-ipv4-ippool    192.168.0.0/17     false   Never      Never       false      false              all()
namespace-dev-ippool   192.168.129.0/24   false   Never      Never       false      false              all()
```

これで、ipPoolが作成できました。


### 新規namespaceの作成

次にdevネームスペースを作成していきます。
namespaceを作成する際に、`cni.projectcalico.org/ipv4pools`のアノテーションをつけることで、特定のipPoolを使ったネームスペースを作成することができます。

今回、ipPool `namespace-dev-ippool`を使いたいので、以下のようなマニフェストになります。

```yaml:namespace_dev.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: dev
  annotations:
    'cni.projectcalico.org/ipv4pools': '["namespace-dev-ippool"]'
  labels:
    environment: dev
spec: {}
status: {}
```

では、ネームスペースを作成します。
```cmd
# kubectl apply -f namespace_dev.yaml
namespace/dev created
```

これで準備が整いました。


## 動作確認

### 既存Podの再起動

この時点で、すでに作成されているPodは既存のネットワークレンジのままになっているので、再起動（一度削除）する必要がります。

削除前のアドレスは、以下の通りでした。

```cmd
# kubectl get pods -n kube-system -owide | grep coredns
coredns-5dd5756b68-cdtfh                        1/1     Running   0          116s    192.168.181.6   kube-worker-001         <none>           <none>
coredns-5dd5756b68-w448v                        1/1     Running   0          2m56s   192.168.170.4   kube-controlplane-001   <none>           <none>
```

ここで、static Podであるcorednsを削除してみます。

```cmd
# kubectl delete pods -n kube-system coredns-5dd5756b68-cdtfh
pod "coredns-5dd5756b68-cdtfh" deleted
# kubectl delete pods -n kube-system coredns-5dd5756b68-w448v
pod "coredns-5dd5756b68-w448v" deleted
```

改めて、アドレスを確認すると、該当の`192.168.0.0/17`のアドレスレンジになっていることが分かります。

```cmd
kubectl get pods -n kube-system -o wide | grep coredns
coredns-5dd5756b68-gl5pq                        1/1     Running   0               10s    192.168.102.1   kube-worker-001         <none>           <none>
coredns-5dd5756b68-z827r                        1/1     Running   0               2d6h   192.168.101.3   kube-worker-001         <none>           <none>
```


### Podの作成
#### デフォルトネームスペース

次に、実際にいくつかのネームスペースにPodを作成して、ipPoolの動作について確認してみます。
まずは、デフォルトのネームスペースにPodを作成してみます。

```cmd
# kubectl run test-default --image nginx
pod/test-default created
```

アドレスを確認してみます。

```cmd
# kubectl get pods -owide
NAME                         READY   STATUS             RESTARTS       AGE     IP                NODE                    NOMINATED NODE   READINESS GATES
test-default                 1/1     Running            0              2m58s   192.168.102.0     kube-worker-001         <none>           <none>
```

割り当てのアルゴリズムについてはわかりませんが、期待通り`192.168.0.0/17`のアドレスレンジに作成されていることが分かります。

次にcontrole-plane-node上のデフォルトのネームスペースにPodを作成してみます。
`nodeSelector`を使用し、以下のマニフェストを作成し、'apply'してみます。

```yaml:sample_pod-default-cp.yaml
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: test-default-cp
  name: test-default-cp
spec:
  containers:
  - image: nginx
    name: test-default-cp
    resources: {}
  nodeSelector:
    kubernetes.io/hostname: kube-controlplane-001
  dnsPolicy: ClusterFirst
  restartPolicy: Always
status: {}
```

それではPodを立ち上げます。

```cmd
# kubectl apply -f sample_pod-default-cp.yaml
pod/test-default-cp created
```

ではアドレスを確認してみます。

```cmd
kubectl get pods -owide
NAME                         READY   STATUS             RESTARTS        AGE     IP                NODE                    NOMINATED NODE   READINESS GATES
test-default-cp              1/1     Running            0               38s     192.168.58.2      kube-controlplane-001   <none>           <none>
```

こちらも期待通り`192.168.0.0/17`のアドレスレンジに作成されていることが分かります。


#### devネームスペース

では次にdevネームスペースにPodを作成してみます。

```cmd
# kubectl run test-dev -n dev --image nginx
pod/test-dev created
```

```cmd
kubectl get pods -n dev -owide
NAME       READY   STATUS    RESTARTS   AGE   IP              NODE              NOMINATED NODE   READINESS GATES
test-dev   1/1     Running   0          21s   192.168.129.0   kube-worker-001   <none>           <none>
```

こちらも期待通り、`192.168.129.0/24`のアドレスレンジに作成されていることが分かりました。ただ、なぜネットワークアドレスが使用されたのかは、調査中です。

次にcontrole-plane-node上のdevネームスペースにPodを作成してみます。
以下のマニフェストを作成し、'apply'してみます。

```yaml:sample-dev-cp.yaml
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: test-dev-cp
  name: test-dev-cp
  namespace: dev
spec:
  containers:
  - image: nginx
    name: test-dev-cp
    resources: {}
  nodeSelector:
    kubernetes.io/hostname: kube-controlplane-001
  dnsPolicy: ClusterFirst
  restartPolicy: Always
status: {}
```

それではPodを立ち上げます。

```cmd
# kubectl apply -f sample-dev-cp.yaml
pod/test-dev-cp created
```

ではアドレスを確認してみます。

```cmd
kubectl get pods -n dev -o wide
NAME          READY   STATUS    RESTARTS   AGE     IP              NODE                    NOMINATED NODE   READINESS GATES
test-dev      1/1     Running   0          22m     192.168.129.0   kube-worker-001         <none>           <none>
test-dev-cp   1/1     Running   0          2m27s   192.168.129.1   kube-controlplane-001   <none>           <none>
```

こちらも期待通り、`192.168.129.0/24`のアドレスが割り当てられました。


### ルーティング確認

次に上位のスイッチからルーティングを確認してみます。

```cmd
#show ip route bgp detail

VRF: default
Codes: C - connected, S - static, K - kernel,
       O - OSPF, IA - OSPF inter area, E1 - OSPF external type 1,
       E2 - OSPF external type 2, N1 - OSPF NSSA external type 1,
       N2 - OSPF NSSA external type2, B - Other BGP Routes,
       B I - iBGP, B E - eBGP, R - RIP, I L1 - IS-IS level 1,
       I L2 - IS-IS level 2, O3 - OSPFv3, A B - BGP Aggregate,
       A O - OSPF Summary, NG - Nexthop Group Static Route,
       V - VXLAN Control Service, M - Martian,
       DH - DHCP client installed default route,
       DP - Dynamic Policy Route, L - VRF Leaked,
       G  - gRIBI, RC - Route Cache Route,
       CL - CBF Leaked Route

 B I      192.16.0.0/12 [200/0] via 172.21.1.8, Vlan3002
 B I      192.168.58.0/24 [200/0] via 172.21.1.8, Vlan3002
 B I      192.168.101.3/32 [200/0] via 172.21.1.18, Vlan3002
 B I      192.168.102.0/24 [200/0] via 172.21.1.18, Vlan3002
 B I      192.168.129.1/32 [200/0] via 172.21.1.8, Vlan3002
 B I      192.168.129.0/24 [200/0] via 172.21.1.18, Vlan3002
```

```cmd
#show ip bgp detail
BGP routing table information for VRF default
Router identifier 1.1.1.1, local AS number 65535
BGP routing table entry for 192.16.0.0/12
 Paths: 2 available
  Local
    172.21.1.8 from 172.21.1.8 (172.21.0.8)
      Origin IGP, metric 0, localpref 100, IGP metric 0, weight 0, tag 0
      Received 00:12:16 ago, valid, internal, best
      Rx path id: 0x2
      Rx SAFI: Unicast
  Local
    172.21.1.18 from 172.21.1.18 (172.21.0.18)
      Origin IGP, metric 0, localpref 100, IGP metric 0, weight 0, tag 0
      Received 00:12:16 ago, valid, internal
      Rx path id: 0x2
      Rx SAFI: Unicast
BGP routing table entry for 192.168.58.0/24
 Paths: 1 available
  Local
    172.21.1.8 from 172.21.1.8 (172.21.0.8)
      Origin IGP, metric 0, localpref 100, IGP metric 0, weight 0, tag 0
      Received 00:12:16 ago, valid, internal, best
      Community: 65535:120
      Large Community: 65535:300:100
      Rx path id: 0x2
      Rx SAFI: Unicast
BGP routing table entry for 192.168.101.3/32
 Paths: 1 available
  Local
    172.21.1.18 from 172.21.1.18 (172.21.0.18)
      Origin IGP, metric 0, localpref 100, IGP metric 0, weight 0, tag 0
      Received 00:12:16 ago, valid, internal, best
      Community: 65535:120
      Large Community: 65535:300:100
      Rx path id: 0x3
      Rx SAFI: Unicast
BGP routing table entry for 192.168.102.0/24
 Paths: 1 available
  Local
    172.21.1.18 from 172.21.1.18 (172.21.0.18)
      Origin IGP, metric 0, localpref 100, IGP metric 0, weight 0, tag 0
      Received 00:12:16 ago, valid, internal, best
      Community: 65535:120
      Large Community: 65535:300:100
      Rx path id: 0x2
      Rx SAFI: Unicast
BGP routing table entry for 192.168.129.0/24
 Paths: 1 available
  Local
    172.21.1.18 from 172.21.1.18 (172.21.0.18)
      Origin IGP, metric 0, localpref 100, IGP metric 0, weight 0, tag 0
      Received 00:12:16 ago, valid, internal, best
      Community: 65535:120
      Large Community: 65535:300:100
      Rx path id: 0x2
      Rx SAFI: Unicast
BGP routing table entry for 192.168.129.1/32
 Paths: 1 available
  Local
    172.21.1.8 from 172.21.1.8 (172.21.0.8)
      Origin IGP, metric 0, localpref 100, IGP metric 0, weight 0, tag 0
      Received 00:12:16 ago, valid, internal, best
      Community: 65535:120
      Large Community: 65535:300:100
      Rx path id: 0x3
      Rx SAFI: Unicast
```

上記で登場した、`test-default-cp`、`test-default`、`test-dev`、`test-dev-cp`のそれぞれのネットワークはそれぞれクラスフルでルート集約されたネットワークが伝播されていることが分かりました。
但し、途中で再起動（一度削除）した`coredns`のみがルート集約されていないネットワークが伝播されておりました。ここのアルゴリズムについては、引き続き調査予定です。