はじめに
以前から、研究室でノードを跨いだNaaS(Notebook as a Service)を作ってみたいという野望がありました。そのためにはまず、研究室サーバーでKubernetesクラスタを構築するのが最も手堅いと言えるでしょう。
ただしNaaSの提供は、コンテナ(DockerやPodman)で研究していたユーザーを完全に移行させるものではありません。ユーザーが選択可能かつメンテナーが居なくなった場合は従来のコンテナのみの運用に戻せるように細心の注意が必要です。
本記事では、既にDockerがインストールされているノードを活用してkubeadmでオンプレミスのKubernetesクラスタを構築するためのノウハウを共有します。
- はじめに
- 1. クラスタのノード構成
- 2. コンテナランタイムのセットアップ
- 3. kubeadmによるクラスタ作成
- 4. Argo CDの導入
- 5. GPU device pluginの導入
- まとめ
1. クラスタのノード構成
元々は、余っていたRaspberry Pi 4BかIntel NUCをコントロールプレーンノードとする予定でした。しかし、過去のおうちKubernetesの経験から、クラスタを安定稼働させるには高性能なコントロールプレーンノードが必要不可欠だと感じていました。
研究室の先生にダメ元で購入をお願いしたところ、既に素晴らしい性能のCPUラックサーバーを注文されていたとのことで、ありがたくコントロールプレーンノードとして利用させていただくことになりました。
最終的には以下のようなノード構成となりました。
ホスト名 | 役割 | OS | CPU | メモリ | GPU |
---|---|---|---|---|---|
polaris |
コントロールプレーンノード | Ubuntu 24.04 | AMD EPYC 7543P (32C/64T, 2.8GHz) |
128GB | N/A |
aries |
ワーカーノード① | Ubuntu 24.04 | AMD EPYC 7542 (32C/64T, 2.9GHz) |
256GB | NVIDIA Quadro RTX 6000 × 4 |
taurus |
ワーカーノード② | Ubuntu 24.04 | Intel Core i9-10940X (14C/28T, 3.3GHz) |
64GB | NVIDIA RTX 6000 Ada Generation × 3 |
gemini |
ワーカーノード③ | Ubuntu 24.04 | Intel Xeon Gold 5218R (20C/40T, 2.1GHz) |
192GB | NVIDIA RTX A6000 × 5 |
先生のおかげで、非常に恵まれた環境でKubernetesクラスタを構築できることになりました。
2. コンテナランタイムのセットアップ
コンテナランタイムの候補としてcontainerdとCRI-Oの間でかなり悩みましたが、以下の2つの理由からcontainerdを採用しました。
1つ目は、全てのワーカーノードにDockerがインストールされており、containerdも一緒にインストールされていることです。Kubernetesクラスタのアップグレードに合わせてDocker Engineのアップグレードが必要になる可能性がありますが、Docker Engineはメンテナンスで年に数回アップグレードしているため、そこまで大きな問題にはならないと考えました。
2つ目は、CRI-OとPodmanでconmonが競合する可能性があることです。今年からデフォルトでrootlessなコンテナエンジンであるPodmanの利用を推奨しており、全てのワーカーノードにインストールされています。コンテナ監視ツールであるconmonはCRI-OとPodmanの両方で使用されているため、バージョン統一が大変になると考えました。
ワーカーノードでcontainerdのバージョンを確認します。
$ containerd -v containerd containerd.io 1.7.25 bcc810d6b9066471b0b6fa75f557a15a1cbf31bb
基本的にはDocker Engineのアップグレード時点で最新バージョンのcontainerdをインストールしているため、執筆時点で最新のKubernetes v1.32もサポートしているcontainerdのバージョンでした(参考)。
それから注意点として公式ドキュメントにも記載がありますが、今回はパッケージからcontainerdをインストールした場合に相当するので、設定ファイルである/etc/containerd/config.toml
に変更が必要でした。デフォルトではdisabled_plugins = ["cri"]
とCRIが無効化されているので、Kubernetesからcontainerdを利用するためにはこれを有効化する必要があります。公式ドキュメントで紹介されていたように以下のコマンドで設定をリセットしてcontainerdを再起動しました。
$ containerd config default > /etc/containerd/config.toml $ sudo systemctl restart containerd
ここで、containerdを再起動する必要があったため、ワーカーノードとしては偶然ユーザーが誰も利用していなかった1台のノードのみを利用することにしました。残りのノードはメンテナンスを周知してからクラスタに参加させる予定です。
3. kubeadmによるクラスタ作成
Kubernetesのバージョンは最新のv1.32を採用しました。
基本的には以下の公式ドキュメントの通りセットアップを行いました。
ただし、手順に沿ってkubeadm init
コマンドとkubeadm join
コマンドを実行しても、コントロールプレーンノードがNotReady
という状態のままでした。
root@polaris:~# kubectl get nodes NAME STATUS ROLES AGE VERSION polaris NotReady control-plane 7m27s v1.32.3 taurus Ready <none> 4m1s v1.32.3
公式ドキュメントにも
You must deploy a Container Network Interface (CNI) based Pod network add-on so that your Pods can communicate with each other. Cluster DNS (CoreDNS) will not start up before a network is installed.
と記載がありますが、以下のようなコマンドでもノードの状態がなぜNotReady
なのかを確認することができます。
root@polaris:~# kubectl describe node polaris Name: polaris Roles: control-plane # 省略 Conditions: Type Status LastHeartbeatTime LastTransitionTime Reason Message ---- ------ ----------------- ------------------ ------ ------- MemoryPressure False Mon, 07 Apr 2025 00:52:13 +0900 Mon, 07 Apr 2025 00:47:04 +0900 KubeletHasSufficientMemory kubelet has sufficient memory available DiskPressure False Mon, 07 Apr 2025 00:52:13 +0900 Mon, 07 Apr 2025 00:47:04 +0900 KubeletHasNoDiskPressure kubelet has no disk pressure PIDPressure False Mon, 07 Apr 2025 00:52:13 +0900 Mon, 07 Apr 2025 00:47:04 +0900 KubeletHasSufficientPID kubelet has sufficient PID available Ready False Mon, 07 Apr 2025 00:52:13 +0900 Mon, 07 Apr 2025 00:47:04 +0900 KubeletNotReady container runtime network not ready: NetworkReady=false reason:NetworkPluginNotReady message:Network plugin returns error: cni plugin not initialized # 省略
原因はCNIプラグインがインストールされていなかったことでした。
CNIプラグインとしては、公式ドキュメントで紹介されている中でもCalicoやCilium、Flannelなどが有名ですが、今回はCiliumを採用しました。Ciliumの機能については別の記事にまとめようと思います。
Ciliumは上記の公式ドキュメントを参考にHelmでデプロイしました。
ただし、今後Istioを導入する場合は公式ドキュメントに従って--set socketLB.hostNamespaceOnly=true --set cni.exclusive=false
を指定する必要があるので注意が必要です。
これによって、無事に全てのノードがReady
状態となりました。
root@polaris:~# kubectl get nodes NAME STATUS ROLES AGE VERSION polaris Ready control-plane 21m v1.32.3 taurus Ready <none> 18m v1.32.3
4. Argo CDの導入
Kubernetesにおけるデプロイ方法のデファクトスタンダードであるGitOpsを実現するためのツールとして、Argo CDを導入します。
上記の公式ドキュメントに従ってArgo CDのインストールから始めました。
ステップ3でArgo CDのAPIサーバーを公開するためには、Service Type Load Balancer / Ingress / Port Forwardingの中から1つを選択しなければいけません。MetalLBはインストールしておらず、デバッグ用途のkubectl port-forward
も不適切であることから、消去法でIngressを選択しました。Ingressの設定にもたくさんの選択肢がありますが、今回はIstioを採用しました。
事前準備として以下の2ステップが必要でした。
- Istioのインストール:Istio / Install with Helm
- Ingress Gatewayのインストール:Istio / Installing Gateways
2.では「Kubernetes YAML」でインストールしましたが、LoadBalancer
ではなくNodePort
を利用したかったので以下のように変更してapplyしました。
apiVersion: v1 kind: Service metadata: name: istio-ingressgateway namespace: istio-ingress spec: - type: LoadBalancer + type: NodePort selector: istio: ingressgateway ports: - port: 80 name: http + targetPort: 8080 + nodePort: 30080 - - port: 443 - name: https --- # 省略
[root@polaris argocd ]# kubectl apply -f ingress.yaml # 省略 [root@polaris argocd ]# kubectl get all -n istio-ingress NAME READY STATUS RESTARTS AGE pod/istio-ingressgateway-5b6548c6d6-wvt7d 1/1 Running 0 6s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/istio-ingressgateway NodePort 10.111.154.125 <none> 80:30080/TCP 6s NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/istio-ingressgateway 1/1 1 1 6s NAME DESIRED CURRENT READY AGE replicaset.apps/istio-ingressgateway-5b6548c6d6 1 1 1 6s
残りは下記の公式ドキュメントに従ってIstioの設定を行います。ただし、今回はHTTPSは使用しないので、設定手順の中のTLSの設定はカットしました。
ブラウザでhttp://{{ IP }}/argocd
にアクセスするとWeb UIにアクセスできます。
ステップ4でadmin
ユーザーの初期パスワードを取得してログインしてPrivate Repositoriesの設定を行う予定でしたが、ログイン後のリダイレクトURLが/argocd/argocd/applications
(正しくは/argocd/applications
)になってしまいました。調査したところ、既にIssueでバグとして報告されており、執筆時点でv2.13では修正が完了したもののv2.14では未完了でした。
今回利用していたのはv2.14.9であったため、バージョンを下げることにしました。
# kustomization.yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: # 以下を変更 - https://raw.githubusercontent.com/argoproj/argo-cd/v2.13.6/manifests/install.yaml patches: - path: ./patch.yaml
この修正で無事に/argocd/applications
にリダイレクトされるようになりました。
さてPrivate Repositoriesの設定ですが、PATやSSH Private Key Credentialなどの属人化した設定をしたくなかったので、公式ドキュメントに従ってGitHub App Credentialの設定を行いました。
最後にWeb UI上でアプリケーションの作成を行い、Getting StartedのサンプルマニフェストをGitHubリポジトリにpushすることによって動作確認を行いました。
Getting Startedの内容はここまででほとんどカバーできました。
公式ドキュメントで紹介されていたApp of Apps Patternは既に導入しましたが、今後はSSOやSync Optionsなども設定する予定です。
5. GPU device pluginの導入
主にコンテナからGPUを利用するため、NVIDIA公式のdevice pluginを導入します。
READMEに記載されている必須要件の中のnvidia-docker >= 2.0 || nvidia-container-toolkit >= 1.7.0
は、ワーカーノードが既に要件を満たしていました。
root@taurus:/home/azuma# nvidia-container-toolkit --version NVIDIA Container Runtime Hook version 1.17.4 commit: 9b69590c7428470a72f2ae05f826412976af1395
一方で、必須要件のうち「nvidia-container-runtimeがデフォルトの低レベルランタイムとして設定されている」については、公式ドキュメントに従ってnvidia-ctk
コマンドで設定した上で以下のような変更を/etc/containerd/config.toml
に加える必要がありました。
# 省略 [plugins."io.containerd.grpc.v1.cri".containerd] - default_runtime_name = "runc" + default_runtime_name = "nvidia" # 省略
containerdをrestartしたら、いよいよDaemonSetをデプロイするだけでGPUを有効化できます。staticなYAMLファイルも提供されていましたが、今回は本番環境で推奨されていたHelmを利用しました*1。
[root@polaris ~]# helm upgrade -i nvdp nvdp/nvidia-device-plugin \ --version=0.17.1 \ --namespace nvidia-device-plugin \ --create-namespace \ --set gfd.enabled=true # 省略 [root@polaris ~ ]# kubectl get daemonset -n nvidia-device-plugin NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE nvdp-node-feature-discovery-worker 1 1 1 1 1 <none> 5m17s nvdp-nvidia-device-plugin 1 1 1 1 1 <none> 5m17s nvdp-nvidia-device-plugin-gpu-feature-discovery 1 1 1 1 1 <none> 5m17s nvdp-nvidia-device-plugin-mps-control-daemon 0 0 0 0 0 nvidia.com/mps.capable=true 5m17s [root@polaris ~ ]# kubectl describe node taurus # 省略 Capacity: # 省略 nvidia.com/gpu: 3 Allocatable: # 省略 nvidia.com/gpu: 3 # 省略
このHelmの設定もGitOpsとして宣言的に管理するため、GitHubリポジトリに追加しました(参考)。
最後に、READMEで紹介されている、GPUを利用するサンプルジョブをデプロイして成功すれば完成です。
Time-slicingやMPSのようなshared GPUのための設定も必要になりそうですが、NaaS構築と同じタイミングでニーズに合わせて設定する予定です。
まとめ
本記事では、Dockerがインストールされたノードを活用してkubeadmでオンプレミスのKubernetesクラスタを構築する方法を紹介しました。つまずきポイントがいくつかあったので、オンプレミスクラスタを構築してみたい方にとって少しでも参考になれば幸いです。
Kubernetes周りでデファクトスタンダードなツールは他にもたくさんあるのですが、のめり込んでしまうとキリがないので、まずはNaaS構築に向けて着実にマイルストーンを達成してこうと思います。
*1:READMEに記載されていた最も基本的なインストールコマンドであるhelm upgrade -i nvdp nvdp/nvidia-device-plugin --namespace nvidia-device-plugin --create-namespace --version 0.17.1は期待通りにデプロイされませんでした。原因はデフォルトで無効化されているgpu-feature-discoveryが、自動的に生成するラベルがDaemonSetのnodeAffinityとして設定されているためでした。GFDを有効にするか、手動でノードにnvidia.com/gpu.present=trueラベルを付与しましょう。