2-6. 本番向け Compose - マルチステージ、override、リソース制限、ログ

所要時間: 50-60分(重い回。2セッションでも可) ゴール: 開発と本番で同じイメージを使いつつ設定を分離でき、resource limits とログ設計を本番運用視点で説明できる コミット内容: ~/learn/infra/day13/ に Dockerfile(マルチステージ)、compose.ymlcompose.override.ymlcompose.prod.yml


このレッスンのゴール

  • マルチステージビルドで本番イメージを最小化できる
  • compose.override.yml / compose.prod.yml で環境差分を分離できる
  • resource limits(cpus / memory)と OOM の関係を理解した
  • read_only / 非 root / cap_drop で攻撃面を絞れる
  • ログドライバ・ローテーション設計を本番視点で語れる
  • Compose と Kubernetes の境界線を判断できる

なぜ学ぶか(実務悩みベース)

  • 「dev のイメージそのまま本番に流す」事故を撲滅したい
  • メモリリークで隣のコンテナまで道連れ OOM を防ぐ
  • ログがディスクを食い潰してサーバーが詰まる事故を予防
  • Compose 運用と k8s 移行の判断軸を持ちたい

前章とのつながり

2-5 までで「複数コンテナを安全に動かす」素材は揃った。今回はそれを 本番運用に耐える形 に仕上げる総合編。マルチステージ(1-5)、ネットワーク分離(2-3)、シークレット(2-4)、ヘルスチェック(2-5)の全部をミックスする。


大前提: 「開発で動いたから本番でも動く」は半分嘘

Docker の最大の売り文句は「環境差を吸収する」だが、それは イメージが同じ が前提。実務では:

  • 開発: bind mount でホットリロード、root で動かす、ポート全開
  • 本番: 読み取り専用、非 root、最小ポート、リソース制限

という 真逆の要件 がある。同じ compose.yml ではどちらも満たせない。

そして本番で起きる事故の多くは:

  • 「dev のままのイメージで本番デプロイ → 開発ツールが入ったまま → 攻撃面が広い」
  • 「リソース制限なしで動かして他コンテナを巻き込んで OOM」
  • 「ログがコンテナ内に溜まり続けてディスク埋まる」

今日はこれらを 全部 compose の機能で解決 する。

本日の到達点

  • マルチステージビルドでイメージサイズを 1/10 に
  • compose override で開発と本番を分離(DRY)
  • resource limits / read_only / non-root の本番設定
  • ログドライバの選定と回転設計
  • CI からのイメージビルド & レジストリ push の入口
  • K8s / ECS との位置付け

セッション①: マルチステージビルドで本番イメージを絞る(25-30分)

0. 録画スタート&作業ディレクトリ

mkdir -p ~/log ~/learn/infra/day13
cd ~/learn/infra/day13
script ~/log/infra_day13.log

1. なぜマルチステージか

# ----- 単一ステージ(NG) -----
FROM golang:1.23-alpine
WORKDIR /app
COPY . .
RUN go mod download
RUN go build -o /app/api .
CMD ["/app/api"]

docker build すると、出来上がりのイメージサイズは 約 800MB。中身:

  • Go コンパイラ
  • ソースコード全部
  • Git 履歴
  • ビルド時の中間ファイル

これを本番に送るのは無駄が多い。

# ----- マルチステージ(推奨) -----
FROM golang:1.23-alpine AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /out/api .
 
FROM gcr.io/distroless/static-debian12
COPY --from=build /out/api /api
USER 65532:65532
EXPOSE 8080
ENTRYPOINT ["/api"]

サイズ: 約 20MB(Go バイナリ + 最小ランタイム)。40分の1 になる。

マルチステージビルドの本質

1つの Dockerfile に複数の FROM を書ける。各ステージは独立したイメージで、最終的に保持されるのは 最後のステージのみ

[build stage]   ← Go コンパイラ、ソース、依存
     |
     | COPY --from=build /out/api /api
     v
[runtime stage] ← 最小ランタイム + 出来上がりバイナリ
     |
     v
最終イメージ(runtime stage のみ)

メリット

  • イメージサイズが激減: pull / push / デプロイが速い
  • 攻撃面が小さい: コンパイラ・パッケージマネージャ・シェルが入ってない
  • CI 環境を Dockerfile 内に閉じ込められる: 「ローカルに Go 1.23 が無い」とかにならない

各ベースイメージの選択肢

ベースサイズ用途
golang:1.23800MBビルド用
golang:1.23-alpine350MBビルド用(小さめ)
alpine:3.208MBランタイム(busybox/sh 入り)
gcr.io/distroless/static-debian122MB静的バイナリ用ランタイム(推奨)
scratch0MB完全空(debugしづらい)

distroless とは: Google が開発した「アプリだけ」入った最小イメージ。シェルすら入っていない。利点はセキュリティと小ささ、欠点はデバッグ時にシェルで入れないこと。

Go ビルドのお作法フラグ

CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /out/api .
  • CGO_ENABLED=0: C 言語連携を切る → 完全静的バイナリ。distroless / scratch で動く
  • GOOS=linux: クロスコンパイル時に明示(Mac から Linux イメージを作る)
  • -ldflags="-s -w": シンボル情報・デバッグ情報を削除してバイナリ縮小(30%減)

2. レイヤーキャッシュを効かせる順序

# ----- 悪い順序(毎回フルビルド) -----
FROM golang:1.23-alpine AS build
WORKDIR /src
COPY . .                          # ソース全部コピー
RUN go mod download
RUN go build -o /out/api .
 
# ----- 良い順序(依存だけキャッシュ) -----
FROM golang:1.23-alpine AS build
WORKDIR /src
COPY go.mod go.sum ./             # 依存定義だけ先にコピー
RUN go mod download                # 依存ダウンロード(変わらなければキャッシュ)
COPY . .                           # ソースコピー(変わるので毎回)
RUN go build -o /out/api .

Docker のレイヤーキャッシュ

Docker は各 RUN COPY をキャッシュ。前のレイヤーが変わっていなければ、それ以降を再利用する。

「ソース変更 → 全部再ビルド」を避けるには、変わりにくいものを先に変わりやすいものを後に 書く。

[依存定義 (go.mod)]    ← 変わりにくい → キャッシュヒット率高
[依存DL]
[ソースコード]          ← 変わりやすい
[ビルド]

開発中の docker build が「毎回 10 分かかる」なら、レイヤー順を見直す。

3. .dockerignore で送信物を絞る

# .dockerignore
.git
node_modules
.env
.env.*
*.log
.vscode
.idea
README.md
docs/
test/fixtures/
*.test
coverage/

.dockerignore の役割

docker build は「context」をビルド時にデーモンに送る。.dockerignore で除外したものは そもそも送られない

効果:

  • ビルドが速い: 巨大な .gitnode_modules を送らない
  • キャッシュが効きやすい: 不要なファイル変更でキャッシュが壊れない
  • シークレット混入防止: .env.dockerignore に入れて事故防止

重要: .gitignore とは別物。.dockerignore を必ず書く。

4. セキュリティ: 非 root ユーザー

FROM gcr.io/distroless/static-debian12
COPY --from=build /out/api /api
USER 65532:65532          # nonroot:nonroot
EXPOSE 8080
ENTRYPOINT ["/api"]

なぜ非 root か

デフォルトのコンテナは root で動く。これは:

  • コンテナ侵害時の権限が広い: 内部での権限昇格が容易
  • ホストへの脱獄リスク: コンテナエスケープ脆弱性があったとき被害が大きい
  • マウントしたファイルを破壊できる: bind mount したホストファイルを root 権限で書き換え

正しい設計:

  1. Dockerfile で USER <uid>:<gid> を指定
  2. ファイル所有権を非 root にして配置
  3. 必要な作業ディレクトリは事前に chown

distroless の 65532:65532nonroot ユーザーの慣習的 UID/GID。

アンチパターン: root で動かす

FROM ubuntu:22.04
RUN apt-get update && apt-get install -y curl
COPY app /app
CMD ["/app"]            # root で実行(USER 指定なし)

なぜNGか:

  • コンテナ内のあらゆる操作が root 権限
  • LSM (AppArmor/SELinux) を回避できる可能性
  • 「コンテナ脱出 → ホスト root」のリスク

対策:

RUN useradd -u 10001 -m app
COPY --chown=app:app app /app
USER app

セッション②: compose override と本番運用設定(25-30分)

5. compose override の階層化

~/learn/infra/day13/
├── compose.yml              # 共通の最小定義
├── compose.override.yml     # 開発用(docker compose up で自動マージ)
├── compose.prod.yml         # 本番用(明示的に -f で指定)
└── .env

compose.yml(共通):

services:
  api:
    image: myregistry/myapi:${TAG:-latest}
    environment:
      LOG_LEVEL: ${LOG_LEVEL:-info}
      DATABASE_URL: ${DATABASE_URL}
    depends_on:
      db:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "/api", "healthcheck"]
      interval: 10s
      timeout: 3s
      retries: 3
      start_period: 20s
 
  db:
    image: mysql:8.0
    environment:
      MYSQL_DATABASE: app
    volumes:
      - db_data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-uroot", "-p$$MYSQL_ROOT_PASSWORD"]
      interval: 5s
      retries: 10
      start_period: 20s
 
volumes:
  db_data:

compose.override.yml(開発、自動マージされる):

services:
  api:
    build: ./api               # 開発はローカルビルド
    image: myregistry/myapi:dev
    volumes:
      - ./api:/app             # ホットリロード用
    ports:
      - "8080:8080"
    environment:
      LOG_LEVEL: debug
      DATABASE_URL: "app:dev_password@tcp(db:3306)/app"
 
  db:
    environment:
      MYSQL_ROOT_PASSWORD: dev_root_pass
      MYSQL_USER: app
      MYSQL_PASSWORD: dev_password
    ports:
      - "127.0.0.1:3306:3306"   # 開発は GUI ツールから繋ぎたい

compose.prod.yml(本番、明示指定):

services:
  api:
    image: myregistry/myapi:${TAG}        # CI でビルド済みイメージを使う
    restart: always
    # ホストからのポート公開はしない(front の nginx 経由)
    ports: []
    environment:
      LOG_LEVEL: info
      DATABASE_URL: ${DATABASE_URL}        # CI Secrets から渡す
    deploy:
      resources:
        limits:
          memory: 512M
          cpus: "1.0"
        reservations:
          memory: 256M
          cpus: "0.5"
    read_only: true
    tmpfs:
      - /tmp
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"
    security_opt:
      - no-new-privileges:true
 
  db:
    image: mysql:8.0
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root_password
      MYSQL_USER: app
      MYSQL_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_root_password
      - db_password
    deploy:
      resources:
        limits:
          memory: 2G
          cpus: "2.0"
    logging:
      driver: json-file
      options:
        max-size: "50m"
        max-file: "5"
 
secrets:
  db_root_password:
    file: /opt/secrets/db_root_password.txt
  db_password:
    file: /opt/secrets/db_password.txt

起動コマンド:

# 開発(override が自動マージ)
docker compose up -d
# = docker compose -f compose.yml -f compose.override.yml up -d
 
# 本番(明示的に2ファイル指定、override はスキップ)
docker compose -f compose.yml -f compose.prod.yml up -d

override のマージルール

Compose は複数の YAML を マージ する。後ろに書いたファイルが優先。

  • スカラー値(image, restart): 上書き
  • リスト(ports, volumes): 結合
  • マップ(environment): マージ(同キーは上書き)

compose.override.ymldocker compose up で自動的に読まれる(ファイル名が固定)。本番用ファイルは別名にして -f で明示。

設計指針:

  • compose.yml: 共通定義(image 名は変数、healthcheck、depends_on 等)
  • compose.override.yml: 開発時のローカル差分(ホットリロード、debug ログ、ポート公開)
  • compose.prod.yml: 本番設定(resource limits、read_only、ログ、secret)
  • compose.staging.yml: ステージング用

6. Resource limits

services:
  api:
    image: myapi:1.0
    # Compose v2 でも mem_limit / cpus は使える(古い書き方)
    mem_limit: 512m
    cpus: "1.0"
 
    # 推奨は deploy.resources(Swarm 用だが Compose も読む)
    deploy:
      resources:
        limits:
          memory: 512M
          cpus: "1.0"
        reservations:
          memory: 256M
          cpus: "0.5"

limits と reservations の違い

  • limits(上限): ここを超えると OOM kill。CPU は throttle
  • reservations(予約): 最低保証。スケジューラが他コンテナとの競合時に守る

実務での値:

  • API(Go/Rust): 256-512M, 0.5-1 CPU
  • API(JVM/Ruby/Node): 512M-2G, 1-2 CPU
  • DB(MySQL/Postgres): 1-4G, 2-4 CPU
  • Cache (Redis): 256-512M, 0.5 CPU

負荷試験で実測してから決める。最初は緩めに付けて、メモリ使用量を観察 → 徐々に絞る。

アンチパターン: limits なし

services:
  api:
    image: myapi:1.0
    # 制限なし

なぜNGか:

  • メモリリークしたコンテナがホスト全体を食い潰す
  • OOM Killer がランダムに他コンテナを kill する可能性
  • 「特定 API が暴走したら全サービスが死ぬ」状態

必ず limits を付ける。「メモリ無制限が必要」なケースは存在しない。

OOM Kill が起きた時の確認

docker compose ps
# STATUS: Exited (137) 5 minutes ago
# ← exit code 137 = SIGKILL (128 + 9)、OOM の可能性大
 
docker inspect <container> | grep -i oom
# "OOMKilled": true
 
# Linux のログ
dmesg | grep -i 'killed process'
# Out of memory: Killed process 12345 (api)

7. read_only と tmpfs

services:
  api:
    image: myapi:1.0
    read_only: true              # ルートFS を読み取り専用に
    tmpfs:
      - /tmp                       # 書き込みが必要な場所だけ tmpfs
      - /var/run

read_only の効果

コンテナの ルートファイルシステムを書き込み禁止 にする。アプリが想定外の場所に書こうとしたら failure。

  • マルウェアがバイナリを書き換えて永続化するのを防ぐ
  • ログを /var/log/ に書く設計を強制的に「外に出す」(マウントか stdout)
  • 「コンテナはイミュータブル」の思想を実装で強制

書き込みが必要な場所tmpfs で別途許可。アプリ終了時にデータも消える(永続不要な一時ファイル向け)。

8. ログドライバ

services:
  api:
    image: myapi:1.0
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"
        compress: "true"

主なログドライバ

ドライバ用途
json-fileデフォルト。ファイルに JSON で書く
localjson-file より効率的なバイナリ形式
journaldsystemd の journal に流す
syslogsyslog サーバーへ
fluentdFluentd / Fluent Bit へ
awslogsCloudWatch Logs
gcplogsCloud Logging
noneログを破棄

json-file の罠: デフォルトでは ログがディスク上限まで溜まり続けるmax-sizemax-file を必ず指定。

アンチパターン: ログサイズ無制限

services:
  api:
    image: myapi:1.0
    # logging 指定なし → 無制限に json-file が書かれる

なぜ事故るか:

  • 1日 100MB ログを吐くアプリ → 数ヶ月でホストのディスクを埋める
  • ディスク枯渇で 全コンテナが死ぬ(書き込み不能で詰む)
  • 復旧時にログファイルを消すまでサービス停止

必須設定:

logging:
  driver: json-file
  options:
    max-size: "10m"     # 1ファイル 10MB
    max-file: "3"       # 3ファイルまで保持(最大30MB)

もしくは Docker daemon の daemon.json でデフォルトを変える:

{
  "log-driver": "json-file",
  "log-opts": { "max-size": "10m", "max-file": "3" }
}

12 Factor App: XI. Logs

ログをイベントストリームとして扱う アプリは出力ストリームのルーティングや保管を考えるべきでない。ログファイルを書くべきでない。代わりに各実行中のプロセスは、イベントストリームを stdout に書き出す

アプリは stdout/stderr に吐く のが正解。Docker / Compose / K8s がそれを拾って好きな場所に送る。

// 良い: 標準出力に書く
log.Println("user created", "user_id", id)
 
// 悪い: ファイルに直接書く
f, _ := os.OpenFile("/var/log/app.log", os.O_APPEND, 0644)
fmt.Fprintln(f, "user created")

9. イメージレジストリとビルドパイプライン

# ビルド & タグ付け
docker build -t myregistry/myapi:1.2.3 ./api
docker tag myregistry/myapi:1.2.3 myregistry/myapi:latest
 
# Login & Push
docker login myregistry.example.com
docker push myregistry/myapi:1.2.3
docker push myregistry/myapi:latest

レジストリの選択肢

サービス特徴
Docker Hub最初の選択肢、無料枠あり(pull 制限あり)
GitHub Container Registry (ghcr.io)GitHub Actions と相性◎、無料
AWS ECRAWS 環境、IAM 連携
GCP Artifact RegistryGCP 環境
Quay.ioRed Hat 系、脆弱性スキャン
Harborセルフホスト OSS

GitHub Actions でのビルド & push

# .github/workflows/release.yml
name: Build and Push
on:
  push:
    tags: ["v*"]
 
jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@v4
      - uses: docker/setup-buildx-action@v3
      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - uses: docker/build-push-action@v6
        with:
          context: ./api
          push: true
          tags: |
            ghcr.io/${{ github.repository }}/api:${{ github.ref_name }}
            ghcr.io/${{ github.repository }}/api:latest
          cache-from: type=gha
          cache-to: type=gha,mode=max

ポイント:

  • タグ運用: v1.2.3 のような SemVer タグでイメージを固定
  • キャッシュ: GitHub Actions cache を Docker BuildKit のキャッシュとして使う
  • マルチアーキ: platforms: linux/amd64,linux/arm64 で複数 CPU 対応

10. K8s / ECS との位置付け

Compose は本番に使えるか?

結論: 小規模なら使える、中規模以上は K8s/ECS が定石

Compose で本番運用が許容されるケース

  • 単一ホストで完結する(VPS 1台)
  • サービス数が少ない(< 10)
  • 高可用性が要らない(ダウンタイムが許容できる)
  • チーム規模が小さい

K8s / ECS に移行すべきケース

  • 複数ホスト跨ぎ
  • オートスケーリングが必要
  • 高度なロールアウト戦略(カナリア、ブルーグリーン)
  • ステートフルなサービスの安全な運用

大事なのは Compose の知識が無駄にならないこと

  • K8s の Pod は Compose の service に近い
  • Deployment は service + replicas
  • Service(ネットワーク)は networks の発展形
  • ConfigMap / Secret は environment / secrets の発展形

Compose で本番運用感覚を身につける → K8s への移行はスムーズ という学習パスは現代の王道。

アンチパターン: dev と本番で同一 yml

# 全環境で1ファイル(NG)
services:
  api:
    build: .                    # 本番でローカルビルド?
    volumes:
      - .:/app                   # 本番でホットリロード?
    environment:
      LOG_LEVEL: debug           # 本番で debug ログ?
    ports:
      - "8080:8080"              # 本番でも 0.0.0.0:8080 公開

なぜNGか:

  • 開発依存(ソースマウント、debug ログ)が本番に漏れる
  • 本番だけ違う設定にしたい時にコメントアウト合戦が始まる
  • 設定の差分が見えにくく、レビューしづらい

正解: override ファイル階層で分離。本番設定は本番 yml に集約。


練習課題

mkdir -p ~/learn/infra/day13/api
cd ~/learn/infra/day13
script ~/log/infra_day13.log
  1. マルチステージ Dockerfile を書き、docker build でサイズを比較(単一ステージ版と)
  2. docker history myapi:dev でレイヤー構成を確認
  3. .dockerignore を設定して、ビルド context のサイズを docker build --progress=plain で観察
  4. compose.yml / compose.override.yml / compose.prod.yml の3ファイルを作成
  5. 開発起動: docker compose up -d で override が効いていることを確認
  6. 本番想定起動: docker compose -f compose.yml -f compose.prod.yml config合成された設定を表示(実起動しなくてもよい)
  7. docker stats で resource limits が効いているか確認
  8. ログサイズ確認: docker inspect --format='{{.LogPath}}' <container> の出力を ls -lh で確認
  9. exit で script 終了

考察課題

  • distroless ベースのイメージで、稼働中に exec で入ってデバッグしたい時どうする?
  • resource limits の値を決めるには、本番でどんな計測をするべき?
  • compose.prod.yml で mem_limit ではなく deploy.resources.limits.memory を使ったのはなぜ?

締め: git で証跡を残す

cd ~/learn/infra/day13
git init -q 2>/dev/null || true
git add api/Dockerfile api/.dockerignore compose.yml compose.override.yml compose.prod.yml
git commit -m "feat(infra): 本番向け compose(マルチステージ、override、limits、ログ)"

チェックリスト

  • マルチステージビルドで本番イメージサイズを 10分の1 にできた
  • レイヤーキャッシュが効く順序で Dockerfile を書ける
  • .dockerignore の意義と書き方が分かる
  • USER で非 root 実行を設定できる
  • override / prod の3階層で設定分離できる
  • resource limits を設定し、docker stats で確認した
  • read_only + tmpfs の組み合わせを使える
  • ログサイズ制限の必要性を語れる
  • レジストリへの push / pull の流れが分かる
  • Compose と K8s/ECS の位置付けを説明できる

詰まった時のチートシート

やりたいこと書き方
ステージ定義FROM ... AS build
ステージから持ってくるCOPY --from=build /src /dst
非 root 実行USER 65532:65532
context 除外.dockerignore
本番起動docker compose -f compose.yml -f compose.prod.yml up -d
設定合成プレビューdocker compose -f ... config
メモリ制限deploy.resources.limits.memory: 512M
CPU 制限deploy.resources.limits.cpus: "1.0"
読み取り専用read_only: true
一時 FStmpfs: [/tmp]
ログサイズ制限logging.options.max-size: "10m"
イメージ pushdocker push <registry>/<image>:<tag>
動作確認docker stats

やらかし事例: 本番Compose の地雷

事例1: dev のイメージそのまま本番デプロイ

ホットリロード用に golang:1.22 をベースにしたまま本番に乗せ、コンパイラ + 開発依存込みの 1.5GB イメージが本番に。マルチステージで分離する。

事例2: リソース制限無しで OOM 連鎖

mem_limit 未設定 → 1コンテナのメモリリークでホストの swap 枯渇 → 他のコンテナまで道連れで死亡。本番では必ず制限。

事例3: ログ無限増殖でディスク満杯

default の json-file ドライバはローテーション無し。1年で50GB に。max-size / max-file を必ず設定。

事例4: root のまま本番起動

Dockerfile に USER appuser を書かず、コンテナ内で root 動作。攻撃で破られた瞬間にホストへの足掛かりに。非 root 必須。

事例5: Compose で済むのに k8s 導入して運用破綻

10コンテナ未満・1台で済む規模なのに k8s クラスタを建てて、自分たちで Etcd / Control Plane の面倒を見る羽目に。Compose で十分なケースは多い

対比表: Compose vs Kubernetes

観点docker composeKubernetes
スケール1台のホスト複数ノードクラスタ
学習コスト
運用負担軽い重い(control plane)
自動復旧restart policy 程度強力(ReplicaSet, Pod 再スケジュール)
Rolling update弱い(Compose Watch)標準機能
適性個人 / 小規模本番 / 開発 / CI中〜大規模本番、マイクロサービス
移行コストYAML をほぼ書き直し-

物語型対比: Blue-Green vs Rolling Update

Blue-Green デプロイ

[Blue 環境(現本番 v1)]  ←─ users
[Green 環境(v2 を起動 + 検証)]
   ↓ ロードバランサ切替
[Green 環境(v2 = 新本番)]  ←─ users
[Blue 環境(v1 = 待機)]    ← 失敗時に戻せる
  • メリット: 切替が一瞬、ロールバックが安全
  • デメリット: 2倍のリソース、DB スキーマ変更が同居期間を考慮要

Rolling Update

[v1 pod x 4] → 1個ずつ v2 に置換
v1: 4 → 3 → 2 → 1 → 0
v2: 0 → 1 → 2 → 3 → 4
  • メリット: リソース小、k8s の標準
  • デメリット: 一時的に v1 と v2 が同居、互換性必須

選び方

場面選択
1サーバー ComposeBlue-Green を手動
KubernetesRolling が標準
DB スキーマ破壊変更Blue-Green(同居期間のリスク回避)
細かい段階リリースCanary(Rolling の派生)

「実務OK」基準

  • 本番イメージが 50MB 未満で作れる: マルチステージを使いこなせる
  • docker compose -f ... -f ... up -d で環境を切り替えられる: 設定分離の感覚
  • resource limits の値を「なぜその数字か」根拠を持って言える: 観測 → 制限の習慣
  • ログ運用の事故シナリオを語れる: ディスク枯渇、回転、外部送信
  • Compose と K8s の境界線を語れる: いつ移行すべきか判断できる

自己評価チェックリスト

知識レベル

  • Compose と k8s の境界線を3つの観点で語れる
  • Blue-Green と Rolling の違いを場面別に判断できる
  • resource limits / read_only / non-root の本番設定3点セットを暗唱できる

実行レベル

  • compose.override.yml で開発設定を分離した
  • マルチステージで本番イメージを 50MB 未満にした
  • ログドライバを max-size 付きで設定した

メタ認知

  • 自分のチームのデプロイ戦略(Blue-Green or Rolling)の妥当性を見直した
  • Compose で十分な規模なのに k8s 導入を考えていないか棚卸しした

さらに深掘るなら


次のレッスン

レベル2 完了!次はレベル3 で Kubernetes 入門 → 本番デプロイの実践 に進む。Compose で学んだ概念(サービス、ネットワーク、ボリューム、ヘルスチェック、シークレット)が K8s の Pod/Service/Volume/Probe/Secret に直接対応する。今日までの理解が次の章で活きる。