【解説】ecspressoはなぜ必要?Terraformと併用するECSデプロイ戦略 #AWS – Qiita

はじめに

私は普段バックエンドエンジニアとして活動しています。Terraformとecspressoを活用しているプロジェクトが多いのですが、両者の役割分担は、一見すると分かりにくいです。この記事では、Terraformとecspressoを使ったサンプルプロジェクトを作りながら、両者のライフサイクルの違いについて説明いたします。

この記事を読むと分かること

  • Terraformとecspressoの役割の違い
  • なぜECSの変更管理にecspressoが向いているのか
  • ecspressoおよびGitHub Actionsのサンプルコード

想定読者

  • バックエンドエンジニアで、インフラもある程度触る人
  • Terraformとecspressoの違いを知りたい人

ecspressoが解決したい課題

TerraformのみでECSの変更管理をする難しさ

「ecspressoはECSの管理に特化したツールです。」
私はこれを聞いたとき「TerraformでもECSの管理できるじゃん、なぜecspressoが必要?」と疑問が湧きました。はじめに、AWSリソースのライフサイクルを整理し、ecspressoの守備範囲を確認します。

まずTerraformはAWS全体の構成管理を行います。ほとんどのAWSリソースは一度設定すると変更頻度は低いという特徴があります。一方で、ECS上で起動させるイメージはアプリエンジニアが開発して頻繁にデプロイします。CI/CDパイプラインが整備されているエリートなチームであれば、1日に1回の高頻度でアプリをデプロイすることもあるでしょう。

image.png

このときECSの更新をTerraformで行うと次のような問題が生じます。

  • ECSのタスク定義のみ変更したいのに、AWS全体の差分チェックが発生する。場合によってはECS以外のリソースも同時に変更される
  • CI/CDなど他の手段でECSの設定を書き換えると、Terraformに差分アリと判断され、terraform apply 時に、ECSの状態が以前の状態に切り戻される可能性がある

特に、商用サービスでは事故防止のため、高頻度で変更したいECS周りの管理と、更新頻度が低い他のAWSリソースの管理を分けておきたいかと思います。

ECSの変更はTerraformで管理しない

そこでインフラの中でライフサイクルが短いECS周りのリソースはTerraformで管理せず、別の管理ツールで管理します。この役割を担うのがecspressoです。

  • 表: AWSリソース管理のすみ分け
項目 Terraform ecspresso 変更頻度
ECSタスク定義
ECSサービス
ECSクラスター
その他AWSリソース

アプリチームとインフラチームの境界を明確にする

開発プロセスにおいて、ECS以外のインフラ管理はインフラ管理用のGitリポジトリで管理し、アプリケーションのデプロイ、ECSのリソース管理はアプリ管理用のGitリポジトリで管理します。

これによって、アプリチームとインフラチームの責任分界点が明確になり、アプリ側はインフラチームに変更依頼を出さずに自分たちの管理でアプリのデプロイを管理できます。また、アプリデプロイ時のインフラチームとの調整コスト(コミュニケーションコスト)の削減も狙えます。

  • インフラリポジトリ: AWS全般のリソース管理 (terraform)
  • アプリリポジトリ: アプリケーションコード + ecspressoの管理

ecspressoimage.png

Terraform管理からecspresso管理への移行

Terraform管理下のコードを削除

下記のコードは、ECS管理用のtfファイルの例です。もともとECS全体をTerraformで管理していましたが、services配下および container_definitions はコメントアウトされています。terraform.tfstateの管理外になったため、コンテナを再デプロイしても tfstateの diff として現れません。

modules/ecs/main.tf

module "ecs" {
  source = "terraform-aws-modules/ecs/aws"

  # NOTE: Terraformでは、ECSクラスタのみ管理する
  cluster_name = "${var.product_name}-${var.env}"

  cluster_configuration = {
  }

  default_capacity_provider_strategy = {
    FARGATE_SPOT = {
      weight = 1
    }
  }

  # NOTE: ecspressoでECSサービスを管理するため、TerraformからはECSサービスの定義を削除
  services = {
    # fastapi-service = {
    #   cpu           = 256
    #   memory        = 512
    #   desired_count = 1

    #   container_definitions = {
    #     fastapi = {
    #       image     = data.aws_ecr_image.this.image_uri
    #       essential = true
    #       readonlyRootFilesystem = false
    #       port_mappings = [
    #         # NOTE: hostPort must be the same as containerPort when using awsvpc network mode
    #         {
    #           name          = "http"
    #           containerPort = 8000
    #           hostPort      = 8000
    #           protocol      = "tcp"
    #         }
    #       ]
    # ... (以下略)
    #  }
  }
}

コメントアウトした箇所をecspressoの定義ファイルに移動

先ほどのmodules/ecs/main.tfでコメントされた部分は、ecspressoの ecs-service-def.jsonnet, ecs-task-def.jsonnet で管理するようになります。それぞれECS Service, ECS Taskdefを制御します。ecspresso deployコマンドを実行すればAWSリソースのうち、ECSサービス, タスクのみ更新されるようになります

ecs-service-def.jsonnet

{
  "deploymentConfiguration": {
    "deploymentCircuitBreaker": {
      "enable": true,
      "rollback": false
    },
    "maximumPercent": 200,
    "minimumHealthyPercent": 50
  },
  "deploymentController": {
    "type": "ECS"
  },
  "desiredCount": 1,
  "enableECSManagedTags": true,
  "enableExecuteCommand": true,
  "healthCheckGracePeriodSeconds": 30,
  "launchType": "FARGATE",
  "networkConfiguration": {
    "awsvpcConfiguration": {
      "assignPublicIp": "ENABLED",
      "securityGroups": [
        "{{ tfstate `module.ecs.aws_security_group.ecs_sg.id` }}"
      ],
      "subnets": [
        "{{ tfstate `module.vpc.module.vpc.aws_subnet.public[0].id` }}",
        "{{ tfstate `module.vpc.module.vpc.aws_subnet.public[1].id` }}"
      ]
    }
  },
  "pendingCount": 0,
  "platformFamily": "Linux",
  "platformVersion": "LATEST",
  "propagateTags": "NONE",
  "runningCount": 0,
  "schedulingStrategy": "REPLICA",
}

ecs-task-def.jsonnet

{
  "containerDefinitions": [
    {
      "cpu": 0,
      "essential": true,
      "image": "{{ tfstate `module.ecs.data.aws_ecr_repository.this.repository_url`}}:latest",
      "interactive": false,
      "linuxParameters": {
        "initProcessEnabled": true
      },
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "{{tfstate `module.ecs.module.ecs.module.cluster.aws_cloudwatch_log_group.this[0].name`}}",
          "awslogs-region": "{{ tfstate `module.ecs.module.ecs.module.cluster.aws_cloudwatch_log_group.this[0].region` }}",
          "awslogs-stream-prefix": "ecs"
        }
      },
      "name": "fastapi",
      "portMappings": [
        {
          "containerPort": 8000,
          "hostPort": 8000,
          "protocol": "tcp"
        }
      ],
      "privileged": false,
      "pseudoTerminal": false,
      "readonlyRootFilesystem": false,
      "startTimeout": 30,
      "stopTimeout": 120,
      "healthCheck": {
        "command": [
          "CMD-SHELL",
          "curl -f http://localhost:8000/ || exit 1"
        ],
        "interval": 30,
        "timeout": 5,
        "retries": 3,
        "startPeriod": 10
      }
    }
  ],
  "cpu": "256",
  "executionRoleArn": "{{ tfstate `module.ecs.aws_iam_role.ecs_task_execution_role.arn` }}",
  "family": "{{ tfstate `module.ecs.module.ecs.module.cluster.aws_ecs_cluster.this[0].tags.ServiceName` }}",
  "ipcMode": "",
  "memory": "512",
  "networkMode": "awsvpc",
  "pidMode": "",
  "requiresCompatibilities": [
    "FARGATE"
  ],
  "runtimePlatform": {
    "cpuArchitecture": "X86_64",
    "operatingSystemFamily": "LINUX"
  },
  "taskRoleArn": "{{tfstate `module.ecs.aws_iam_role.ecs_task_role.arn`}}"
}

詳しい使い方を知りたい方は、ecspresso作者の執筆している下記の書籍がおすすめです。(¥500)

CI/CDにecspressoを組み込む

アプリリポジトリにおいて、mainブランチにコードがマージされたときに自動でGitHub Actionsが実行されるように設定します。GitHub Actionsのワークフローは下記のような流れで構成します。

  1. GHAでDockerイメージをビルド
    イメージタグには latest タグと、コミットハッシュを両方付与する
  2. DockerイメージをECRにプッシュ
  3. ecspressoでデプロイ
    --force-new-deployment を使うことで最新イメージを確実に反映

image.png

.github/workflows/ecs-deploy.yml

name: ECS Deploy
on:
  push:
    branches:
      - main
  workflow_dispatch: # GitHub UIからの手動実行用

jobs:
  build_and_push:
    name: Docker Build and Push Docker Image to ECR
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ secrets.AWS_ROLE_AllowGitHubActions }}
          aws-region: ap-northeast-1

      - name: Login to Amazon ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v2

      - name: Build, tag, and push image to Amazon ECR
        env:
          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
          ECR_REPOSITORY: n700a/fastapi-mini
          IMAGE_TAG: latest # ${{ GitHub.sha }}
        run: |
          docker build -f backend/Dockerfile -t $ECR_REPOSITORY:$IMAGE_TAG backend
          docker tag $ECR_REPOSITORY:$IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
          docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG

  deploy:
      name: Deploy to ECS
      needs: build_and_push
      runs-on: ubuntu-latest
      permissions:
        id-token: write
        contents: read

      steps:
        - name: Checkout code
          uses: actions/checkout@v4

        - name: Configure AWS credentials
          uses: aws-actions/configure-aws-credentials@v4
          with:
            role-to-assume: ${{ secrets.AWS_ROLE_AllowGitHubActions }}
            aws-region: ap-northeast-1

        - name: Deploy to ECS service
          uses: kayac/ecspresso@v2
          with:
            version: v2.6.0
            args: deploy --config ./infra/dev/ecspresso/ecspresso.yml

このワークフローを設定しておくと、アプリチームはECSのデプロイ作業から解放され、アプリソースコードの作成に集中することができます。

まとめ

本記事では、AWSのリソースのうちアプリ領域とインフラ領域において、デプロイの頻度の違いがあることを述べ、全てをTerraformで管理することの課題を示しました。ECSの安全かつ高頻度なデプロイを実現するために、ecspressoとTerraformのすみわけを示しました。また、二者を併用することでAWSリソースのうちアプリ管理の領域とインフラ管理の領域を明確にできそうです。

この記事ではecspressoの具体的な環境構築方法については記載していません。ご興味のある方はぜひ一度手を動かしてみて下さい。
この記事が、ecspressoの意義や概念について知るきっかけになると幸いです。

参考記事




Source link

関連記事

コメント

この記事へのコメントはありません。