Docker 入門

Docker Composeのデペンデンシー管理とオーケストレーション

Docker Compose は、マルチコンテナアプリケーションの定義と管理において非常に強力であり、サービス間のデペンデンシー(依存関係)を宣言し、それらの起動とシャットダウンをオーケストレーションするメカニズムを提供します。本章では、Docker Compose がアプリケーションのデペンデンシーをどのように処理するか、また、各サービスが正しい順序で起動し、効率的に連携して動作することを保証するために提供される豊富なオーケストレーション機能について深く掘り下げます。

1. サービスデペンデンシーの定義

Docker Compose を使用すると、サービス間のデペンデンシーを明示的に定義でき、サービスが特定の順序で作成および起動されることを保証できます。これは、データベースの準備が整うまで待機する必要がある Web アプリケーションなど、特定のコンポーネントが実行されないと正常に動作しないアプリケーションにとって不可欠です。

docker-compose.yml ファイルでデペンデンシーを定義するには、主に2つのアプローチがあります。単独で depends_on を使用する方法と、healthcheck (ヘルスチェック)を depends_on と組み合わせて使用する方法です。

1.1 depends_on を使用した起動順序の制御

depends_on キーは、サービスの起動順序を決定するためのデペンデンシーを確立します。depends_on を使用すると、Compose は、デペンデンシーを宣言しているサービスよりも前に、依存されている側のサービスが起動することを保証します。同時に、サービスを停止または削除する際には、逆の順序で処理されることも保証します。

フロントエンド Web サーバー(Nginx など)、バックエンド API(Python Flask アプリなど)、およびデータベース(PostgreSQL など)を含むシンプルな Web アプリケーションを想像してください。バックエンド API はデータの読み書きのためにデータベースの実行を必要とし、Nginx フロントエンドはリクエストをプロキシするためにバックエンド API を必要とします。

以下は、depends_on をデモンストレーションする docker-compose.yml の例です:

version: '3.8'
services:
  db:
    image: postgres:13
    environment:
      POSTGRES_DB: mydatabase
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
    volumes:
      - db_data:/var/lib/postgresql/data
    networks:
      - app_network
      
  backend:
    build: ./backend # 'backend' ディレクトリに Dockerfile があると仮定
    ports:
      - "8000:8000"
    environment:
      DATABASE_URL: postgres://user:password@db:5432/mydatabase
    depends_on:
      - db # backend サービスは db サービスに依存する
    networks:
      - app_network
      
  frontend:
    image: nginx:latest
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - backend # frontend サービスは backend サービスに依存する
    networks:
      - app_network
      
networks:
  app_network:
    driver: bridge
volumes:
  db_data:

このコンフィギュレーションでは:

  • backend は、db が起動するまで待機してから起動を開始します。
  • frontend は、backend が起動するまで待機してから起動を開始します。

重要な注意事項: 単純な depends_on は、依存されているサービスのコンテナが「起動した」ことのみを保証します。コンテナ内部のアプリケーションの準備が整う、あるいは healthy な状態になるまで待機するわけではありません。例えば、db コンテナが稼働し始めると、db 内部の PostgreSQL サーバーがまだ初期化中で接続を受け付けられない状態であっても、backend は即座に起動してしまいます。これにより、バックエンドがデータベースの準備完了前に接続を試みてしまう「レースコンディション(Race Conditions)」が発生し、最終的に接続エラーやアプリケーションのクラッシュを引き起こす可能性があります。

1.2 condition と healthcheck を組み合わせたサービス状態の確証

上記のレースコンディション問題を解決するために、Compose はバージョン 3.4 から depends_oncondition オプションを導入しました。これにより、依存されているサービスが満たすべき条件を指定でき、その条件が満たされて初めて現在のサービスが起動するようになります。利用可能な条件には、service_started(起動済み、デフォルト値)、service_healthy(ヘルスチェック通過済み)、および service_completed_successfully(正常に完了)が含まれます。

service_healthy 条件は、healthcheck 定義と組み合わせて使用すると特に効果的です。healthcheck を使用するとコマンドを定義でき、Docker はコンテナ内部で定期的にそのコマンドを実行して、サービスが正常に稼働し続けているかどうかを判断します。

healthcheckdepends_on: condition: service_healthy を利用して、先ほどの例を最適化してみましょう:

version: '3.8'
services:
  db:
    image: postgres:13
    environment:
      POSTGRES_DB: mydatabase
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
    volumes:
      - db_data:/var/lib/postgresql/data
    networks:
      - app_network
    healthcheck: # データベースサービス用のヘルスチェックを定義
      test: ["CMD-SHELL", "pg_isready -U user -d mydatabase"]
      interval: 5s
      timeout: 5s
      retries: 5
      
  backend:
    build: ./backend
    ports:
      - "8000:8000"
    environment:
      DATABASE_URL: postgres://user:password@db:5432/mydatabase
    depends_on:
      db:
        condition: service_healthy # バックエンドは、db が healthy ステータスを報告するまで待機する
    networks:
      - app_network
    healthcheck: # バックエンドサービス用のヘルスチェックを定義
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"] # ヘルスチェック用 API エンドポイントがあると仮定
      interval: 10s
      timeout: 5s
      retries: 3
      
  frontend:
    image: nginx:latest
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      backend:
        condition: service_healthy # フロントエンドは、バックエンドが healthy ステータスを報告するまで待機する
    networks:
      - app_network
      
networks:
  app_network:
    driver: bridge
volumes:
  db_data:

この更新された例では:

  • db サービスには healthcheck があり、pg_isready コマンドを使用して PostgreSQL が接続を受け付ける準備ができているかを確認します。
  • backend サービスは、db サービスのヘルスチェックが healthy ステータスを報告するまで起動しません。
  • backend サービスにも独自の healthcheck があり、frontend はそれを待機します。
  • frontend サービスは、backend サービスが healthy を報告するまで起動しません。

このアプローチにより、起動プロセスが極めて堅牢になり、サービスがまだ利用できない依存リソースに接続しにいくことを防ぎます。

1.3 仮定のシナリオ:マイクロサービス決済システム

決済処理システム用のマイクロサービスアーキテクチャを想像してみてください:

  • payment-gateway (ペイメントゲートウェイ): 外部からの決済リクエストを処理し、トランザクションの詳細を保存する必要があります。
  • transaction-database (トランザクションデータベース): すべてのトランザクション記録を保存します。
  • fraud-detection-service (不正検知サービス): トランザクションに不審なアクティビティがないか分析します。transaction-database のデータを必要とします。
  • notification-service (通知サービス): 決済の成功または失敗のメール/SMS アラートを送信します。通知をトリガーするために payment-gateway に依存します。
version: '3.8'
services:
  transaction-database:
    image: mysql:8
    environment:
      MYSQL_ROOT_PASSWORD: root_password
      MYSQL_DATABASE: transactions
    volumes:
      - transaction_db_data:/var/lib/mysql
    networks:
      - payment_network
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-proot_password"]
      interval: 10s
      timeout: 5s
      retries: 5
      
  fraud-detection-service:
    build: ./fraud-detection
    environment:
      DB_HOST: transaction-database
      DB_NAME: transactions
      DB_USER: root
      DB_PASSWORD: root_password
    depends_on:
      transaction-database:
        condition: service_healthy
    networks:
      - payment_network
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:5001/health"] # 不正検知サービスにヘルスエンドポイントがあると仮定
      interval: 15s
      timeout: 5s
      retries: 3
      
  payment-gateway:
    build: ./payment-gateway
    ports:
      - "8080:8080"
    environment:
      TRANSACTION_SERVICE_URL: http://fraud-detection-service:5001 # ゲートウェイは不正検知サービスと通信する
    depends_on:
      fraud-detection-service:
        condition: service_healthy # ペイメントゲートウェイは不正検知サービスが healthy になるまで待機する
    networks:
      - payment_network
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/status"] # ゲートウェイにステータスエンドポイントがあると仮定
      interval: 10s
      timeout: 5s
      retries: 3
      
  notification-service:
    build: ./notification-service
    environment:
      GATEWAY_URL: http://payment-gateway:8080 # 通知サービスはゲートウェイのイベントをサブスクライブする可能性がある
    depends_on:
      payment-gateway:
        condition: service_healthy # 通知サービスはイベントを受信するためにゲートウェイの稼働を必要とする
    networks:
      - payment_network
      
networks:
  payment_network:
    driver: bridge
volumes:
  transaction_db_data:

これにより、堅牢な起動シーケンスが確保されます:データベースが healthy になった後に不正検知サービスが起動し、不正検知サービスが healthy になった後にペイメントゲートウェイがリクエストの受付を開始し、ペイメントゲートウェイが正常に稼働した後に通知サービスが準備完了となります。

2. オーケストレーション機能の拡張

シンプルな起動順序の制御に加えて、Docker Compose はマルチコンテナアプリケーションを効果的に管理するためのいくつかの追加オーケストレーション機能を提供しています。

2.1 コンテナリスタートポリシー (Restart Policies)

リスタートポリシーは、コンテナが停止した際に Docker がどのように反応すべきかを規定します。これは、アプリケーションの可用性を維持し、障害から自動的にリカバリするために不可欠です。各サービスに対して restart キーを使用してリスタートポリシーを定義できます。

一般的な restart ポリシーの値:

  • no: コンテナを自動的に再起動しません(デフォルト値)。
  • on-failure: コンテナが非ゼロのエグジットコード(エラーの発生を示す)で終了した場合にのみ再起動します。
  • always: エグジットコードに関係なく、コンテナが停止した場合は常に再起動します。Docker デーモンが起動した際、デーモン停止時に実行中だった always 設定のコンテナも再起動されます。
  • unless-stopped: 明示的に停止されない限り(例:docker stop <コンテナ名> 経由)、常にコンテナを再起動します。Docker デーモンが再起動した場合、このコンテナも追従して再起動されます。

リスタートポリシーを伴う例:

version: '3.8'
services:
  db:
    image: postgres:13
    # ... (環境変数やデータボリューム等のコンフィギュレーションは省略)
    restart: unless-stopped # データベースは手動で停止されない限り常に実行し続けるべき
    
  backend:
    build: ./backend
    # ...
    depends_on:
      db:
        condition: service_healthy
    restart: on-failure # バックエンドがクラッシュした場合は再起動すべき
    
  frontend:
    image: nginx:latest
    # ...
    depends_on:
      backend:
        condition: service_healthy
    restart: always # フロントエンド Web サーバーは常に利用可能状態を維持しようと試みるべき

この例では:

  • db サービスは、docker stop で手動で停止されない限り、自動的に再起動を続けます。
  • backend サービスは、エラーに遭遇して非ゼロのステータスコードで終了した場合にのみ再起動します。
  • frontend サービスは、停止した場合は常に再起動を試み、Web インターフェースが継続的に利用可能であることを保証します。

2.2 サービススケーリング (Service Scaling)

Docker Compose を使用するとサービスを「スケール」させることができます。これは、増加する負荷に対応したりデータのリダンダンシ(冗長性)を提供したりするために、特定のサービスの同一のインスタンス(レプリカ)を複数実行できることを意味します。Compose 自体は Docker Swarm や Kubernetes のように動的かつ自動的なスケーリングを提供するわけではありませんが、docker compose up --scale コマンドを使用して手動でサービスをスケールさせることができます。

例えば、backend サービスのインスタンスを 3 つ実行するには:

docker compose up -d --scale backend=3

Compose は backend サービス用に 3 つの独立したコンテナを作成し、各コンテナには一意の名前(例:myapp-backend-1, myapp-backend-2, myapp-backend-3)が付けられます。これにより負荷が分散され、一定のフォールトトレランスがもたらされます。1つのバックエンドインスタンスがダウンしても、他のインスタンスがリクエストの処理を継続できます(今回の例で言えば、Nginx のようなロードバランサーがトラフィックを各インスタンスへディスパッチするように設定されていることが前提となります)。

2.3 実践アプリケーション:ECプラットフォームアーキテクチャ

いくつかの重要なサービスを含む EC プラットフォームを検討してみましょう:

  • Product Catalog Service (プロダクトカタログサービス): 商品情報、画像、価格を提供します。(データベースから読み取り)
  • Order Processing Service (オーダープロセッシングサービス): ユーザーの注文を処理し、在庫を更新します。(データベースへ書き込み、ペイメントゲートウェイとインタラクション)
  • User Authentication Service (ユーザー認証サービス): ユーザーのログインと登録を管理します。(データベースの読み書き)
  • Redis Cache (Redis キャッシュ): セッション管理と高速なデータ検索のために複数のサービスで使用されます。
  • PostgreSQL Database (PostgreSQL データベース): すべてのサービスの永続化ストレージ。
  • Load Balancer (Nginx ロードバランサー): トラフィックをプロダクトカタログ、オーダープロセッシング、およびユーザー認証サービスにルーティングします。

堅牢な Compose コンフィギュレーションでは、デペンデンシーとオーケストレーション機能をフル活用します:

version: '3.8'
services:
  database:
    image: postgres:13
    environment:
      POSTGRES_DB: ecommerce_db
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
    volumes:
      - db_data:/var/lib/postgresql/data
    networks:
      - ecommerce_network
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U user -d ecommerce_db"]
      interval: 5s
      timeout: 5s
      retries: 5
      
  redis:
    image: redis:6-alpine
    networks:
      - ecommerce_network
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 1s
      timeout: 3s
      retries: 5
      
  product-catalog:
    build: ./product-catalog
    environment:
      DB_HOST: database
      REDIS_HOST: redis
    depends_on:
      database:
        condition: service_healthy
      redis:
        condition: service_healthy
    networks:
      - ecommerce_network
    restart: on-failure
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8001/health"]
      interval: 10s
      timeout: 5s
      retries: 3
      
  user-auth:
    build: ./user-auth
    environment:
      DB_HOST: database
      REDIS_HOST: redis
    depends_on:
      database:
        condition: service_healthy
      redis:
        condition: service_healthy
    networks:
      - ecommerce_network
    restart: on-failure
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8002/health"]
      interval: 10s
      timeout: 5s
      retries: 3
      
  order-processing:
    build: ./order-processing
    environment:
      DB_HOST: database
      PRODUCT_CATALOG_URL: http://product-catalog:8001
    depends_on:
      database:
        condition: service_healthy
      product-catalog:
        condition: service_healthy
    networks:
      - ecommerce_network
    restart: on-failure
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8003/health"]
      interval: 10s
      timeout: 5s
      retries: 3
      
  nginx:
    image: nginx:latest
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      product-catalog:
        condition: service_healthy
      user-auth:
        condition: service_healthy
      order-processing:
        condition: service_healthy
    networks:
      - ecommerce_network
    restart: always
    
networks:
  ecommerce_network:
    driver: bridge
volumes:
  db_data:

このコンフィギュレーションにより以下が保証されます:

  • どのアプリケーションサービスが接続を試みるよりも前に、Database と Redis が healthy であること。
  • Nginx ロードバランサーがトラフィックのルーティングを開始する前に、すべてのアプリケーションサービス(プロダクトカタログ、ユーザー認証、オーダープロセッシング)が healthy であること。
  • すべてのコアインフラストラクチャ(database, redis, nginx)に restart: unless-stopped または always が設定され、高可用性が確保されていること。
  • アプリケーションサービスに restart: on-failure が設定され、内部エラーからのリカバリが可能であること。