Foregでデプロイすると、QueueのWorkerとかSupervisorとかを勝手に起動してくれるらしい。あと、Laravel Horizonも自動で起動できるらしい。これを試してみたいと思います。
コードでQueueを使う
Queues - Laravel 11.x - The PHP Framework For Web Artisans
QueueはRedisを使う想定です。Redisのキューから1つ取り出してjobを実行するといったプログラムも必要になりますが、これがWorkerと呼ばれます。このWorkerはjobが完了したら、Queueから次を取り出します。jobが失敗したらDBにログ記録をして、改めてキューに入れたりします。つまりこのWorkerが止まっちゃうと、Queueは一切機能しなくなるわけであります。本番サーバでもこのような仕組みを使う場合、当然Workerを起動しますが、Workerの動きを監視し、止まっちゃったら自動で再起動させる仕組みが必要で、これはSupervisorというのをよく使います。 Forgeを使うと、Supervisorの起動や管理をForgeにお任せできるそうなので、便利だし安心だと思いました。
まずは、コードでQueueを使うようにしましょう。
作成する仕組み
- トップページに「MailQueue」リンクを追加。
MailQueue
コントローラを作成。- トップページのリンクをクリックすると、
MailQueue
のindex
に遷移。 index
には送信先メールアドレスと内容を入力するフォームと送信ボタンがある。- 送信ボタンを押すとPOSTで、
MailQueue
のsendMail
が実行される。 sendMail
関数では、SendMailJob
がdispatchされる。SendMailJob
では、5秒間停止した後に、送信先メールアドレス宛に内容を送信する。
Job作成
<?php namespace App\Jobs; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Facades\Mail; class SendMailJob implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; public $email; public $content; public function __construct($email, $content) { $this->email = $email; $this->content = $content; } public function handle() { sleep(5); Mail::raw($this->content, function ($message) { $message->to($this->email) ->subject('キューからのメール'); }); } }
Controllerを作る
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Jobs\SendMailJob; class MailQueueController extends Controller { public function index() { return view('mail-queue.index'); } public function sendMail(Request $request) { $request->validate([ 'email' => 'required|email', 'content' => 'required|string', ]); SendMailJob::dispatch($request->email, $request->content); return back()->with('status', 'メールがキューに追加されました!'); } }
Viewを作る
<x-guest-layout> <div class="py-12"> <div class="max-w-7xl mx-auto sm:px-6 lg:px-8"> <div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg"> <div class="p-6 text-gray-900 dark:text-gray-100"> <div class="flex justify-between items-center mb-4"> <a href="{{ url('/') }}" class="text-sm text-gray-700 dark:text-gray-500 underline">トップに戻る</a> <h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight"> {{ __('メールキュー') }} </h2> </div> @if (session('status')) <div class="mb-4 font-medium text-sm text-green-600 dark:text-green-400"> {{ session('status') }} </div> @endif <form method="POST" action="{{ route('mail.queue.send') }}"> @csrf <div> <x-label for="email" value="{{ __('Email') }}" /> <input id="email" class="block mt-1 w-full" type="email" name="email" required autofocus /> </div> <div class="mt-4"> <x-label for="content" value="{{ __('Content') }}" /> <textarea id="content" class="block mt-1 w-full form-textarea rounded-md shadow-sm text-black" name="content" required></textarea> </div> <div class="flex items-center justify-end mt-4"> <x-button class="ml-4"> {{ __('Send Mail') }} </x-button> </div> </form> </div> </div> </div> </div> </x-guest-layout>
routerに追加する
<?php use App\Http\Controllers\MailQueueController; Route::get('/mail-queue', [MailQueueController::class, 'index'])->name('mail.queue.index'); Route::post('/mail-queue/send', [MailQueueController::class, 'sendMail'])->name('mail.queue.send');
環境変数調整
Redisを使うので、下記のようにします。
QUEUE_CONNECTION=redis
Workerを起動する
php artisan queue:work
Forgeでデプロイしてみる
リポジトリにプッシュして、Forgeで「Deploy Now」をクリックします。 すると、デプロイは失敗しました。
サーバには変更されているファイルがあり、package-lock.jsonであるとのことです。 これをコミットするなりしないと、git pullできないよ、といったようなエラーになります。
たしかに、デプロイ時に、pullした後に、npm install && npm run buildしてますので、それでpackage.jsonの中身が変わったのかもしれません。 npm ci && npm run buildに変更したら、デプロイが成功しました。
ForgeでQueue Workerの設定をする
サイト設定のQueueの画面は下記です。createをクリックしてWorkerを作成します。
また、デプロイスクリプトの最後に、下記を追加する必要があるようです。
$FORGE_PHP artisan queue:restart
基本的にはこれだけで、キューは動作しました。
本当にWorkerが止まっても再起動するか確認してみる
DigitalOceanのサーバでプロセスを止めたらどうなるか、一応見てみようと思います。
> ps -aux | grep queue 100467 .... php8.3 /home/forge/default/artisan queue:work redis --sleep=10 --daemon --quiet --queue="default" > kill -9 100467
上記のようにやっても、一瞬で復活してきました。何回やっても復活しました。 ForgeのQueueの「Check worker status」のプロセスIDとも一致しております。 これを自動で勝ってに管理してくれるので安全安心ですね。
Laravel Horizonを試してみる
Laravel Horizon - Laravel 11.x - The PHP Framework For Web Artisans
どうもこれも、Windows環境だとインストールできなそうですね。。
上記に書いてあった、下記をやったらインストールできました。 Windowsでは動きませんが、これでForegeでデプロイした際にHorizonが使えるようになるのではないかと思いますので、やってみます。
> composer require laravel/horizon --ignore-platform-reqs > php artisan horizon:install > php artisan vendor:publish --provider="Laravel\Horizon\HorizonServiceProvider"
おーでましたよ。horizonが。
それにしても、デプロイが速くてよいのですが、単純にgit pull
しているだけというのが、ちょっと厳しいかもなーと思いました。
先程もなぜかpullが成功せず、結局サーバ側でgit reset --hard HEAD
をして、git pull origin main
をしたら、なぜか成功しました。
git reset --hard HEAD
をしてから、Forgeの画面でデプロイすると、失敗するという謎挙動を繰り返しており、結局手動でやりました。
blue/greenデプロイとかいうやつは、別サーバに入れて入れ替える感じだと思うので、こういう謎挙動も起こらない上に、停止しないというメリットがありますね。その代わり、超遅いと思いますが。
Envoyerがそのようなサービスに該当するのかもしれません。
ちなみに、DigitalOceanのサーバは、rootだとgitコマンド使うとエラーになるので、sudo su -- forge
としまして、forgeに変えてからgitコマンドを使う必要がありました。
ForgeでHorizonの設定をしてみる
上記によると、キューワーカーは削除する必要があるらしい。 デプロイコマンドやデーモンの設定は自動で設定してくれたようです。
結局、ForgeでHorizonをONにするだけでほぼ自動で設定してくれたってことですね。キューワーカの作成をしていなければ何もしなくても設定できたということかなと思いました。お手軽ですね。 ただ、Horizonはブラウザの画面上で色々見られるっぽいので、それを確認してみたいです。
Horizonのダッシュボードを確認してみる
基本的には、/horizon
でダッシュボードにアクセスできるようになっているようです。ローカル環境では普通に閲覧できますが、本番環境の場合は、403になります。
app/Providers/HorizonServiceProvider.php
で許可メールアドレスを追加したりすると、認証ユーザのメールアドレスをチェックして、OKの場合、アクセスできるようになります。
誰でも閲覧するようにする場合は、下記でいけます。ためしにこれで見てみたいと思います。
/**
* Register the Horizon gate.
*
* This gate determines who can access Horizon in non-local environments.
*/
protected function gate(): void
{
Gate::define('viewHorizon', function ($user) {
return true;
// return in_array($user->email, [
// ]);
});
}
あとは、5分おきに、horizon:snapshot
を実行するようにすると、「ジョブとキューの待機時間とスループットに関する情報」を提供するメトリックをダッシュボードで表示できるようになるらしい。
Laravel Horizon - Laravel Upcoming - The PHP Framework For Web Artisans
5分おきに実行するように設定してみる
Task Scheduling - Laravel 11.x - The PHP Framework For Web Artisans
これを使ってスケジュール設定できるということで、しかも、Forgeの場合このサーバ側の設定が簡単にできるらしいです。
Forgeのスケジュール設定機能は、Laravelのスケジュール設定機能に限定されないみたいですね。普通にCRONをForgeの管理画面で設定できるみたいな感じかなと思いました。
Laravelのスケジュール機能を使う場合は、このForgeのCRONに、1 分ごとにschedule:run
が実行されるように設定する必要があるようです。
そして、実際に実行したい内容と実行頻度等は、routes/console.php
に書くようですね。
use Illuminate\Support\Facades\Schedule;
Schedule::command('horizon:snapshot')->everyFiveMinutes();
これで設定完了なようです。簡単ですね。下記でスケジュール設定が確認できます。
> php artisan schedule:list
あとはForgeのスケジュール画面で、下記を設定したらOKだと思います。