釜爺がk8sクラスタのお世話をしてくれるようなので試してみた

**チビども、はやくせんか！**

皆さんご存じスタジオジブリの映画である千と千尋の神隠しに登場する釜爺。
普段は薬草の調合をしたり、風呂釜の日を管理したりと油屋の縁の下の力持ちです。

そんな釜爺が[kamaji](https://kamaji.clastix.io/)となってkubernetes/k8sのクラスタの管理をしてくれるようになっています。
このkamajiについての説明とハンズオンのログを残しておこうと思います。

https://kamaji.clastix.io

【参考】
k8sについて

https://qiita.com/tadashiro_ninomiya/items/6e6fea807b2a16732b5b


# kamajiとは?
要点としては、

* 既存のk8sクラスタ上にユーザテナントのcontrol planeの機能を展開できる
  * 既存のk8sクラスタと展開したcontrol planeを含むクラスタは別物
  * control planeの機能は既存k8sクラスタ上ではk8sの1リソースとして扱うことができる
* control planeの機能は複数展開可能
  * k8sのAPIを公開してnodeとの通信を実施 
  * 展開したcontrol planeを含むクラスタのnode一覧にはcontrol planeのノードは存在しない
* nodeに相当するホストは別途用意する必要あり
  * control planeとはk8s APIを用いて通信

【kamaji公式サイトから引用】
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172922/08a9b216-9e81-ce94-4d54-94aac19c5abe.png)

# 何が嬉しいの？
* k8s上のリソースを作成するのみでcontrol planeの構築が済むため楽
  * kubeadmでクラスタを構築するケースと比較してcontrol plane用のホストを構築しなくて済む
  * 複数のクラスタを作成する際にはk8s上でのリソースを作成するのみでOK
* オンプレ/プライベートクラウド上など、managed k8sが使いにくい環境でmanaged k8sのような仕組みを提供できる選択肢の1つになりうる
  * 提供側はkamajiシステム関連のコンテナおよびそれが動作している既存k8sクラスタの管理/利用側は展開されるcontrol plane機能リソースおよびnode相当のホストの管理のように所掌分界点を設けることが可能
  * 複数の利用者間でそれぞれクラスタが割り当てられており、分離されている
    * control plane部分は共用リソースに載っているのでリソース消費量等には注意が必要

# ハンズオン
元手順

https://kamaji.clastix.io/getting-started

## 用意するもの
* k8sクラスタ: 1つ
  * 投稿者はシングルノードのk3sクラスタを利用
* node用ホスト: 1つ(仮想マシン/物理マシン問わず)

※OSはUbuntu24.04を使用

## 構成
今回の構成ではテナントクラスタのcontrol planeにあるk8s APIをMetalLB+type LoadBalancerで公開する

## 事前準備
### k8s API公開のためのMetalLBの構築
1. 以下コマンドを実行
   ```shell
   kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.13.11/config/manifests/metallb-native.yaml
   ```
   
2. 以下のconfigmapを適用
   ```shell
   cat <<EOF > metallb-config.yaml
   apiVersion: metallb.io/v1beta1
   kind: IPAddressPool
   metadata:
     name: my-ip-pool
     namespace: metallb-system
   spec:
     addresses:
     - 192.168.1.100-192.168.1.110<<使用する環境に応じて修正>>
   ---
   apiVersion: metallb.io/v1beta1
   kind: L2Advertisement
   metadata:
     name: my-l2-advertisement
     namespace: metallb-system
   spec:
     ipAddressPools:
     - my-ip-pool
   EOF
   kubectl apply -f metallb-config.yaml
   ```

### (optional)k3sで構築している人で楽をしたい人向け
k3sにはKlipperLBがバインドされているためそれを有効化することで上記を代替することが可能
k3sのインストールすることでデフォルトでKlipperLBが有効化される
ただし、この場合kamaji control planeリソースを6443/tcpで公開することはできないので注意(k3sクラスタのAPI公開ポートと重複が起こるため)

https://docs.k3s.io/quick-start#install-script

## kamajiシステムリソース作成
### helmインストール
OSおよびCPUアーキテクチャに応じて`linux`,`amd64`を適宜変更すること
```shell
$ wget https://get.helm.sh/helm-v3.16.4-linux-amd64.tar.gz
$ tar xvf helm-v3.16.4-linux-amd64.tar.gz
$ mv linux-amd64/helm /usr/local/bin/helm
```

### Cert Managerインストール
```shell
$ helm repo add jetstack https://charts.jetstack.io
$ helm repo update
$ helm install \
  cert-manager jetstack/cert-manager \
  --namespace cert-manager \
  --create-namespace \
  --version v1.11.0 \
  --set installCRDs=true
```
以下のような出力があることを確認
```shell
$ kubectl -n cert-manager get po
NAME                                       READY   STATUS    RESTARTS   AGE
cert-manager-86844d9d7-ldqdr               1/1     Running   0          2d5h
cert-manager-cainjector-6d5f558c69-7hcck   1/1     Running   0          2d5h
cert-manager-webhook-bd76f6cf9-8f72z       1/1     Running   0          2d5h
```


### Kamaji Controllerインストール

```shell
$ helm repo add clastix https://clastix.github.io/charts
$ helm repo update
$ helm install kamaji clastix/kamaji -n kamaji-system --create-namespace
```
以下のような出力があることを確認
```shell
$ kubectl -n kamaji-system get pods
NAME                         READY   STATUS      RESTARTS      AGE
kamaji-etcd-0                1/1     Running     0             50s
kamaji-etcd-1                1/1     Running     0             60s
kamaji-etcd-2                1/1     Running     0             90s
kamaji-7949578bfb-lj44p      1/1     Running     0             12s
```

## テナントクラスタ作成
### テナントコントロールプレーン作成

<details><summary>kamajiのquickstartに記載されている手順</summary>
以下の環境変数が必要

* TENANT_NAMESPACE
* TENANT_NAME
* TENANT_VERSION
* TENANT_PORT=6443
* TENANT_DOMAIN
* TENANT_SVC_CIDR
* TENANT_POD_CIDR
* TENANT_DNS_SERVICE
* TENANT_PROXY_PORT

 投稿者は以下の環境変数ファイルをセット
 ```shell
$ cat << EOF > env_kamaji-test
TENANT_NAMESPACE=kamaji-test
TENANT_NAME=kamaji-test
TENANT_VERSION=v1.29.1
TENANT_PORT=6443
TENANT_DOMAIN=kamaji.192.168.11.241.nip.io
TENANT_SVC_CIDR=10.44.0.0/16
TENANT_POD_CIDR=10.45.0.0/16
TENANT_DNS_SERVICE=10.44.0.10
TENANT_PROXY_PORT=8132
EOF
$ export $(cat env_kamaji-test | xargs) 
 ```

```shell
$ cat > ${TENANT_NAMESPACE}-${TENANT_NAME}-tcp.yaml <<EOF
apiVersion: kamaji.clastix.io/v1alpha1
kind: TenantControlPlane
metadata:
  name: ${TENANT_NAME}
  namespace: ${TENANT_NAMESPACE}
  labels:
    tenant.clastix.io: ${TENANT_NAME}
spec:
  dataStore: default
  controlPlane:
    deployment:
      replicas: 3
      additionalMetadata:
        labels:
          tenant.clastix.io: ${TENANT_NAME}
      extraArgs:
        apiServer: []
        controllerManager: []
        scheduler: []
      resources:
        apiServer:
          requests:
            cpu: 250m
            memory: 512Mi
          limits: {}
        controllerManager:
          requests:
            cpu: 125m
            memory: 256Mi
          limits: {}
        scheduler:
          requests:
            cpu: 125m
            memory: 256Mi
          limits: {}
    service:
      additionalMetadata:
        labels:
          tenant.clastix.io: ${TENANT_NAME}
      serviceType: LoadBalancer
  kubernetes:
    version: ${TENANT_VERSION}
    kubelet:
      cgroupfs: systemd
    admissionControllers:
      - ResourceQuota
      - LimitRanger
  networkProfile:
    port: ${TENANT_PORT}
    certSANs:
    - ${TENANT_NAME}.${TENANT_DOMAIN}
    serviceCidr: ${TENANT_SVC_CIDR}
    podCidr: ${TENANT_POD_CIDR}
    dnsServiceIPs:
    - ${TENANT_DNS_SERVICE}
  addons:
    coreDNS: {}
    kubeProxy: {}
    konnectivity:
      server:
        port: ${TENANT_PROXY_PORT}
        resources:
          requests:
            cpu: 100m
            memory: 128Mi
          limits: {}
EOF

$ kubectl -n ${TENANT_NAMESPACE} apply -f ${TENANT_NAMESPACE}-${TENANT_NAME}-tcp.yaml
```
</details>

投稿者は以下のymlファイルを用意

:::note warn
前述のとおりk3sでkKlipperLBを使用している場合はk3sですでに6443ポートを使用している場合があるため16443ポートなどに適宜変更してください。
kamaji公式のマニュフェストとは異なり[konectivity](https://www.creationline.com/tech-blog/cloudnative/kubernetes/46177)はブランクとしてます。
:::

```yml:kamaji-test-kamaji-test-tcp.yaml
---
apiVersion: v1
kind: Namespace
metadata:
  name: kamaji-test

---
apiVersion: kamaji.clastix.io/v1alpha1
kind: TenantControlPlane
metadata:
  name: kamaji-test
  namespace: kamaji-test
  labels:
    tenant.clastix.io: kamaji-test
spec:
  dataStore: default
  controlPlane:
    deployment:
      replicas: 3
      additionalMetadata:
        labels:
          tenant.clastix.io: kamaji-test
      extraArgs:
        apiServer: []
        controllerManager: []
        scheduler: []
      resources:
        apiServer:
          requests:
            cpu: 250m
            memory: 512Mi
          limits: {}
        controllerManager:
          requests:
            cpu: 125m
            memory: 256Mi
          limits: {}
        scheduler:
          requests:
            cpu: 125m
            memory: 256Mi
          limits: {}
    service:
      additionalMetadata:
        labels:
          tenant.clastix.io: kamaji-test
      serviceType: LoadBalancer
  kubernetes:
    version: "v1.29.12"
    kubelet:
      cgroupfs: systemd
    admissionControllers:
      - ResourceQuota
      - LimitRanger
  networkProfile:
    port: 6443
    certSANs:
    - kamaji-test.kamaji.192.168.11.241.nip.io
    serviceCidr: 10.44.0.0/16
    podCidr: 10.45.0.0/16
    dnsServiceIPs:
    - 10.44.0.10
  addons:
    coreDNS: {}
    kubeProxy: {}
    konnectivity: {}
```

以下コマンドでデプロイ
```shell
kubectl apply -f kamaji-test-kamaji-test-tcp.yaml
```

しばらく経つと以下のようなリソースが作成されているはず

:::note info
kamajiのcontrol planeリソースがv1.29.12かつ192.168.11.152:6443をエンドポイントとして作成されていることがわかる。
:::
```shell
$ kubectl -n kamaji-test get tenantcontrolplanes.kamaji.clastix.io
NAME          VERSION    STATUS   CONTROL-PLANE ENDPOINT   KUBECONFIG                     DATASTORE   AGE
kamaji-test   v1.29.12   Ready    192.168.11.152:6443      kamaji-test-admin-kubeconfig   default     96s
```

:::note info
上記のcontrol planeリソースの実体は以下のpod/service
:::
```shell
$ kubectl -n kamaji-test get all
NAME                               READY   STATUS    RESTARTS   AGE
pod/kamaji-test-549cfc69c9-2lt2n   4/4     Running   0          89s
pod/kamaji-test-549cfc69c9-q56dw   4/4     Running   0          89s
pod/kamaji-test-549cfc69c9-wvdv4   4/4     Running   0          89s

NAME                  TYPE           CLUSTER-IP      EXTERNAL-IP      PORT(S)                         AGE
service/kamaji-test   LoadBalancer   10.43.135.152   192.168.11.152   6443:32641/TCP,8132:31207/TCP   2m15s

NAME                          READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/kamaji-test   3/3     3            3           90s

NAME                                     DESIRED   CURRENT   READY   AGE
replicaset.apps/kamaji-test-549cfc69c9   3         3         3       89s
replicaset.apps/kamaji-test-89c59cf8f    0         0         0       90s
```

これでcontrol planeの作成は完了。

### テナントノード作成
:::note warn
以下の手順は中期個所を除き原則テナントノード上で実施
:::

#### containerdのインストール
いつも通りの手順
なお、投稿者はcgroupのインターフェイス設定をsystemdにしていないせいで躓いていた。

1. カーネルパラメータ周辺の設定 [k8sドキュメント](https://kubernetes.io/ja/docs/setup/production-environment/container-runtimes/#%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB%E3%81%A8%E8%A8%AD%E5%AE%9A%E3%81%AE%E5%BF%85%E9%A0%88%E8%A6%81%E4%BB%B6)
   ```shell
   cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
   overlay
   br_netfilter
   EOF

   sudo modprobe overlay
   sudo modprobe br_netfilter

   # この構成に必要なカーネルパラメーター、再起動しても値は永続します
   cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
   net.bridge.bridge-nf-call-iptables  = 1
   net.bridge.bridge-nf-call-ip6tables = 1
   net.ipv4.ip_forward                 = 1
   EOF

   # 再起動せずにカーネルパラメーターを適用
   sudo sysctl --system
   ```
2. containerd/runc/criのダウンロード
   ```shell
   $ wget https://github.com/containerd/containerd/releases/download/v1.7.24/containerd-1.7.24-linux-amd64.tar.gz
   $ wget https://github.com/opencontainers/runc/releases/download/v1.2.3/runc.amd64
   $ wget https://github.com/containernetworking/plugins/releases/download/v1.6.1/cni-plugins-linux-amd64-v1.6.1.tgz
   ```
3. バイナリ配置
   ```shell
   $ sudo tar Cxzvf /usr/local containerd-1.7.24-linux-amd64.tar.gz
   $ sudo install -m 755 runc.amd64 /usr/local/sbin/runc
   $ sudo mkdir -p /opt/cni/bin
   $ sudo tar Cxzvf /opt/cni/bin cni-plugins-linux-amd64-v1.6.1.tgz
   ```
4. systemdファイル設置
   ```shell
   $ wget https://raw.githubusercontent.com/containerd/containerd/main/containerd.service
   $ sudo mkdir -p /usr/local/lib/systemd/system
   $ sudo mv containerd.service /usr/local/lib/systemd/system
   ```
5. containerd設定ファイル設置
   ```shell
   $ sudo mkdir /etc/containerd
   $ containerd config default > config.toml
   $ sudo mv config.toml /etc/containerd/config.toml
   $ sudo vi /etc/containerd/config.toml
   ```
   :::note info
   `config.toml`内`SystemdCgroup = false`を`SystemdCgroup = true`に修正
   :::
   ```toml:修正前
           [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
            BinaryName = ""
            CriuImagePath = ""
            CriuPath = ""
            CriuWorkPath = ""
            IoGid = 0
            IoUid = 0
            NoNewKeyring = false
            NoPivotRoot = false
            Root = ""
            ShimCgroup = ""
            SystemdCgroup = false  
   ```

   ```toml:修正後
          [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
            BinaryName = ""
            CriuImagePath = ""
            CriuPath = ""
            CriuWorkPath = ""
            IoGid = 0
            IoUid = 0
            NoNewKeyring = false
            NoPivotRoot = false
            Root = ""
            ShimCgroup = ""
            SystemdCgroup = true
   ```
   
6. containerdスタート
   ```shell
   $ sudo systemctl daemon-reload
   $ sudo systemctl enable --now containerd.service
   $ sudo systemctl status containerd.service
   ```

#### kubeadm/kubectl/kubeletのインストール
[k8sの公式ドキュメント](https://kubernetes.io/ja/docs/setup/production-environment/tools/kubeadm/install-kubeadm/#kubeadm-kubelet-kubectl%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB)のとおりに実施

1. apt更新
   ```shell
   $ sudo apt-get update
   $ sudo apt-get install -y apt-transport-https ca-certificates curl gpg
   ```
2. gpg鍵追加
   ```shell
   $ curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.29/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
   ```
3. リポジトリ追加
   ```shell
   $ echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.29/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list
   ```
4. パッケージインストール
   ```shell
   $ sudo apt-get update
   $ sudo apt-get install -y kubelet kubeadm kubectl
   $ sudo apt-mark hold kubelet kubeadm kubectl
   ```

#### テナントノードのクラスタへの参加
:::note warn
以下手順のうち項番1,2のみマネジメントクラスタ上で実施
:::
1. **マネジメントクラスタ** 上で以下のコマンドを実行
   ```shell
   $ kubectl get secrets -n kamaji-test kamaji-test-admin-kubeconfig -o json \
     | jq -r '.data["admin.conf"]' \
     | base64 --decode \
     > kamaji-test-kamaji-test.kubeconfig
   ```
2. `kamaji-test-kamaji-test.kubeconfig`ファイルをテナントノードに転送
3. **テナントノード** 上で以下のコマンドを実行 
   ```shell
   $ $(echo "sudo ")$(kubeadm --kubeconfig=kamaji-test-kamaji-test.kubeconfig token create --print-join-command)
   ```
   ```shell
   $ export KUBECONFIG=kamaji-test-kamaji-test.kubeconfig
   $ kubectl get node 
   NAME                    STATUS      ROLES    AGE   VERSION
   gt01-kamaji-test-kw01   NotReady    <none>   1m    v1.29.12
   ```
   :::note info
   この時点だとCNIがデプロイされていないので`NotReady`となる
   :::
4. flannelデプロイ
   ```shell
   $ wget https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml
   $ vi kube-flannel.yml
   ```
   `kamaji-test-kamaji-test-tcp.yaml`を作成した際に`podCidr`を`10.45.0.0/16`としたのでflannelのマニュフェストも修正(84行目)

   ```yml:修正前
   net-conf.json: |
     {
       "Network": "10.244.0.0/16",
       "EnableNFTables": false,
       "Backend": {
         "Type": "vxlan"
       }
     }
   ```
   ```yml:修正後
   net-conf.json: |
     {
       "Network": "10.45.0.0/16",
       "EnableNFTables": false,
       "Backend": {
         "Type": "vxlan"
       }
     }
   ```
5. デプロイ&確認
   デプロイ
   ```shell
   $ kubectl apply -f kube-flannel.yml
   namespace/kube-flannel created
   serviceaccount/flannel created
   clusterrole.rbac.authorization.k8s.io/flannel created
   clusterrolebinding.rbac.authorization.k8s.io/flannel created
   configmap/kube-flannel-cfg created
   daemonset.apps/kube-flannel-ds created
   ```
   node確認
   ```shell
   $ kubectl get node
   NAME                    STATUS   ROLES    AGE   VERSION
   gt01-kamaji-test-kw01   Ready    <none>   17m   v1.29.12
   ```

   pod確認(kube-flannel)
   すべてRunningであることを確認   
   ```shell
   NAME                    READY   STATUS    RESTARTS   AGE
   kube-flannel-ds-pgd97   1/1     Running   0          90s
   ```

   pod確認(kube-system)
   すべてRunningであることを確認   
   ```shell
   $ kubectl -n kube-system get po
   NAME                       READY   STATUS    RESTARTS   AGE
   coredns-7f988d7799-r4fgk   1/1     Running   0          41m
   coredns-7f988d7799-zzv4g   1/1     Running   0          41m
   konnectivity-agent-jtvzz   1/1     Running   0          59s
   kube-proxy-hsbzk           1/1     Running   0          17m
   ```

## 動作確認
試しにnginx用のデプロイメントを作成
```shell
$ kubectl create deployment nginx --image nginx --replicas 3
deployment.apps/nginx created
$ kubectl get po
NAME                     READY   STATUS    RESTARTS   AGE
nginx-7854ff8877-q2h5t   1/1     Running   0          18s
nginx-7854ff8877-szzdg   1/1     Running   0          18s
nginx-7854ff8877-wf4xl   1/1     Running   0          18s
```
問題なくpodが作成された

# 終わりに
ここまでkamajiを用いたマルチテナントでのmanaged k8s環境の構築およびユーザサイドで利用するテナントクラスタの作成方法について手順を追ってみました。
粗削りではあるもののひとまず利用可能なクラスタは作成することができました。
実用に落とし込んでいく上では
* テナントクラスタのcontrol planeのk8s APIエンドポイントをingessで公開
* テナントクラスタのcontrol planeのetcdのデータ保存の永続化
* kamajiのシステムリソース上のデータ保存の永続化

についても確認/詰める必要がある部分に見えているのでその部分も追加で調査をしていきたいと思います。
ここまでお読みいただきありがとうございました。