AWS Copilot JobsでECSタスクの定期実行を行う
この記事でやること
先月、AWS CopilotがGAになりましたね。
このAWS Copilotですが、GAになる一ヶ月ほど前(v0.5)に、ECSタスクの定期実行を行う「Jobs」を構築できるようになりました。
以前、CopilotでAPIサーバーを作成したのですが、定期実行するバッチ処理が欲しくなったためCopilot Jobsを利用して処理を追加していきたいと思います。
AWS Copilotとは
AWSでコンテナ化されたアプリケーションの開発、リリースを容易に行うためのコマンドラインツールです。
コマンドを叩くとCloudFormationが動き、必要なリソースの作成やデプロイを行うことができます。
CI/CDパイプラインもコマンド一つで作成できます。
Copilotを支える概念
Service
ECS上で動くコンテナアプリケーションのことです。
主にECSのサービスやタスク定義と関連があります。
Environment
本番環境やステージング環境といったステージのことです。
Environmentが異なるとVPCレベルで異なります。
主に以下のリソースと関連があります。
- VPC, Subnet
- ECS Cluster
- ALB, Security Group, Internet Gateway
- Route53
Application
ServiceとEnvironmentを束ねたひとまとまりのことです。
全体像
Service、Environment、Applicationをまとめた全体像は以下のようになります。
Copilot Jobsとは
ECSタスクを定期実行するアーキテクチャです。
上記で説明した概念の中だとServiceと同じレイヤーの概念で、JobsのAWSリソースはEnvironmentに内包されます。
2種類の定期実行方法があります。
- Rate: 固定レート。「3時間毎」のように固定の時間間隔で定期実行できます。
- Cron: クーロン。「月初の12時30分」のように柔軟に定期実行できます。
実体はECSクラスタのScheduled Tasksかと思いきや、Step functionsのステートマシンです。
理由は、Step functionsの方がリトライやタイムアウト等の定期実行処理に必要な機能を持っているからとのことです。(David Killmonさんありがとうございます)
サンプル
すでにこちらの手順でCopilotを利用してAPIサーバーを作成済みなので、フォルダ構成は以下のような状態から始めます。Railsを使っていますが、もちろん何の言語でも大丈夫です。
├── config
├── copilot
│ ├── api
│ │ └── manifest.yml
│ └── .workspace
├── db
...
作成する定期実行処理の中身には触れずに、早速Jobを作成していきましょう。
まず、ECSタスク用のDockerfileを作成します。ほぼAPIサーバー用のDockerfileのコピーなので、不要な部分が混じっていますがご容赦ください。
FROM ruby:2.7.1-alpine3.12
ENV ROOT="/marketing" \
LANG=C.UTF-8 \
TZ=Asia/Tokyo \
EDITOR=vim
WORKDIR ${ROOT}
RUN apk update && \
apk upgrade && \
apk add --no-cache \
tzdata \
nodejs \
mysql-dev \
mysql-client \
vim && \
apk add --virtual build-packs --no-cache \
build-base \
curl-dev \
gcc \
g++ \
libc-dev \
libxml2-dev \
linux-headers \
make
COPY Gemfile ${ROOT}
COPY Gemfile.lock ${ROOT}
RUN bundle install
RUN apk del build-packs
COPY . ${ROOT}
CMD ["bundle", "exec", "rake", "test_task"]
Jobの定義
Dockerfileと定期実行したい処理(test_task
)を作成したらJobを定義していきます。
このように対話型のコマンドでJobを定義できます。
$ copilot job init
# Jobの名前
What do you want to name this Scheduled Job? [? for help]
> task
# ECSタスク用のDockerfileの場所
Which Dockerfile would you like to use for test? [Use arrows to move, type to filter, ? for more help]
./Dockerfile
> Enter custom path for your Dockerfile
Use an existing image instead
What is the path to the Dockerfile for test? [? for help]
> ./task.Dockerfile
# 定期実行方法(RateかCronか)
How would you like to schedule this job? [Use arrows to move, type to filter, ? for more help]
Rate
> Fixed Schedule
# 具体的なスケジュール
What schedule would you like to use? [Use arrows to move, type to filter, ? for more help]
> Custom
Hourly
Daily
Weekly
Monthly
Yearly
What custom cron schedule would you like to use? [? for help] (0 * * * *)
> 0 */3 * * *
# スケジュールの確認
Your job will run at the following times: At 0 minutes past the hour, every 3 hours
Would you like to use this schedule? [? for help] (y/N)
> y
上記が完了すると、以下のようにJobが定義された旨のメッセージが表示されると共に、Dockerイメージ格納用のECRリポジトリが1つ作成されます。
✔ Wrote the manifest for job task at copilot/task/manifest.yml
Your manifest contains configurations like your container size and job schedule (0 */3 * * *).
✔ Created ECR repositories for job task.
Recommended follow-up actions:
- Update your manifest copilot/task/manifest.yml to change the defaults.
- Run `copilot job deploy --name task --env test` to deploy your job to a test environment.
manifest.ymlの変更
この時点でフォルダ構成は以下のようになっているはずです。
├── config
├── copilot
│ ├── api
│ │ └── manifest.yml
│ ├── task
│ │ └── manifest.yml
│ └── .workspace
├── db
...
manifest.ymlはECSのタスク定義のいくつかのパラメータの変更、環境に応じた実行スケジュールの変更等ができます。
今回は、リトライ回数とタイムアウト時間、Environmentがproductionの場合にはRAILS_ENV
を変更するような設定をしました。
name: task
type: Scheduled Job
image:
build: ./task.Dockerfile
cpu: 256
memory: 512
retries: 3
timeout: 10m
on:
schedule: "0 */3 * * *"
variables:
RAILS_ENV: development
environments:
production:
variables:
RAILS_ENV: production
on:
schedule: "0 */3 * * *"
デプロイ
デプロイしていきます。
Service(APIサーバー)ではなく、今回定義したJobをデプロイするよう選択します。
$ copilot deploy
Select a service or job in your workspace [Use arrows to move, type to filter]
api
> task
以下のように、イメージビルド -> タグ付け -> ECRリポジトリログイン -> イメージのプッシュ -> デプロイという流れでデプロイが完了しました。
Name: task
Only found one environment, defaulting to: production
Sending build context to Docker daemon 186.4kB
Step 1/10 : FROM ruby:2.7.1-alpine3.12
---> b46ea0bc5984
...
Successfully built f6b288315c52
Successfully tagged ************.dkr.ecr.ap-northeast-1.amazonaws.com/marketing/task:2da91fe
Login Succeeded
The push refers to repository [************.dkr.ecr.ap-northeast-1.amazonaws.com/marketing/task]
7cf9918bf5c1: Pushed
...
2da91fe: digest: sha256:48bdc970f9c3a09c6cbe5ada90a4b5d287e5c57c9bd6098b5685d830f299f8d8 size: 2831
✔ Deployed task.
動作確認
CloudFormationを確認すると、以下のようなリソースが作成されます。
ServiceではECSサービスが作成されていたのに対して、JobではEventBridgeとStep functionsのリソースが作成されています。
CloudFormation
しばらく時間を置いてStep functionsのコンソールを見ると、ステートマシンが3時間置きに実行されていることがわかりました。(確認のため初回だけ手動実行してしまいました)
Step funcions
ハマりどころ
manifest.ymlでは、Enviromentによって環境変数や実行スケジュールを変更できます。
Environmentによって同一の実行スケジュールを使う場合には、environments
に実行スケジュールを記述する必要はなさそうですが、記述しないとデプロイ時にエラーが発生します。(多分バグだと思います)
on:
schedule: "0 */3 * * *"
variables:
RAILS_ENV: development
environments:
production:
variables:
RAILS_ENV: production
# 以下の記述がないとエラー
on:
schedule: "0 */2 * * *"
エラーログ。schedule
を定義しろと怒られています。
$ copilot deploy
...
...
✘ Failed to deploy job.
✘ execute job deploy: deploy job: convert schedule for job task: missing required field "schedule" in manifest for job task
こちらのissueでも触れられていますが、2020/12/16時点で治りそうな気配はありませんでした。
It looks like schedule is required when we have environments on the manifest.
We have to add
on:
schedule
on the environments too.
example:
environments:
test:
on:
schedule: "expersion"if not added, it will generate an error schedule required.
おわりに
Copilot Jobsを利用して定期実行するECSタスクを作成することができました。
Dockerfileだけ作成すればアーキテクチャ構築からデプロイまでやってくれるので、単発処理をサクッと作りたいような時にはLambdaやECSのスケジューリングタスクよりも簡単にできるのではないでしょうか。
Discussion