こんにちは、iimonでエンジニアをしています。須藤です。 本記事はiimonアドベントカレンダー20日目の記事となります。
業務中にbatch処理を追加する際、
サーバー側リポジトリのgithub actionsで、コンテナ実行コマンドの上書きと、batch job定義の登録をして、
インフラ側リポジトリでbatch jobのscheduleを設定するということが何度かあり、
サーバー側リポジトリだけで完結させたいと思っていたところ、 ecscheduleを使うとできそうなので試してみることにしました。
- container imageの準備
- ECS clusterを作成
- ecsEventsRoleの作成
- github actions関連
- ECS task定義を作成
- ecscheduleの設定
- Github Actionsを構成
- まとめ
- 最後に
container imageの準備
今回は、try_ecscheduleというリポジトリに適当なDjangoのprojectを作成したので、それを手動でpushしていきます。
https://github.com/hapchoke/try_ecschedule
ECRのプライベートレジストリにtest_ecs_batch_djangoというリポジトリを作成し、imageをpushします。

ECS clusterを作成
test_ecs_batchという名前で作成します。

ecsEventsRoleの作成
scheduleされたtaskをコンソールから作成すると自動で作成してくれるらしいですが、
ない場合はecschedule実行時にerrorが発生するので、docに従って作成します。
https://docs.aws.amazon.com/AmazonECS/latest/developerguide/CWE_IAM_role.html#cw-iam-role-create
github actions関連
github actions variablesにAWS_ACCOUNT_ID, SECURITY_GROUP_ID, SUBNET_IDを設定し、
github actions実行用のiam roleの作成します。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect" : "Allow",
"Action" : [
"events:ListTargetsByRule",
"events:ListRules",
"events:DescribeRule",
"events:PutRule",
"events:PutTargets",
"events:DeleteRule",
"events:RemoveTargets",
"events:TagResource"
],
"Resource" : [
"arn:aws:events:ap-northeast-1:111111111111:rule/*"
]
},
{
"Effect" : "Allow",
"Action" : [
"resource-groups:SearchResources",
"tag:GetResources",
"ecs:DescribeTaskDefinition",
"ecs:RegisterTaskDefinition"
],
"Resource" : "*"
},
{
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": [
"arn:aws:iam::111111111111:role/ecsTaskExecutionRole",
"arn:aws:iam::111111111111:role/ecsEventsRole"
]
}
]
}
https://aws.amazon.com/jp/blogs/security/use-iam-roles-to-connect-github-actions-to-actions-in-aws/
ECS task定義を作成
今回はecspressoを使って登録していきます。
directory構成
ecspresso ├──ecs-task-def.jsonnet └──ecspresso.jsonnet
ecs-task-def.jsonnet
{
"containerDefinitions": [
{
"essential": true,
"image": "{{ must_env `AWS_ACCOUNT_ID` }}.dkr.ecr.{{ must_env `AWS_REGION` }}.amazonaws.com/test_ecs_batch_django:{{ must_env `IMAGE_TAG` }}",
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/test_batch",
"awslogs-region": "{{ must_env `AWS_REGION` }}",
"awslogs-stream-prefix": "test_batch_django"
}
},
"name": "django",
"portMappings": [
{
"appProtocol": "",
"containerPort": 8000,
"hostPort": 8000,
"protocol": "tcp"
}
]
}
],
"cpu": "256",
"executionRoleArn": "arn:aws:iam::{{ must_env `AWS_ACCOUNT_ID` }}:role/ecsTaskExecutionRole",
"family": "test_ecs_batch_task_definition",
"ipcMode": "",
"memory": "512",
"networkMode": "awsvpc",
"runtimePlatform": {
"operatingSystemFamily": "LINUX",
"cpuArchitecture": "ARM64"
},
"pidMode": "",
"requiresCompatibilities": ["FARGATE"]
}
ecspresso.jsonnet
{
region: "{{ must_env `AWS_REGION` }}",
cluster: "{{ must_env `ECS_CLUSTER` }}",
task_definition: "ecs-task-def.jsonnet",
timeout: "10m0s",
}
ecspressoの既存のリソースがある場合は、
ecspresso ├──ecs-service-def.jsonnet ├──ecs-task-def.jsonnet ├──ecs-batch-task-def.jsonnet └──ecspresso.jsonnet
上記のように構成して、ecspresso.jsonnetで指定するtask definitionを切り替える構成にしても良さそうです。
ecscheduleの設定
directory構成
ecschedule └── ecschedule.jsonnet
コンテナの実行コマンドのみ異なる複数のECS batchがあると想定して、jsonnetで作成してみました。
ecschedule.jsonnet
local scheduleExpression = 'cron(0 0/5 * * ? *)';
// バッチを追加したい場合はここにcommandを追加していく
local cmds = [
'command_a',
'command_b',
'command_c',
];
local generateRule(cmd) = {
name: cmd,
scheduleExpression: scheduleExpression,
taskDefinition: 'test_ecs_batch_task_definition',
containerOverrides: [
{
name: 'django',
command: [
'python',
'manage.py',
cmd,
],
},
],
platform_version: '1.4.0',
launch_type: 'FARGATE',
network_configuration: {
aws_vpc_configuration: {
subnets: ['{{ must_env `SUBNET_ID` }}'],
security_groups: ['{{ must_env `SECURITY_GROUP_ID` }}'],
},
},
};
local generateRules = [generateRule(cmd) for cmd in cmds];
{
region: '{{ must_env `AWS_REGION` }}',
cluster: "{{ must_env `ECS_CLUSTER` }}",
rules: generateRules,
}
複雑になりそうな場合は、jsonnetのimport機能をうまく使うことで複数fileでruleを作成し、それらを結合して使う構成が良さそうな気がします。 (同じtask定義を参照しているruleを一つのfileに結合せずに2回に分けて実行すると、1回目の結果が削除されそう。)
Github Actionsを構成
name: Deploy production
on:
push:
branches:
- main
workflow_dispatch:
env:
IMAGE_TAG: ${{ github.sha }}
AWS_ACCOUNT_ID: ${{ vars.AWS_ACCOUNT_ID }}
AWS_REGION: ap-northeast-1
ECS_CLUSTER: test_ecs_batch
ENVIRONMENT: prod
permissions:
id-token: write
contents: read
actions: read
jobs:
Build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::${{ env.AWS_ACCOUNT_ID }}:role/test_ecs_batch_github_actions_role
aws-region: ${{ env.AWS_REGION }}
- uses: kayac/ecspresso@v2
with:
version: v2.2.4
- name: task update
run: |
ecspresso register --config ecspresso/ecspresso.jsonnet
env:
IMAGE_TAG: ${{ env.IMAGE_TAG }}
- uses: Songmu/ecschedule@main
- run: |
ecschedule -conf ecschedule/ecschedule.jsonnet apply -all -prune
env:
SUBNET_ID: ${{ vars.SUBNET_ID }}
SECURITY_GROUP_ID: ${{ vars.SECURITY_GROUP_ID }}
ecschedule実行時、pruneオプションをつけることで自動削除に対応することができる。(これは嬉しい)
動作確認
Github Actionsを実行すると、正常にスケジュールされたタスクが作成されていることが確認できました。

ruleを削除した時に正常に反映されるかの確認として
variablesを一つ削除して、workflowを実行すると、

正常に削除されていることが確認できました。

eventbridgeを確認したところ、ecschedule:tracking-idというtagが割り当てられていたので、それで削除されたことを認識していると思われます。

まとめ
ecscheduleを利用することで、特につまづくこともなく、ecs batchをサーバー側だけで管理することができました。
個人的にecspresso, ecscheduleとgoで書かれているlibraryを多用していることから、goの知見を広めていきたいと感じました!
最後に
最後まで読んでいただきありがとうございます。
この記事を読んで興味を持って下さった方がいらっしゃればカジュアルにお話させていただきたく、是非ご応募をお願いします。
次のアドベントカレンダーの記事はさいとうさんです! どんな記事を書くのかとても楽しみです!
参考
https://github.com/Songmu/ecschedule https://github.com/kayac/ecspresso https://docs.aws.amazon.com/AmazonECS/latest/developerguide/CWE_IAM_role.html#cw-iam-role-create https://aws.amazon.com/jp/blogs/security/use-iam-roles-to-connect-github-actions-to-actions-in-aws/