TL;DR
- ECS の RunTask を実行でマイグレーション込みのデプロイが自動化できた
いきなりですが
2 ヶ月前ぐらいまで下記の手順でデプロイを行っていました
@ローカル
- コンテナ作成 (docker build)
- コンテナにタグづけ (docker tag)
- ECR のコンテナイメージを更新 (docker push)
- AWS の RDS にマイグレーション実行 (rails db:migrate)
- AWS コンソール画面で ECS の手動でリビジョン更新
- サービスでタスク定義の更新
- 新旧タスクが入れ替わるのを見守る
現在は・・・
- デプロイブランチ(production や statging)にプッシュ
- slack で結果を待つ
一番の壁、マイグレーション実行
実は、今回のこの記事でお伝えしたいのは前述の中でも 「4 の RDS のマイグレーション」の部分です![]()
ローカルで実行していたころは、許可された IP アドレスでマイグレーション実行できるのですが、
CircleCI は AWS の外部で実行されていて、かつ IP アドレスが不定です。
それ故、許可されていない IP アドレスで実行される CicleCI のマイグレーションコマンドを RDS が弾いてしまい、マイグレーション実行ができません![]()
Run Task で乗り越えましょ
マイグレーション前に ECR にデプロイした最新の schema ファイルを持ったイメージを使用してECS の RunTaskを行うことで、
AWS 内でのマイグレーション実行を行えるようになりました![]()
これで実行元の IP アドレスは考えなくてもよくなります![]()
Run Task は、AWS 内の ECR からイメージを使用して、docker run するような動きになります![]()
現在動いているコンテナとは別に、マイグレーションするためだけのコンテナが立ち上がる📦
↓
マイグレーション実行🐳
↓
コンテナ終了
ポイント
- Run Task 前に ECR に最新イメージをプッシュしていること(Run Task を行う前に、最新の schema ファイルを持ってないとマイグレーションが滑る)
→ CircleCI の requiresで順番を担保 - 実行してもらいたいコマンドを override という形で json で渡す
→ AWS CLI を実行
対象読者
- CircleCI 2.1 以上
- ECS・ECR 設定済み
- Rails
レシピ
さてさーて![]()
長い文脈ではありましたが、どういうふうに調理していくか流れおさえます![]()
デプロイの手作業は下記でした![]()
- コンテナ作成 (docker build)
- コンテナにタグずけ (docker tag)
- ECR のコンテナイメージを更新 (docker push)
- AWS の RDS にマイグレーション実行 (rails db:migrate)
- AWS コンソール画面で ECS の手動でリビジョン更新
- サービスでタスク定義の更新
- 新旧タスクが入れ替わるのを見守る
上記に加え、「8. 結果を slack 通知」も加えます
AWS のコンソール見守る手間省き受動的に対処するためですね![]()
CircleCI の Orbs で置き換えると
- 1 ~ 3 → CircleiCI Orbs のaws-ecr/build-and-push-imageを使用
- 4 → CircleiCI Orbs のaws-cli/setupを使用
- 5 ~ 7 → CircleiCI Orbs のaws-cli/install、aws-cli/setup、aws-ecs/update-serviceを使用
- 8 → CircleiCI Orbs のslack/status使用
Orbs だけでいけそうですね![]()
Orbs を開発してくれている方々、本当に感謝です![]()
1 ~ 3 → CircleiCI Orbs のaws-ecr/build-and-push-imageを使用
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 使って、パラメーター渡しただけで終わってしまいました![]()
ポイント
-
Orbs 使っているかどうかは、Orbs の key 名と、steps で
aws-ecr/build-and-push-imageで、どの job を参照しているかがわかります
-
parameters で env を設定
parameters を使う意図としては、prodction → 本番環境、staging → テスト環境で同じ job を定義したいけど、違いが、AWS の ECR のリポジトリ名しかない時に、コンフィグを再利用できるので便利です![]()
例:parameters を使わない時
repoの違いだけなのに、2 倍定義しないといけない
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 を再利用できますね![]()
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 から設定した環境変数から渡しているだけですね
CircleCI での環境変数の設定はこちら
-
docker の multi stage buildでも大丈夫
extra-build-argsで docker コマンドに引数を渡せるので対象の layer だけのビルドも可能ですね
〜〜〜余談〜〜〜
multi stage build は、コンテナを小さくする手段でもあり、最近発表された[レポート] コンテナおよび Kubernetes のベストプラクティストップ 5 #reinvent #CON307にも載っていましたね![]()
4 → CircleiCI Orbs のaws-cli/setupを使用
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 に必要なファイルが入っております![]()
docker
├── run_task_db_migrate_prod.json
└── run_task_db_migrate_stg.json
name は container 名が入り、それが環境違いで用意されているだけですね![]()
{
"containerOverrides": [
{
"name": "hoge-prod",
"command": ["rails", "db:migrate"]
}
]
}
{
"containerOverrides": [
{
"name": "hoge-stg",
"command": ["rails", "db:migrate"]
}
]
}
ポイント
- aws-cli コマンドで run task を実行するため、aws-cli のセッティングをする
ECS の Orbsでも run-task 実行できそうですね!
これ実装して検証してた時はなかったので、同じ Orbs でもできる内容が広がってますね!
できてしまえば、あっさりですね![]()
5 ~ 8 → CircleiCI Orbs のaws-cli/install、aws-cli/setup、aws-ecs/update-service、slack/statusを使用
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-attemptsとpoll-intervalで新旧見守りしなくていいっすね
300 秒間、10 秒に 1 回切り替わったかねー?![]()
って、聞いている処理してくれてる認識です![]()
shell とかで実装している例とかありますが、これだけで OK ですね![]()
- 結果は slack で
steps のaws-ecs/update-serviceが成功ステータスだとsuccess_message、ダメだとfailure_messageが流れます![]()
デプロイがうまくいかない理由はいろいろありますが、見守り時間内にデプロイ終わらなかったのがほとんどです![]()
この slack の Orbs のいいところは、結果を条件分岐をする必要がないところがシンプルでいいですね![]()
細かい設定方法についてはこちら
これらを workflow で流れを定義
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
ポイント
例えば、staging に push が走ると
- build-image-and-push
- db-migrate-on-task-run
- service-update-and-notify
の順番で job が実行されていきます![]()
requires 定義しないと、すべての job が並列で実行されます![]()
参考
- ECS RunTask
- CircleCI Orbs
- CircleCI Workflow
- [レポート] コンテナおよび Kubernetes のベストプラクティストップ 5 #reinvent #CON307
あとがき
CircleCI という AWS の外でも、Run Task ができることで、乗り越えられる壁があることを知ることができました![]()
これを応用すれば、E2E テストとか外部から DB にアクセスを拒否されるようなケースも
Run Task で E2E テストするSPA コンテナだけ立ち上げてテスト実行ができそうですね![]()
これを Orbs なしで実装することになっていたら背筋に稲妻が走りそう(白目)
Orbsの開発者様たち、本当にありがとうございます![]()

