😺

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

に公開

チビども、はやくせんか!

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

そんな釜爺がkamajiとなって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

何が嬉しいの?

  • 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. 以下コマンドを実行

    kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.13.11/config/manifests/metallb-native.yaml
    
  2. 以下のconfigmapを適用

    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を適宜変更すること

$ 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インストール

$ 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

以下のような出力があることを確認

$ 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インストール

$ helm repo add clastix https://clastix.github.io/charts
$ helm repo update
$ helm install kamaji clastix/kamaji -n kamaji-system --create-namespace

以下のような出力があることを確認

$ 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

投稿者は以下の環境変数ファイルをセット

$ 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) 
$ 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はブランクとしてます。
:::

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: {}

以下コマンドでデプロイ

kubectl apply -f kamaji-test-kamaji-test-tcp.yaml

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

:::note info
kamajiのcontrol planeリソースがv1.29.12かつ192.168.11.152:6443をエンドポイントとして作成されていることがわかる。
:::

$ 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
:::

$ 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ドキュメント

    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のダウンロード

    $ 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. バイナリ配置

    $ 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ファイル設置

    $ 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設定ファイル設置

    $ 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.tomlSystemdCgroup = falseSystemdCgroup = trueに修正
    :::

    修正前
            [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  
    
    修正後
           [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スタート

    $ sudo systemctl daemon-reload
    $ sudo systemctl enable --now containerd.service
    $ sudo systemctl status containerd.service
    

kubeadm/kubectl/kubeletのインストール

k8sの公式ドキュメントのとおりに実施

  1. apt更新
    $ sudo apt-get update
    $ sudo apt-get install -y apt-transport-https ca-certificates curl gpg
    
  2. gpg鍵追加
    $ 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. リポジトリ追加
    $ 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. パッケージインストール
    $ sudo apt-get update
    $ sudo apt-get install -y kubelet kubeadm kubectl
    $ sudo apt-mark hold kubelet kubeadm kubectl
    

テナントノードのクラスタへの参加

:::note warn
以下手順のうち項番1,2のみマネジメントクラスタ上で実施
:::

  1. マネジメントクラスタ 上で以下のコマンドを実行

    $ 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. テナントノード 上で以下のコマンドを実行

    $ $(echo "sudo ")$(kubeadm --kubeconfig=kamaji-test-kamaji-test.kubeconfig token create --print-join-command)
    
    $ 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デプロイ

    $ wget https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml
    $ vi kube-flannel.yml
    

    kamaji-test-kamaji-test-tcp.yamlを作成した際にpodCidr10.45.0.0/16としたのでflannelのマニュフェストも修正(84行目)

    修正前
    net-conf.json: |
      {
        "Network": "10.244.0.0/16",
        "EnableNFTables": false,
        "Backend": {
          "Type": "vxlan"
        }
      }
    
    修正後
    net-conf.json: |
      {
        "Network": "10.45.0.0/16",
        "EnableNFTables": false,
        "Backend": {
          "Type": "vxlan"
        }
      }
    
  5. デプロイ&確認
    デプロイ

    $ 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確認

    $ kubectl get node
    NAME                    STATUS   ROLES    AGE   VERSION
    gt01-kamaji-test-kw01   Ready    <none>   17m   v1.29.12
    

    pod確認(kube-flannel)
    すべてRunningであることを確認

    NAME                    READY   STATUS    RESTARTS   AGE
    kube-flannel-ds-pgd97   1/1     Running   0          90s
    

    pod確認(kube-system)
    すべてRunningであることを確認

    $ 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用のデプロイメントを作成

$ 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のシステムリソース上のデータ保存の永続化

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

Discussion