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の開発者様たち、本当にありがとうございます