deploy: Worker-Prüfung am Ende von deploy.sh — monitoring:queue-probe stellt einen Test-Job ein und wartet bis 60 s; ohne Worker Exit 3 mit Hinweis
Anlass 06.10.2026: Nach dem Deploy von v1.3.0 hat queue:restart den Worker beendet, Supervisor lief nicht, über eine Stunde blieben KI-Prüfungen und Mails liegen, ohne dass es auffiel. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
2c25eea045
commit
c223daa563
4 changed files with 136 additions and 0 deletions
51
app/Console/Commands/MonitoringQueueProbe.php
Normal file
51
app/Console/Commands/MonitoringQueueProbe.php
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Jobs\Monitoring\QueueProbe;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Sleep;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* Prüft, ob ein Queue-Worker läuft: stellt einen Test-Job ein und wartet,
|
||||
* bis er abgeholt ist (Aufruf am Ende von dev/betrieb/deploy.sh).
|
||||
*
|
||||
* Anlass 06.10.2026: Nach dem Deploy von v1.3.0 hat `queue:restart` den
|
||||
* Worker beendet, Supervisor lief nicht und startete ihn nicht neu — über
|
||||
* eine Stunde blieben KI-Prüfungen und Mails liegen, ohne dass es auffiel.
|
||||
*/
|
||||
class MonitoringQueueProbe extends Command
|
||||
{
|
||||
protected $signature = 'monitoring:queue-probe
|
||||
{--wait=60 : Sekunden, die auf den Worker gewartet wird}';
|
||||
|
||||
protected $description = 'Prüft mit einem Test-Job, ob ein Queue-Worker läuft.';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$token = Str::random(16);
|
||||
$wait = max(0, (int) $this->option('wait'));
|
||||
|
||||
QueueProbe::dispatch($token);
|
||||
|
||||
for ($second = 0; $second <= $wait; $second++) {
|
||||
if (Cache::has(QueueProbe::cacheKey($token))) {
|
||||
Cache::forget(QueueProbe::cacheKey($token));
|
||||
$this->info(sprintf('Queue-Worker läuft (Test-Job nach %d s abgeholt).', $second));
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
if ($second < $wait) {
|
||||
Sleep::for(1)->second();
|
||||
}
|
||||
}
|
||||
|
||||
$this->error(sprintf('Kein Queue-Worker hat den Test-Job innerhalb von %d s abgeholt.', $wait));
|
||||
$this->line('Worker in Ploi prüfen (Site ▸ Queue). Läuft Supervisor? Notfalls: nohup php artisan queue:work database --queue=classification,default --tries=3 --max-time=3600 &');
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
}
|
||||
31
app/Jobs/Monitoring/QueueProbe.php
Normal file
31
app/Jobs/Monitoring/QueueProbe.php
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace App\Jobs\Monitoring;
|
||||
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Queue\Queueable;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
/**
|
||||
* Test-Job für `monitoring:queue-probe`: tut nichts außer zu quittieren,
|
||||
* dass ein Worker ihn abgeholt hat. Läuft auf `default` — der Worker hört
|
||||
* auf `classification,default`.
|
||||
*/
|
||||
class QueueProbe implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public int $tries = 1;
|
||||
|
||||
public function __construct(public string $token) {}
|
||||
|
||||
public static function cacheKey(string $token): string
|
||||
{
|
||||
return 'monitoring:queue-probe:'.$token;
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
Cache::put(self::cacheKey($this->token), now()->toIso8601String(), now()->addMinutes(10));
|
||||
}
|
||||
}
|
||||
|
|
@ -18,6 +18,9 @@
|
|||
# (Standard: echo "" | sudo -S service php8.4-fpm reload).
|
||||
# Leer setzen, wenn FPM von Hand in Ploi neu geladen wird.
|
||||
#
|
||||
# Exit-Codes: 0 fertig, 1 abgebrochen (siehe Ausgabe), 2 falscher Aufruf,
|
||||
# 3 live, aber kein Queue-Worker hat den Test-Job abgeholt.
|
||||
#
|
||||
# Rollback: dasselbe Skript mit dem vorherigen Tag.
|
||||
|
||||
set -euo pipefail
|
||||
|
|
@ -141,5 +144,17 @@ step "Version"
|
|||
php artisan about --only=pressekonto
|
||||
|
||||
trap - ERR
|
||||
|
||||
# Läuft ein Worker? `queue:restart` oben beendet ihn; neu starten muss ihn
|
||||
# Supervisor. Am 06.10.2026 lief Supervisor nicht, und nach dem Deploy von
|
||||
# v1.3.0 blieben KI-Prüfungen und Mails über eine Stunde liegen.
|
||||
step "Queue-Worker"
|
||||
if ! php artisan monitoring:queue-probe --wait=60; then
|
||||
echo
|
||||
echo "!! Deploy $tag ist live, aber KEIN Queue-Worker läuft." >&2
|
||||
echo "!! Jetzt in Ploi Site ▸ Queue (und Supervisor) prüfen — sonst bleiben Prüfungen und Mails liegen." >&2
|
||||
exit 3
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "Deploy $tag fertig (vorher $previous). Weiter mit den Prüfungen in dev/betrieb/git-deploy.md, Abschnitt D."
|
||||
|
|
|
|||
39
tests/Feature/Monitoring/QueueProbeTest.php
Normal file
39
tests/Feature/Monitoring/QueueProbeTest.php
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
use App\Jobs\Monitoring\QueueProbe;
|
||||
use Illuminate\Support\Facades\Queue;
|
||||
use Illuminate\Support\Sleep;
|
||||
|
||||
/**
|
||||
* Worker-Prüfung am Ende von deploy.sh (06.10.2026: Supervisor lief nicht,
|
||||
* der Worker kam nach `queue:restart` nicht zurück, niemand merkte es).
|
||||
*/
|
||||
test('a running worker picks up the probe job', function () {
|
||||
Sleep::fake();
|
||||
|
||||
$this->artisan('monitoring:queue-probe', ['--wait' => 30])
|
||||
->expectsOutputToContain('Queue-Worker läuft (Test-Job nach 0 s abgeholt).')
|
||||
->assertSuccessful();
|
||||
|
||||
Sleep::assertSleptTimes(0);
|
||||
});
|
||||
|
||||
test('without a worker the probe fails after the waiting time with a hint', function () {
|
||||
Sleep::fake();
|
||||
Queue::fake();
|
||||
|
||||
$this->artisan('monitoring:queue-probe', ['--wait' => 5])
|
||||
->expectsOutputToContain('Kein Queue-Worker hat den Test-Job innerhalb von 5 s abgeholt.')
|
||||
->expectsOutputToContain('Worker in Ploi prüfen')
|
||||
->assertFailed();
|
||||
|
||||
Queue::assertPushed(QueueProbe::class, fn (QueueProbe $job): bool => $job->queue === null);
|
||||
Sleep::assertSleptTimes(5);
|
||||
});
|
||||
|
||||
test('the probe job only acknowledges its own token', function () {
|
||||
(new QueueProbe('abc'))->handle();
|
||||
|
||||
expect(cache()->has(QueueProbe::cacheKey('abc')))->toBeTrue()
|
||||
->and(cache()->has(QueueProbe::cacheKey('xyz')))->toBeFalse();
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue