実現したいこと
- 従来はWheneverでcron定義を自動生成し、EC2上で定期バッチを実行させていた
- EC2からFargateへのインフラ移行に際して、定期バッチもFargateで実行できる形にしたい
- 定義の反映はアプリケーションのCI/CDに載せて自動化したい
(「手作業でECS Schesuled Tasksを編集する」のような形は避けたい) - 定義は設定ファイルで構成管理したい
(「スケジュール追加スクリプトを実行する」ような形は避けたい)
選択肢
- CloudFormation でEventBridgeルールの定義を行い、CircleCI 内で
aws cloudformation deploy
を実行する - ecschedule(ECS Scheduled Task 設定を管理する gem)を使い、circleci 内でコマンドを実行する
-
Elastic Whenever(Whenever のように cron ジョブのタスク一覧を
config/schedule.rb
で管理できるようになる gem)を使い、circleci 内でコマンドを実行する
デファクトスタンダードな手段が存在しなかったため、できるだけライブラリには依存しない形にする、という意図で1のCloudFormationで管理する形としました。
設定
ECSタスク定義
まずバッチが起動するECSのタスク定義を作成します。今回はあらかじめ以下が存在する前提とします。
- クラスター
sample-app
- コンテナイメージ(アプリケーションで使うコンテナイメージと同じもの)
362548988451.dkr.ecr.ap-northeast-1.amazonaws.com/sample-app:rails-21f7b833babf545f86fea29239ffb34c6969177d
EcsTaskDefinition0001:
Type: AWS::ECS::TaskDefinition
Properties:
Family: sample-app # 起動させるクラスター名
Cpu: '512'
Memory: '1024'
NetworkMode: awsvpc
RequiresCompatibilities:
- FARGATE
ExecutionRoleArn: arn:aws:iam::362548988451:role/ecsTaskExecutionRole
ContainerDefinitions:
- Name: batch # コンテナ名
Image: 362548988451.dkr.ecr.ap-northeast-1.amazonaws.com/sample-app:rails-21f7b833babf545f86fea29239ffb34c6969177d
Essential: true
Environment:
- Name: RAILS_ENV
Value: production
- Name: RAILS_LOG_TO_STDOUT
Value: true
Command: ['bash'] # EventBridgeルールでOverrideするのでなんでもOK
Secrets:
- Name: DB_HOST
ValueFrom: arn:aws:ssm:ap-northeast-1:362548988451:parameter/sample-app/production/DB_HOST
- Name: DB_USER
ValueFrom: arn:aws:ssm:ap-northeast-1:362548988451:parameter/sample-app/production/DB_USER
- Name: DB_PASSWORD
ValueFrom: arn:aws:ssm:ap-northeast-1:362548988451:parameter/sample-app/production/DB_PASSWORD
- Name: DB_DATABASE
ValueFrom: arn:aws:ssm:ap-northeast-1:362548988451:parameter/sample-app/production/DB_DATABASE
LogConfiguration:
LogDriver: awslogs
Options:
awslogs-group: /ecs/sample-app-batch
awslogs-region: ap-northeast-1
awslogs-stream-prefix: ecs
PortMappings:
- ContainerPort: 8080
定義ファイルの作成が完了したら、CloudFormationを適用します。
EventsRule定義
Railsプロジェクトのlib/tasks/schedule.yml
に以下を作成します。
今回はsitemap:refresh:no_ping
を定期実行する定義とします。
Resources:
EventsRule0001:
Type: AWS::Events::Rule
Properties:
Name: sample-app-0001 # ルール名
Description: run rake sitemap:refresh:no_ping
# 日本時間の毎日4時(JST)に実行する
ScheduleExpression: cron(0 19 * * ? *)
Targets:
- Id: !Sub 'sample-app-0001'
Arn: !Sub 'arn:aws:ecs:ap-northeast-1:362548988451:cluster/sample-app'
RoleArn: arn:aws:iam::362548988451:role/ecsEventsRole
Input: '{"containerOverrides":[{"name":"batch","command":["bundle","exec","rake","sitemap:refresh:no_ping"]}]}'
EcsParameters:
TaskDefinitionArn: !Sub 'arn:aws:ecs:ap-northeast-1:362548988451:task-definition/sample-app-batch'
TaskCount: 1
NetworkConfiguration:
AwsVpcConfiguration:
SecurityGroups:
- sg-xxx
Subnets:
- subnet-xxx # private-1a
- subnet-xxx # private-1c
LaunchType: FARGATE
PlatformVersion: LATEST
注意点
- Roleはマネージドな
RoleArn: arn:aws:iam::362548988451:role/ecsEventsRole
を使う
(自前のルールだと起動しなくてハマった) - JSTでの時間指定はできないのでUTCで指定する必要がある
- TaskDefinitionArnのバージョンを記載しないことにより、最新のタスク定義が実行される
CircleCI定義
.circleci/config.yml
で
version: 2.1
orbs:
aws-ecs: circleci/aws-ecs@4.0.0
aws-ecr: circleci/aws-ecr@9.0.3
としてaws-ecsのorbsを使える状態にしつつ、jobs内で以下と定義します
jobs:
build_and_deploy:
<<: *defaults
machine:
image: ubuntu-2204:2024.01.1
environment:
DOCKER_BUILDKIT: 1
steps:
- checkout
- run:
name: Create master.key
command: |
touch ./config/master.key
echo $RAILS_MASTER_KEY > ./config/master.key
- aws-cli/setup
- aws-ecr/build_and_push_image:
auth:
- aws-cli/setup
account_id: '${AWS_ACCOUNT_ID}'
region: '${AWS_DEFAULT_REGION}'
dockerfile: Dockerfile
repo: sample-app
tag: 'rails-${CIRCLE_SHA1}'
- aws-ecs/update_task_definition:
container_image_name_updates: 'container=batch,tag=rails-${CIRCLE_SHA1}'
family: 'sample-app-batch'
- run:
name: Update ECS Scheduled Tasks
command: aws cloudformation deploy --template-file lib/tasks/schedule.yml --stack-name ecs-scheduled-tasks-sample-app
workflows:
version: 2
build-test-and-deploy:
jobs:
- build_and_deploy:
filters:
branches:
only:
- master
これにより、
- masterブランチへのプッシュ駆動で
build_and_deploy
が実行される -
aws-ecr/build_and_push_image
でコンテナイメージのビルドとECRへのデプロイが行われる -
aws-ecs/update_task_definition
でタスク定義にビルドしたイメージを指定する -
aws cloudformation deploy --template-file lib/tasks/schedule.yml --stack-name ecs-scheduled-tasks-sample-app
でCloudFormationの反映を実行する
という流れでEventBridgeルールを最新化が行われます。
CircleCIジョブの実行後、Amazon EventBridgeのルール管理画面でsample-app-0001
が作成されていればOKです。