概要
Laravelでキュー処理を使うことがあり、開発環境としてdockerでローカル環境を整備する。
その際に調べた内容をメモします。
ファイル構成
以下のようなファイル構成で構築してみる。
※ ソースコード:Github-reflet/laravel5.6
├ docker ... docker関連ファイルを配置するフォルダ
│ ├ php
│ │ ├ .dockerignore
│ │ ├ Dockerfile ... PHP環境を整備します
│ │ ├ php.ini
│ │ └ www.conf
│ │
│ └ supervisor
│ ├ application.conf ... Laravelワーカーを定義します
│ ├ Dockerfile ... PHPコンテナのイメージをベースにカスタマイズする
│ └ supervisord.conf ... supervisorの設定ファイル
│
├ src ... Laravelのソースコードを配置するフォルダ
│ ├ app
│ └ config
│ └ logging.php ... job実行ログを追加する
│
└ docker-compose.yml ... PHPコンテナやsupervisorコンテナを定義する
Supervisorコンテナ
supervisorをインストールしたコンテナを用意します。
※ PHPコンテナのdockerイメージ(local/php:7.3)
をベースにして作ります
FROM my-laravel5.6/php:7.3
USER root
RUN apt-get install -y supervisor
COPY ./docker/supervisor/supervisord.conf /etc/supervisor/supervisord.conf
COPY ./docker/supervisor/application.conf /etc/supervisor/conf.d/application.conf
CMD ["/usr/bin/supervisord"]
設定ファイルはこんな感じにしました。
[unix_http_server]
file=/var/run/supervisor.sock
chmod=0700
[supervisord]
nodaemon=true
logfile=/var/log/supervisor/supervisord.log
pidfile=/var/run/supervisord.pid
childlogdir=/var/log/supervisor
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
[supervisorctl]
serverurl=unix:///var/run/supervisor.sock
[include]
files = /etc/supervisor/conf.d/*.conf
Laravelのワーカーはこんな感じで設定してみました。
; [program:php-fpm]
; command = /usr/local/bin/docker-php-entrypoint php-fpm -D
; autostart = true
[program:laravel-sync]
command=php /var/www/www.example.com/artisan queue:work --tries=1 --queue=laravel-sync
process_name=%(program_name)s_%(process_num)02d
numprocs=1
autostart=true
autorestart=true
user=root
redirect_stderr=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
; stdout_logfile=/var/log/supervisor/laravel-sync.log
[program:laravel-async]
command=php /var/www/www.example.com/artisan queue:work --tries=1 --queue=laravel-async
process_name=%(program_name)s_%(process_num)02d
numprocs=5
autostart=true
autorestart=true
user=root
redirect_stderr=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
; stdout_logfile=/var/log/supervisor/laravel-async.log
; [program:crond]
; command = /usr/sbin/crond
; user = root
; autostart = true
; stdout_logfile=/dev/stdout
; stdout_logfile_maxbytes=0
; stderr_logfile=/dev/stderr
; stderr_logfile_maxbytes=0
docker-compose.ymlファイル
下記のようにPHPコンテナの後に起動するように定義しました。
version: '3'
services:
httpd:
image: my-laravel5.6/apache:2.4
...
php:
image: my-laravel5.6/php:7.3
build:
context: ./
dockerfile: ./docker/php/Dockerfile
ports:
- '9000:9000'
volumes:
- ./src:/var/www/www.example.com:cached
supervisor:
image: my-laravel5.6/supervisor:7.3
build:
context: ./
dockerfile: ./docker/supervisor/Dockerfile
volumes:
- ./src:/var/www/www.example.com:cached
depends_on:
- php
サーバ起動
dockerイメージをビルドして、dockerコンテナを起動します。
# dockerイメージ作成
$ docker-compose build
# dockerコンテナ起動
$ docker-compose up -d
ログ出力
ジョブ実行ログを出力したいので、ロギングを追加する。
'channels' => [
...
// laravel job log.
'job' => [
'driver' => 'daily',
'path' => storage_path('logs/job.log'),
'level' => 'info',
'permission' => 0666,
'days' => 7,
],
...
]
DDL - Data Definition Language
データベースをQueueで使う場合は、テーブルが必要となるので、下記を実行する。
$ docker-compose exec php php artisan queue:table
$ docker-compose exec php php artisan queue:failed-table
$ docker-compose exec php php artisan migrate
環境設定 (env)
今回は、データベースをコネクションとして使うので以下のようにします。
QUEUE_CONNECTION=database
テスト準備
動作確認するため、コマンドを1つとジョブを7つ作成してみる。
$ docker-compose exec php php artisan make:command StartJobsCommand
$ docker-compose exec php php artisan make:job SampleJobA
$ docker-compose exec php php artisan make:job SampleJobB
$ docker-compose exec php php artisan make:job SampleJobC
$ docker-compose exec php php artisan make:job SampleJobD
$ docker-compose exec php php artisan make:job SampleJobE
$ docker-compose exec php php artisan make:job SampleJobF
$ docker-compose exec php php artisan make:job SampleJobG
ジョブを実行するコマンドはこのようにしました。
※ ワーカー(laravel-sync
): プロセス1つ → 順番に実行される
※ ワーカー(laravel-async
): プロセス5つ → 5つまで 並列実行される
<?php
namespace App\Console\Commands;
use Carbon\Carbon;
use Illuminate\Console\Command;
use App\Jobs\SampleJobA;
use App\Jobs\SampleJobB;
use App\Jobs\SampleJobC;
use App\Jobs\SampleJobD;
use App\Jobs\SampleJobE;
use App\Jobs\SampleJobF;
use App\Jobs\SampleJobG;
class StartJobsCommand extends Command
{
protected $signature = 'job:start';
...
public function handle()
{
$this->info(sprintf('[%s] job:start command start.', Carbon::now()->format('Y-m-d H:i:s')));
// SampleJobAを同期(laravel-sync)で実行する
SampleJobA::dispatch()->onQueue('laravel-sync')->delay(Carbon::now());
$this->info(sprintf('[%s] job:start command - sample job A.', Carbon::now()->format('Y-m-d H:i:s')));
// SampleJobBを同期(laravel-sync)で実行する
SampleJobB::dispatch()->onQueue('laravel-sync')->delay(Carbon::now());
$this->info(sprintf('[%s] job:start command - sample job B.', Carbon::now()->format('Y-m-d H:i:s')));
// SampleJobCを非同期(laravel-async)で実行する
SampleJobC::dispatch()->onQueue('laravel-async')->delay(Carbon::now());
$this->info(sprintf('[%s] job:start command - sample job C.', Carbon::now()->format('Y-m-d H:i:s')));
...
// SampleJobGを非同期(laravel-async)に向けて発行
SampleJobG::dispatch()->onQueue('laravel-async')->delay(Carbon::now());
$this->info(sprintf('[%s] job:start command - sample job G.', Carbon::now()->format('Y-m-d H:i:s')));
}
}
ジョブについては、以下のように記載しています。
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Support\Facades\Log;
class SampleJobA implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct()
{
//
}
public function handle()
{
$Log = Log::channel('job');
$Log->info(self::class . ' start.');
for ($i = 1; $i <= 5; $i++) {
$Log->info(self::class . '::' . $i);
sleep(1);
}
$Log->info(self::class . ' complete.');
}
}
動作確認
supervisorの状態を確認してみる。
$ docker-compose exec supervisor service supervisor status
supervisord is running
ワーカーの起動状況を確認してみる (laravel-sync: 1つ, laravel-async: 5つ)
$ docker-compose exec supervisor supervisorctl status
laravel-async:laravel-async_00 RUNNING pid 12, uptime 0:20:12
laravel-async:laravel-async_01 RUNNING pid 11, uptime 0:20:12
laravel-async:laravel-async_02 RUNNING pid 10, uptime 0:20:12
laravel-async:laravel-async_03 RUNNING pid 9, uptime 0:20:12
laravel-async:laravel-async_04 RUNNING pid 13, uptime 0:20:12
laravel-sync:laravel-sync_00 RUNNING pid 14, uptime 0:20:12
ジョブを実行してみる。
$ docker-compose exec php php artisan job:start
[2021-06-25 17:52:42] job:start command start.
[2021-06-25 17:52:42] job:start command - sample job A. # ← 直列実行
[2021-06-25 17:52:42] job:start command - sample job B. # ← 直列実行 (Aの後に実行される)
[2021-06-25 17:52:42] job:start command - sample job C. # ← 並列実行
[2021-06-25 17:52:42] job:start command - sample job D. # ← 並列実行
[2021-06-25 17:52:42] job:start command - sample job E. # ← 並列実行
[2021-06-25 17:52:42] job:start command - sample job F. # ← 並列実行
[2021-06-25 17:52:42] job:start command - sample job G. # ← 並列実行
dockerのログを見てみる。
$ docker-compose logs supervisor
supervisor_1 | [2021-06-25 16:05:19][3] Processing: App\Jobs\SampleJobC # ← 並列(ジョブC)が開始される
supervisor_1 | [2021-06-25 16:05:20][1] Processing: App\Jobs\SampleJobA # ← 直列(ジョブA)が開始される
supervisor_1 | [2021-06-25 16:05:20][4] Processing: App\Jobs\SampleJobD # ← 並列(ジョブD)が開始される
supervisor_1 | [2021-06-25 16:06:20][4] Processed: App\Jobs\SampleJobD # ← 並列(ジョブD)が終了した
supervisor_1 | [2021-06-25 16:06:20][3] Processed: App\Jobs\SampleJobC # ← 並列(ジョブC)が終了した
supervisor_1 | [2021-06-25 16:06:20][1] Processed: App\Jobs\SampleJobA # ← 直列(ジョブA)が終了した
supervisor_1 | [2021-06-25 16:06:20][2] Processing: App\Jobs\SampleJobB # ← 直列(ジョブB)が開始される
supervisor_1 | [2021-06-25 16:07:20][2] Processed: App\Jobs\SampleJobB # ← 直列(ジョブB)が終了した
Laravelのログを見てみる。
[2021-06-29 16:06:12] local.INFO: App\Jobs\SampleJobA start.
[2021-06-29 16:06:15] local.INFO: App\Jobs\SampleJobC start.
[2021-06-29 16:06:15] local.INFO: App\Jobs\SampleJobD start.
[2021-06-29 16:06:15] local.INFO: App\Jobs\SampleJobE start.
[2021-06-29 16:06:15] local.INFO: App\Jobs\SampleJobF start.
[2021-06-29 16:06:15] local.INFO: App\Jobs\SampleJobG start.
...
[2021-06-29 16:06:18] local.INFO: App\Jobs\SampleJobA complete.
[2021-06-29 16:06:18] local.INFO: App\Jobs\SampleJobC complete.
[2021-06-29 16:06:18] local.INFO: App\Jobs\SampleJobD complete.
[2021-06-29 16:06:18] local.INFO: App\Jobs\SampleJobE complete.
[2021-06-29 16:06:18] local.INFO: App\Jobs\SampleJobF complete.
[2021-06-29 16:06:18] local.INFO: App\Jobs\SampleJobG complete.
[2021-06-29 16:06:18] local.INFO: App\Jobs\SampleJobB start.
[2021-06-29 16:06:18] local.INFO: App\Jobs\SampleJobB::1
[2021-06-29 16:06:19] local.INFO: App\Jobs\SampleJobB::2
[2021-06-29 16:06:20] local.INFO: App\Jobs\SampleJobB::3
[2021-06-29 16:06:21] local.INFO: App\Jobs\SampleJobB::4
[2021-06-29 16:06:22] local.INFO: App\Jobs\SampleJobB::5
[2021-06-29 16:06:23] local.INFO: App\Jobs\SampleJobB complete.
問題ないようです。
参考資料
- Laravel 5.6 キュー
- Supervisor を Docker で使う
- slashfan/docker-supervisor-php-workers
- Dockerでsupervisordを使うときにログを標準出力する
- Laravelでキューを使った並列処理
- Supervisorの基本コマンド
- Dockerでsupervisorを使う時によくハマる点まとめ
- LaravelでRedisとSupervisorを導入してQueue/Jobを扱う【docker-compose】
- AWS Elastic Beanstalk使ってバックグラウンド処理のサーバ構築【Laravel】
- Laravel の Queue で非同期処理を実装する
- 「キュー」は何しにLaravelへ?
以上