はじめに
GCP(Google Cloud)上に構築したGPU付きのVM(Ubuntu 24.04)を、外部のLLM/AIエージェント(Vertex AI Agent Platformなど)から呼び出して計算ジョブを回せるように、FastAPIを組み込んだDockerコンテナをセットアップしました。
すでに同じVM上で JupyterHub が稼働しているため、以下の点に配慮して環境を構築しています。
- ポート競合の回避(JupyterHubのデフォルト8000番を避け、エージェント用APIは8080番にマッピング)
- データの共有(エージェントが生成した成果物やログを、JupyterHubのブラウザ画面からシームレスに確認・解析できるマウント設定)
- 環境の隔離(計算化学ライブラリのASEやPyTorchなどの依存関係をDockerコンテナ内に閉じ込め、ホスト環境を汚さない)
全体構成イメージ
┌────────────────── GCP GPU VM (Ubuntu 24.04) ──────────────────┐
│ │
│ JupyterHub (ポート: 8000) ◄───► 共有ディレクトリ │
│ ▲ │
│ │ (ボリュームマウント) │
│ ┌────────────────── Docker Container │ ──────────────────┐ │
│ │ ▼ │ │
│ │ FastAPI (ポート: 8000) ◄───► /app/jobs/ │ │
│ │ │ │ │
│ │ ▼ (subprocess) │ │
│ │ Python計算ジョブ (PyTorch / ASE) │ │
│ └────────────────────────────────────┬───────────────────┘ │
│ ▼ (GPUパススルー) │
│ NVIDIA GPU (L4) │
└───────────────────────────────────────────────────────────────┘
1. ホストVM側のセットアップ(Docker & NVIDIA Toolkit)
Ubuntu 24.04 LTSに、Docker Engineと、コンテナ内でGPUを認識させるための NVIDIA Container Toolkit をインストールします。
1-1. Docker Engine のインストール
公式リポジトリから安定版を導入します。
# 必要な依存パッケージのインストール
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg
# Docker公式のGPG鍵を追加
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
# リポジトリをAPTソースリストに追加
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# インストール
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# カレントユーザーをdockerグループに追加(sudo省略用)
sudo usermod -aG docker $USER
※設定反映のため、一度ログアウトして再ログインするか、newgrp docker を実行してください。
1-2. NVIDIA Container Toolkit のインストール
コンテナへGPUをパススルーするためのミドルウェアを設定します。
# NVIDIA公式リポジトリのGPG鍵とリストを追加
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
# インストール
sudo apt-get update
sudo apt-get install -y nvidia-container-toolkit
# DockerのランタイムとしてNVIDIAを設定し、再起動
sudo nvidia-container-toolkit runtime configure --runtime=docker
sudo systemctl restart docker
2. APIサーバー(FastAPI)とDockerの配置
2-1. 共有用ディレクトリの作成
JupyterHubの作業スペース内に、エージェントがファイルを吐き出すためのフォルダをあらかじめ作っておきます。
mkdir -p /home/username/work/agent_jobs
mkdir -p ~/gpu-agent-api
cd ~/gpu-agent-api
2-2. main.py の作成
エージェントが非同期でジョブを投入・監視できるよう、FastAPIで最小限のジョブ管理APIを実装します。
# main.py
import uuid
import os
import subprocess
from fastapi import FastAPI, BackgroundTasks, HTTPException
from pydantic import BaseModel
from typing import Dict, Optional
app = FastAPI(title="GPU VM Job Runner API", version="1.0")
jobs_db: Dict[str, dict] = {}
class JobRequest(BaseModel):
script_name: str
parameters: Optional[str] = ""
def run_python_job(job_id: str, script_name: str, parameters: str):
log_dir = "./jobs/logs"
os.makedirs(log_dir, exist_ok=True)
log_path = f"{log_dir}/{job_id}.log"
# コンテナ内のPython環境でスクリプトを実行
cmd = f"python {script_name} {parameters}"
try:
with open(log_path, "w") as log_file:
process = subprocess.Popen(
cmd, shell=True, stdout=log_file, stderr=subprocess.STDOUT, cwd="./jobs/"
)
process.wait()
if process.returncode == 0:
jobs_db[job_id]["status"] = "completed"
else:
jobs_db[job_id]["status"] = "failed"
except Exception as e:
jobs_db[job_id]["status"] = "failed"
with open(log_path, "a") as log_file:
log_file.write(f"\nAPI Error: {str(e)}")
@app.post("/run-job")
def start_job(request: JobRequest, background_tasks: BackgroundTasks):
job_id = str(uuid.uuid4())
jobs_db[job_id] = {
"status": "running",
"script_name": request.script_name,
"log_path": f"./jobs/logs/{job_id}.log"
}
background_tasks.add_task(run_python_job, job_id, request.script_name, request.parameters)
return {"job_id": job_id, "status": "running"}
@app.get("/job-status/{job_id}")
def get_status(job_id: str):
if job_id not in jobs_db:
raise HTTPException(status_code=404, detail="Job not found")
job_info = jobs_db[job_id]
recent_log = ""
if os.path.exists(job_info["log_path"]):
with open(job_info["log_path"], "r") as f:
recent_log = "".join(f.readlines()[-10:])
return {"job_id": job_id, "status": job_info["status"], "recent_log": recent_log}
2-3. Dockerfile の作成
NVIDIA公式のPyTorchベースイメージ(CUDA最適化済み)を採用し、計算化学用のASEなどを追加します。
# Dockerfile
FROM nvcr.io/nvidia/pytorch:24.01-py3
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
git \
&& rm -rf /var/lib/apt/lists/*
RUN pip install --no-cache-dir fastapi uvicorn pydantic ase
COPY . /app
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
3. コンテナのビルドと起動
競合を避けるため、ホスト側のマッピングポートを 8080 に変更し、JupyterHubのディレクトリをマウントして起動します。
# ビルド
docker build -t gpu-agent-api .
# 起動(JupyterHubとの共存設定)
docker run -d \
--name my-gpu-agent \
--gpus all \
-p 8080:8000 \
-v /home/username/work/agent_jobs:/app/jobs \
--restart unless-stopped \
gpu-agent-api
4. 動作確認テスト
コンテナ内部に入り、PyTorchがホストのGPU(NVIDIA L4)を正常に認識できているかテストコマンドを叩きます。
docker exec -it my-gpu-agent python -c "
import torch
print('GPU Available:', torch.cuda.is_available())
print('Device Name:', torch.cuda.get_device_name(0) if torch.cuda.is_available() else 'None')
"
出力結果
GPU Available: True
Device Name: NVIDIA L4
無事にコンテナ内の隔離環境から、ホスト側の NVIDIA L4 が認識されていることが確認できました!
まとめ・今後の展望
JupyterHubを落とすことなく、同じVM上に安全にAIエージェント用のバックエンドを構築できました。
今後は、このFastAPIが自動生成するAPI仕様書(http://<VMのIP>:8080/openapi.json)をダウンロードし、Vertex AI Agent Platform の「Tools(OpenAPI)」に登録することで、Geminiが自律的にこのGPUリソースを叩いて計算化学や機械学習のシミュレーションを回す自動化パイプラインを組む予定です。
が、Agent Platformの使い方が正直よくわからんので、ローカルエージェントからFastAPIをたたく方向でいじってみる。