はじめに
LaravelとDockerを使って、バッチ処理の勉強していました。バッチ処理はcronを使ってphp artisan schedule:run
定期実行することができるのですが、php-fpmが動いているコンテナでcronを単純に動かそうとすると、思わぬ落とし穴にはまります。最悪の場合、バッチが動いていないことに誰も気づかない「サイレント障害」を引き起こす可能性もあるそうです。そのため、Dockerコンテナでcronを安全に動かすための2つ方法を解説します。
前提知識 - LaravelスケジューラとDockerの基本ルール
Laravelスケジューラの仕組み
Laravelのスケジュールタスクは、app/Console/Kernel.phpに定義しますが、Laravel自体が時間を監視してタスクを起動してくれるわけではありません。外部の誰かが1分ごとにphp artisan schedule:runというコマンドを実行してくれることで、初めてスケジュールが評価され、実行すべきタスクが動きます。
この「外部の誰か」の役割を担うのが、伝統的にcronです。
Dockerコンテナの絶対的なルール:「主役は一人だけ」
Dockerコンテナは、起動時に指定されたたった一つの「メインプロセス(主役)」しか持つことができません。そして、その主役が終了すると、コンテナ自身も停止してしまいます。 コンテナを動かし続けるには、主役がフォアグラウンドで動き続ける必要があるのです。
プロセスの基礎知識
フォアグラウンド (Foreground): 目の前で実行され、終了するまでターミナルを占有するプロセス。コンテナの「主役」はこれである必要があります。php-fpmは、Docker環境ではこの役を担います。
バックグラウンド (Background): 裏方で実行されるプロセス。コマンドの末尾に&を付けることで実行できます。
デーモン (Daemon): cronのように、バックグラウンドで動き続けることを宿命づけられたプログラム。&を付けなくても、自らバックグラウンドで起動する性質を持ちます。
1つのコンテナにphp-fpmとcronを単純に共存させられない
1つのコンテナ内にcronをバックグラウンドで起動し、php-fpmをフォアグラウンドで起動した場合、技術的には共存可能なのですが、この構成は非常に危険です。
もし、バックグラウンドで動いているcronが何らかの理由(設定ミス、メモリ不足など)で停止してしまっても、主役であるphp-fpmは動き続けているため、コンテナは正常なフリを続けます。結果、誰も気づかないうちに、全てのバッチ処理が停止してしまい、cronがサイレント障害を起こしてしまいます。
解決法① - supervisordで同居させる方式
この問題を解決する一つ目の方法が、supervisordというプロセス管理ツールを導入することです。
supervisordは、コンテナ内で複数のプロセスを監視・管理してくれる「マネージャー」です。自身がコンテナの「主役」となり、php-fpmやcronを「部下」として管理します。これにより1つのコンテナ内で共存が可能になります。
解決策② - コンテナを分離する方式
もう一つの方法は、Webサーバーのコンテナとスケジューラのコンテナを完全に分離する方法です。
この方式では、1つのコンテナに複数の仕事を兼任させるのではなく、「Webリクエスト担当」と「定期タスク担当」という形で、それぞれのコンテナを単一の役割を持つ「専門家」として独立させます。これにより、お互いのプロセスが干渉しない、クリーンで堅牢な構成を実現できます。
おわりに
Dockerコンテナでcronのようなデーモンを扱う際は、コンテナの「主役は一人だけ」というルールを理解し、プロセスのライフサイクルをきちんと管理することが重要です。
サイレント障害を避けるためにも、今回紹介したsupervisordか「コンテナ分離」のいずれかの方法を試してみてください。