複数マイクロサービスの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 つの仕組みを組み合わせて解決します。
- 共有インフラの独立管理
- 冪等性のある起動スクリプト
- 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. 共有インフラの定義
# 共有インフラ定義
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. 冪等性のある起動スクリプト
#!/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
{
"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
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
使い方
初回起動
- VSCode で任意のサービスフォルダを開く
-
Ctrl+Shift+P→Dev Container: Reopen in Container - 自動的に共有インフラがチェック&起動される
- DevContainer が起動し、開発開始!
2 つ目以降のサービス
- VSCode で別のサービスフォルダを開く
Dev Container: Reopen in Container- 共有インフラは既に起動しているのでスキップ
- 開発開始!
# 実行ログの例
# 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)の追加
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)の追加
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