iimon TECH BLOG

iimonエンジニアが得られた経験や知識を共有して世の中をイイモンにしていくためのブログです

ecscheduleを触ってみる!(v0.11)

こんにちは、iimonでエンジニアをしています。須藤です。 本記事はiimonアドベントカレンダー20日目の記事となります。

業務中にbatch処理を追加する際、

サーバー側リポジトリgithub actionsで、コンテナ実行コマンドの上書きと、batch job定義の登録をして、

インフラ側リポジトリでbatch jobのscheduleを設定するということが何度かあり、

サーバー側リポジトリだけで完結させたいと思っていたところ、 ecscheduleを使うとできそうなので試してみることにしました。

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の知見を広めていきたいと感じました!

最後に

最後まで読んでいただきありがとうございます。

この記事を読んで興味を持って下さった方がいらっしゃればカジュアルにお話させていただきたく、是非ご応募をお願いします。

Wantedly / Green

次のアドベントカレンダーの記事はさいとうさんです! どんな記事を書くのかとても楽しみです!

参考

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/