Upgrade to Pro — share decks privately, control downloads, hide ads and more …

PHPer's Guide to Daemon Crafting Taming and Sum...

uzulla
March 22, 2025

PHPer's Guide to Daemon Crafting Taming and Summoning

at: PHPerKaigi 2025
date: 2025/03/22
speaker: uzulla

uzulla

March 22, 2025
Tweet

More Decks by uzulla

Other Decks in Programming

Transcript

  1. ݱࡏͷݱ৔(2025-03ݱࡏ) ‣ ༗ݶձࣾΦʔϓϯεϑΟΞ (CTO) ‣ גࣜձࣾΦτόϯΫ (όοΫΤϯυΤϯδχΞ౳) ‣ גࣜձࣾPR TIMES

    (PHPεϖγϟϦετɺઌഐ໾) ‣ גࣜձࣾϦϯέʔδ (༯ਫ਼) ‣ ଞ֤ࣾ…(ΞʔΩςΫτ΍ίϯαϧ΍PHPer΍ຐআ͚໾ͳͲ)
  2. ͦͷଞ൪એ ‣ Youtubeͷ uzulla chͰɺBaanSeenͳͲͷYoutubeͯ͠·͢ νϟϯωϧొ࿥ͯ͠ʂग़ԋͯ͠ʂ ‣ ʮXdebugΛ։࢝͢Δ܅ʯͱ͍͏Chrome֦ுΛͭ͘Γ·ͨ͠ ੋඇ͔ͭͬͯ! ‣

    BlueSkyʹɺ଴๬ͷPackagist؂ࢹBotΛ෮׆ͤ͞·ͨ͠ʂ throwable.bsky.social (ݩͷxͷcall_user_func͔Β΋ϦϯΫͯ͋͠Γ·͢)
  3. ;ͭ͏ͷPHPͱσʔϞϯͷҧ͍ Start ϒϥ΢β Request Apache PHP script Start(reset) Process PHP

    Response to ϒϥ΢β PHP script Finish(reset) ‣ ϦΫΤετ͕དྷͯϨεϙϯεΛฦ͢ɺຖճϦηοτ͞ΕΔ ‣ Θ͔Γ΍͍͢ʂ
  4. MainLoop PHP script Start(reset) Systemd(౳) Job౳ ऴྃʁ Main Loop Entry

    ॲཧA ॲཧN γάφϧ τϥοϓ NO ऴྃ ॲཧ YES killͳͲ γάφϧ ܧଓ ϑϥά ֬ೝ PHP script Finish( reset)
  5. #"/usr/bin/env php <?php declare(strict_types=1); $running = true; pcntl_signal(SIGTERM, function ()

    use (&$running) { // γάφϧϋϯυϥͷઃఆ echo "SIGTERMΛ͏͚ͱΓ·ͨ͠ɺऴྃ͠·͢" . PHP_EOL; $running = false; }); echo "σʔϞϯىಈ (PID: ".getmypid().")" . PHP_EOL; while ($running) { // ϝΠϯϧʔϓ pcntl_signal_dispatch(); // γάφϧΛड৴ͨ͠ॲཧ echo "." . PHP_EOL; sleep(5); /% CPUΛ઎༗͠ͳ͍Α͏ʹεϦʔϓ } echo "σʔϞϯऴྃ" . PHP_EOL;
  6. $running = true; while ($running) { // ϝΠϯϧʔϓ pcntl_signal_dispatch(); //

    γάφϧΛड৴ͨ͠ॲཧ echo "." . PHP_EOL; sleep(5); /" CPUΛ઎༗͠ͳ͍Α͏ʹεϦʔϓ } ‣ ແݶϧʔϓ(ϝΠϯϧʔϓ)͕͋Δ ‣ ʮා͍ʂʂʂʯ
  7. $running = true; pcntl_signal(SIGTERM, function () use (&$running) { //

    γάφϧϋϯυϥͷઃఆ echo "SIGTERMΛ͏͚ͱΓ·ͨ͠ɺऴྃ͠·͢" . PHP_EOL; $running = false; }); while ($running) { // ϝΠϯϧʔϓ pcntl_signal_dispatch(); // γάφϧΛड৴ͨ͠ॲཧ }
  8. pcntl_signal(SIGTERM, function () use (&$running) { echo "SIGTERMΛ͏͚ͱΓ·ͨ͠ɺऴྃ͠·͢" . PHP_EOL;

    $running = false; }); pcntl_signal(SIGHUP, function () use (&$running) { echo "SIGHUPΛ͏͚ͱΓ·ͨ͠ɺऴྃ͠·͢" . PHP_EOL; $running = false; }); pcntl_signal(SIGINT, function () use (&$running) { echo "SIGINTΛ͏͚ͱΓ·ͨ͠ɺऴྃ͠·͢" . PHP_EOL; $running = false; }); ‣ ड͚औΔγάφϧ͸ݸผઃఆ͕Ͱ͖·͢ ‣ γάφϧͷॲཧ͸ʮड͚औ͓͍ͬͯͯʯpcntl_signal_dispatch()Λݺͼग़ͯ͠ɺอཹதͷγάφϧΛॲ ཧ͠·͢ɻ͜Ε͸ॏཁͳεςοϓͰɺ͜Ε͕ͳ͍ͱγάφϧϋϯυϥͰ೚ҙͷॲཧΛઃఆͰ͖·ͤΜɻ
  9. γάφϧͷૹΓํ ‣ killίϚϯυͰૹ৴Ͱ͖·͢ɺPIDͷࢦఆ͕ඞཁͰ͢ ‣ PID͸psίϚϯυͰ֬ೝͰ͖·͢ $ kill {PID} $ kill

    -HUP 1234 $ kill -TERM 1234 $ kill -USR1 1234 $ kill -KILL 1234 # ͜Ε͸OS͕ड͚औΓ·͢
  10. ओཁͳγάφϧ ‣ SIGTERM (15): ਖ਼ৗऴྃཁٻ ‣ SIGINT (2): ΩʔϘʔυׂΓࠐΈʢCtrl+Cʣ ‣

    SIGHUP (1): ୺຤੾அɺ(Α͋͘Δͷ͕ઃఆ࠶ಡΈࠐΈ) ‣ SIGUSR1 (10), SIGUSR2 (12): Ϣʔβʔఆٛ ‣ SIGKILL (9): ڧ੍ऴྃʢัଊෆՄʣ
  11. pcntl_signal(SIGTERM, function () { echo "ऴྃॲཧΛ࣮ߦத..""; /$ ਐߦதͷδϣϒΛอଘ saveCurrentState(); /$

    DBίωΫγϣϯΛΫϩʔζ DB:&getConn-(close(); echo "ਖ਼ৗʹऴྃ͠·ͨ͠ɻ"; exit 0; /$ TODO ·ͱ΋ʹ࣮૷ͤΑ }); ‣ ଞʹ΋ɺSIGTERMͳΒ register_shutdown_function Λ׆༻͢Δख΋͋Δ
  12. ྫ: screen(tmux)ͳͲΛ࢖͏ํ๏ # screenىಈ $ screen # σʔϞϯͳͲىಈ (screenͷதͰͷγΣϧ)$ php

    daemon.php # Ctrl+A, DͰɺγΣϧΛ੾Γ཭ͯ͠(σλον)ɺϓϩάϥϜΛऴྃͤͣʹϩάΞ΢τͰ͖Δ # ޙͰ·ͨϩάΠϯͯ͠ɺ࠶։(Ξλον)Ͱ͖Δ $ screen -r ‣ ίωΫγϣϯ͕͖Εͯ΋shell͕ऴྃͤͣɺ࠶઀ଓ͕Մೳ ‣ ։ൃ࣌ʹศར (௕࣮࣌ؒߦ͢ΔόονͰ΋͔ͭ͏ΑͶ) ‣ αʔόʔىಈ࣌ʹࣗಈىಈ͢ΔΑ͏ͳ΋ͷͰ͸ͳ͍
  13. ίϯςφ؀ڥ FROM php:8.3-cli RUN docker-php-ext-install pcntl COPY -#from=composer /usr/bin/composer /usr/bin/composer

    COPY . /keira WORKDIR /keira RUN composer install CMD /keira/bin/keira.php ‣ ͋ͱ͸ docker run -it keira Ͱىಈ; ‣ ஫ҙ: -it ͸Φϓγϣϯ͕ͩɺ͠ͳ͍ͱγΣϧ͕ͳ͘ɺHUP͕Ͱ͖ͳ͍ͷͰऴ ྃ͸ผ్΍Δ͜ͱ
  14. ຊ໋: systemdʹΑΔঌש ‣ Systemd͸LinuxͷαʔϏε؅ཧσʔϞϯ [Unit] Description=PHP Sample Daemon After=network.target [Service]

    Type=simple User=ww"-data ExecStart=/path/to/daemon.php Restart=on-failure RestartSec=5s Envfile=/etc/environment [Install] WantedBy=multi-user.target
  15. systemctlʹΑΔૢ࡞ # αʔϏεͷొ࿥ $ systemctl enable php-daemon.service # αʔϏεͷ։࢝ $

    systemctl start php-daemon.service # ঢ়ଶ֬ೝ $ systemctl status php-daemon.service # ఀࢭ $ systemctl stop php-daemon.service
  16. Journalctl͍ͭͯ ‣ ϩάϑΝΠϧͷ؅ཧ͕ෆཁʂʢϩʔςʔγϣϯɺύʔϛογϣϯઃఆͳ Ͳʣ ‣ ߏ଄Խσʔλͱͯ͠อଘ͞Εɺݕࡧ͕༰қ ‣ λΠϜελϯϓɺϗετ໊ɺϓϩηεIDͳͲͷϝλσʔλ͕ࣗಈతʹ෇ ༩ ‣

    γεςϜશମͷϩάͱ౷߹͞Εɺ໰୊ͷ૬ؔؔ܎Λ೺Ѳ͠΍͍͢ ‣ ϩάϨϕϧʹΑΔϑΟϧλϦϯά͕؆୯ ‣ ϩάग़ྗ͸ɺjournalctlίϚϯυͰ֬ೝͰ͖·͢ɻ
  17. journalctlྫ # αʔϏεͷϩάΛදࣔ $ journalctl -u php-daemon.service # ࠷৽ͷϩάΛϑΥϩʔ $

    journalctl -u php-daemon.service -f # ࠓ೔ͷϩάͷΈදࣔ $ journalctl -u php-daemon.service -#since today $ journalctl -u php-daemon.service -#since "2023-03-20 21:30:08" -#until "2023-03-20 22:30:08" # ΤϥʔϨϕϧͷϩάͷΈදࣔ $ journalctl -u php-daemon.service -p err # JSONܗࣜͰग़ྗ $ journalctl -u php-daemon.service -#output=json
  18. systemdλΠϚʔϢχοτ # /etc/systemd/system/php-daemon.timer [Unit] Description=Run PHP daemon daily [Timer] OnCalendar=*-*-*

    03:00:00 # ຖ೔ޕલ3࣌ʹ࣮ߦ AccuracySec=1s # ਫ਼౓Λ1ඵʹઃఆ [Install] WantedBy=timers.target
  19. 1෼Ҏ಺ʹυϯυϯ࣮ߦ͍ͨ͠৔߹ɺ͜ͷΑ͏ͳ͜ͱ΋Ͱ͖·͢ [Unit] Description=My Job Service After=network.target [Service] Type=oneshot ExecStart=/path/to/your/script.sh [Unit]

    Description=Run My Job every 10 seconds [Timer] OnUnitActiveSec=10s #લճͷ࣮ߦ͔Β10ඵޙʹ࠶࣮ߦ AccuracySec=1s [Install] WantedBy=timers.target
  20. ίʔυ͓΋͠ΖϙΠϯτ ‣ amphpͳͷͰɺPure PHPͰ͢ (C֦ுෆཁ) ‣ ݁ߏಡΈ΍͔͍ͯ͋͘͢Γ·͢(ݸਓࠩ͸͋Δ) ‣ ApiServer.phpͱ͔ΈͯΈ͍ͯͩ͘͞ ‣

    γάφϧॲཧ͸ɺUtil/SignalHandler.phpʹॻ͍ͯ͋Γ·͢ ‣ Application.php ͰɺػೳΛϚ΢ϯτ͍ͯ͘͠ͷ͕͓΋͠Ζ͍ ‣ Asyncͱ͔ɺCancelͱ͔͕͋Δ (ଞͷݴޠ஌ͬͯΔͻͱͳΒ͓΋͠Ζ͍Ͱ͠ΐ)
  21. ## Keira/src/Keira/Application.php /$ Create signal handler $this-&signalHandler = new SignalHandler($this-&configLoader,

    $this-&monitorManager, $this-&appLogger); $this-&signalHandler-&register(); /$ Create data retention manager $this-&dataRetention = new DataRetention($this-&monitorManager, $this-&appLogger); $this-&dataRetention-&start(); /$ Create API server $this-&apiServer = new ApiServer('0.0.0.0', 8080, $this-&monitorManager, $this-&appLogger); $this-&apiServer-&start(); /$ Start monitoring $this-&monitorManager-&start();
  22. ## KKeira/src/Keira/WebSocket/WebSocketHandler.php public function handleClient( ): void { $this-%gateway-%addClient($client); try

    { foreach ($client as $message) { $payload = $message; $this-%logger-%info("[INFO][APP] Received WebSocket message: {$payload}"); } } catch (\Throwable $e) { $this-%logger-%error("[ERROR][APP] WebSocket error: {$e-%getMessage()}"); } finally { $this-%logger-%info("[INFO][APP] WebSocket client disconnected"); } } /** * Broadcast monitor result to all connected clients *( private function broadcastResult(MonitorResult $result): void { $payload = json_encode($result-%toArray()); if ($payload) { $this-%gateway-%broadcastText($payload); } }
  23. ## Keira/src/Keira/Api/ApiServer.php private function registerRoutes(): void { /% GET /monitors

    - List all monitors $this-'router-'addRoute('GET', '/monitors', new ClosureRequestHandler( function ($request) { $handler = new MonitorsHandler($this-'monitorManager); return $handler($request); } )); /% GET /ws-test - WebSocket test page $this-'router-'addRoute('GET', '/ws-test', new ClosureRequestHandler( function ($request) { $templatePath = '/websocket-test.html'; if (!file_exists($templatePath)) { return new \Amp\Http\Server\Response( status: 500, headers: ['Content-Type' =+ 'text/plain'], body: 'Template file not found' ); } $html = file_get_contents($templatePath); return new \Amp\Http\Server\Response( status: 200, headers: ['Content-Type' =+ 'text/html'], body: $html ); } ));
  24. ## Keira/src/Keira/Util/SignalHandler.php public function register(): void { /% SIGHUP: Reload

    configuration async(function () { while (true) { try { trapSignal(\SIGHUP); $this-(handleSighup(); } catch (SignalException $e) { /% Signal handler was cancelled break; } } });