第2章:コンテナ化技術

Dockerによる機械学習モデルの可搬性と再現性

📖 読了時間: 25-30分 📊 難易度: 初級-中級 💻 コード例: 8個 📝 演習問題: 0問

学習目標

この章を読むことで、以下を習得できます:


2.1 Dockerの基礎

コンテナとは

コンテナ(Container) は、アプリケーションとその依存関係を独立した環境にパッケージ化する技術です。

「Build once, Run anywhere」- 一度ビルドすれば、どこでも同じように実行できる

Docker vs 仮想マシン

特徴Docker コンテナ仮想マシン (VM)
起動時間秒単位分単位
リソース軽量(MB単位)重い(GB単位)
分離レベルプロセスレベル完全な OS 分離
性能ネイティブに近いオーバーヘッドあり
可搬性高い中程度
```mermaid
graph TD
    subgraph "仮想マシン"
        A1[アプリ1] --> B1[ゲストOS1]
        A2[アプリ2] --> B2[ゲストOS2]
        B1 --> C[ハイパーバイザー]
        B2 --> C
        C --> D[ホストOS]
        D --> E[物理サーバー]
    end

    subgraph "Docker コンテナ"
        F1[アプリ1] --> G[Docker Engine]
        F2[アプリ2] --> G
        G --> H[ホストOS]
        H --> I[物理サーバー]
    end

    style A1 fill:#e3f2fd
    style A2 fill:#e3f2fd
    style F1 fill:#c8e6c9
    style F2 fill:#c8e6c9
```

Docker基本コマンド

# Dockerバージョン確認
docker --version

# イメージ一覧表示
docker images

# コンテナ一覧表示(実行中)
docker ps

# コンテナ一覧表示(全て)
docker ps -a

# イメージのダウンロード
docker pull python:3.9-slim

# コンテナの実行
docker run -it python:3.9-slim bash

# コンテナの停止
docker stop <container_id>

# コンテナの削除
docker rm <container_id>

# イメージの削除
docker rmi <image_id>

# システム全体のクリーンアップ
docker system prune -a

イメージとコンテナの関係

イメージ(Image) :アプリケーションの設計図(読み取り専用)

コンテナ(Container) :イメージから作成された実行可能なインスタンス

```mermaid
graph LR
    A[Dockerfile] -->|docker build| B[Docker Image]
    B -->|docker run| C[Container 1]
    B -->|docker run| D[Container 2]
    B -->|docker run| E[Container 3]

    style A fill:#ffebee
    style B fill:#fff3e0
    style C fill:#e8f5e9
    style D fill:#e8f5e9
    style E fill:#e8f5e9
```

重要 : 1つのイメージから複数のコンテナを起動できます。各コンテナは独立した環境です。


2.2 Dockerfileの作成

ベースイメージ選択

機械学習モデル用の代表的なベースイメージ:

イメージサイズ用途
python:3.9-slim約120MB軽量なPython環境
python:3.9約900MBフル機能のPython環境
nvidia/cuda:11.8.0-cudnn8-runtime-ubuntu22.04約2GBGPU推論用
nvidia/cuda:11.8.0-cudnn8-devel-ubuntu22.04約4GBGPU開発・学習用

基本的なDockerfile構造

# ベースイメージの指定
FROM python:3.9-slim

# 作業ディレクトリの設定
WORKDIR /app

# システムパッケージの更新とインストール
RUN apt-get update && apt-get install -y \
    build-essential \
    && rm -rf /var/lib/apt/lists/*

# Pythonパッケージのインストール
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# アプリケーションコードのコピー
COPY . .

# ポート公開
EXPOSE 8000

# 起動コマンド
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

マルチステージビルド

イメージサイズを削減し、セキュリティを向上させる技術:

# ステージ1: ビルド環境
FROM python:3.9 as builder

WORKDIR /build

# 依存関係のインストール
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt

# ステージ2: 実行環境(軽量)
FROM python:3.9-slim

WORKDIR /app

# ビルドステージから必要なファイルのみコピー
COPY --from=builder /root/.local /root/.local
COPY . .

# PATHの設定
ENV PATH=/root/.local/bin:$PATH

EXPOSE 8000

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

効果 : マルチステージビルドにより、イメージサイズを50-70%削減できることがあります。

最適化テクニック

レイヤーキャッシュの活用

# ❌ 非効率: コードが変更されるたびに依存関係を再インストール
FROM python:3.9-slim
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt

# ✅ 効率的: 依存関係が変更されない限りキャッシュを利用
FROM python:3.9-slim
WORKDIR /app

# 先に依存関係をインストール(変更頻度が低い)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 後からコードをコピー(変更頻度が高い)
COPY . .

不要なファイルの除外

.dockerignoreファイルの例:

# .dockerignore
__pycache__
*.pyc
*.pyo
*.pyd
.Python
*.so
*.egg
*.egg-info
dist
build
.git
.gitignore
.env
.venv
venv/
data/
notebooks/
tests/
*.md
Dockerfile
docker-compose.yml

2.3 MLモデルのコンテナ化

FastAPI + PyTorchのDockerfile

# マルチステージビルド
FROM python:3.9 as builder

WORKDIR /build

# 依存関係ファイルのコピー
COPY requirements.txt .

# 依存関係のインストール
RUN pip install --user --no-cache-dir \
    torch==2.0.0 \
    torchvision==0.15.0 \
    fastapi==0.104.0 \
    uvicorn[standard]==0.24.0 \
    pydantic==2.5.0 \
    pillow==10.1.0

# 実行環境
FROM python:3.9-slim

WORKDIR /app

# ビルドステージから依存関係をコピー
COPY --from=builder /root/.local /root/.local

# アプリケーションコードとモデルをコピー
COPY app/ ./app/
COPY models/ ./models/

# 環境変数の設定
ENV PATH=/root/.local/bin:$PATH \
    PYTHONUNBUFFERED=1 \
    MODEL_PATH=/app/models/model.pth

# 非rootユーザーの作成(セキュリティ向上)
RUN useradd -m -u 1000 appuser && \
    chown -R appuser:appuser /app

USER appuser

# ヘルスチェック
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
    CMD python -c "import requests; requests.get('http://localhost:8000/health')"

EXPOSE 8000

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

requirements.txtの例

# requirements.txt
torch==2.0.0
torchvision==0.15.0
fastapi==0.104.0
uvicorn[standard]==0.24.0
pydantic==2.5.0
pillow==10.1.0
numpy==1.24.3
python-multipart==0.0.6

イメージビルドと実行

# イメージのビルド
docker build -t ml-api:v1.0 .

# ビルドログの詳細表示
docker build -t ml-api:v1.0 --progress=plain .

# キャッシュを使わずにビルド
docker build -t ml-api:v1.0 --no-cache .

# コンテナの実行
docker run -d \
    --name ml-api \
    -p 8000:8000 \
    -v $(pwd)/models:/app/models \
    ml-api:v1.0

# ログの確認
docker logs ml-api

# リアルタイムログ表示
docker logs -f ml-api

# コンテナ内でコマンド実行
docker exec -it ml-api bash

# コンテナの停止と削除
docker stop ml-api
docker rm ml-api

ポートマッピング

オプション説明
-p 8000:8000ホスト:コンテナホストの8000番をコンテナの8000番に
-p 8080:8000異なるポートホストの8080番をコンテナの8000番に
-p 127.0.0.1:8000:8000ローカルのみローカルホストからのみアクセス可能

2.4 Docker Composeによるオーケストレーション

docker-compose.yml構成

複数のサービスを統合管理するための設定ファイル:

# docker-compose.yml
version: '3.8'

services:
  # FastAPI アプリケーション
  api:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: ml-api
    ports:
      - "8000:8000"
    environment:
      - MODEL_PATH=/app/models/model.pth
      - REDIS_HOST=redis
      - REDIS_PORT=6379
    volumes:
      - ./models:/app/models:ro
      - ./logs:/app/logs
    depends_on:
      - redis
    restart: unless-stopped
    networks:
      - ml-network
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

  # Redis キャッシュ
  redis:
    image: redis:7-alpine
    container_name: ml-redis
    ports:
      - "6379:6379"
    volumes:
      - redis-data:/data
    restart: unless-stopped
    networks:
      - ml-network
    command: redis-server --appendonly yes

networks:
  ml-network:
    driver: bridge

volumes:
  redis-data:

複数サービスの統合例

# docker-compose.yml (拡張版)
version: '3.8'

services:
  # MLモデル推論API
  ml-api:
    build: ./api
    ports:
      - "8000:8000"
    environment:
      - REDIS_HOST=redis
      - DB_HOST=postgres
    volumes:
      - ./models:/app/models:ro
    depends_on:
      - redis
      - postgres
    networks:
      - ml-network

  # キャッシュ層
  redis:
    image: redis:7-alpine
    volumes:
      - redis-data:/data
    networks:
      - ml-network

  # データベース
  postgres:
    image: postgres:15-alpine
    environment:
      - POSTGRES_USER=mluser
      - POSTGRES_PASSWORD=mlpass
      - POSTGRES_DB=mldb
    volumes:
      - postgres-data:/var/lib/postgresql/data
    networks:
      - ml-network

  # モニタリング
  prometheus:
    image: prom/prometheus:latest
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - prometheus-data:/prometheus
    networks:
      - ml-network

networks:
  ml-network:
    driver: bridge

volumes:
  redis-data:
  postgres-data:
  prometheus-data:

ボリュームマウント

タイプ構文用途
バインドマウント./host/path:/container/path開発時のコード同期
名前付きボリュームvolume-name:/container/path永続的なデータ保存
読み取り専用./path:/path:roモデルファイルなど

環境変数管理

.envファイルの例:

# .env
MODEL_PATH=/app/models/resnet50.pth
REDIS_HOST=redis
REDIS_PORT=6379
LOG_LEVEL=INFO
MAX_WORKERS=4

docker-compose.ymlでの使用:

services:
  api:
    env_file:
      - .env
    # または個別に指定
    environment:
      - MODEL_PATH=${MODEL_PATH}
      - REDIS_HOST=${REDIS_HOST}

Docker Compose コマンド

# サービスの起動(バックグラウンド)
docker-compose up -d

# サービスの起動(ログ表示)
docker-compose up

# サービスのビルドと起動
docker-compose up -d --build

# 特定のサービスのみ起動
docker-compose up -d api redis

# サービスの停止
docker-compose stop

# サービスの停止と削除
docker-compose down

# ボリュームも含めて削除
docker-compose down -v

# ログの確認
docker-compose logs -f

# 特定のサービスのログ
docker-compose logs -f api

# サービスの状態確認
docker-compose ps

# サービスの再起動
docker-compose restart api

2.5 実践: GPU対応MLコンテナ

NVIDIA Dockerセットアップ

前提条件:

CUDAイメージ使用のDockerfile

# GPU推論用 Dockerfile
FROM nvidia/cuda:11.8.0-cudnn8-runtime-ubuntu22.04

# Python のインストール
RUN apt-get update && apt-get install -y \
    python3.10 \
    python3-pip \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app

# PyTorch GPU版のインストール
COPY requirements-gpu.txt .
RUN pip3 install --no-cache-dir -r requirements-gpu.txt

# アプリケーションコードとモデル
COPY app/ ./app/
COPY models/ ./models/

ENV PYTHONUNBUFFERED=1 \
    CUDA_VISIBLE_DEVICES=0

EXPOSE 8000

CMD ["python3", "-m", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

requirements-gpu.txt

# requirements-gpu.txt
torch==2.0.0+cu118
torchvision==0.15.0+cu118
--extra-index-url https://download.pytorch.org/whl/cu118
fastapi==0.104.0
uvicorn[standard]==0.24.0
pydantic==2.5.0
pillow==10.1.0
numpy==1.24.3

GPU推論の実装

app/main.pyの例:

# app/main.py
import torch
from fastapi import FastAPI, File, UploadFile
from PIL import Image
import io

app = FastAPI()

# GPU使用可否の確認
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"Using device: {device}")

# モデルのロード
model = torch.load("/app/models/model.pth", map_location=device)
model.eval()

@app.get("/health")
async def health_check():
    return {
        "status": "healthy",
        "device": str(device),
        "cuda_available": torch.cuda.is_available(),
        "gpu_name": torch.cuda.get_device_name(0) if torch.cuda.is_available() else None
    }

@app.post("/predict")
async def predict(file: UploadFile = File(...)):
    # 画像の読み込み
    image_bytes = await file.read()
    image = Image.open(io.BytesIO(image_bytes))

    # 前処理(省略)
    # tensor = preprocess(image)

    # GPU推論
    with torch.no_grad():
        # tensor = tensor.to(device)
        # output = model(tensor)
        pass

    return {"prediction": "result"}

Docker ComposeでGPU使用

# docker-compose-gpu.yml
version: '3.8'

services:
  ml-api-gpu:
    build:
      context: .
      dockerfile: Dockerfile.gpu
    container_name: ml-api-gpu
    ports:
      - "8000:8000"
    volumes:
      - ./models:/app/models:ro
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 1
              capabilities: [gpu]
    environment:
      - CUDA_VISIBLE_DEVICES=0
    restart: unless-stopped

起動コマンド:

# GPU対応コンテナの起動
docker-compose -f docker-compose-gpu.yml up -d

# GPU使用状況の確認
docker exec ml-api-gpu nvidia-smi

# ログの確認
docker-compose -f docker-compose-gpu.yml logs -f

パフォーマンス比較

環境推論時間(1画像)スループット(画像/秒)備考
CPU (8コア)150ms6.7python:3.9-slim
GPU (RTX 3090)15ms66.7nvidia/cuda:11.8.0
高速化比10倍10倍バッチサイズ1

注意 : バッチサイズを増やすことで、GPUのスループットをさらに向上できます。


2.6 本章のまとめ

学んだこと

  1. Dockerの基礎

    • コンテナと仮想マシンの違い
    • 基本的なDockerコマンド
    • イメージとコンテナの関係
  2. Dockerfileの作成

    • 適切なベースイメージの選択
    • マルチステージビルドによる最適化
    • レイヤーキャッシュの活用
  3. MLモデルのコンテナ化

    • FastAPI + PyTorchのDocker化
    • .dockerignoreによる効率化
    • セキュリティとヘルスチェック
  4. Docker Composeオーケストレーション

    • 複数サービスの統合管理
    • ボリュームと環境変数の管理
    • サービス間の依存関係
  5. GPU対応MLコンテナ

    • NVIDIA Dockerのセットアップ
    • CUDAイメージの使用
    • CPU比10倍のパフォーマンス

ベストプラクティス

原則説明
軽量イメージslimやalpineベースを優先
レイヤー最適化変更頻度の低いものを先に
マルチステージビルドビルドと実行環境を分離
非rootユーザーセキュリティ向上のため
.dockerignore不要なファイルを除外
ヘルスチェックサービスの健全性監視
環境変数設定の外部化

次の章へ

第3章では、Kubernetes によるオーケストレーション を学びます: