猫でもわかるWebプログラミングと副業

本業エンジニアリングマネージャー。副業Webエンジニア。Web開発のヒントや、副業、日常生活のことを書きます。

Laravel の Scheduler を AWS ECS と CloudWatch Logs に最適化させるには

前提

PHP + Laravel で、Web サービスを開発しており、Laravel で実装した Web API が WAS ECS (Elastic Container Service) で動いているのだが、ここに追加で定期処理を実装したい。

定期処理を AWS 上に実現するにはいろいろやり方があるのだが、コードと一緒にスケジュール管理ができる等のメリットがあるので、 Laravel のタスクスケジューリングの機能を使いたい。

そこで、 ECS 上に追加で cron コンテナを動かすようにし、そこで Laravel のタスクスケジューラーを動かすことにした。 すでに ECS は構築されているので、そこにコンテナを追加するコストも低く、管理コストも抑えられるのでこの構成に決めた。

Laravel のタスクスケジューリング機能については以下を参照。これを見れば、 Laravel のスケジューラーが便利そうなのはわかってもらえると思う。

Laravel のスケジューラーを Docker で動かす

詳しくは説明しないが、 AWS ECS から渡される環境変数を cron で実行した PHP に渡せるように、 busybox を使う。

shimoju.jp

ログ(CloudWatch Logs)の課題

ECS には、標準出力や標準エラー出力に出たログを CloudWatch Logs に送る機能があり、 Web API の方ではこの機能を使っていましたが、cron 経由で実行される PHP のログが Docker コンテナの標準出力に出ず、CloudWatch Logs に送られないという課題がある。

そこで、Laravel のタスクスケジューラーの appendOutputTo を使い、ログを /proc/1/fd/1 に出力することでこの問題を解決する。 /proc/1/fd/1 は Docker において標準出力に対応するもので、 appendOutputTo によって、 標準出力*1 の内容を /proc/1/fd/1 に出るように設定することで、 Docker コンテナの標準出力に出るようになり、CloudWatch Logs に送られるようになる。

<?php

class Kernel extends ConsoleKernel
{
    protected function schedule(Schedule $schedule)
    {
        $schedule->command(NanikaCommand::class)
            ->everyMinute()
            ->appendOutputTo('/proc/1/fd/1');
    }

}

JSON 形式でログを出す

CloudWatch Logs に保存されるログは JSON 形式にしたほうが、検索しやすいなどのメリットがある。

Laravel の Command クラスには $this->info などのログのためのメソッドが生えているが、これらには JSON 形式でログを出力する機能が無い。そこで、Command 内でも普通に Laravel の Log ファサードを使ってログを出力する。

ログを JSON 化する方法はこっちの記事を見て欲しい

www.utakata.work

デメリットとして、ログに色がつかなくなるが、些細なデメリットな気がする。

まとめ

AWS で Laravel にタスクスケジューラーを動かした

  • busybox + cron を使って AWS ECS で動かす
  • ログを標準出力に出すために appendOutputTo('/proc/1/fd/1') を使う
  • Command クラスの info メソッドは使わずに、Laravel の Log Facade を使う

*1:正確には標準エラー出力