0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Hybrid License System Day 21: Docker Composeでの統合

Last updated at Posted at 2025-12-20

🎄 科学と神々株式会社 アドベントカレンダー 2025

Hybrid License System Day 21: Docker Composeでの統合

統合・デプロイ編 (1/5)


📖 はじめに

Day 21では、Docker Composeを使ったマイクロサービスの統合を学びます。3つのサービスをコンテナ化し、docker-compose.ymlで一括管理する方法を理解しましょう。


🐳 Docker Composeとは?

定義

Docker Composeは、複数のDockerコンテナを定義・実行するツールです。YAMLファイル1つで、複数のサービスの設定、ネットワーク、ボリュームを管理できます。

version: '3.8'

services:
  api-gateway:    # サービス1
  auth-service:   # サービス2
  admin-service:  # サービス3

volumes:          # 共有ボリューム
  database-data:

networks:         # ネットワーク設定
  license-network:

🏗️ Hybrid License Systemの構成

システム全体図

┌────────────────────────────────────────────┐
│  Docker Compose Environment                │
│                                            │
│  ┌──────────────┐  ┌──────────────┐      │
│  │ API Gateway  │  │ Auth Service │      │
│  │  Port: 3000  │  │  Port: 3001  │      │
│  │  Node.js     │  │  Node.js     │      │
│  │  Alpine      │  │  Alpine      │      │
│  └──────┬───────┘  └──────┬───────┘      │
│         │                 │                │
│         │  ┌──────────────┘                │
│         │  │  ┌──────────────┐            │
│         │  │  │ Admin Service│            │
│         │  │  │  Port: 3002  │            │
│         │  │  │  Node.js     │            │
│         │  │  │  Alpine      │            │
│         │  │  └──────┬───────┘            │
│         └──┴─────────┘                     │
│              │                             │
│         ┌────▼────────────┐                │
│         │ Shared Database │                │
│         │    (Volume)     │                │
│         │  SQLite File    │                │
│         └─────────────────┘                │
│                                            │
│  Network: license-network (Bridge)        │
└────────────────────────────────────────────┘

📄 docker-compose.yml設計

完全版設定ファイル

version: '3.8'

services:
  # API Gateway (JavaScript/Express.js)
  api-gateway:
    build:
      context: ./api-gateway
      dockerfile: Dockerfile
    container_name: license-api-gateway
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - GATEWAY_PORT=3000
      - GATEWAY_HOST=0.0.0.0
      - AUTH_SERVICE_URL=http://auth-service:3001
      - ADMIN_SERVICE_URL=http://admin-service:3002
      - SERVICE_SECRET=${SERVICE_SECRET:-shared-secret-between-services}
      - CORS_ORIGINS=${CORS_ORIGINS:-*}
      - JWT_SECRET=${JWT_SECRET}
    depends_on:
      auth-service:
        condition: service_healthy
      admin-service:
        condition: service_healthy
    networks:
      - license-network
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "node", "-e", "require('http').get('http://localhost:3000/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1))"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 10s

  # Auth Service (JavaScript/Node.js)
  auth-service:
    build:
      context: ./auth-service
      dockerfile: Dockerfile
    container_name: license-auth-service
    ports:
      - "3001:3001"
    environment:
      - NODE_ENV=production
      - AUTH_SERVICE_PORT=3001
      - JWT_SECRET=${JWT_SECRET}
      - DATABASE_PATH=/app/data/licenses.db
      - SERVICE_SECRET=${SERVICE_SECRET:-shared-secret-between-services}
    volumes:
      - database-data:/app/data
    networks:
      - license-network
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "node", "-e", "require('http').get('http://localhost:3001/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1))"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 5s

  # Admin Service (JavaScript/Express.js)
  admin-service:
    build:
      context: ./admin-service
      dockerfile: Dockerfile
    container_name: license-admin-service
    ports:
      - "3002:3002"
    environment:
      - NODE_ENV=production
      - ADMIN_SERVICE_PORT=3002
      - DATABASE_PATH=/app/data/licenses.db
      - ADMIN_USERNAME=${ADMIN_USERNAME:-admin}
      - ADMIN_PASSWORD=${ADMIN_PASSWORD:-admin123}
    volumes:
      - database-data:/app/data
    networks:
      - license-network
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "node", "-e", "require('http').get('http://localhost:3002/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1))"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 5s

volumes:
  database-data:
    driver: local

networks:
  license-network:
    driver: bridge

🔧 Dockerfile最適化

Auth Service Dockerfile

# Auth Service Dockerfile
# JavaScript/Node.js implementation

FROM node:20-alpine

WORKDIR /app

# Install dependencies for better-sqlite3
RUN apk add --no-cache \
    python3 \
    make \
    g++ \
    sqlite-dev

# Copy package files
COPY package*.json ./

# Install dependencies
RUN npm ci --only=production

# Copy source code
COPY src/ ./src/

# Create directories
RUN mkdir -p /app/data

# Set non-root user
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nodejs -u 1001 && \
    chown -R nodejs:nodejs /app

USER nodejs

# Expose port
EXPOSE 3001

# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
  CMD node -e "require('http').get('http://localhost:3001/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1))"

# Run the service
CMD ["node", "src/standalone.js"]

API Gateway Dockerfile

# API Gateway Dockerfile
# JavaScript/Express.js implementation

FROM node:20-alpine

WORKDIR /app

# Copy package files
COPY package*.json ./

# Install dependencies
RUN npm ci --only=production

# Copy source code
COPY src/ ./src/

# Set non-root user
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nodejs -u 1001 && \
    chown -R nodejs:nodejs /app

USER nodejs

# Expose port
EXPOSE 3000

# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
  CMD node -e "require('http').get('http://localhost:3000/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1))"

# Run the service
CMD ["node", "src/server.js"]

Admin Service Dockerfile

# Admin Service Dockerfile
# JavaScript/Express.js + React implementation

# Stage 1: Build stage
FROM node:20-alpine AS builder

WORKDIR /app

# Install dependencies for better-sqlite3
RUN apk add --no-cache \
    python3 \
    make \
    g++ \
    sqlite-dev

# Copy package files
COPY package*.json ./

# Install dependencies
RUN npm ci --only=production

# Copy source code
COPY src/ ./src/

# Stage 2: Production stage
FROM node:20-alpine

WORKDIR /app

# Copy from builder
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package*.json ./
COPY --from=builder /app/src ./src

# Create directories
RUN mkdir -p /app/data

# Set non-root user
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nodejs -u 1001 && \
    chown -R nodejs:nodejs /app

USER nodejs

# Expose port
EXPOSE 3002

# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
  CMD node -e "require('http').get('http://localhost:3002/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1))"

# Run the service
CMD ["node", "src/server.js"]

🔒 環境変数管理

.env.example

# License System - Hybrid Implementation
# Environment Variables Configuration

# ============================================================
# API Gateway Configuration
# ============================================================
GATEWAY_PORT=3000
GATEWAY_HOST=0.0.0.0

# ============================================================
# Auth Service Configuration (JavaScript/Node.js)
# ============================================================
AUTH_SERVICE_URL=http://localhost:3001
AUTH_SERVICE_PORT=3001

# JWT Configuration
# IMPORTANT: Change this to a secure random string in production
# Generate with: openssl rand -base64 48
JWT_SECRET=your-super-secret-jwt-key-minimum-32-characters-change-this-in-production

# ============================================================
# Admin Service Configuration (JavaScript)
# ============================================================
ADMIN_SERVICE_URL=http://localhost:3002
ADMIN_SERVICE_PORT=3002

# Admin Credentials
# IMPORTANT: Change these in production
ADMIN_USERNAME=admin
ADMIN_PASSWORD=admin123

# ============================================================
# Database Configuration
# ============================================================
# For local development
DATABASE_PATH=./shared/database/licenses.db

# ============================================================
# Inter-Service Authentication
# ============================================================
# Shared secret for service-to-service communication
# Generate with: openssl rand -hex 32
SERVICE_SECRET=shared-secret-between-services-change-this-in-production

# ============================================================
# CORS Configuration
# ============================================================
# Comma-separated list of allowed origins
CORS_ORIGINS=http://localhost:3000,http://localhost:3002,http://localhost:8080

🚀 使用方法

1. 環境変数設定

# .envファイルを作成
cp .env.example .env

# セキュアな値を生成
openssl rand -base64 48  # JWT_SECRET用
openssl rand -hex 32     # SERVICE_SECRET用

# .envファイルを編集
vim .env

2. サービスビルド

# すべてのサービスをビルド
docker-compose build

# 特定のサービスのみビルド
docker-compose build auth-service

# キャッシュなしでビルド
docker-compose build --no-cache

3. サービス起動

# バックグラウンドで起動
docker-compose up -d

# フォアグラウンドで起動(ログ表示)
docker-compose up

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

4. ログ確認

# すべてのサービスのログ
docker-compose logs -f

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

# 最新100行のログ
docker-compose logs --tail=100 api-gateway

5. ヘルスチェック

# API Gateway
curl http://localhost:3000/health

# Auth Service
curl http://localhost:3001/health

# Admin Service
curl http://localhost:3002/health

6. サービス停止

# サービスを停止(コンテナは保持)
docker-compose stop

# サービスを停止してコンテナを削除
docker-compose down

# ボリュームも削除(データベースも削除される)
docker-compose down -v

📊 ヘルスチェックの詳細

ヘルスチェックの仕組み

healthcheck:
  test: ["CMD", "node", "-e", "require('http').get('http://localhost:3001/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1))"]
  interval: 30s      # 30秒ごとにチェック
  timeout: 10s       # 10秒でタイムアウト
  retries: 3         # 3回失敗したらunhealthy
  start_period: 10s  # 起動後10秒は失敗してもカウントしない

ヘルスチェックのライフサイクル

Starting → Healthy → Unhealthy → Healthy
           ↑          ↓
           └──────────┘
           (自動再試行)

depends_onとヘルスチェック

api-gateway:
  depends_on:
    auth-service:
      condition: service_healthy  # Auth ServiceがHealthyになってから起動
    admin-service:
      condition: service_healthy  # Admin ServiceがHealthyになってから起動

🗃️ データ永続化

Named Volumeの使用

volumes:
  database-data:  # Named Volume
    driver: local

services:
  auth-service:
    volumes:
      - database-data:/app/data  # Named Volumeをマウント

  admin-service:
    volumes:
      - database-data:/app/data  # 同じVolumeを共有

ボリュームの管理

# ボリューム一覧
docker volume ls

# ボリュームの詳細情報
docker volume inspect hybrid_database-data

# ボリュームのバックアップ
docker run --rm \
  -v hybrid_database-data:/data \
  -v $(pwd)/backup:/backup \
  alpine tar czf /backup/database-backup-$(date +%Y%m%d).tar.gz /data

# ボリュームのリストア
docker run --rm \
  -v hybrid_database-data:/data \
  -v $(pwd)/backup:/backup \
  alpine tar xzf /backup/database-backup-20251121.tar.gz -C /

🌐 ネットワーク設定

Bridge Networkの使用

networks:
  license-network:
    driver: bridge  # Dockerのデフォルトネットワークドライバー

サービス間通信

// API Gatewayから Auth Serviceへの通信
const AUTH_SERVICE_URL = 'http://auth-service:3001';  // サービス名で解決

// Docker Composeが自動的にDNS解決してくれる
// auth-service → 172.18.0.2 (内部IP)

🚀 次のステップ

Day 22では、テスト戦略について学びます。統合テスト、E2Eテスト、パフォーマンステスト、セキュリティテストの実装方法を理解しましょう。


🔗 関連リンク


次回予告: Day 22では、Jestを使った統合テストとE2Eテストの実装を詳しく解説します!


Copyright © 2025 Gods & Golem, Inc. All rights reserved.

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?