概要
If you configure your worker environment with an existing SQS queue and choose an Amazon SQS FIFO queue, periodic tasks aren't supported.
FIFOキューと定期的なタスクが併用できない旨が記載されています。
対策方針
オートスケールによって台数が増減しても、ワーカー環境の定期的なタスク
は重複実行されないようになっています。
その仕組みとしては、DynamoDBの AWSEBWorkerCronLeaderRegistry
に書き込みできたインスタンスをリーダー(Leader)として、そのリーダーインスタンスのみで実行することでそれを実現しています。
そのリーダーの選出を流用し、ワーカー環境のEC2でcron実行をさせることで表題の併用を実現してみます。
検証環境
Ruby 2.6 running on 64bit Amazon Linux 2/3.1.1
Amazon Linux 2
なので注意してください。
ステップ : ダミーのcron.yamlを作成し、 AWSEBWorkerCronLeaderRegistry
を書き込みさせる
version: 1
cron: # UTC
- name: "dummy-job" # 何でもよい
url: "/health" # 何でもよい
schedule: "7 7 7 7 7" # 何でもよい
ステップ : 自身がLeaderか判定するスクリプトを用意
#!/usr/bin/env bash
# EC2でないときはexit
if [[ ! -f /var/lib/cloud/data/instance-id ]]; then
exit
fi
instance_id=$(cat /var/lib/cloud/data/instance-id)
# AWSEBWorkerCronLeaderRegistry のテーブル名を取得
table_name=$(awk -F': ' '$1=="registry_table" {printf $2}' /etc/aws-sqsd.d/default.yaml)
# 定期的に更新されている、leader_idを取得する (ex: i-XXXXX.${registration-record.worker_id})
leader_id=$(aws dynamodb get-item --region ${AWS_REGION} --table-name ${table_name} --key '{"id": {"S": "leader-election-record"} }' | jq -r .Item.leader_id.S)
echo ${leader_id} | grep -q ${instance_id}
exit $?
ステップ : cronをセット
ステップ2で作成した bin/eb_is_worker_leader
が成功したら、処理を実行するようにcronをセットします。
例えば、Rubyでcronをセットする whenever を使う場合、下記のようになります。
job_type :leader_runner, "cd :path && bin/eb_is_worker_leader && bin/rails runner -e :environment ':task' :output"
every :hour do
leader_runner "SomeModel.ladeeda"
end
なお、whenever で用意されている runner
は下記のとおりです。
比べると bin/eb_is_worker_leader
が追加されているだけなのがわかるかと思います。
job_type :runner, "cd :path && bin/rails runner -e :environment ':task' :output"
おまけ: wheneverでcronを更新する
#!/usr/bin/env bash
# Workerでなければ何もしない
env_name=$(jq -r .Name /opt/elasticbeanstalk/config/ebenvinfo/envtier.json)
if [[ ! ${env_name} = 'Worker' ]]; then
exit
fi
/opt/elasticbeanstalk/.rbenv/shims/bundle exec whenever --user webapp --update-crontab
まとめ
これらによって、すべてのワーカーインスタンスでcronが実行されます。
cronではリーダーのみ処理が継続されるため、定期的なタスクの重複した実行が防げます。
似たような解決策
- https://github.com/awsdocs/elastic-beanstalk-samples/blob/main/configuration-files/aws-provided/instance-configuration/cron-leaderonly-linux.config
- https://github.com/dignoe/whenever-elasticbeanstalk
どちらも Amazon Linux 1
が対象です
これらはインスタンス数でLeaderを判断しています。
この記事のやり方では、 AWSEBWorkerCronLeaderRegistry
を流用することで、シンプルな実装になるかなと思っています。