0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

LaravelのJob/バッチ(Command)でSIG_TERMに対応する

Posted at

TL;DL

時間がかかるバッチ処理をECSで運用する場合は、SIM_TERM発火から30秒までに安全に終了させる考慮が必要。

(AWS Batchなどを使った方が、サーバー停止考慮しなくて楽かもしれません)

課題

Laravelをコンテナを利用した環境で運用する場合に、コンテナ入れ替え時に長時間かかる処理が動いている場合に途中でコンテナが停止してしまい、予期せず処理が中断してしまう場合があります。
私の経験としてAWS ECSでの運用を例にして説明します。

(内容には間違いを含む可能性があります)

タスクが停止すると、各コンテナのエントリプロセス (通常は PID 1) に SIGTERM シグナルを送信します。タイムアウトが経過すると、今度は SIGKILL シグナルをプロセスに送信します。デフォルトでは、SIGTERM シグナルの送信後 30 秒のタイムアウトで SIGKILL シグナルを送信します。この値は、ECS タスクのパラメータの stopTimeout を更新することによってタスクのコンテナ単位で調整するか、ECS エージェントの環境変数

supervisorの起動をentrypoint.shで起動している場合、ecsからsupervisorに向けてSIG_TERMがシグナリングされます。

exec /usr/bin/supervisord -c /etc/supervisord.conf

supervisorがシグナルを受け取ると、supervisorにて起動されたすべての子プロセスにシグナリングされ、すべての子プロセスが完了するとsupervisorが停止します。

phpでのシグナリング処理

pcntl_signalを利用してシグナルハンドラを登録します
https://www.php.net/manual/ja/function.pcntl-signal.php#refsect1-function.pcntl-signal-examples

Laravelのシグナリング処理

アプリケーションサーバーの場合

基本的にユーザーへレスポンスをすぐに返すはずなので、考慮不要

Workerの場合

Laravelがすでにpcntl_signalでハンドラを設定済みなので、Laravel側の変数値を参照する
https://github.com/laravel/framework/blob/12.x/src/Illuminate/Queue/Worker.php#L794

※注意:pcntl_signal() は、既存のシグナルハンドラがある場合にはそれを上書きします。
https://www.php.net/manual/ja/function.pcntl-signal.php#refsect1-function.pcntl-signal-notes

Schedulerの場合

Laravel側は利用していないので、自分でシグナルハンドラを登録する

どうするか

worker

キューワーカーで処理する場合、LaravelがSIG_TERMのハンドラを設定済みなので、上書きしないようにしなければいけません。
app('queue.worker')->shouldQuit の値を見ることで、LaravelがSIG_TERMを検知したかどうかを判定することが出来ます

<?php

declare(strict_types=1);

namespace App\Exceptions;

use RuntimeException;

/**
 * 実行中に SIGTERM シグナルを受け取りました
 */
class ReceivedSigTermException extends RuntimeException
{
}
class SampleJob implements ShouldQueue
{
    public function __construct(public int $startWithId)
    {
    }

    public function handle(): void
    {
        $list = TargetList::where('id', '<=', $this->startWithId)->limit(100)->get();

        try {
            foreach ($list as $one) {
                $this->checkSigTerm();
                try {
                    Log::debug("実行中: {$one->id}");

                    // 10秒程度のかかる処理

                    $this->checkSigTerm();

                    // 10秒程度のかかる処理

                    $this->checkSigTerm();

                    // 10秒程度のかかる処理

                    Log::debug("完了: {$one->id}");

                } catch (ReceivedSigTermException $e) {
                    Log::warning("SIGTERMを検知したので処理を中断しました: {$one->id}");

                    // 新しいJobキューを発行して一旦Jobを完了する
                    self::dispatch($one->id)
                        ->onQueue($this->job->getQueue())
                        ->delay(1);

                    throw $e;
                }
            }
        } catch (ReceivedSigTermException) {
            Log::warning('SIGTERMを検知したので処理を中断しました');
        }
    }

    /**
     * @return void
     * @throws ReceivedSigTermException
     */
    public function checkSigTerm(): void
    {
        // Laravelが`SIGTERM`を検知するとtrueに設定される
        if (optional(app('queue.worker'))->shouldQuit) {
            throw new ReceivedSigTermException();
        }
    }
}

scheduler(cron)

あとで書きます

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?