「kwok」でKubernetesクラスターをシュミレーションしてみよう

2024年9月19日(木)
Daiki Hayakawa
第7回の今回は、大規模なKubernetesクラスターの検証を容易にする「kwok」について紹介します。

はじめに

3-shakeのSreake事業部に所属する早川(@bells17)です。第7回目の今回は、大規模なKubernetesクラスターの検証を容易にする「kwok」について紹介します。

kwokとは“Kubernetes WithOut Kubelet”の略で、実際のKubernetes Nodeではなく仮想的なNodeやPodを使用することで、1,000台以上のNodeのあるKubernetesクラスターを手元環境で手軽にセットアップして検証を行えるツールキットです。

つまり、kwokを使用することで実際のNodeを用意することなく大規模なKubernetesクラスターの検証を部分的に行えるようになります。

kwokを試してみる

「実際のNodeを使用しない」と言われてもピンとこないと思うので、まずは試してみましょう。

kwokは「kwok」と「kwokctl」の2つのコマンドラインツールによって構成されているので、はじめにインストールを行います(kwokコマンドが仮想的なNodeやPodが動作してるかのように振る舞うためのアプリケーション本体、kwokctlがkwokを便利に使うためのコマンドラインツールとなっています)。

Installationページを見ると、下記の3つの方法でインストールできます。

  • Package Manager
  • go installコマンド
  • バイナリのダウンロード

ここでは、go installコマンド使ってインストールを行います。今回はv0.6.0を使用するため、下記のコマンドでインストールしてください(事前にGoがインストールされている必要があります)。

$ go install sigs.k8s.io/kwok/cmd/{kwok,kwokctl}@v0.6.0

インストールが完了したら、続けて下記のコマンドを実行してクラスターと3台のNodeを構築します。

$ kwokctl create cluster
$ kwokctl scale node --replicas=3

kwokctlには対象クラスター向けのkubectlやetcdctlなども含まれるので、今回はkwokctl経由でkubectlを使って行きましょう。

上記のコマンドを実行後kwokctl kubectl get nodeコマンドを実行すると、下記のように3台のNodeが作成されていることを確認できます。

$ kwokctl kubectl get node
NAME          STATUS   ROLES   AGE   VERSION
node-000000   Ready    agent   7s    kwok-v0.6.0
node-000001   Ready    agent   7s    kwok-v0.6.0
node-000002   Ready    agent   7s    kwok-v0.6.0

Manage nodes and pods with kwokページのCreate a Nodeにあるように、Nodeを手動で作成することもできます。

実際に下記のようにして作成してみましょう。

$ kwokctl kubectl apply -f [<https://raw.githubusercontent.com/kubernetes-sigs/kwok/v0.6.0/site/static/examples/node.yaml>](<https://github.com/kubernetes-sigs/kwok/blob/v0.6.0/site/static/examples/node.yaml>)

コマンド実行後にkwokctl kubectl get nodeコマンドを実行してみると、下記のように手動で作成したkwok-node-0が作成されていることを確認できます。

$ kwokctl kubectl get node
NAME          STATUS   ROLES   AGE     VERSION
kwok-node-0   Ready    agent   1m57s   kwok-v0.6.0
node-000000   Ready    agent   5m      kwok-v0.6.0
node-000001   Ready    agent   5m      kwok-v0.6.0
node-000002   Ready    agent   5m      kwok-v0.6.0

それでは、引き続き下記のコマンドでPodを3個作成してみます。

$ kwokctl scale pod --replicas=3

kwokctl kubectl get podコマンドを実行してみると、すぐにPodがRunningになっていることを確認できます。

$ kwokctl kubectl get pod
NAME         READY   STATUS    RESTARTS   AGE
pod-000000   1/1     Running   0          4s
pod-000001   1/1     Running   0          4s
pod-000002   1/1     Running   0          4s

このように、簡単にNodeを作成し、作成したNodeでPodを動かしているのが確認できたかと思います。ちなみにkwokctl scale podコマンドで生成されるPodが下記のようなmanifestsになります(不要そうなフィールドは除去しています)。

apiVersion: v1
kind: Pod
metadata:
  labels:
    kwok.x-k8s.io/kwokctl-scale: pod
  name: pod-000000
  namespace: default
spec:
  containers:
  - image: busybox
    imagePullPolicy: Always
    name: container-0
    volumeMounts:
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      name: kube-api-access-742pf
      readOnly: true
  tolerations:
  - effect: NoExecute
    key: node.kubernetes.io/not-ready
    operator: Exists
    tolerationSeconds: 300
  - effect: NoExecute
    key: node.kubernetes.io/unreachable
    operator: Exists
    tolerationSeconds: 300
  volumes:
  - name: kube-api-access-742pf
    projected:
      defaultMode: 420
      sources:
      - serviceAccountToken:
          expirationSeconds: 3607
          path: token
      - configMap:
          items:
          - key: ca.crt
            path: ca.crt
          name: kube-root-ca.crt
      - downwardAPI:
          items:
          - fieldRef:
              apiVersion: v1
              fieldPath: metadata.namespace
            path: namespace

kwokで多くのリソースを作ってみる

それでは、実際に大量のNodeやPodを作成できるのか試してみます。まずは下記のコマンドを実行してNode数を増やしてみましょう。

$ kwokctl scale node --replicas=20

kubectl get nodeコマンドを実行すると、node-000xxのNodeが20台に増えていることを確認できます。

$ kwokctl kubectl get node --no-headers=true  | wc -l
      21
$ kwokctl kubectl get node
NAME          STATUS   ROLES   AGE   VERSION
kwok-node-0   Ready    agent   7m    kwok-v0.6.0
node-000000   Ready    agent   10m   kwok-v0.6.0
node-000001   Ready    agent   10m   kwok-v0.6.0
node-000002   Ready    agent   10m   kwok-v0.6.0
node-000003   Ready    agent   7s    kwok-v0.6.0
node-000004   Ready    agent   7s    kwok-v0.6.0
node-000005   Ready    agent   7s    kwok-v0.6.0
node-000006   Ready    agent   7s    kwok-v0.6.0
node-000007   Ready    agent   7s    kwok-v0.6.0
node-000008   Ready    agent   7s    kwok-v0.6.0
node-000009   Ready    agent   7s    kwok-v0.6.0
node-000010   Ready    agent   7s    kwok-v0.6.0
node-000011   Ready    agent   7s    kwok-v0.6.0
node-000012   Ready    agent   7s    kwok-v0.6.0
node-000013   Ready    agent   7s    kwok-v0.6.0
node-000014   Ready    agent   7s    kwok-v0.6.0
node-000015   Ready    agent   7s    kwok-v0.6.0
node-000016   Ready    agent   7s    kwok-v0.6.0
node-000017   Ready    agent   7s    kwok-v0.6.0
node-000018   Ready    agent   7s    kwok-v0.6.0
node-000019   Ready    agent   7s    kwok-v0.6.0

次に、下記のコマンドでPodもスケールしてみましょう。

$ kwokctl scale pod --replicas=20

続けてkubectl get podコマンドを実行すると、Podが20台起動していることを確認できます。

$ kubectl get node --no-headers=true  | wc -l
      20
$ kubectl get pod
NAME         READY   STATUS    RESTARTS   AGE
pod-000000   1/1     Running   0          2m54s
pod-000001   1/1     Running   0          2m54s
pod-000002   1/1     Running   0          2m54s
pod-000003   1/1     Running   0          19s
pod-000004   1/1     Running   0          19s
pod-000005   1/1     Running   0          19s
pod-000006   1/1     Running   0          19s
pod-000007   1/1     Running   0          19s
pod-000008   1/1     Running   0          19s
pod-000009   1/1     Running   0          19s
pod-000010   1/1     Running   0          19s
pod-000011   1/1     Running   0          19s
pod-000012   1/1     Running   0          19s
pod-000013   1/1     Running   0          19s
pod-000014   1/1     Running   0          19s
pod-000015   1/1     Running   0          19s
pod-000016   1/1     Running   0          19s
pod-000017   1/1     Running   0          19s
pod-000018   1/1     Running   0          19s
pod-000019   1/1     Running   0          19s

先ほどは簡単に確認するためにNodeもPodも20台にスケールしましたが、手元の環境でも簡単に1000台程度にスケールさせることができました。

# Node
$ kwokctl scale node --replicas=1000
No resource found, use default resource                                                   resource=node cluster=kwok
Load resources                          counter=980 elapsed=4.2s resource=nodes replicas=1000 name=node cluster=kwok
$ kubectl get node --no-headers=true  | wc -l
    1001

# Pod
$ kwokctl scale pod --replicas=1000
No resource found, use default resource                                                                 resource=pod cluster=kwok
Load resources                       counter=980 elapsed=2.9s namespace=default resource=pods replicas=1000 name=pod cluster=kwok
$ kubectl get pod --no-headers=true  | wc -l
    1000

Nodeについては少し待つだけで1,000台に増やすことができましたが、PodについてはDeploymentのreplicas変更を受けて徐々にスケールするようでした。

クラスターのクリーンアップ

これで基本的な動作が確認できたので、最後に作成したクラスターを削除してみます。削除するにはkwokctl delete clusterコマンドを実行するだけです。実行すると、下記のようにクラスターが削除されたことを確認できます。

$ kwokctl delete cluster
Cluster is stopping                                                                                           cluster=kwok
Cluster is stopped																					  elapsed=0.9s cluster=kwok
Cluster is deleting                                                                                                  cluster=kwok
Cluster is deleted

kwokのアーキテクチャ

このように、NodeやPodを仮想的に動作させる仕組みを持つkwokは、指定されたラベルやアノテーションが設定されたNodeを仮想的なNodeとして扱います。そして、仮想的なNodeにスケジュールされたPodに対し、kwokが管理するカスタムリソースであるStageリソースの条件をもとにPodのステータスをreadyに変更することで、Podが起動している状態をシミュレートする実装みになっているようでした。

また、その他Stageリソースを使ってNodeのステータス管理を行ったり、仮想的なNodeのNode Leaseを更新することでKubernetesから見て正常に動作しているNodeであると認識させるといったことも行っているようです。

Stageリソースは、例えば下記のようになっています。

apiVersion: kwok.x-k8s.io/v1alpha1
kind: Stage
metadata:
  name: node-initialize
spec:
  resourceRef:
    apiGroup: v1
    kind: Node
  selector:
    matchExpressions:
    - key: '.status.conditions.[] | select( .type == "Ready" ) | .status'
      operator: 'NotIn'
      values:
      - 'True'
  next:
    statusTemplate: |
      {{ $now := Now }}
      {{ $lastTransitionTime := or .metadata.creationTimestamp $now }}
      conditions:
      {{ range NodeConditions }}
      - lastHeartbeatTime: {{ $now | Quote }}
        lastTransitionTime: {{ $lastTransitionTime | Quote }}
        message: {{ .message | Quote }}
        reason: {{ .reason | Quote }}
        status: {{ .status | Quote }}
        type: {{ .type  | Quote}}
      {{ end }}

      addresses:
      {{ with .status.addresses }}
      {{ YAML . 1 }}
      {{ else }}
      {{ with NodeIP }}
      - address: {{ . | Quote }}
        type: InternalIP
      {{ end }}
      {{ with NodeName }}
      - address: {{ . | Quote }}
        type: Hostname
      {{ end }}
      {{ end }}

      {{ with NodePort }}
      daemonEndpoints:
        kubeletEndpoint:
          Port: {{ . }}
      {{ end }}

      allocatable:
      {{ with .status.allocatable }}
      {{ YAML . 1 }}
      {{ else }}
        cpu: 1k
        memory: 1Ti
        pods: 1M
      {{ end }}
      capacity:
      {{ with .status.capacity }}
      {{ YAML . 1 }}
      {{ else }}
        cpu: 1k
        memory: 1Ti
        pods: 1M
      {{ end }}

      {{ $nodeInfo := .status.nodeInfo }}
      {{ $kwokVersion := printf "kwok-%s" Version }}
      nodeInfo:
        architecture: {{ or $nodeInfo.architecture "amd64" }}
        bootID: {{ or $nodeInfo.bootID `""` }}
        containerRuntimeVersion: {{ or $nodeInfo.containerRuntimeVersion $kwokVersion }}
        kernelVersion: {{ or $nodeInfo.kernelVersion $kwokVersion }}
        kubeProxyVersion: {{ or $nodeInfo.kubeProxyVersion $kwokVersion }}
        kubeletVersion: {{ or $nodeInfo.kubeletVersion $kwokVersion }}
        machineID: {{ or $nodeInfo.machineID `""` }}
        operatingSystem: {{ or $nodeInfo.operatingSystem "linux" }}
        osImage: {{ or $nodeInfo.osImage `""` }}
        systemUUID: {{ or $nodeInfo.systemUUID `""` }}
      phase: Running

このリソースで、kwokに下記のような指示を出すことにより対象リソースを更新しています。

  • どのリソースが(.spec.resourceRef)
  • どの条件の際に(.spec.selector)
  • どのようなステータスに設定されるのか?(.spec.next.statusTemplate)

これにより、NodeやPodの状態をコントロールして仮想的なNodeやPodとして動作するような状態を構築しています。今回のようにkwokctlを使用して環境を構築した際は実際にはStageリソースは作られず、組み込みのStageリソースが使用されます。

一方、kwokはDeploy kwok in a Clusterページにあるように、既存のクラスターに対してデプロイすることも可能です。

Stageリソースは、こういったケースで使用されるようになっているようです。

kwokの利用用途

kwokは元々はfake-kubeletとfake-k8sというリポジトリが元となっており、現在はKubernetesコミュニティのsig-schedulingのサブプロジェクトとして開発されているようです。

IntroductionページのUser Storiesを見ると、下記のような主にパフォーマンスに対するテストを中心としたユースケースが想定されているようです。

  • 多数のNodeとPodを使用したkube-schedulerのテスト
  • CRD Controllerの開発
  • control planeのパフォーマンステスト

Adoptersページを見ると実際にkwokを使用したテストの例を見ることができますが、特に下記からパフォーマンステストやスケーラビリティのテストに使用されていることが伺えます。

また、Kubernetesにおけるe2eテストツールでの利用がサポートされていたり、kube-scheduler-simulatorというKubernetesのスケジューラーの動作をシュミレートするツールの内部でkwokが利用されているようです。

おわりに

今回は仮想的なNodeとPodを使用して、大量のNodeやPodを起動した際のシュミレートを行い、パフォーマンスやスケーラビリティのテストなどを簡単に行えるkwokについて紹介しました。

ソースコードを見ると「kubeletなしでNodeやPodをどのようにしてreadyな状態で管理するのか」といったKubernetesの仕組みに対する理解にも繋がり非常に面白いツールだと感じたので、ぜひみなさんも利用してみてください。

株式会社スリーシェイク Sreake事業部
Webサービスの開発やクラウドインフラの構築・運用、マネージドKubernetesサービスの開発を経てスリーシェイクにJoin。顧客システムへのKubernetes導入支援を中心に、インフラ構築・設計の支援を行っています。著書に「Kubeletから読み解くKubernetesのコンテナ管理の裏側」「Kube API Server ~Kubernetes API Serverの内部実装を見てみよう~」など。
---
スリーシェイクは、ITインフラ領域の技術力に強みをもつテクノロジーカンパニーです。SREコンサルティング事業「Sreake」では、AWS/Google Cloud/Kubernetesに精通したプロフェッショナルが技術戦略から設計・開発・運用を一貫してサポートしています。また、ノーコード型ETLツール「Reckoner」、フリーランスエンジニア特化型人材紹介サービス「Relance」、セキュリティサービス「Securify」を提供しています。
会社HP: https://3-shake.com/

連載バックナンバー

仮想化/コンテナ技術解説
第7回

「kwok」でKubernetesクラスターをシュミレーションしてみよう

2024/9/19
第7回の今回は、大規模なKubernetesクラスターの検証を容易にする「kwok」について紹介します。
仮想化/コンテナ技術解説
第6回

「kind」でローカル環境にKubernetesクラスターを構築する

2024/8/28
第6回の今回は、ローカル環境で柔軟なKubernetesクラスターを構築できる「kind」について紹介します。
仮想化/コンテナ技術解説
第5回

「Robusta」でKubernetesクラスタの監視と管理自動化を行う

2024/5/23
第5回の今回は、Kubernetesクラスタの監視と管理自動化を行える「Robusta」について紹介します。

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています