Closed15

ECSで開発用と本番用のCodePipelineを作る

ハトすけハトすけ

前提

  • 本番用と開発用のアカウントが別れている
  • GitHubを使っている
  • 開発用はdevブランチがマージされるとcodepipelineが発火
  • 本番用はmasterブランチがマージされるとcodepipelineが発火
  • それぞれビルドが走りイメージが作成されそれぞれのアカウントのECRに登録されたのちに、CodeDeployが発火する

問題点

  • 一旦開発ようにtaskdef.jsonやappspec.yamlとbuildspec.yamlを作成したが本番アカウントに流用できない
ハトすけハトすけ

以下3つのファイルをレポジトリ上で管理している。

appspec.yaml

version: 0.0
Resources:
  - TargetService:
      Type: AWS::ECS::Service
      Properties:
        TaskDefinition: <TASK_DEFINITION>
        LoadBalancerInfo:
          ContainerName: "app-container"
          ContainerPort: 3000

ビルド時に必要な環境変数があればssmのパラメータストアから渡す。ssmからも渡せる。
AWS_ACCOUNT_IDとAWS_DEFAULT_REGIONとIMAGE_NAMEに関してはcode buildの環境変数で定義している。
buildspec.yaml


version: 0.2

env:
  parameter-store:
    DOCKER_USER: dockerhub-user
    DOCKER_TOKEN: dockerhub-token
    DB_USER_NAME: db-user-name
    DB_PASSWORD: db-password
    DB_NAME: db-name
    DB_HOST: db-host

phases:
  install:
    runtime-versions:
      docker: 18
  pre_build:
    commands:
      - echo Logging in to Amazon ECR...
      - aws --version
      ## ECRにログイン
      - $(aws ecr get-login --no-include-email --region ap-northeast-1)
      ## dockerhubにログイン。pull rate limit回避
      - echo $DOCKER_TOKEN | docker login -u $DOCKER_USER --password-stdin
      - REPOSITORY_URI=${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com/${IMAGE_NAME}
      ## Dockerイメージのタグとして使用するため、ハッシュを取得
      - IMAGE_TAG=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | tr -dc 'a-zA-Z' | cut -c 1-7)
      ## テストようの.envファイル準備
      - cp ./infra/.env.testing .env.testing
  build:
    commands:
      ## Dockerイメージのビルド
      - echo Build started on `date`
      - echo Building the Docker image...
      - docker build -f infra/node/Dockerfile -t $REPOSITORY_URI:latest .
      - docker tag $REPOSITORY_URI:latest $REPOSITORY_URI:$IMAGE_TAG
      #  NOTE: execute migration
      #  - docker run --rm -e DB_USER_NAME -e DB_PASSWORD -e DB_NAME -e DB_NAME -e DB_HOST ${REPOSITORY_URI}:${IMAGE_TAG} migration  command

  post_build:
    commands:
      ## DockerイメージのECRへのプッシュ
      - echo Build completed on `date`
      - echo Pushing the Docker images...
      - docker push $REPOSITORY_URI:latest
      - docker push $REPOSITORY_URI:$IMAGE_TAG
      - echo Writing image definitions file...
      # blue/greenデプロイ出ない場合はこちらのjsonが必要
      # - echo "[{\"name\":\"${APP_NAME}\",\"imageUri\":\"${REPOSITORY_URI}:${IMAGE_TAG}\"}]" > imagedefinitions.json
      - printf '{"Version":"1.0","ImageURI":"%s"}' $REPOSITORY_URI:$IMAGE_TAG > imageDetail.json

artifacts:
  ## buildの最後で作成したファイルをアーティファクトとして流す
  ## blue/green deployの場合はimageDetail.jsonが必要
  files:
    - imageDetail.json

タスク定義の雛形。codepipelineのデプロイフェーズでblue/greenデプロイを選択したときに実行される。
環境変数をS3にファイルとして保存して渡している。

taskdef.json

{
  "executionRoleArn": "arn:aws:iam::MY_ACCOUNT_ROLE:role/ecsTaskExecutionRole",
  "containerDefinitions": [
      {
          "name": "app-container",
          "image": "<IMAGE1_NAME>",
          "essential": true,
          "environmentFiles": [
            {
              "value": "MY_S3_BUCKET_NAME/.env",
              "type": "s3"
            }
          ],
          "healthCheck": {
            "retries": 10,
            "command": [
              "CMD-SHELL",
              "curl -f http://localhost:3000/healthcheck || exit 1"
            ],
            "timeout": 30,
            "interval": 10,
            "startPeriod": 60
          },
          "logConfiguration": {
            "logDriver": "awslogs",
            "secretOptions": null,
            "options": {
              "awslogs-group": "/ecs/stg-app",
              "awslogs-region": "ap-northeast-1",
              "awslogs-stream-prefix": "ecs"
            }
          },
          "portMappings": [
            {
              "hostPort": 3000,
              "protocol": "tcp",
              "containerPort": 3000
            }
          ]
      }
  ],
  "requiresCompatibilities": [
      "FARGATE"
  ],
  "networkMode": "awsvpc",
  "cpu": "256",
  "memory": "512",
  "family": "app",
  "taskRoleArn": "arn:aws:iam::MY_ACCOUNT_ID:role/myTaskRole"
}

ハトすけハトすけ

問題点
このレポジトリをそのまま本番用のアカウントでECSを立てようとすると次の部分でつまずく

  • taskdef.jsonのハードコーディングされた設定が本番では適用できない
    • taskRoleArn
    • logConfiguration
    • environmentFiles
    • executionRoleArn

appspec.yamlのコンテナ名は本番環境と開発環境で同じ名前にすると差異が吸収できるがtaskdef.jsonの内容は他に対応が必要そう。

ハトすけハトすけ

おさらい

以下の手順を自動化するためにCodePipelineを利用する
最初にECS上にサービスクラスターを構築

  1. ローカルでDockerイメージを作成しECRにpush
  2. タスク定義ファイルを作成し、先程登録されたレポジトリを指定。この際にhealthcheckの設定とか環境変数の設定とかを同時に記述する
  3. サービスクラスター上のタスク定義ファイルを最新のものに変更

自動化するにあたって以下3つのファイルが必要になる

  • buildspec.yaml__CodeBuildステージにおいて上記1を実現する。
  • appspec.yaml__CodeDeployステージにおいて上記2,3を実現する。
  • taskdef.json__CodeDeployステージにおいてタスク定義ファイルを作成するさいに利用される雛形ファイル
ハトすけハトすけ

問題点に対応するためにtaskdef.jsonに環境変数的に外部から値を注入できればよさげ。

以下の記事によるとそもそも構築に2通りの方法があるみたい。
https://dev.classmethod.jp/articles/2pattern-cfn-fargate-blue-green-deployment-codepipeline/

2つめのほうほうは完全にCloudFormation管理していないと使えないのでなし。
taskdef.jsonのプレースホルダーにカスタムな値を利用できないか調べてみる。

ハトすけハトすけ

CodePipelineのDeployステージにて以下のPlaceholder text in the task definitionの項目にカスタムな値いれればいけるか検証

画像

ハトすけハトすけ

ひとまず、
appsepc.prod.yaml
taskdef.prod.yaml
みたいに複数ファイルで管理はしたくない。

ハトすけハトすけ

ちょっと調べてみたけどプレースホルダにカスタムな値を埋め込むみたいなのがない。

ハトすけハトすけ

結論

Terraform化している場合は、Terraformにtaskdef.jsonのアカウントごとの差分を吸収させる

Terraform化していない場合は、環境ごとのファイルを作成してCodePipelineでファイル名を選択する
buildspec.stg.yaml
appspec.stg.yaml
taskdef.stg.json
buildspec.prod.yaml
appspec.prod.yaml
taskdef.prod.json

もしくはBuildステージでsedコマンドを使いtaskdef.jsonの内容を環境変数に置き換える

ハトすけハトすけ

https://dev.classmethod.jp/articles/ecs-deploy-all/
上記サイトで以下のような問答があった

Q「タスク定義はIaCで管理するのが良いんでしょうか、GithubやCodecommitにのせて、パイプラインで更新するみたいなことは出来るんでしょうか?」

オススメとしては、TerraformのLifeCycleでイメージURIの変更を無視する方法です。さらに組み合わせて、本日のセッションで説明した、CodeDeployに含ませるtaskdef.jsonの雛形機能や、GitHub Actionsの機能を使えば、IaCで管理するECSタスク定義をデプロイとTerraformの両方で併用可能です。

タスク定義はterraformで管理するにしてもLifeCycleによってイメージURIを無視という方法。

ハトすけハトすけ

とりあえずbuildspec.yamlとappspec.yamlはなるべく開発と本番同じ値にして、taskdef.jsonを本番だけtaskdef.prod.jsonとした。
CodePipelineでtaskdef.prod.jsonを見るように変更。

ここらへんterraformを本格導入したらうまいこと管理できるようにしたい。

ハトすけハトすけ

環境変数は結局S3で管理するよりもssmのパラメータストアで管理するほうがいいなとなった。理由は、S3だとダウンロードして編集してアップロードしてと、変更がとにかくしにくいから
こちらを参考に、taskdef.jsonとtaskdef.prod.jsonを変更する
https://dev.classmethod.jp/articles/ecs-secrets/

ハトすけハトすけ

なんかへんに検索上位にあがってしまってるからちゃんと記事化しようかな。。

このスクラップは2021/05/31にクローズされました