はじめに
この記事は Uzabase Advent Calendar 2025 の 14 日目の記事です。
背景
今の世の中、何を開発するにしても AI が切っても切り離せない時代になってきています。
弊社でも AI 利用は活発に進んでおり、サービス開発や開発体験の向上に GCP の Vertex AI を積極的に利用しています。
しかし、もちろんこれらのサービスは無料ではありません。積極的に利用を進めていたところ、コストがとてつもなく膨らんでしまうという課題がありました。
それでも AI の利用自体は継続的に推進していきたいと考えていたため、「オンプレ環境でも AI の開発ができるようにしよう」という方針になりました。
本記事では、この方針のもとでオンプレミスの Kubernetes クラスタに GPU ノードを追加し、GPU Operator を用いて GPU 利用環境とメトリクス収集基盤を構築した際の考え方と具体的な手順をまとめます。
前提
NVIDIA の GPU サーバが導入されている前提です。
GPU の使い方のパターン
弊社ではいくつかの Kubernetes クラスタを管理しているので、そのクラスタに GPU 利用専用のノードを追加します。
まずは「GPU をどのように利用するか」を決める必要があります。
GPU を Kubernetes 上でどのように割り当てるかは、概ね次の 4 パターンがあります。
-
MIG(Multi-Instance GPU)
- 1 つの物理 GPU を最大7つのインスタンスに分割して利用する方式
- インスタンス間のリソース分離性が高く、ワークロードごとに安定した性能を確保しやすい一方で、対応している GPU や構成パターンが限定される
-
Time-slicing
- 1 GPU を時間でスライスして 複数の GPU インスタンスがあるように見せる方式
- スライス幅やスケジューリングのチューニングが必要
-
MPS (Multi-Process Service)
- 1 GPU を複数プロセスで共有する方式
- 利用率は上げやすいが、アプリ側の対応や検証コストがそれなりに重い
-
個別使用(1 Pod ごとに 1 GPU)
- 1 Pod に対して 1 GPU を素直に割り当てる方式
- 利用効率は落ちるが、運用とトラブルシュートが圧倒的にシンプル
今回選んだやり方
「まずはシンプルに稼働させてフィードバックを得たい」というフェーズだったので、個別使用(1 Pod ごとに 1 GPU) の方針で進めることにしました。
最初から MPS や time-slicing のような複雑性が高い方式を採用してしまうと、デバッグポイントが増えます。
そのため、まずは単純な 1:1 割り当てから始め、ニーズが見えてきた段階で高度な共有戦略に移行するのが妥当だと考えています。
MPS や Time-slicing については「次のフェーズで検証する」と割り切り、今回はスコープから外しました。
また、MIG に関しては、そもそも利用しているサーバーが対応していませんでした。
kubespray vs GPU Operator
GPU ノードを追加する方法としては、kubespray の機能をそのまま利用する方法と、GPU Operator を導入する方法の 2 つが候補に挙がりました。
まず kubespray を用いた構築方法では、GPU ノードの追加を前提としたオプションがあらかじめ用意されています。
https://github.com/kubernetes-sigs/kubespray/blob/master/inventory/sample/group_vars/k8s_cluster/k8s-cluster.yml#L299-L313
このオプションを有効にすることで、対象ノードに対して NVIDIA ドライバや device-plugin までは自動的にインストールされます。
一方で、nvidia-container-toolkit や DCGM Exporter など、モニタリングや実行環境まわりのコンポーネントについては、別途手動でセットアップする必要があります。
これに対して GPU Operator を用いる構築方法では、Operator が関連コンポーネントをまとめて管理してくれます。インストール手順自体はシンプルでありつつ、ドライバや device-plugin に加えて、toolkit や dcgm-exporter までを含めた GPU 環境一式を自動で展開できます。
両者を比較した結果、既にクラスタ自体は kubespray で構築しているものの、GPU 周りについては新たに GPU Operator というモジュールを導入した方が、運用と再現性の観点でメリットが大きいと判断しました。
GPU Operator 導入の流れ
細かいオプションや記述方法は環境に依存しますが、弊社の環境では以下のような Helm リリースを適用するだけで GPU 環境のセットアップが完了しました。
repositories:
- name: nvidia
url: https://nvidia.github.io/gpu-operator
releases:
- name: gpu-operator
namespace: gpu-operator
chart: nvidia/gpu-operator
version: v25.10.0
values:
- values.yaml
dcgmExporter:
env:
- name: DCGM_EXPORTER_LISTEN
value: "0.0.0.0:9400"
Pod から GPU を利用する
Pod の設定は、リソース要求の設定に nvidia.com/gpu を追加するだけです。
apiVersion: v1
kind: Pod
metadata:
name: gpu-sample
spec:
nodeSelector:
nvidia.com/gpu-node: "true"
containers:
- name: app
image: your-registry/your-gpu-app:latest
resources:
requests:
cpu: 1
memory: 1Gi
nvidia.com/gpu: 1
limits:
cpu: 1
memory: 1Gi
nvidia.com/gpu: 1
DCGM Exporter を利用してメトリクスを収集
DCGM Exporter は NVIDIA DCGM(Data Center GPU Manager)のメトリクスを Prometheus 形式で公開する Exporter です。GPU Operator がこれらの設定を自動で行ってくれるため、GPU ノード上で DCGM Exporter が自動的に起動し、/metrics エンドポイントからメトリクスを取得できるようになります。
また、可視化には Grafana を利用しています。
もともと Grafana を利用しているという理由に加えて、DCGM Exporter に対応したダッシュボードが提供されており、簡単にセットアップ可能です。
弊社では こちら のダッシュボードを利用しています。
はまりどころ
DCGM Exporter をそのまま利用すると、いくつか運用上の落とし穴がありました。まず、デフォルト設定のままでは内部からの通信しか受け付けず、外部の Prometheus から直接アクセスできません。具体的には、DCGM_EXPORTER_LISTEN のデフォルト値が :9400(コンテナ内部のみで待ち受け)になっているうえ、hostNetwork と hostPort も無効になっているためです。そのため、hostNetwork と hostPort を有効化するとともに、DCGM_EXPORTER_LISTEN を 0.0.0.0:9400 に変更し、ノード側のポートを開ける必要があります。これについては、後述のとおり kubectl patch で DaemonSet の設定を部分的に上書きすることで対応しました。
また、Prometheus のスクレイピング対象を自動で検出するために prometheus.io/scrape が metadata.annotations に付与されますが、これが DCGM Exporter の Service に付与される構造になっているため、同一クラスタ内に複数の GPU ノードが存在する場合でも、一度のスクレイプで 1 ノード分のメトリクスしか取得できないという問題がありました。この点については、Service 側のアノテーションから当該タグを削除したうえで、Prometheus の設定側でノードごとにメトリクスをスクレイピングするように調整しています。
$ kubectl patch daemonsets -n gpu-operator nvidia-dcgm-exporter --type 'json' -p '[{"op": "add", "path": "/spec/template/spec/containers/0/ports/0/hostPort", "value": 9400}]'
$ kubectl patch daemonsets -n gpu-operator nvidia-dcgm-exporter --type 'json' -p '[{"op": "add", "path": "/spec/template/spec/hostNetwork", "value": true}]'
$ kubectl patch services -n gpu-operator nvidia-dcgm-exporter --type 'json' -p '[{"op": "remove", "path": "/metadata/annotations/prometheus.io~1scrape"}]'
まとめ
本記事では、Vertex AI による GPU コストの増大をきっかけに、オンプレミスの Kubernetes クラスタに GPU ノードを追加し、GPU Operator で GPU 利用環境とモニタリング基盤を整備した事例を紹介しました。
GPU の利用方法としてはいくつか選択肢がある中で、まずは「1 Pod ごとに 1 GPU」というシンプルな割り当てを採用し、構築方式としては GPU Operator を用いることでドライバや device-plugin、toolkit、DCGM Exporter までをまとめて管理する方針としました。オンプレ環境でこれから GPU ノードを追加しようとしている方の参考になれば幸いです。