🐳

複数マイクロサービスのDevContainer開発環境で共有インフラを自動起動する

に公開

はじめに

マイクロサービスアーキテクチャの開発では、複数のサービスが共通のインフラ(データベース、キャッシュ、メッセージキューなど)を共有することが一般的です。VSCode の DevContainer 機能を使うと、各サービスごとに独立した開発環境を構築できますが、共有インフラの管理が課題になります。

本記事では、複数の DevContainer で共有インフラを効率的に管理し、自動起動する方法を紹介します。

課題

従来の構成では、以下のような問題がありました。

問題 1: インフラの重複起動

各サービスの DevContainer がそれぞれ PostgreSQL や Redis を起動すると、リソースの無駄遣いになります。

# service-a/compose.yml
services:
  service-a: ...
  postgres: # service-aのPostgreSQL
    image: postgres:15
# service-b/compose.yml
services:
  service-b: ...
  postgres: # service-bのPostgreSQL(別インスタンス!)
    image: postgres:15

問題 2: 手動での起動が必要

開発者が毎回手動で共有インフラを起動する必要があり、ちょっと面倒です。

# 毎回これが必要...
cd infrastructure
docker compose up -d
cd ../service-a
code .

問題 3: コンテナ名の競合

複数のサービスが同じコンテナ名を使おうとして競合します。

Error: The container name "/postgres" is already in use

解決策

以下の 3 つの仕組みを組み合わせて解決します。

  1. 共有インフラの独立管理
  2. 冪等性のある起動スクリプト
  3. DevContainer の自動起動機能(initializeCommand)

アーキテクチャ

┌─────────────────────────────────────────┐
│ 共有インフラ(1回だけ起動)              │
│  ┌──────────────┐  ┌──────────────┐    │
│  │  PostgreSQL  │  │    Redis     │    │
│  └──────────────┘  └──────────────┘    │
│          共有ネットワーク                │
└─────────────────────────────────────────┘
              │
      ┌───────┼───────┐
      │       │       │
┌─────▼──┐ ┌──▼────┐ ┌──▼─────┐
│Service │ │Service│ │Service │
│   A    │ │   B   │ │   C    │
└────────┘ └───────┘ └────────┘

実装

ディレクトリ構造

project-root/
├── .devcontainer/
│   ├── shared-infrastructure.yml    # 共有インフラ定義
│   ├── start-infrastructure.sh      # 起動スクリプト
│   ├── service-a/
│   │   ├── devcontainer.json
│   │   └── compose.yml
│   ├── service-b/
│   │   ├── devcontainer.json
│   │   └── compose.yml
│   └── service-c/
│       ├── devcontainer.json
│       └── compose.yml
├── service-a/
├── service-b/
└── service-c/

1. 共有インフラの定義

shared-infrastructure.yml
# 共有インフラ定義
services:
  postgres:
    image: postgres:15
    container_name: shared-postgres
    environment:
      - POSTGRES_USER=devuser
      - POSTGRES_PASSWORD=devpass
      - POSTGRES_DB=devdb
    ports:
      - "5432:5432"
    volumes:
      - postgres-data:/var/lib/postgresql/data
    networks:
      - shared-network
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U devuser"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    container_name: shared-redis
    ports:
      - "6379:6379"
    volumes:
      - redis-data:/data
    networks:
      - shared-network
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

volumes:
  postgres-data:
  redis-data:

networks:
  shared-network:
    name: shared-network
    driver: bridge

2. 冪等性のある起動スクリプト

start-infrastructure.sh
#!/bin/bash
# 共有インフラ起動スクリプト(冪等性あり)

set -e

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

# PostgreSQLとRedisが両方起動しているかチェック
POSTGRES_RUNNING=$(docker ps --filter "name=shared-postgres" \
  --filter "status=running" --format "{{.Names}}" 2>/dev/null || echo "")
REDIS_RUNNING=$(docker ps --filter "name=shared-redis" \
  --filter "status=running" --format "{{.Names}}" 2>/dev/null || echo "")

if [ -n "$POSTGRES_RUNNING" ] && [ -n "$REDIS_RUNNING" ]; then
    echo "共有インフラは既に起動しています"
    exit 0
fi

echo "共有インフラを起動します..."

# ネットワークが存在しない場合は作成
if ! docker network inspect shared-network >/dev/null 2>&1; then
    echo "ネットワーク 'shared-network' を作成..."
    docker network create shared-network
fi

# 共有インフラを起動
cd "$SCRIPT_DIR"
docker compose -f shared-infrastructure.yml up -d

echo "共有インフラの起動が完了しました"

3. 各サービスの DevContainer 設定

devcontainer.json

service-a/devcontainer.json
{
  "name": "service-a",
  "dockerComposeFile": "./compose.yml",
  "service": "service-a",
  "workspaceFolder": "/workspace/service-a",

  // 共有インフラを自動チェック&起動
  "initializeCommand": "bash -c 'SCRIPT_DIR=${localWorkspaceFolder}/.devcontainer; [ ! -d \"$SCRIPT_DIR\" ] && SCRIPT_DIR=$(dirname ${localWorkspaceFolder})/.devcontainer; cd \"$SCRIPT_DIR\" && ./start-infrastructure.sh'",

  "customizations": {
    "vscode": {
      "extensions": [
        "ms-python.python"
      ]
    }
  }
}

compose.yml

service-a/compose.yml
services:
  service-a:
    build:
      context: ../..
      dockerfile: ./service-a/Dockerfile
    container_name: service-a-dev
    environment:
      - DATABASE_URL=postgresql://devuser:devpass@shared-postgres:5432/devdb
      - REDIS_URL=redis://shared-redis:6379
    ports:
      - "8001:8000"
    volumes:
      - ../../:/workspace
    networks:
      - shared-network
    command: sleep infinity

# 共有ネットワークを参照
networks:
  shared-network:
    name: shared-network
    external: true

使い方

初回起動

  1. VSCode で任意のサービスフォルダを開く
  2. Ctrl+Shift+PDev Container: Reopen in Container
  3. 自動的に共有インフラがチェック&起動される
  4. DevContainer が起動し、開発開始!

2 つ目以降のサービス

  1. VSCode で別のサービスフォルダを開く
  2. Dev Container: Reopen in Container
  3. 共有インフラは既に起動しているのでスキップ
  4. 開発開始!
# 実行ログの例

# 1つ目のサービス(service-a)
共有インフラを起動します...
ネットワーク 'shared-network' を作成...
共有インフラの起動が完了しました

# 2つ目のサービス(service-b)
共有インフラは既に起動しています

メリット

1. ゼロコンフィグ

開発者は何も意識せず、VSCode で DevContainer を開くだけで開発を開始できます。

2. リソース効率

共有インフラは 1 インスタンスのみで、複数のサービスから参照されます。

従来: PostgreSQL × 3 = メモリ 1.5GB
改善後: PostgreSQL × 1 = メモリ 0.5GB

3. 複数サービスの同時開発

複数のサービスを同時に起動して開発できます。

service-a (port: 8001)
service-b (port: 8002)
service-c (port: 8003)
shared-postgres (port: 5432)
shared-redis (port: 6379)

4. 冪等性

何度実行しても安全で、予測可能な動作をします。

ポイント

パス解決の工夫

initializeCommandでは、プロジェクトルート全体を開いた場合と、個別サービスを開いた場合の両方に対応する必要があります。

# プロジェクトルート全体を開いた場合
${localWorkspaceFolder}/.devcontainer が存在する

# 個別サービスを開いた場合
$(dirname ${localWorkspaceFolder})/.devcontainer を使用

ネットワークの共有

各サービスの compose.yml で、共有ネットワークをexternal: trueで参照します。

networks:
  shared-network:
    name: shared-network
    external: true # <- 重要!

ヘルスチェック

共有インフラにヘルスチェックを設定することで、サービスが確実に起動してから接続できます。

healthcheck:
  test: ["CMD-SHELL", "pg_isready -U devuser"]
  interval: 10s
  timeout: 5s
  retries: 5

応用例

ローカル S3(MinIO)の追加

shared-infrastructure.yml
services:
  minio:
    image: minio/minio:latest
    container_name: shared-minio
    command: server /data --console-address ":9001"
    environment:
      - MINIO_ROOT_USER=minioadmin
      - MINIO_ROOT_PASSWORD=minioadmin
    ports:
      - "9000:9000"
      - "9001:9001"
    volumes:
      - minio-data:/data
    networks:
      - shared-network

メッセージキュー(RabbitMQ)の追加

shared-infrastructure.yml
services:
  rabbitmq:
    image: rabbitmq:3-management-alpine
    container_name: shared-rabbitmq
    ports:
      - "5672:5672"
      - "15672:15672"
    volumes:
      - rabbitmq-data:/var/lib/rabbitmq
    networks:
      - shared-network

まとめ

本記事では、複数のマイクロサービス開発環境で共有インフラを効率的に管理する方法を紹介しました。

ポイント

  • 共有インフラを独立して管理
  • 冪等性のある起動スクリプト
  • DevContainer の initializeCommand で自動起動
  • 複数サービスの同時開発が可能
  • ゼロコンフィグで開発者体験が向上

この構成により、チーム全体で統一された開発環境を構築でき、開発効率が大幅に向上します。

Discussion