- はじめに
- Schedule Task とは
- Schedule Task を使う事による利点
- Schedule Task のつらみ
- Laravel と一緒に使う時のデザインパターン
- Schedule task からの呼び出し
- 多重起動対策
- その他
はじめに
DROBE では cron として実行したい Application にまつわる処理を ECS の Schedule Task を利用して運用しています
この記事では Fargate Schedule Task を Laravel でどう使っているかや管理方法などを記載します
Schedule Task とは
ECS の Schedule task は、cron のようなスケジュールされた Task を起動してくれる機能です
タスクのスケジューリング (cron)
「翻訳は機械翻訳により提供されています。提供された翻訳内容と英語版の間で齟齬、不一致または矛盾がある場合、英語版が優先します。」 Amazon ECS は、 cron のようなスケジュール、または CloudWatch イベント に応答してタスクをスケジュールする機能をサポートしています。この機能は、Fargate および EC2 の両方の起動タイプを使用する Amazon ECS タスクでサポートされています。 バックアップオペレーションやログスキャンなど、クラスター内で一定間隔で実行するタスクがある場合は、Amazon ECS コンソールを使用して、指定した時間にクラスターで 1 つ以上のタスクを実行する CloudWatch イベント ルールを作成できます。スケジュールされたイベントルールは、特定の間隔 ( N 分、時間、日ごとに実行) に設定できます。または、より複雑なスケジュールに設定する場合は、 cron 式を使用することもできます。詳細については、 Amazon CloudWatch Events ユーザーガイドの「 ルールのスケジュール式 」を参照してください。 また、Fargate タスクを CloudWatch イベント のタスクターゲットとして設定できるようになりました。これにより、発生した変更に応答してタスクを起動することができます。さらに、CloudWatch イベント コンソールおよび AWS CLI を介して awsvpc ネットワークを使用するときに、ネットワーク設定を修正でき、CloudWatch イベント によってトリガーされた Fargate
docs.aws.amazon.com
DROBE では Cloud Watch の Event をトリガーとして、Fargate の Task を起動しています
Schedule Task を使う事による利点
Schedule Task を利用して Fargate を cront として活用する最大の利点は cpu や memory などのリソースが task 毎に完全に独立となるので、原理的に task 同士が干渉しずらくなる事だと考えています。例えばプログラムのミスでどこかの task が大量に memory を使ってしまったとしても、それが原因で他のタスクに影響が出るという事はほぼありません
また Fargate は 1 秒辺りで課金されるので、cron 用に数台サーバーを常時持っていくよりも安くなる可能性もあります
料金 - AWS Fargate | AWS
AWS Fargate では、前払いは発生せず、使用しているリソースに対してのみ料金が発生します。コンテナ化されたアプリケーションで消費される vCPU およびメモリリソースの量に対する料金が発生します。 料金は、タスク または Pod のために要求された vCPU およびメモリリソースに基づきます。2 つの料金体系は単独で設定可能です。 Fargate の使用状況が一定の場合、Savings Plans が活用できます。Savings Plans を利用して、1 年間または 3 年間、特定量のコンピューティング使用量 (1 時間あたりのドル単位で測定) で契約をしていただくと、AWS Fargate の使用料金を最大 50% 節約できます。 料金は 1 秒あたりで、最低 1 分です。時間は、コンテナイメージのダウンロード (docker pull) を開始した時刻からタスクが終了するまでで計算され、最も近い秒数に切り上げられます。 コンテナで他の AWS のサービスを使用したり、データを転送したりする場合は、追加料金がかかります。例えば、コンテナがアプリケーションのログ記録に Amazon CloudWatch Logs を使用する場合は、CloudWatch の使用量に対して課金されます。 AWS サービス料金の詳細については、該当する AWS サービスの詳細ページにある料金セクションを参照してください。よく使用されるサービスの料金表へのリンクを以下に示します。 データ転送: 標準の
aws.amazon.com
Schedule Task のつらみ
一見いい事ずくめの Schedule task ですが、一つ大きなつらみがあります。それは task の多重起動問題です
これは公式でも言及されており、残念ながら暫くは仕様として飲み込むしかなさそうだと考えています
まれに、単一のイベントまたはスケジュールされた期間に対して同じルールを複数回トリガーしたり、特定のトリガーされたルールに対して同じターゲットを複数回起動したりする場合があります。
CloudWatch イベント のトラブルシューティング
Amazon EventBridge is the preferred way to manage your events. CloudWatch イベント and EventBridge are the same underlying service and API, but EventBridge provides more features. Changes you make in either CloudWatch or EventBridge will appear in each console. For more information, see Amazon EventBridge.
docs.aws.amazon.com
多重起動をインフラレベルで抑えるすべが無いとすると、アプリケーションレイヤーで対策を行うしかありません。DROBE に於いては以下のような対策を取っています
- Task で実行される処理は冪等性が担保されるように書く
- 多重起動されても大丈夫なように Task の処理にロックを取る
1 はそもそも実行が失敗するなどもあり得るので頑張ってやっていこうという話だと考えています
この記事では以降 2 の Task 処理のロックについて書いていきます
Laravel と一緒に使う時のデザインパターン
ここから Laravel と Schedule task を使う場合のデザインについて書いていきます
Schedule task からの呼び出し
まず Schedule task から任意の処理をどうやって呼び出すかですが、DROBE では Laravel の Artisan command を自作して、それを schedule task から叩いてもらうようにしています
Artisan Console
Artisan is the command line interface included with Laravel. Artisan exists at the root of your application as the artisan script and provides a number of helpful commands that can assist you while you build your application.
laravel.com
Artisan command はオプションや引数も受け取れるので、必要に応じて schedule task からそれらを受け取れるように汎用的に作る事も可能です
多重起動対策
多重起動対策としては redis の atomic lock 機能を利用しています
Cache
Some of the data retrieval or processing tasks performed by your application could be CPU intensive or take several seconds to complete. When this is the case, it is common to cache the retrieved data for a time so it can be retrieved quickly on subsequent requests for the same data.
laravel.com
atomic lock を使う事で分散環境においてもレースコンディションを心配する事なくロックを取る事が可能です
具体的には以下のようなコードでロックを取っています
// lock を取る処理
public static function runIfNotRunning(string $cacheKey, callable $func, int $lockSec = 120)
{
$lockResult = Cache::lock($cacheKey, $lockSec);
if ($lockResult['locked']) {
$func(); // ここで処理を実行する
} else {
Log::info("{$cacheKey} is already running. do nothing.");
}
}
// 使う側
self::runIfNotRunning("key", function() {
// task 内で行いたい処理
});
$lockSec
は task が終わるまでの時間によって増減させるのが良いと思います
その他
Schedule task の多重起動は、経験上最初の起動の 1 ~ 2 分後に次のタスクが起動してしまうといった事もありました
その場合は、例えば task の処理がすぐに終わってしまったり、次のタスクの起動前に $lockSec
以上の時間がたってしまったりすると 2 つめの task の実行が始まってしまいます
これに関してはあまり良い対策が思いついていないというのが正直な所で、 $lockSec
の調整とアプケーション側の冪等性に頼るという辛みを抱えた運用になっています