NRIネットコム Blog

NRIネットコム社員が様々な視点で、日々の気づきやナレッジを発信するメディアです

Scheduled tasks(ECS)をecscheduleで管理してみた

本記事は  基盤デザインウィーク  8日目の記事です。
🌈  7日目  ▶▶ 本記事 ▶▶  9日目  💻


はじめに

こんにちは、加藤です。

以前ecspressoについての記事を書きました☕️

tech.nri-net.com

今回はecspressoに影響を受けて開発されたecscheduleというツールについて触ってみたので紹介したいと思います。

ecscheduleとは

ecscheduleはスケジュールされたタスク(ECS)を管理するツール(OSS)です。

github.com

ecspressoの記事にも書きましたがIaCでインフラリソースを管理する場合、ECSのリソースと他インフラリソースとではライフサイクルが異なるため構成管理の手法を分けた方が運用負荷を軽減できる場合があります。

IaCでECSのリソースを管理しない方針とした際にecspressoではスケジュールされたタスクは管理出来ないため、ecscheduleを併用することで方針に沿った構成管理が可能となります。

つまり、

  • ecspresso → ECSデプロイ
  • ecschedule → スケジュールされたタスクの管理

という棲み分けになります。

また、ecscheduleはecspressoライクに作られている(Terraform連携が可能、環境変数の設定方法が同じなど)ためecspressoとセットで考えられることが多いようです。一部界隈では、ecspressoとecscheduleをまとめて"エスプレスタック"と呼ばれていたりもします🐤

「スケジュールされたタスク」

ECSといえば、ECSクラスター内にコンテナを常時起動させLB経由でリクエストを待ち受ける構成を想像するかもしれませんが、今回取り上げているスケジュールされたタスクは別の利用方法です。

スケジュールされたタスクとは、

  • Amazon EventBridgeを利用して定期的に実行されるECSタスク

のことを指し、ECSを基盤とした定期実行処理を実装する仕組みとなります。

docs.aws.amazon.com

AWSで定期実行処理を実装する際はLambdaを利用することが多いかと思います。しかし、Lambdaには実行時間が15分と制限されており、15分を超える処理が必要な場合は不適となります。ECSでは実行時間に制限はないので、そのような要件がある場合はECSのスケジュールされたタスクを採用することも考えられるでしょう。

またECSを採用する場合、コンテナイメージを再利用することが可能なためプログラミング言語の統一やCI/CDワークフローの簡略化など運用上のメリットもあるかと思います。

具体的な構成例としては、Ruby on Railsのコンテナイメージを用いてWebサーバと定期JOBをECSで構築する構成などが考えられます。

尚、AWS公式ドキュメントの日本語版では「スケジュールされたタスク」とされているのですが、英語版の「Scheduled tasks」の方がスッキリして見栄えも良いので、本記事ではこちらの単語で扱うようにします。

Amazon EventBridgeについて

2024年1月時点では、以下のサービスのどちらかを利用することでScheduled tasksは設定可能です。

  • Amazon EventBridge Rule
  • Amazon EventBridge Scheduler

どちらも"EventBridge"のサービスですが"Scheduler"の方が後発でありAWSからはこちらの利用が推奨されています。

"Scheduler"と"Rule"の違いについては下記記事が分かりやすいので参照下さい。

dev.classmethod.jp

ecscheduleでの管理対象のリソースは"EventBridge Rule"の"ルール"と"ターゲット"です。"EventBridge Scheduler"のリソースについては対象外ですのでご注意ください。

尚、ECSのコンソールからScheduled tasksを設定した場合"EventBridge Rule"の"ルール"と"ターゲット"が作成されます。"EventBridge Scheduler"を利用したい場合は、EventBridgeのコンソールから作成する必要があり、かつ作成されたスケジュールはECSコンソールには表示されません。(この辺りマネジメントコンソールの操作性が微妙なので、AWS側での改善を期待したいところです。)

構成図

前置きが長くなりましたが、ecscheduleを実際に使ってみようと思います。

本記事ではecspressoの記事で作成した構成を再利用します。新規で構築するのはScheduled tasks設定用のEventBridgeのみです。

また目的としてecscheduleの使用感を確認することであるため、コンテナイメージはnginxを使用しコマンドオーバーライドでnginxバージョンを出力するような定期処理を実装します。本来の用途ではありませんので、ご留意ください。

コード体系

コードについても再利用します。最終的なコード体系を下記に示します。

├── config.yaml
├── ecs-service-def.json
├── ecs-task-def.json
├── ★ecschedule.yaml★
├── ★target.json★
└── terraform
    └── aws
        ├── envs
        │   ├── dev
        │   │   ├── backend.tf
        │   │   ├── main.tf
        │   │   └── output.tf
        │   └── prod
        │       ├── (devと同じ)
        └── modules
            ├── ecs
            │   └── main.tf
            └── network
                └── main.tf

★マークを付けたものが今回追加したファイルです。

ecschedule.yaml

ecscheduleの設定ファイルです。管理対象のECSクラスターとルール、pluginを定義します。

ecspressoではECSサービスとECSタスク定義の設定ファイルがありましたが、ecscheduleで使用する設定ファイルはこのファイルのみです。

また環境間で共通ファイルとして使用することとします。

target.json

ルールに紐つけるターゲットを作成する際に使用したファイルです。ecscheduleの利用において必須ではありません。

構築手順

手順は下記の通りです。

  1. 必要リソース構築(dev)
  2. ecschedule dump(dev)
  3. 設定ファイルの修正
  4. ecschedule apply(prod)

devは手動で構築し、prodはecscheduleの設定ファイルからコマンドを用いて構築します。

尚、ecscheduleはタスク定義を作成する手段はないため事前に用意しておく必要があります。 本記事では前回作成したタスク定義を再利用します。

0. 前提

前回の環境を再利用するため、AWSリソース(VPC、サブネット、ALB、ECSクラスター、ECSサービス...etc)は既に構築されていることを前提とします。

一点、前回オレゴンリージョン(us-west-2)で構築していましたが、今回は大阪リージョン(ap-northeast-3)で構築します。Terraformから作成された既存リソースは全て大阪リージョンに作り直していますので、その差分だけあることにご留意ください。(Terraformのbackendで使用するS3、DynamoDBについてはオレゴンのままとしています。)

ecscheduleは以下のバージョンを使用します。

ecschedule → v0.11.3

1. 必要リソース構築(dev)

ではScheduled tasksを設定していきましょう。

まず、dev環境に手動でリソースを構築していきます。構築するリソースは下記の通りです。

  • EventBridge ルール
    • イベントスケジュールはcron形式で任意の値を指定
  • EventBridge ターゲット
    • 作成したルールに紐付け
    • 実行ロールはデフォルトのecsEventsRoleを指定
    • 対象のECSクラスターとECSタスク定義を指定
    • サブネット、セキュリティグループを指定

コンソールだとややこしいのでCLIで作ります。 まずルールを作成します。

> aws events put-rule --name "t3-kato-dev" \
--schedule-expression "cron(0 10 * * ? *)" \
--state ENABLED \
--region ap-northeast-3

続いてターゲットを作成します。

> aws events put-targets --rule "t3-kato-dev" \
--targets file://target.json \
--region ap-northeast-3

target.json

[
    {
        "Id": "t3-kato-dev",
        "Arn": "arn:aws:ecs:ap-northeast-3:<<AWSアカウントID>>:cluster/t3-kato-dev",
        "RoleArn": "arn:aws:iam::<<AWSアカウントID>>:role/ecsEventsRole",
        "Input": "{\"containerOverrides\": [{\"name\": \"nginx\", \"command\": [\"nginx\", \"-v\"]}]}",
        "EcsParameters": {
            "TaskDefinitionArn": "arn:aws:ecs:ap-northeast-3:<<AWSアカウントID>>:task-definition/t3-kato-dev",
            "TaskCount": 1,
            "LaunchType": "FARGATE",
            "NetworkConfiguration": {
                "awsvpcConfiguration": {
                    "Subnets": [
                        "subnet-0384aa261cd82d385",
                        "subnet-0f638c3b58f4e91cb"
                    ],
                    "SecurityGroups": [
                        "sg-0168b19957a56f367"
                    ],
                    "AssignPublicIp": "DISABLED"
                }
            },
            "EnableECSManagedTags": true,
            "EnableExecuteCommand": false
        }
    }
]

CLI実行後ECSコンソールから「Scheduled tasks(スケジュールされたタスク)」が追加されておればOKです。

またcronで指定した時間にECSのログを確認しnginxバージョンが出力されていればOKです。

2. ecschedule dump(dev)

次に作成したリソースをecschedule dumpコマンドを実行して設定ファイルに取り込みます。

> ecschedule dump --cluster t3-kato-dev --region ap-northeast-3 > ecschedule.yaml

ecschedule.yamlを見てみましょう。

ecschedule.yaml

region: ap-northeast-3
cluster: t3-kato-dev
rules:
- name: t3-kato-dev
  scheduleExpression: cron(0 10 * * ? *)
  taskDefinition: t3-kato-dev
  containerOverrides:
  - name: nginx
    command: [nginx, -v]
  launch_type: FARGATE
  network_configuration:
    aws_vpc_configuration:
      subnets:
      - subnet-0384aa261cd82d385
      - subnet-0f638c3b58f4e91cb
      security_groups:
      - sg-0168b19957a56f367
      assign_public_ip: DISABLED

手動で構築したリソースが取り込めていることが分かります。

試しにdiffを取ってみます。下記のようにecschedule.yamlを修正します。

diff --git a/ecschedule.yaml b/ecschedule.yaml
index 90cee2b..1b3648c 100644
--- a/ecschedule.yaml
+++ b/ecschedule.yaml
@@ -2,7 +2,7 @@ region: ap-northeast-3
 cluster: t3-kato-dev
 rules:
 - name: t3-kato-dev
-  scheduleExpression: cron(0 10 * * ? *)
+  scheduleExpression: cron(0 20 * * ? *)
   taskDefinition: t3-kato-dev
   containerOverrides:
   - name: nginx

修正後、ecschedule diffコマンドを実行します。

> ecschedule -conf ecschedule.yaml diff -all
[ecschedule] 💡 diff of the rule "t3-kato-dev"
name: t3-kato-dev
scheduleExpression: cron(0 120 * * ? *)
taskDefinition: t3-kato-dev
containerOverrides:
- name: nginx
  command: [nginx, -v]
launch_type: FARGATE
network_configuration:
  aws_vpc_configuration:
    subnets:
    - subnet-0384aa261cd82d385
    - subnet-0f638c3b58f4e91cb
    security_groups:
    - sg-0168b19957a56f367
    assign_public_ip: DISABLED

実際は"cron(0 120 * * ? *)"となっている文字列の"1"が赤色"2"が緑色で表現されており差分が表示されていることが分かります。

3. 設定ファイルの修正

取り込んだ設定ファイルについて、prodでも使えるよう修正していきます。

環境差分については、前回同様「tfstate」と「環境変数」で切り分けることとします。

index 90cee2b..16513dd 100644
--- a/ecschedule.yaml
+++ b/ecschedule.yaml
@@ -1,9 +1,9 @@
-region: ap-northeast-3
-cluster: t3-kato-dev
+region: {{ must_env `AWS_DEFAULT_REGION` }}
+cluster: t3-kato-{{ must_env `ENV` }}
 rules:
-- name: t3-kato-dev
+- name: t3-kato-{{ must_env `ENV` }}
   scheduleExpression: cron(0 10 * * ? *)
-  taskDefinition: t3-kato-dev
+  taskDefinition: t3-kato-{{ must_env `ENV` }}
   containerOverrides:
   - name: nginx
     command: [nginx, -v]
@@ -11,8 +11,12 @@ rules:
   network_configuration:
     aws_vpc_configuration:
       subnets:
-      - subnet-0384aa261cd82d385
-      - subnet-0f638c3b58f4e91cb
+      - "{{ tfstate `module.network.module.vpc.aws_subnet.private[0].id` }}"
+      - "{{ tfstate `module.network.module.vpc.aws_subnet.private[1].id` }}"
       security_groups:
-      - sg-0168b19957a56f367
+      - "{{ tfstate `module.network.aws_security_group.ecs.id` }}"
       assign_public_ip: DISABLED
+plugins:
+- name: tfstate
+  config:
+    url: s3://t3-kato-terraform-backend-osaka/{{ must_env `ENV` }}/terraform.tfstate

tfstateのURLがt3-kato-terraform-backend-osakaとなっています(前回記事ではt3-kato-terraform-backendでした)が、これはとある問題の解消のためです。

詳細は後述の[ 検証中にハマったポイント ]の項目2で説明します。

4. ecschedule apply(prod)

最後にecschedule applyを実行してリソースを構築します。先にdiffを取ります。

> ENV=prod ecschedule -conf ecschedule.yaml diff -all
[ecschedule] 💡 diff of the rule "t3-kato-prod"
name: t3-kato-prod
scheduleExpression: cron(0 10 * * ? *)
taskDefinition: t3-kato-prod
containerOverrides:
- name: nginx
  command: [nginx, -v]
launch_type: FARGATE
network_configuration:
  aws_vpc_configuration:
    subnets:
    - subnet-039cb8363ae3f2b74
    - subnet-03f653bcc55408971
    security_groups:
    - sg-03d360a9d98c40504
    assign_public_ip: DISABLED

全文で表示されたので良さそうです。

では、applyしていきます。

❯ ENV=prod ecschedule -conf ecschedule.yaml apply -all -prune
[ecschedule] applying the rule "t3-kato-prod"
[ecschedule] 💡 applying following changes
name: t3-kato-prod
scheduleExpression: cron(0 10 * * ? *)
taskDefinition: t3-kato-prod
containerOverrides:
- name: nginx
  command: [nginx, -v]
launch_type: FARGATE
network_configuration:
  aws_vpc_configuration:
    subnets:
    - subnet-039cb8363ae3f2b74
    - subnet-03f653bcc55408971
    security_groups:
    - sg-03d360a9d98c40504
    assign_public_ip: DISABLED

[ecschedule] ✅ following rule applied
name: t3-kato-prod
scheduleExpression: cron(0 10 * * ? *)
taskDefinition: t3-kato-prod
containerOverrides:
- name: nginx
  command: [nginx, -v]
launch_type: FARGATE
network_configuration:
  aws_vpc_configuration:
    subnets:
    - subnet-039cb8363ae3f2b74
    - subnet-03f653bcc55408971
    security_groups:
    - sg-03d360a9d98c40504
    assign_public_ip: DISABLED
region: ap-northeast-3
cluster: t3-kato-prod

問題なくapplyできてそうです。devと同じようにECSコンソールからの確認とログの確認ができればOKです。

検証中にハマったポイント

検証中にハマったポイントがいくつかあったので書き残しておきます。

1. ecschedule dumpコマンドが成功しない

結論から書くと1つの"ルール"に付き複数の"ターゲット"を紐つけていたためでした。

こちらにも記載されているのですが、管理対象のリソースには以下の制約があるようです。

  • Rule名がユニークであること
  • RuleにはTargetが1つだけ紐付いており、TaskのContainer Overridesでタスクを実行している

2つ目の制約によりdumpが正常に動いていなかったものと思われます。

2. 別リージョンからtfstateの読み込みは出来ない

Terraformのbackend(S3とDynamoDB)をオレゴンリージョンに、AWSリソースを大阪リージョンに設置していたのですが、tfsstateプラグインでS3からstate情報を取得しようとするとエラーとなりました。

これは、ecscheduleで使用されているtfstate-lookupの問題らしく、リリース履歴を見る限りv1.1.6で解消されていそうです。

github.com

しかし、ecschedule v0.11.3ではtfstate-lookupはv1.1.4が埋め込まれており、上記の事象がfixされていない状態に見受けられます。(ecspreesoの方では解消されていました。)

仕方ないので、今回はオレゴンリージョンのS3バケットを大阪リージョンのS3バケットにレプリケーションすることで対応しました。

3. サブコマンドからリソース削除が出来ない

ハマったというより仕様の話ですが・・

ecspressoではサブコマンドにdeletederegisterがあり設定ファイルを更新することなくリソースの削除が可能だったのですが、ecscheduleには類似したサブコマンドがありませんでした。

そのためルールを定義していない設定ファイルを用意しそちらを読み込ませることでリソース削除を実施しました。

❯ ENV=prod ecschedule -conf ecschedule-delete.yaml apply -all -prune
[ecschedule] orphaned rules will be deleted 
[ecschedule] 🪓 deleting following rule
name: t3-kato-prod
scheduleExpression: cron(0 10 * * ? *)
taskDefinition: t3-kato-prod
containerOverrides:
- name: nginx
  command: [nginx, -v]
role: arn:aws:iam::957735096129:role/ecsEventsRole
launch_type: FARGATE
network_configuration:
  aws_vpc_configuration:
    subnets:
    - subnet-039cb8363ae3f2b74
    - subnet-03f653bcc55408971
    security_groups:
    - sg-03d360a9d98c40504
    assign_public_ip: DISABLED
region: ap-northeast-3
cluster: t3-kato-prod

ecschedule-delete.yaml

region: {{ must_env `AWS_DEFAULT_REGION` }}
cluster: t3-kato-{{ must_env `ENV` }}
rules:

まとめ

今回はecspressoに影響を受けて開発されたecscheduleについて紹介しました。

ecspressoと併用してScheduled tasksも管理したい場合は有用なツールだと感じましたが、取り扱いには少し注意がいるので用法を守って使うのが良いかと思います☕️

執筆者: 加藤 俊稀

🧑‍💻クラウドエンジニア
📝https://tech.nri-net.com/archive/author/t3-kato