ECS の Schedule Task (Fargate) と Laravel
ECS の Schedule Task (Fargate) と Laravel

ECS の Schedule Task (Fargate) と Laravel

はじめに

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

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 Fargate | AWS

Schedule Task のつらみ

一見いい事ずくめの Schedule task ですが、一つ大きなつらみがあります。それは task の多重起動問題です

これは公式でも言及されており、残念ながら暫くは仕様として飲み込むしかなさそうだと考えています

まれに、単一のイベントまたはスケジュールされた期間に対して同じルールを複数回トリガーしたり、特定のトリガーされたルールに対して同じターゲットを複数回起動したりする場合があります。

多重起動をインフラレベルで抑えるすべが無いとすると、アプリケーションレイヤーで対策を行うしかありません。DROBE に於いては以下のような対策を取っています

  1. Task で実行される処理は冪等性が担保されるように書く
  2. 多重起動されても大丈夫なように Task の処理にロックを取る

1 はそもそも実行が失敗するなどもあり得るので頑張ってやっていこうという話だと考えています

この記事では以降 2 の Task 処理のロックについて書いていきます

Laravel と一緒に使う時のデザインパターン

ここから Laravel と Schedule task を使う場合のデザインについて書いていきます

Schedule task からの呼び出し

まず Schedule task から任意の処理をどうやって呼び出すかですが、DROBE では Laravel の Artisan command を自作して、それを schedule task から叩いてもらうようにしています

Artisan command はオプションや引数も受け取れるので、必要に応じて schedule task からそれらを受け取れるように汎用的に作る事も可能です

多重起動対策

多重起動対策としては redis の atomic lock 機能を利用しています

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 の調整とアプケーション側の冪等性に頼るという辛みを抱えた運用になっています