From 2965f231ce191a9880f46d060f650078e9b08864 Mon Sep 17 00:00:00 2001 From: E98Developer Date: Mon, 17 Aug 2026 06:41:19 +0200 Subject: [PATCH] =?UTF-8?q?ADD=20Deployment=20csomagol=C3=B3=20queue=20wor?= =?UTF-8?q?ker=20=C3=A9s=20scheduler=20=C3=BAjraind=C3=ADt=C3=A1s=20a=20te?= =?UTF-8?q?end=C5=91k=20k=C3=B6z=C3=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A 2026-08-17-i eset általánosítása: a d2d/e2e Docker Swarmban ugyanazt a kódot több service futtatja közös volume-ról, és a queue:work hosszan futó folyamat a betöltött osztályokat a memóriájában tartja. A fájlmásolás után a worker RÉGI kóddal dolgozta fel a jobot - a felület jónak látszott, a háttérfeladat némán a régi viselkedést hozta. - config: 'runtime' minta (app/, config/, database/, routes/, bootstrap/, composer.lock), mert a restart nem csak a jobok változásakor kell; targetenként a service nevek és a Swarm manager elérése - a felületen figyelmeztetés, a teendőkben a konkrét ssh + docker service update parancs - a restart szándékosan a cache ürítés UTÁN: a frissen induló worker különben a régi, cache-elt konfigot töltené be - a d2d schedulernél jelezzük, hogy 0 replikán fut (a restart üresjárat lehet), a t2t-nél pedig azt, hogy queue service még nincs, de a névkonvenció alapján t2t_queue lesz Co-Authored-By: Claude Opus 5 --- app/Services/Deployment/ChangeSetAnalyzer.php | 24 ++++++++++ .../Deployment/DeploymentPackageBuilder.php | 25 ++++++++++ app/Services/Deployment/DeploymentTargets.php | 42 +++++++++++++++-- config/deployment.php | 46 +++++++++++++++++++ tests/Feature/DeploymentPackageTest.php | 34 ++++++++++++++ 5 files changed, 167 insertions(+), 4 deletions(-) diff --git a/app/Services/Deployment/ChangeSetAnalyzer.php b/app/Services/Deployment/ChangeSetAnalyzer.php index 4338d6b..0acd2a9 100644 --- a/app/Services/Deployment/ChangeSetAnalyzer.php +++ b/app/Services/Deployment/ChangeSetAnalyzer.php @@ -135,6 +135,30 @@ private function warnings(array $copied, array $deleted, int $excludedCount, ?ar ]; } + if ($this->matching($copied, 'runtime') !== []) { + $jobs = count($this->matching($copied, 'job')); + $consoles = count($this->matching($copied, 'console')); + + $detail = []; + + if ($jobs > 0) { + $detail[] = $jobs.' job'; + } + + if ($consoles > 0) { + $detail[] = $consoles.' konzol parancs'; + } + + $warnings[] = [ + 'level' => 'warning', + 'title' => 'Hosszan futó konténerek érintve'.($detail === [] ? '' : ' ('.implode(', ', $detail).')'), + 'body' => 'A queue worker és a scheduler külön konténerben fut, és a betöltött osztályokat a memóriájában ' + .'tartja - a fájlok felmásolása után is a RÉGI kóddal dolgozik, amíg újra nem indul. Ez némán rossz ' + .'viselkedést okoz (a job "elkészül", de nem azt csinálja, amit az új kód). A konkrét restart parancsok ' + .'a csomag _TEENDOK.md fájljában lesznek.', + ]; + } + if ($deleted !== []) { $warnings[] = [ 'level' => 'warning', diff --git a/app/Services/Deployment/DeploymentPackageBuilder.php b/app/Services/Deployment/DeploymentPackageBuilder.php index dc73541..af82464 100644 --- a/app/Services/Deployment/DeploymentPackageBuilder.php +++ b/app/Services/Deployment/DeploymentPackageBuilder.php @@ -298,6 +298,9 @@ private function writeInstructions(string $path, array $manifest, array $target) .' — mappa-összeolvasztás, NEM a teljes app mappa cseréje. A `_` kezdetű fájlokat ne másold fel.', ]; + /** @var array $restartSteps */ + $restartSteps = []; + if ($manifest['deleted'] !== []) { $steps[] = sprintf( 'Töröld a `_TORLENDO.txt`-ben felsorolt %d fájlt a célgépen. Nagyobb mennyiségnél a mellékelt ' @@ -336,9 +339,31 @@ private function writeInstructions(string $path, array $manifest, array $target) if (str_contains($warning['title'], 'Frontend')) { $steps[] = 'A fordított assetek NINCSENEK a csomagban (`.gitignore`) — `npm run build` után kézzel másold fel a `public/build` tartalmát.'; } + + // A restart lépéseket külön gyűjtjük: azoknak a cache ürítés UTÁN kell jönniük, + // különben a frissen induló worker még a régi, cache-elt konfigot tölti be. + if (str_contains($warning['title'], 'Hosszan futó')) { + $commands = $this->targets->restartCommands($target['key']); + + if ($commands !== []) { + $restartSteps[] = 'Utolsó lépésként indítsd újra a hosszan futó konténereket. A queue worker és a ' + .'scheduler **a memóriájában tartja a betöltött kódot**, ezért a fájlmásolás önmagában NEM elég — ' + ."enélkül a háttérfeladatok némán a régi viselkedést hozzák:\n\n" + ." ```bash\n ".implode("\n ", $commands)."\n ```"; + } + + if ($target['services_note']) { + $restartSteps[] = $target['services_note']; + } + } } $steps[] = 'Ürítsd a cache-t: `php artisan config:clear && php artisan view:clear && php artisan route:clear`'; + + foreach ($restartSteps as $restartStep) { + $steps[] = $restartStep; + } + $steps[] = 'Ellenőrizd a kirakott verziót: `curl https://'.$target['domain'].'/deploy-version.json`' .' — a commitnak `'.substr((string) $manifest['to'], 0, 7).'` kell lennie.'; diff --git a/app/Services/Deployment/DeploymentTargets.php b/app/Services/Deployment/DeploymentTargets.php index aabf9a1..3805090 100644 --- a/app/Services/Deployment/DeploymentTargets.php +++ b/app/Services/Deployment/DeploymentTargets.php @@ -11,18 +11,25 @@ class DeploymentTargets { /** - * @return array + * @return array, services_note:?string}> */ public function all(): array { - /** @var array> $targets */ + /** @var array> $targets */ $targets = (array) config('deployment.targets', []); $resolved = []; foreach ($targets as $key => $target) { $resolved[$key] = array_merge( - ['label' => $key, 'domain' => '', 'remote_path' => '', 'expected_branch' => null], + [ + 'label' => $key, + 'domain' => '', + 'remote_path' => '', + 'expected_branch' => null, + 'services' => [], + 'services_note' => null, + ], $target, ['key' => $key], ); @@ -32,7 +39,34 @@ public function all(): array } /** - * @return array{key:string, label:string, domain:string, remote_path:string, expected_branch:?string}|null + * A célkörnyezet hosszan futó service-einek újraindító parancsai. + * + * @return array + */ + public function restartCommands(?string $key): array + { + $target = $this->find($key); + + if (! $target || $target['services'] === []) { + return []; + } + + $user = (string) config('deployment.swarm.user', 'root'); + $host = (string) config('deployment.swarm.host'); + + return array_values(array_map( + fn (string $service): string => sprintf( + 'ssh %s@%s "docker service update --force %s"', + $user, + $host, + $service, + ), + $target['services'], + )); + } + + /** + * @return array{key:string, label:string, domain:string, remote_path:string, expected_branch:?string, services:array, services_note:?string}|null */ public function find(?string $key): ?array { diff --git a/config/deployment.php b/config/deployment.php index aa42de5..2585e8a 100644 --- a/config/deployment.php +++ b/config/deployment.php @@ -98,6 +98,41 @@ 'package.json', 'package-lock.json', ], + + /* + | A queue worker és a scheduler hosszan futó PHP folyamat: a már betöltött + | osztályokat a memóriájában tartja, ezért a fájlok felmásolása után is a RÉGI + | kóddal dolgozik, amíg újra nem indul. Ez némán rossz viselkedést okoz (2026-08-17, + | árlista végrehajtás: a job "elkészült" státuszt adott anélkül, hogy bármit írt volna), + | ezért minden háttérben is futó kód változása restartot igényel - nem csak a jobok. + */ + 'runtime' => [ + 'app/*', + 'config/*', + 'database/*', + 'routes/*', + 'bootstrap/*', + 'composer.lock', + ], + + /* Csak a figyelmeztetés szövegének pontosításához, a restart ettől függetlenül kell. */ + 'job' => ['app/Jobs/*'], + 'console' => ['app/Console/*'], + ], + + /* + |-------------------------------------------------------------------------- + | Docker Swarm + |-------------------------------------------------------------------------- + | + | A restart parancsokat a Swarm leader node-on keresztül kell kiadni + | (ld. docs/cicd-context.md). + | + */ + + 'swarm' => [ + 'host' => env('DEPLOYMENT_SWARM_HOST', '46.29.140.234'), + 'user' => env('DEPLOYMENT_SWARM_USER', 'root'), ], /* @@ -121,12 +156,23 @@ 'domain' => 'd2d.emegrendeles.hu', 'remote_path' => '/delirest/d2d.emegrendeles.hu/app/', 'expected_branch' => 'test', + // Ugyanabból az imageből, közös volume-ról több service is futtatja a kódot. + // A web konténer (d2d-environment_app) kérésenként újratölti, ezért az nem kell ide. + 'services' => [ + 'queue' => 'd2d-environment_queue', + 'scheduler' => 'd2d-environment_scheduler', + ], + 'services_note' => 'A d2d scheduler jelenleg 0 replikán fut — ha leskálázva marad, a restartja üresjárat.', ], 'e2e' => [ 'label' => 'e2e — t2t éles', 'domain' => 'e2e.emegrendeles.hu', 'remote_path' => '/delirest/test.t2t.emegrendeles.hu/app/', 'expected_branch' => 'main', + 'services' => [ + 'scheduler' => 't2t_scheduler', + ], + 'services_note' => 'A t2t-n még nincs queue worker service; ha létrejön, a névkonvenció alapján t2t_queue lesz, és azt is újra kell majd indítani.', ], ], diff --git a/tests/Feature/DeploymentPackageTest.php b/tests/Feature/DeploymentPackageTest.php index ad49006..af0a25a 100644 --- a/tests/Feature/DeploymentPackageTest.php +++ b/tests/Feature/DeploymentPackageTest.php @@ -265,6 +265,40 @@ function deploymentCleanup(string $path): void deploymentCleanup($repository['path']); }); +test('a teendők kérik a queue worker és a scheduler újraindítását, a cache ürítés után', function () { + deploymentAllowEnvironment(); + $repository = deploymentTestRepository(); + [$first, , $third] = $repository['commits']; + + $package = app(DeploymentPackageBuilder::class)->build('d2d', 'test', $first, $third); + $teendok = File::get($package['path'].'/_TEENDOK.md'); + + expect($teendok)->toContain('docker service update --force d2d-environment_queue') + ->and($teendok)->toContain('docker service update --force d2d-environment_scheduler') + // A 0 replikás scheduler miatt tudni kell, hogy a restart üresjárat lehet. + ->and($teendok)->toContain('0 replikán fut'); + + // A restartnak a cache ürítés UTÁN kell jönnie: a frissen induló worker különben + // a régi, cache-elt konfigot töltené be. + expect(strpos($teendok, 'docker service update'))->toBeGreaterThan(strpos($teendok, 'config:clear')); + + deploymentCleanup($repository['path']); +}); + +test('a queue worker nélküli környezetnél is kiderül, mit kell majd újraindítani', function () { + deploymentAllowEnvironment(); + $repository = deploymentTestRepository(); + [$first, , $third] = $repository['commits']; + + $package = app(DeploymentPackageBuilder::class)->build('e2e', 'main', $first, $third); + $teendok = File::get($package['path'].'/_TEENDOK.md'); + + expect($teendok)->toContain('docker service update --force t2t_scheduler') + ->and($teendok)->toContain('t2t_queue'); + + deploymentCleanup($repository['path']); +}); + test('a teendők között szerepel a seeder futtatása a konkrét osztálynévvel', function () { deploymentAllowEnvironment(); $repository = deploymentTestRepository();