CUDA環境構築のベストプラクティス | ベアメタル・Docker・VMの構造比較と最適な選び方
一覧に戻るCUDA環境構築のベストプラクティス | ベアメタル・Docker・VMの構造比較と最適な選び方 GPU を使った AI 推論や LLM の稼働環境を構築する際、「とりあえず Docker で」と考えるエンジニアは多い。しかし CUDA と Docker の組み合わせは、CPU ベースのアプリケーションとは根本的に異なる課題を抱えている。この記事では、CUDA 環境を Docker で構築する際にハマりやすいポイントを整理し、ベアメタル・Docker・VM それぞれの構造的な違いと、ユースケース別の最適解を解説する。
Docker と CUDA の相性が悪い根本的な理由
Docker の設計思想は「ハードウェアを抽象化し、どこでも同じように動く 」こと。一方、CUDA は「特定の NVIDIA GPU ハードウェアに密結合し、ドライバのバージョンまで一致が必要 」という性質を持つ。
この2つは本質的に矛盾している。
Docker のポータビリティが GPU で崩れる
通常の Docker コンテナでは、アプリケーションの依存関係をすべてイメージ内に閉じ込めることで環境差異をなくす。しかし GPU を使う場合、ホスト OS の NVIDIA ドライバに依存する ため、この「閉じ込め」が成立しない。
通常の Docker:
コンテナ内に全依存関係 → どの環境でも動く ✅
GPU Docker:
コンテナ内の CUDA ← ホストの NVIDIA ドライバに依存 → 環境ごとに動作が変わる ❌
結果として、ホスト側のドライババージョンとコンテナ内の CUDA バージョンの互換性を常に意識する必要があり、Docker の最大のメリットであるポータビリティが大きく損なわれる。
CUDA × Docker でハマる 5 つのポイント
1. nvidia-container-toolkit の設定
Docker コンテナから GPU にアクセスするには、nvidia-container-toolkit(旧 nvidia-docker2)が必要になる。この「橋渡し」ツールが必要な時点で、Docker 単体では GPU を扱えないことを意味している。
ご紹介 弊社では下記プロダクトを取り扱いしております。ご興味ありましたらご覧ください。
# インストール後、daemon.json の設定が必要
{
"runtimes" : {
"nvidia" : {
"path" : "/usr/bin/nvidia-container-runtime" ,
"runtimeArgs" : [ ]
}
}
}
この設定を忘れると could not select device driver エラーが発生する。Docker 初心者が最初にハマるポイントだ。
2. CUDA バージョンとドライバの互換性 NVIDIA ドライバには「Forward Compatibility」という仕組みがあるが、すべての組み合わせで動くわけではない。
ホストドライバ 対応 CUDA よくあるトラブル 535.x CUDA 12.2 まで CUDA 12.3 以降のイメージが動かない 545.x CUDA 12.3 まで 最新の PyTorch イメージでエラー 550.x+ CUDA 12.4+ 比較的安定
ドライバをアップデートすればいいが、カーネルモジュールの再ビルドが必要な場合もあり、本番環境では気軽にできない。
3. cgroup v2 問題 Ubuntu 22.04 以降はデフォルトで cgroup v2 を使用する。古いバージョンの Docker や nvidia-container-toolkit では cgroup v2 のデバイス管理に対応しておらず、コンテナから GPU が見えない という問題が発生する。
# コンテナ内で nvidia-smi が動かない
$ docker run --gpus all nvidia/cuda:12.2-base nvidia-smi
Error: .. .
解決には Docker と toolkit の両方を最新版にアップデートする必要がある。
4. 権限とセキュリティ コンテナから GPU デバイス(/dev/nvidia*)にアクセスするには、適切な権限設定が必要。--privileged フラグを使えば動くが、コンテナのセキュリティ分離が無意味になる。
# これは動くが、セキュリティ的に問題あり
docker run --privileged nvidia/cuda:12.2-base nvidia-smi
# 正しいやり方
docker run --gpus all nvidia/cuda:12.2-base nvidia-smi
NVIDIA Developer Forum では、2025 年になっても「sudo でしか GPU にアクセスできない」という報告が上がっている。
5. パフォーマンスオーバーヘッド Docker 自体のオーバーヘッドは通常小さいが、GPU パススルーの場合はデバイスマッピングのレイヤーが入る。特に以下のケースで影響が出やすい。
メモリ管理 : コンテナの memory limit と GPU メモリの管理が別系統
I/O : モデルファイルの読み込みで、ボリュームマウント経由のレイテンシが加わる
マルチ GPU : 複数 GPU の割り当てとスケジューリングが複雑化
ベアメタル・Docker・VM の構造比較 3 つの実行環境がどのようにGPUにアクセスするか、構造的な違いを見てみよう。
ベアメタル(物理サーバー直接実行) ┌─────────────────────┐
│ アプリケーション │
│ (CUDA / PyTorch) │
├─────────────────────┤
│ CUDA Toolkit │
├─────────────────────┤
│ NVIDIA ドライバ │
├─────────────────────┤
│ OS (Linux) │
├─────────────────────┤
│ GPU ハードウェア │
└─────────────────────┘
GPU へのアクセスが最短経路。オーバーヘッドなし
CUDA とドライバの組み合わせをホストで一度設定すれば安定
環境の再現性は手動管理(Ansible 等で対応可能)
Docker(コンテナ実行) ┌─────────────────────┐
│ アプリケーション │
│ (CUDA / PyTorch) │
├─────────────────────┤
│ CUDA Toolkit │ ← コンテナ内
│ (コンテナイメージ) │
├─────────────────────┤
│ nvidia-container- │ ← 橋渡しレイヤー
│ toolkit │
├─────────────────────┤
│ Docker Engine │
├─────────────────────┤
│ NVIDIA ドライバ │ ← ホスト側に依存
├─────────────────────┤
│ OS (Linux) │
├─────────────────────┤
│ GPU ハードウェア │
└─────────────────────┘
nvidia-container-toolkit という追加レイヤーが必須
コンテナ内の CUDA とホストのドライバの互換性を常に管理
複数の CUDA バージョンを共存させたい場合には有効
VM(仮想マシン) ┌─────────────────────┐
│ アプリケーション │
│ (CUDA / PyTorch) │
├─────────────────────┤
│ CUDA Toolkit │
├─────────────────────┤
│ NVIDIA ドライバ │ ← ゲスト OS 用ドライバ
├─────────────────────┤
│ ゲスト OS │
├─────────────────────┤
│ ハイパーバイザー │ ← 仮想化レイヤー
│ (GPU パススルー │
│ / vGPU) │
├─────────────────────┤
│ ホスト OS │
├─────────────────────┤
│ GPU ハードウェア │
└─────────────────────┘
GPU パススルー(SR-IOV / vGPU)の設定が複雑
ハイパーバイザーによるオーバーヘッドが最も大きい
マルチテナントで GPU を分割共有する場合に使われる(クラウド事業者向け)
ユースケース別の最適解
単一 GPU × 1 台のマシン → ベアメタル推奨 小型 PC やワークステーション 1 台で LLM 推論を動かすケースでは、Docker のメリットがほぼない。
観点 ベアメタル Docker GPU 性能 100%(オーバーヘッドなし) 95〜99%(デバイスマッピング経由) セットアップ CUDA インストールのみ CUDA + Docker + nvidia-container-toolkit トラブル時 原因が1箇所 ホスト/コンテナの切り分けが必要 運用コスト OS のアップデートのみ Docker + toolkit のバージョン管理も必要
具体例 : NVIDIA GB10 や ASUS NUC に Ollama を入れて社内 AI を稼働させるケース。ベアメタルなら curl -fsSL https://ollama.com/install.sh | sh 一発で CUDA が自動検出される。Docker 経由だと nvidia-container-toolkit のセットアップから始まる。
複数バージョンの CUDA を使い分けたい → Docker が有効 研究開発で PyTorch の異なるバージョン(CUDA 11.8 / 12.1 / 12.4)を切り替えたい場合、Docker は価値がある。
# CUDA 11.8 環境
docker run --gpus all pytorch/pytorch:2.0.1-cuda11.8-cudnn8-runtime
# CUDA 12.4 環境
docker run --gpus all pytorch/pytorch:2.5.0-cuda12.4-cudnn9-runtime
ただし、これはあくまで開発・検証環境の話であり、本番の推論サーバーでは1つの CUDA バージョンに固定するのが普通だ。
複数ノードでスケールアウト → Kubernetes(K8s) GPU サーバーが複数台あり、ワークロードを動的に振り分けたい場合は Kubernetes + NVIDIA GPU Operator が事実上の標準になる。
# GPU リソースのリクエスト例
resources :
limits :
nvidia.com/gpu : 1
ただし、K8s の運用には専任のインフラエンジニアが必要になる。
規模 最適解 必要なスキル GPU 1台 ベアメタル Linux 基礎のみ GPU 数台(同一用途) ベアメタル + Ansible Linux + 構成管理 GPU 数台〜数十台(混在ワークロード) K8s + GPU Operator K8s + MLOps 大規模クラスタ(100+) Slurm / Ray HPC 専門知識
ベアメタルでの構築手順:Ollama 編 小型 PC × 単一 GPU で LLM を稼働させる最もシンプルな構成を紹介する。
1. NVIDIA ドライバのインストール # Ubuntu の場合
sudo apt update
sudo apt install -y nvidia-driver-550
sudo reboot
# 確認
nvidia-smi
2. Ollama のインストール curl -fsSL https://ollama.com/install.sh | sh
Ollama は CUDA を自動検出するため、追加の設定は不要。
3. モデルの取得と起動 # 7B モデル
ollama pull llama3.1
# 70B モデル(GB10 など大容量メモリ環境向け)
ollama pull llama3.1:70b
# API サーバーとして起動(デフォルト: localhost:11434)
ollama serve
4. LAN 内からのアクセス設定 # 外部からのアクセスを許可
OLLAMA_HOST = 0.0 .0.0 ollama serve
これだけで社内ネットワークから AI 推論 API にアクセスできる。Docker なら docker-compose.yml の作成、ボリュームマウント、ポートマッピング、nvidia-runtime の指定…と、はるかに多くの手順が必要になる。
ベアメタルでの構築手順:vLLM 編(uv 使用) Ollama はシンプルさが強みだが、本格的な推論サーバーが必要な場合は vLLM が選択肢に入る。OpenAI 互換の API サーバーを高スループットで動かせるのが特徴だ。
ここでは Python のパッケージマネージャ uv を使ったセットアップ手順を紹介する。pip + venv よりも高速で、依存関係の解決も安定している。
1. uv のインストール curl -LsSf https://astral.sh/uv/install.sh | sh
2. vLLM プロジェクトの作成 # プロジェクトディレクトリを作成
mkdir vllm-server && cd vllm-server
# Python 3.11 で仮想環境を作成(vLLM の推奨バージョン)
uv venv --python 3.11
source .venv/bin/activate
# vLLM をインストール(CUDA 対応版が自動選択される)
uv pip install vllm
uv は pip の 10〜100 倍高速にパッケージを解決・インストールする。vLLM のような依存関係が多いパッケージで特に差が出る。
3. モデルのダウンロードと起動 # OpenAI 互換 API サーバーとして起動
vllm serve meta-llama/Llama-3.1-8B-Instruct \
--host 0.0 .0.0 \
--port 8000 \
--max-model-len 4096
Hugging Face からモデルが自動ダウンロードされ、CUDA を検出して GPU 上で推論が始まる。
4. API の動作確認 curl http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "meta-llama/Llama-3.1-8B-Instruct",
"messages": [{"role": "user", "content": "こんにちは"}],
"max_tokens": 256
}'
OpenAI SDK からもそのまま接続できるため、既存のアプリケーションの接続先を変えるだけでローカル LLM に切り替えられる。
Ollama vs vLLM:どちらを選ぶか 観点 Ollama vLLM セットアップ ワンライナー uv + 数コマンド 対応モデル Ollama ライブラリから選択 Hugging Face 上の全モデル API 互換性 独自 + OpenAI 互換 OpenAI 完全互換 スループット 単一リクエスト向き 高並列処理(Continuous Batching) メモリ効率 標準 PagedAttention で最適化 ユースケース 個人〜小規模チーム 高負荷の本番推論サーバー
小規模で手軽に始めるなら Ollama、高スループットが必要な本番環境なら vLLM という使い分けが現実的だ。どちらもベアメタルで動かすことで、Docker のオーバーヘッドとトラブルを回避できる。
まとめ 判断基準 ベアメタル Docker K8s GPU 1台で推論 ◎ △ ✕(過剰) CUDA バージョン切替 △ ◎ ◎ 本番推論サーバー ◎ ○ ○ マルチノード管理 △ ○ ◎ 非エンジニアの運用 ◎ ✕ ✕ トラブル時の切り分け 簡単 中程度 複雑
「とりあえず Docker」は GPU 環境では正解ではないかもしれない。 単一マシンで LLM を動かすなら、ベアメタルがもっともシンプルで、もっともパフォーマンスが出ます。Docker が本当に必要になるのは、複数の CUDA バージョンを切り替える開発環境か、K8s でオーケストレーションするマルチノード構成の時が多い様に思う。自社の GPU 環境が 1 台で完結するなら、余計な抽象化レイヤーを挟まずベアメタルで動かすことを推奨します。