LoginSignup
37
30

More than 3 years have passed since last update.

CircleCI Orbsを使ってRailsのECR・ECSへのデプロイ(マイグレーション込み)を自動化した話

Last updated at Posted at 2019-12-06

TL;DR

  • ECS の RunTask を実行でマイグレーション込みのデプロイが自動化できた

いきなりですが

2 ヶ月前ぐらいまで下記の手順でデプロイを行っていました

@ローカル

  1. コンテナ作成 (docker build)
  2. コンテナにタグづけ (docker tag)
  3. ECR のコンテナイメージを更新 (docker push)
  4. AWS の RDS にマイグレーション実行 (rails db:migrate)
  5. AWS コンソール画面で ECS の手動でリビジョン更新
  6. サービスでタスク定義の更新
  7. 新旧タスクが入れ替わるのを見守る

現在は・・・

  1. デプロイブランチ(production や statging)にプッシュ
  2. slack で結果を待つ

一番の壁、マイグレーション実行

実は、今回のこの記事でお伝えしたいのは前述の中でも 「4 の RDS のマイグレーション」の部分です:santa:

ローカルで実行していたころは、許可された IP アドレスでマイグレーション実行できるのですが、

CircleCI は AWS の外部で実行されていて、かつ IP アドレスが不定です。
それ故、許可されていない IP アドレスで実行される CicleCI のマイグレーションコマンドを RDS が弾いてしまい、マイグレーション実行ができません:santa:

Run Task で乗り越えましょ

マイグレーション前に ECR にデプロイした最新の schema ファイルを持ったイメージを使用してECS の RunTaskを行うことで、
AWS 内でのマイグレーション実行を行えるようになりました:santa:

これで実行元の IP アドレスは考えなくてもよくなります:santa:

Run Task は、AWS 内の ECR からイメージを使用して、docker run するような動きになります:santa:

現在動いているコンテナとは別に、マイグレーションするためだけのコンテナが立ち上がる📦

↓

マイグレーション実行🐳

↓

コンテナ終了

ポイント

  • Run Task 前に ECR に最新イメージをプッシュしていること(Run Task を行う前に、最新の schema ファイルを持ってないとマイグレーションが滑る) → CircleCI の requiresで順番を担保
  • 実行してもらいたいコマンドを override という形で json で渡す → AWS CLI を実行

対象読者

  • CircleCI 2.1 以上
  • ECS・ECR 設定済み
  • Rails

レシピ

さてさーて:santa:

長い文脈ではありましたが、どういうふうに調理していくか流れおさえます:santa:

デプロイの手作業は下記でした:santa:

  1. コンテナ作成 (docker build)
  2. コンテナにタグずけ (docker tag)
  3. ECR のコンテナイメージを更新 (docker push)
  4. AWS の RDS にマイグレーション実行 (rails db:migrate)
  5. AWS コンソール画面で ECS の手動でリビジョン更新
  6. サービスでタスク定義の更新
  7. 新旧タスクが入れ替わるのを見守る

上記に加え、「8. 結果を slack 通知」も加えます:santa:

AWS のコンソール見守る手間省き受動的に対処するためですね:santa:

CircleCI の Orbs で置き換えると

Orbs だけでいけそうですね:santa:

Orbs を開発してくれている方々、本当に感謝です:santa:

1 ~ 3 → CircleiCI Orbs のaws-ecr/build-and-push-imageを使用

config.yml は下記のようになりました:santa:

あくまで自分のプロジェクトの場合です:santa:
必須なパラメーターはドキュメントをみてください〜

circleci/config.yml
version: 2.1

orbs:
  aws-ecr: circleci/aws-ecr@6.2.0

executors:
  (省略)

jobs:
  build-image-and-push:
    executor: container
    parameters:
      env:
        type: enum
        enum: ['stg', 'prod']
    steps:
      - aws-ecr/build-and-push-image:
          aws-access-key-id: AWS_ACCESS_KEY_ID
          aws-secret-access-key: AWS_SECRET_ACCESS_KEY
          region: AWS_REGION
          repo: '${AWS_RESOURCE_NAME_PREFIX}-<< parameters.env >>'
          extra-build-args: --target apps
          attach-workspace: true
          setup-remote-docker: true

Orbs 使って、パラメーター渡しただけで終わってしまいました:santa:

ポイント

  • Orbs 使っているかどうかは、Orbs の key 名と、steps でaws-ecr/build-and-push-imageで、どの job を参照しているかがわかります:santa:

  • parameters で env を設定

parameters を使う意図としては、prodction → 本番環境、staging → テスト環境で同じ job を定義したいけど、違いが、AWS の ECR のリポジトリ名しかない時に、コンフィグを再利用できるので便利です:santa:

例:parameters を使わない時

repoの違いだけなのに、2 倍定義しないといけない

circleci/config.yml
jobs:
  build-prod-image-and-push:
    executor: container
    steps:
      - aws-ecr/build-and-push-image:
          aws-access-key-id: AWS_ACCESS_KEY_ID
          aws-secret-access-key: AWS_SECRET_ACCESS_KEY
          region: AWS_REGION
          repo: '${AWS_RESOURCE_NAME_PREFIX}-prod'
          extra-build-args: --target apps
          attach-workspace: true
          setup-remote-docker: true

  build-stg-image-and-push:
    executor: container
    steps:
      - aws-ecr/build-and-push-image:
          aws-access-key-id: AWS_ACCESS_KEY_ID
          aws-secret-access-key: AWS_SECRET_ACCESS_KEY
          region: AWS_REGION
          repo: '${AWS_RESOURCE_NAME_PREFIX}-stg'
          extra-build-args: --target apps
          attach-workspace: true
          setup-remote-docker: true
例:parameters を使うと ↓

config を再利用できますね:santa:

circleci/config.yml
jobs:
  build-image-and-push:
    executor: container
    parameters:
      env:
        type: enum
        enum: ['stg', 'prod']
    steps:
      - aws-ecr/build-and-push-image:
          aws-access-key-id: AWS_ACCESS_KEY_ID
          aws-secret-access-key: AWS_SECRET_ACCESS_KEY
          region: AWS_REGION
          repo: '${AWS_RESOURCE_NAME_PREFIX}-<< parameters.env >>'
          extra-build-args: --target apps
          attach-workspace: true
          setup-remote-docker: true

  • 必要なパラメーター(AWS_ACCESS_KEY_ID など)は、CircleCI から設定した環境変数から渡しているだけですね:santa:

CircleCI での環境変数の設定はこちら

  • docker の multi stage buildでも大丈夫 extra-build-argsで docker コマンドに引数を渡せるので対象の layer だけのビルドも可能ですね:santa:

〜〜〜余談〜〜〜

multi stage build は、コンテナを小さくする手段でもあり、最近発表された[レポート] コンテナおよび Kubernetes のベストプラクティストップ 5 #reinvent #CON307にも載っていましたね:santa:

4 → CircleiCI Orbs のaws-cli/setupを使用

circleci/config.yml
orbs:
  aws-cli: circleci/aws-cli@0.1.16

jobs:
  db-migrate-on-task-run:
    executor: container
    parameters:
      env:
        type: enum
        enum: ['stg', 'prod']
    steps:
      - checkout
      - aws-cli/setup:
          aws-access-key-id: AWS_ACCESS_KEY_ID
          aws-secret-access-key: AWS_SECRET_ACCESS_KEY
          aws-region: AWS_REGION
      - run:
          name: "db migrate"
          command: |
            aws ecs run-task --region $AWS_REGION \
              --cluster hoge-<< parameters.env >> \
              --task-definition hoge-service-<< parameters.env >> \
              --overrides file://docker/run_task_db_migrate_<< parameters.env >>.json

Rails のフォルダには、RunTask に必要なファイルが入っております:santa:

docker
├── run_task_db_migrate_prod.json
└── run_task_db_migrate_stg.json

name は container 名が入り、それが環境違いで用意されているだけですね:santa:

run_task_db_migrate_prod.json
{
  "containerOverrides": [
    {
      "name": "hoge-prod",
      "command": ["rails", "db:migrate"]
    }
  ]
}
run_task_db_migrate_stg.json
{
  "containerOverrides": [
    {
      "name": "hoge-stg",
      "command": ["rails", "db:migrate"]
    }
  ]
}

ポイント

  • aws-cli コマンドで run task を実行するため、aws-cli のセッティングをする

ECS の Orbsでも run-task 実行できそうですね!
これ実装して検証してた時はなかったので、同じ Orbs でもできる内容が広がってますね!

できてしまえば、あっさりですね:santa:

5 ~ 8 → CircleiCI Orbs のaws-cli/installaws-cli/setupaws-ecs/update-serviceslack/statusを使用

circleci/config.yml
orbs:
  aws-ecs: circleci/aws-ecs@0.0.11
  aws-cli: circleci/aws-cli@0.1.16
  slack: circleci/slack@3.3.0

  service-update-and-notify:
    executor: container
    parameters:
      env:
        type: enum
        enum: ['stg', 'prod']
    steps:
      - checkout
      - aws-cli/install
      - aws-cli/setup:
          aws-access-key-id: AWS_ACCESS_KEY_ID
          aws-secret-access-key: AWS_SECRET_ACCESS_KEY
          aws-region: AWS_REGION
      - aws-ecs/update-service:
          family: hoge-apps-service-<< parameters.env >>
          cluster-name: hoge-<< parameters.env >>
          container-image-name-updates: 'container=hoge-<< parameters.env >>,tag=latest'
          verify-revision-is-deployed: true
          max-poll-attempts: 300
          poll-interval: 10
      - slack/status:
          success_message: ':circleci-pass: $CIRCLE_BRANCH のデプロイが完了しました\n:github_octocat: User:$CIRCLE_USERNAME'
          failure_message: ':circleci-fail: $CIRCLE_BRANCH のデプロイが失敗しました\n:github_octocat: User:$CIRCLE_USERNAME'
          webhook: '${SLACK_WEBHOOK}'
  • max-poll-attemptspoll-intervalで新旧見守りしなくていいっすね

300 秒間、10 秒に 1 回切り替わったかねー?:santa:

って、聞いている処理してくれてる認識です:santa:

shell とかで実装している例とかありますが、これだけで OK ですね:santa:

  • 結果は slack で

steps のaws-ecs/update-serviceが成功ステータスだとsuccess_message、ダメだとfailure_messageが流れます:santa:

デプロイがうまくいかない理由はいろいろありますが、見守り時間内にデプロイ終わらなかったのがほとんどです:santa:

この slack の Orbs のいいところは、結果を条件分岐をする必要がないところがシンプルでいいですね:santa:

細かい設定方法についてはこちら

これらを workflow で流れを定義

circleci/config.yml
workflows:
  db-migrate-and-deploy-to-stg:
    jobs:
      - build-image-and-push:
          env: stg
          filters:
            branches:
              only: staging
      - db-migrate-on-task-run:
          requires:
            - build-image-and-push
          env: stg
          filters:
            branches:
              only: staging
      - service-update-and-notify:
          requires:
            - db-migrate-on-task-run
          env: stg
          filters:
            branches:
              only: staging

  db-migrate-and-deploy-to-prod:
    jobs:
      - build-image-and-push:
          env: prod
          filters:
            branches:
              only: production
      - db-migrate-on-task-run:
          requires:
            - build-image-and-push
          env: prod
          filters:
            branches:
              only: production
      - service-update-and-notify:
          requires:
            - db-migrate-on-task-run
          env: prod
          filters:
            branches:
              only: production

ポイント

  • 流れはあくまで、workflow
  • filters で対象ブランチ絞る
  • 実行の順番はrequiresで制御

例えば、staging に push が走ると

  1. build-image-and-push
  2. db-migrate-on-task-run
  3. service-update-and-notify

の順番で job が実行されていきます:santa:

【直列実行イメージ図】
screenshot 2019-12-06 14.18.05.png

requires 定義しないと、すべての job が並列で実行されます:santa:

【並列実行イメージ図】
screenshot 2019-12-06 14.18.11.png

イメージの参照元

参考

あとがき

CircleCI という AWS の外でも、Run Task ができることで、乗り越えられる壁があることを知ることができました:santa:

これを応用すれば、E2E テストとか外部から DB にアクセスを拒否されるようなケースも
Run Task で E2E テストするSPA コンテナだけ立ち上げてテスト実行ができそうですね:santa:

これを Orbs なしで実装することになっていたら背筋に稲妻が走りそう(白目)

Orbsの開発者様たち、本当にありがとうございます:santa:

37
30
2

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
37
30