Windows+GPUでAI環境をコンテナで作ろう!
生成AIモデルの推論や学習における環境は、Linuxが利用されていることが多いですが、最近はコンシューマで利用されるGeForceなども性能向上し、モデルの推論やファインチューニングなどを行えるようになってきています。
- ただし、GeForceを利用してモデルの推論や学習を行おうとしたときには、OSがWindowsであることがほとんどだと思います。
- Windows上でLinuxを動作させる仕組みであるWindows Subsystem for Linux(WSL)とコンテナの仕組みを利用すれば、コンテナを利用して簡単にAI環境を構築することができます。
- 本稿では、WSLとContainerdを利用したWindows上にGPU利用環境を構築する方法を紹介します。
本記事で得られること:
- WSL2上で、containerdを用いてGPUコンテナを構築・実行する具体的な手順
- WSL2上のコンテナ内でGPUの有効化が、どのようなアーキテクチャで実現されているかの技術的理解
👉 WSL+kubernetesで環境構築する方法は、続編で説明予定です。
STEP1 : 環境構築
1.1 前提条件
以下、利用するPCの前提を確認してください。
- Windows 側で NVIDIA Driver(WSL2 GPU サポート対応版)が導入されている必要があること
- 基本的にドライバは下位互換があるため、最新のバージョンを導入しておくことを推奨します。
 
- WSL2 側のカーネル・ディストリビューションが最新であること
- wsl --update コマンドでカーネルを更新し、利用している Linux ディストリビューションも最新化しておくと安心です。
 
1.2 必須ツールのインストール
- 
containerd&nerdctl
- CNI (Container Network Interface) Plugin
 1.2.1 containerd & nerdctl
nerdctlは、containerdをDockerライクなUIで操作できる便利なCLIツールです。Fullパッケージにはcontainerd本体も同梱されています。
- 
nerdctlの公式リリースページから、 nerdctl-full-****-linux-amd64.tar.gzをダウンロードします。
- 
ダウンロードしたファイルをWSL2内でパスの通ったディレクトリ(例: /usr/local/bin)に展開します。
tar Cxzvvf /usr/local nerdctl-full-2.1.5-linux-amd64.tar.gz
1.2.2 CNI (Container Network Interface) Plugin
nerdctl runに必要なのでCNI Pluginもインストールします。
- バイナリを取得します。
CNI Pluginの公式リリースページから、cni-plugins-linux-amd64-****.tgzをダウンロードします。
# バージョンは適宜最新版を確認してください
CNI_VERSION="v1.8.0"
wget "https://github.com/containernetworking/plugins/releases/download/${CNI_VERSION}/cni-plugins-linux-amd64-${CNI_VERSION}.tgz"
- 取得したファイルを所定のディレクトリに展開します。
sudo mkdir -p /opt/cni/bin
sudo tar -C /opt/cni/bin -xzvvf cni-plugins-linux-amd64-${CNI_VERSION}.tgz
1.2.3 NVIDIA Container Toolkit
ホストのGPUをコンテナに透過的に公開するためのキーコンポーネントです。
公式ガイドのインストール手順に従い、WSL2ディストリビューションに合わせたパッケージをインストールしてください。
- WSLで実行しているディストリビューション(UbuntuならUbuntu用)に合わせてインストールをしてください。
- 参考として2025/10/15時点の内容を転記します。(3の手順でのバージョンは適宜最新のバージョンを確認してください)
- リポジトリ情報を更新します。
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg \
  && curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \
    sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
    sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
- リポジトリをUpgradeします。
sudo apt-get update
- NVIDIA Container Toolkitのパッケージをインストールします。
export NVIDIA_CONTAINER_TOOLKIT_VERSION=1.17.8-1
  sudo apt-get install -y \
      nvidia-container-toolkit=${NVIDIA_CONTAINER_TOOLKIT_VERSION} \
      nvidia-container-toolkit-base=${NVIDIA_CONTAINER_TOOLKIT_VERSION} \
      libnvidia-container-tools=${NVIDIA_CONTAINER_TOOLKIT_VERSION} \
      libnvidia-container1=${NVIDIA_CONTAINER_TOOLKIT_VERSION}
- 以上で終了です。この後の手順ではnerdctlを使うので、設定作業は不要です。
STEP 2 実践 :GPUコンテナの起動
nerdctlに--gpus all が指定されるとコンテナ生成時にnvidia‑container‑cliが呼ばれ GPU を注入する流れになります。
2.1 GPUコンテナの起動
機械学習ライブラリTensorFlowの公式コンテナイメージを使用して実際に試してみましょう。
sudo nerdctl run --gpus all --rm -it nvcr.io/nvidia/tensorflow:25.02-tf2-py3
- 
【コマンドオプション解説】
- 
--gpus all: 利用可能な全てのGPUをコンテナにアタッチするための指示です。- all以外のオプションの説明は、nerdctlのGPUに関するドキュメントを参照ください。
 
- 以下は通常のコンテナを利用する際のオプションと同じです。
- 
--rm: 検証終了後に、コンテナを自動で削除するように指定しています。
- 
-it: 検証作業で実際にコンテナ内でPythonのスクリプトを実行するために指定しています。
 
- 
 
- 
2.2 コンテナ内でのGPU認識確認
先ほど起動したコンテナで実際にGPUが検出されるか確認してみましょう。
- 
python3を実行して対話モードに入ります。
- 以下のPythonコードを入力します。
import tensorflow as tf
print(tf.config.list_physical_devices('GPU'))
成功した場合、以下のような出力が得られます。
GPU:0というデバイスがリストにあれば、コンテナはホストのGPUを正しく認識しています。
[PhysicalDevice(name='/physical_device:GPU:0', device_type='GPU')]
- 補足
- 以下のメッセージは無視して問題ありません。
- import時 : Unable to register cuFFT/cuDNN/cuBLAS factory
- 
could not open file to read NUMA node- WSL2 の Linux カーネルが NUMA を expose していないためです。
 
 
- import時 : 
 
- 以下のメッセージは無視して問題ありません。
2.3 トラブルシュート
- もし空のリスト []が返された(=GPU が見えていない)場合、以下を確認してください。- 
ホスト(Windows)側: PowerShellで nvidia-smiを実行し、GPUが認識されているか確認します。
- 
ゲスト(WSL2)側: WSL2のターミナルで nvidia-smiを実行し、同様にGPUが見えるか確認します。見えない場合はwsl --updateを再試行してください。
- バージョン不整合: エラーログに「driver/library version mismatch」といったメッセージがないか確認します。ホストドライバとコンテナ内のCUDAバージョンが非互換の場合、コンテナイメージのバージョン変更を検討してください。
 
- 
ホスト(Windows)側: PowerShellで 
 STEP 3:アーキテクチャ解説  --gpus allはどのように機能するのか?
以降は、興味のある人向けです。
ここまで試してみて、なぜ、--gpus allだけでGPUが利用できるようになったか不思議に思いませんか?
この章では、gpusオプションを指定したときに、各コンポーネントがどのように協調して動作しているかを、次の流れで説明します。
- 全体の流れと、各コンポーネントの役割
- 各コンポーネントの詳細
3.1 主要なコンポーネントの役割と、全体の流れ
3.1.1 主要なコンポーネントの役割
- 
nerdctl:nvidia-container-cliを呼び出す仕掛けを作る。
- 
containerd:runcでコンテナのライフサイクル全体を管理します。
- 
runc: 実際にコンテナ環境を作成するエンジンです
- 
nvidia-container-cli: runcから呼び出されて、コンテナ上でデバイスを使えるようにします。
3.1.2 全体の流れ

- 補足
- 
containerdが受け取った情報をもとにconfig.jsonを作成し、runcを実行するのは、containerdの通常の動作です。
- 
nvidia-container-cliがコンテナのrootfs配下にデバイスファイルや、ライブラリを扱えるようにセットアップしています。
 
- 
3.2 個別コンポーネント
3.2.1 nerdctlコマンドからcontainerdの呼び出し
- 
nerdctlがgrpcクライアントとして、containerdのAPIを呼び出します。 
- 
その際のパラメータでOCI Specのhook定義に組み立て渡しています。 
- 
シーケンス図 - 
nerdctl内の実装も気になる方は付録を参照ください。関連するコードへのリンクを張っておきます。
 
- 
3.2.2 runcからnvidia-container-cliの呼び出し
- 
runcがcontainerdが作成したconfig.jsonをもとにコンテナ環境を作成します。
- その流れの中で呼び出されるnvidia-container-cliがホストにあるGPUや、ライブラリを埋め込みます。- OCI Specを書き換えてruncに任せるといった実装ではありません。
- nvidia-container-cliが直接デバイスファイルの作成や、マウント処理を行っています。
 
- oci specを実際に確認する方法
sudo nerdctl container inspect --mode=native tensorflow
- 
config.json抜粋 - この辺りの定義を作成している実装箇所に興味がある方は、最後に関連するコードの抜粋を載せておきますので、参照ください。
- JSONでは厳密にはコメントはありませんが、説明用に//でコメントを記載しています。
 "hooks": { // 公式ドキュメントのアーキテクチャ概要ではpreStartと書かれていますが、 // 実装はcreateRuntimeになっています。 // `prestart`は`OCI Runtime Spec`で非推奨になったため実装変更されたようです。 // hookの補足は付録を参照ください "createRuntime": [ { "path": "/usr/local/bin/containerd", "args": [ "containerd", "oci-hook", "--", // Nvidia Container Toolkitで提供されるコマンドです "/usr/bin/nvidia-container-cli", "--load-kmods", "configure", // nerdctl呼び出し時に設定したオプションに依存します "--device=all", // utility computeは追加オプションを指定しない場合のデフォルトオプションです "--utility", "--compute", "--pid={{pid}}", "{{rootfs}}" ],
付録
付録1. Hookに関する補足
- 
OCI Runtime SpecificationのLifecycleで定義されています。 
- 
コンテナの作成中などに独自の処理を組み込んだりするのに使われます。 フックステージ タイミング(OCIライフサイクル中) 実行名前空間 典型的なユースケース createRuntime create操作時:名前空間作成後、pivot_root前 ランタイム(ホスト) ホストの状態に基づいてコンテナ設定を変更。 
 例:NVIDIA GPUの注入、デバイスのパススルー。createContainer create操作時:createRuntime後、pivot_root前 コンテナ ユーザープロセス開始前にコンテナの名前空間内でセットアップ。 
 例:vethインターフェースの設定。startContainer start操作時:ユーザープロセスのexec直前 コンテナ 完全にマウントされたコンテナファイルシステム内での最終セットアップタスク。 poststart start操作時:ユーザープロセス開始直後(非ブロッキング) ランタイム(ホスト) コンテナが実行中であることを外部システムに通知。 poststop delete操作時:コンテナ破棄後 ランタイム(ホスト) コンテナに関連付けられた外部リソースのクリーンアップ。 
付録2. nerdctlの実装
- 関連するソースコード
- nerdctl/cmd/nerdctl/container/container_run.go
- processCreateCommandFlagsInRun
- setPlatformOptions
- 
parseGPUOpt
- GPUオプションの保持する内部構造体を作成する
 
- 
nvidia.WithGPUs
- conatinerdに渡すためのOCISpecのオプションパラメータに変換する
 
 
nerdctl gpusフラグ定義の個所
// フラグの定義個所
// cmd/nerdctl/container/container_run.go`  
cmd.Flags().StringArray("gpus", nil, "GPU devices to add to the container ('all' to pass all GPUs)")
cmd.RegisterFlagCompletionFunc("gpus", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
    return []string{"all"}, cobra.ShellCompDirectiveNoFileComp
})
// 取得した`--gpus`フラグの値を`ContainerCreateOptions.GPUs`に格納。
// cmd/nerdctl/container/container_create.go
opt.GPUs, err = cmd.Flags().GetStringArray("gpus")
nerdctl(gpusフラグのオプション解析)
- run_linux.go の parseGPUOpt 内でオプションを内部構造体変換
// pkg/cmd/container/run_linux.go
gpuOpt, err := parseGPUOpts(options.GPUs) // GPUsフラグの値が設定されている
opts = append(opts, gpuOpt...)
//pkg/cmd/container/run_gpus.go` 
//`--gpus`オプションの構文(`count=all`, `device=GPU-xxxx`, `capabilities=compute,utility`など)を
//構造体に変換
func ParseGPUOptCSV(value string) (*GPUReq, error) { ... }
// nvidia.WithGPUを呼び出して、構造体に変換する
nvidia.WithGPUs(gpuOpts...), nil
nerdctl実装(構造体への変換)
- 構造体への変換 nvidia.WithGPUs
- gpusフラグに渡された文字列をもとにparseGPUOptメソッドでOCI Specのオプションに設定する構造体を生成します。
// github.com/containerd/containerd/v2/contrib/nvidia
// WithGPUs adds NVIDIA gpu support to a container
func WithGPUs(opts ...Opts) oci.SpecOpts {
    return func(_ context.Context, _ oci.Client, _ *containers.Container, s *specs.Spec) error {
        //省略
        if c.OCIHookPath == "" {
            path, err := exec.LookPath("containerd")
            //省略
            c.OCIHookPath = path
        }
        // nvidia-container-cliのパスを取得する。
        nvidiaPath, err := exec.LookPath(NvidiaCLI)
        //省略
        s.Hooks.CreateRuntime = append(s.Hooks.CreateRuntime, specs.Hook{
            Path: c.OCIHookPath,
            Args: append([]string{
                "containerd",
                "oci-hook",
                "--",
                nvidiaPath,
                // ensures the required kernel modules are properly loaded
                "--load-kmods",
            }, c.args()...),
            Env: os.Environ(),
        })
        return nil
    }
}
- 関連リンク
- 公式ドキュメントのアーキテクチャ概要
- 
containerdの公式リポジトリ
- containerd,nerdctlともに、この配下にあります。
 
- OCI Runtime SpecificationのLifecycleの定義箇所
 

NTT DATA公式アカウントです。 技術を愛するNTT DATAの技術者が、気軽に楽しく発信していきます。 当社のサービスなどについてのお問い合わせは、 お問い合わせフォーム nttdata.com/jp/ja/contact-us/ へお願いします。



