LoginSignup
20
19

More than 1 year has passed since last update.

dockerでLaravelキュー環境を整える

Last updated at Posted at 2021-06-29

概要

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)をベースにして作ります

./docker/supervisor/Dockerfile
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"]

設定ファイルはこんな感じにしました。

./docker/supervisor/supervisord.conf
[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のワーカーはこんな感じで設定してみました。

./docker/supervisor/application.conf
; [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コンテナの後に起動するように定義しました。

docker-compose.yml
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

ログ出力

ジョブ実行ログを出力したいので、ロギングを追加する。

./src/config/logging.php
'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)

今回は、データベースをコネクションとして使うので以下のようにします。

src/.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つまで 並列実行される

./app/Console/Commands/StartJobsCommand.php
<?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')));
    }
}

ジョブについては、以下のように記載しています。

./src/app/Jobs/SampleJobA.php
<?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)が終了した

jobテーブルを見てみる。
jobs-table.png

Laravelのログを見てみる。

./src/storage/logs/job-2021-06-29.log
[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.  

問題ないようです。

参考資料

以上

20
19
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
20
19