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

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

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
Avatar for uzulla uzulla
March 22, 2025

PHPer's Guide to Daemon Crafting Taming and Summoning

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

Avatar for 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; } } });