FastAPI構成メモ：4つのコンテナ（cws-instrumentation-init, datadog-agent, migration-runner, my-app）による本番設計

## はじめに

AWS ECS Fargate 上で FastAPI アプリを運用するためのタスク定義を作成した際の構成を整理しました。

:::note info
Datadog のログ監視、初期化処理、マイグレーション実行などを複数のコンテナで分担する構成としています。
:::

**個人の備忘録程度の走り書きとなっておりますが、温かい目で見守っていただければ幸いです。**

## 書こうと思ったきっかけ

受講しているITスクールのハッカソンの開発の一環で、Datadogやマイグレーションの構成に悩んだため、今後の再利用と共有のために記事としてまとめておくことにしました。

**作成後の画面**

![Screenshot 2025-05-11 at 8.24.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3662571/1d769d87-4979-4eab-9f0a-066478105587.png)

## 作成したタスク定義JSON

```json
{
  "containerDefinitions": [
    {
      "name": "cws-instrumentation-init",
      "image": "datadog/cws-instrumentation:latest",
      "cpu": 0,
      "portMappings": [],
      "essential": false,
      "command": [
        "/cws-instrumentation",
        "setup",
        "--cws-volume-mount",
        "/cws-instrumentation-volume"
      ],
      "environment": [],
      "mountPoints": [
        {
          "sourceVolume": "cws-instrumentation-volume",
          "containerPath": "/cws-instrumentation-volume",
          "readOnly": false
        }
      ],
      "volumesFrom": [],
      "user": "0",
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/datadog",
          "awslogs-region": "ap-northeast-1",
          "awslogs-stream-prefix": "cws-instrumentation-init"
        }
      },
      "systemControls": []
    },
    {
      "name": "datadog-agent",
      "image": "datadog/agent:latest",
      "cpu": 0,
      "portMappings": [],
      "essential": true,
      "environment": [
        {
          "name": "DD_CONTAINER_METRICS_ENABLED",
          "value": "true"
        },
        {
          "name": "DD_RUNTIME_SECURITY_CONFIG_ENABLED",
          "value": "false"
        },
        {
          "name": "DD_TRACE_ENABLED",
          "value": "false"
        },
        {
          "name": "DD_ORCHESTRATOR_EXPLORER_ENABLED",
          "value": "false"
        },
        {
          "name": "DD_SYSTEM_PROBE_ENABLED",
          "value": "false"
        },
        {
          "name": "DD_APM_ENABLED",
          "value": "false"
        },
        {
          "name": "DD_LOGS_ENABLED",
          "value": "false"
        },
        {
          "name": "DD_API_KEY",
          "value": "xxx"
        },
        {
          "name": "DD_SECURITY_AGENT_ENABLED",
          "value": "false"
        },
        {
          "name": "DD_SITE",
          "value": "ap1.datadoghq.com"
        },
        {
          "name": "DD_PROCESS_AGENT_ENABLED",
          "value": "false"
        },
        {
          "name": "ECS_FARGATE",
          "value": "true"
        },
        {
          "name": "DD_PROCESS_CONFIG_ENABLED",
          "value": "false"
        },
        {
          "name": "DD_RUNTIME_SECURITY_CONFIG_EBPFLESS_ENABLED",
          "value": "false"
        }
      ],
      "mountPoints": [],
      "volumesFrom": [],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/datadog",
          "awslogs-region": "ap-northeast-1",
          "awslogs-stream-prefix": "datadog-agent"
        }
      },
      "healthCheck": {
        "command": [
          "CMD-SHELL",
          "/probe.sh"
        ],
        "interval": 30,
        "timeout": 5,
        "retries": 2,
        "startPeriod": 60
      },
      "systemControls": []
    },
    {
      "name": "my-app-repo",
      "image": "xxx.dkr.ecr.ap-northeast-1.amazonaws.com/my-app-repo:latest",
      "cpu": 0,
      "portMappings": [
        {
          "containerPort": 8000,
          "hostPort": 8000,
          "protocol": "tcp"
        }
      ],
      "essential": true,
      "entryPoint": [
        "/bin/sh",
        "-c"
      ],
      "command": [
        "uvicorn main:app --host 0.0.0.0 --port 8000 --log-level info"
      ],
      "environment": [],
      "mountPoints": [
        {
          "sourceVolume": "cws-instrumentation-volume",
          "containerPath": "/cws-instrumentation-volume",
          "readOnly": true
        }
      ],
      "volumesFrom": [],
      "linuxParameters": {
        "capabilities": {
          "add": [
            "SYS_PTRACE"
          ],
          "drop": []
        }
      },
      "dependsOn": [
        {
          "containerName": "datadog-agent",
          "condition": "HEALTHY"
        },
        {
          "containerName": "cws-instrumentation-init",
          "condition": "SUCCESS"
        },
        {
          "containerName": "migration-runner",
          "condition": "SUCCESS"
        }
      ],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/datadog",
          "awslogs-region": "ap-northeast-1",
          "awslogs-stream-prefix": "my-app-repo"
        }
      },
      "systemControls": []
    },
    {
      "name": "migration-runner",
      "image": "xxx.dkr.ecr.ap-northeast-1.amazonaws.com/my-app-repo:latest",
      "essential": false,
      "entryPoint": [
        "/bin/sh",
        "-c"
      ],
      "command": [
        "cd /app && alembic upgrade head"
      ],
      "environment": [
        {
          "name": "AWS_REGION",
          "value": "ap-northeast-1"
        },
        {
          "name": "MYSQL_PW",
          "value": "xxx"
        },
        {
          "name": "MYSQL_USER",
          "value": "myuser"
        },
        {
          "name": "MYSQL_HOST",
          "value": "xxx.ap-northeast-1.rds.amazonaws.com"
        },
        {
          "name": "MYSQL_DB",
          "value": "mydatabase"
        }
      ],
      "dependsOn": [
        {
          "containerName": "datadog-agent",
          "condition": "HEALTHY"
        }
      ],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/datadog",
          "awslogs-region": "ap-northeast-1",
          "awslogs-stream-prefix": "migration-runner"
        }
      }
    }
  ],
  "family": "Datadog",
  "taskRoleArn": "arn:aws:iam::xxx:role/ecs-task-execution-role",
  "executionRoleArn": "arn:aws:iam::xxx:role/ecs-task-execution-role",
  "networkMode": "awsvpc",
  "volumes": [
    {
      "name": "cws-instrumentation-volume",
      "host": {}
    }
  ],
  "requiresCompatibilities": [
    "FARGATE"
  ],
  "cpu": "256",
  "memory": "512"
}
```


### コンテナ構成

* `cws-instrumentation-init`: DatadogのCWS（Cloud Workload Security）初期化用コンテナ。セットアップスクリプトを実行して終了します。
* `datadog-agent`: Datadogエージェント本体。ログ収集やメトリクス監視を担当し、すべてのコンテナの起動に先立って健康状態チェックを提供します。
* `migration-runner`: Alembic を使ってマイグレーションを実行する補助的なコンテナ。`alembic upgrade head` を実行後に終了します。
* `my-app-repo`: 本番アプリケーション（FastAPI）。依存関係がすべて整った後に `uvicorn` によって起動されます。

### 特徴的な設定ポイント

* `my-app-repo` と `migration-runner` は同一イメージを使用しますが、起動時の `command` によって動作が異なります。
* `migration-runner` は `essential: false` に設定し、アプリ本体とは独立して動作します。
* すべてのログは Datadog のロググループ `/ecs/datadog` に送信され、`awslogs-stream-prefix` によって分類されます。
* `dependsOn` を活用し、アプリの起動順序を制御しています。

## まとめ

このように、Fargate 上で複数コンテナを使い分けることで、初期化処理・マイグレーション・監視・アプリ本体の機能を疎結合に保ちながら、安全にアプリを起動できます。

:::note warn
特に Alembic のマイグレーションを ECS タスク起動時に組み込むことで、デプロイ前後の整合性も確保できると思っています...！
:::

今後は CI/CD パイプラインと連携し、この構成をより自動化していく予定です。