Slide 1

Slide 1 text

Des workers PHP avec Symfony Messenger et systemd Loïck Piera - Forum PHP 2022 -

Slide 2

Slide 2 text

Hello there! Moi, c'est Loïck Piera Consultant web chez depuis 2015. Travaille au quotidien avec PHP et Symfony. Un peu de front avec React, TypeScript ou encore de l'ops avec Ansible. @pyrech @pyrech 2

Slide 3

Slide 3 text

Des workers en PHP ?

Slide 4

Slide 4 text

C'est quoi un worker ? ● Processus PHP qui exécute des tâches ● en dehors du serveur web ● Exécution de code en "asynchrone" 5

Slide 5

Slide 5 text

Pourquoi faire ? ● Retarder les traitements qui ne sont pas nécessaires pour la réponse du serveur web ● Cas d'utilisation : ○ Envoyer des mails ○ Exécuter des tâches lourdes ou longues ○ Réindexer des documents Elasticsearch ○ Interagir avec une api tierce ○ … ● Avoir du retry en cas d'erreur 6

Slide 6

Slide 6 text

Fil rouge : YAUT (Yet Another Url Tool) ● Application de monitoring d'urls ● Checks à interval régulier ○ statut HTTP ○ certificat SSL ○ présence/absence contenu sur la page ● Sondes à plusieurs emplacements (AWS US et EU, en plus du serveur OVH) ● Notifications Slack en cas d'alerte 7

Slide 7

Slide 7 text

Fonctionnement souhaité ● CRON toutes les minutes ● Commande Symfony ● Check de l'url de manière asynchrone 8

Slide 8

Slide 8 text

Symfony Messenger

Slide 9

Slide 9 text

C'est quoi ? ● Composant Symfony disponible depuis 2018 / Symfony 4.2 ● Pattern Message bus ● Envoi d'un message, un handler traitera ce message ❞ The Messenger component helps applications send and receive messages to / from other applications or via message queues. ❞ 10

Slide 10

Slide 10 text

Exemple : le message ● N'importe quelle classe PHP, sérialisable final class CheckUrl { public function __construct( public readonly string $url, ) { } } 11

Slide 11

Slide 11 text

Exemple : le handler ● AsMessageHandler attribute ● __invoke() method that's type-hinted with the message class #[AsMessageHandler] class CheckUrlHandler { public function __invoke(CheckUrl $message) { $response = $this->httpClient->request('GET', $message->getUrl()); $this->logger->alert(sprintf( 'Monitored url "%s", response status code is "%s"', $message->getUrl(), $response->getStatusCode() )); } } 12

Slide 12

Slide 12 text

Exemple : envoi du message #[AsCommand(name: 'app:check-url')] class CheckUrlCommand extends Command { public function __construct(private MessageBusInterface $messageBus) { parent::__construct(); } protected function execute(InputInterface $input, OutputInterface $output): int { $this->messageBus->dispatch(new CheckUrl('https://afup.org/test')); return Command::SUCCESS; } } 13

Slide 13

Slide 13 text

Et voilà $ bin/console app:check-url [alert] Monitored url "https://afup.org/test", response status code is "404" 14

Slide 14

Slide 14 text

Transports ● Par défaut, les messages sont traités tout de suite (synchrone) ● Empiler les messages dans une file d'attente, puis les dépiler un par un ● Plein de transports supportés nativement : AMQP, Redis, Doctrine, SQS, … 15

Slide 15

Slide 15 text

Configuration ● Configuration facile avec Symfony flex : MESSENGER_TRANSPORT_DSN=amqp://guest:guest@localhost:5672/%2f/messages MESSENGER_TRANSPORT_DSN=doctrine://default MESSENGER_TRANSPORT_DSN=redis://localhost:6379/messages 16

Slide 16

Slide 16 text

Configuration ● Note : un message non configuré dans le routing sera traité immédiatement # config/packages/messenger.yaml framework: messenger: transports: async: "%env(MESSENGER_TRANSPORT_DSN)%" routing: # async is whatever name you gave your transport above 'App\Message\CheckUrl: async 17

Slide 17

Slide 17 text

Démarrage du worker $ bin/console messenger:consume async [OK] Consuming messages from transports "async". // The worker will automatically exit once it has received a stop signal via the messenger:stop-workers command. // Quit the worker with CONTROL-C. // Re-run the command with a -vv option to see logs about consumed messages. ❞ By default, the command will run forever: looking for new messages on your transport and handling them. This command is called your "worker". ❞ 18

Slide 18

Slide 18 text

Oui mais… ● Que se passe-t-il si une erreur fatale intervient ? ● Comment relancer le worker ? 19

Slide 19

Slide 19 text

systemd

Slide 20

Slide 20 text

C'est quoi ? ● Ensemble de composants pour Linux ● Gestion de services ● 1er processus lancé sur la machine, démarre tout le reste ❞ systemd is a suite of basic building blocks for a Linux system. It provides a system and service manager that runs as PID 1 and starts the rest of the system. ❞ 21

Slide 21

Slide 21 text

Pourquoi systemd ? ● Alternative à runit / supervisord ● Standard dans la plupart des distributions Linux ● Utilisable sans être root ("user unit") 22

Slide 22

Slide 22 text

Fonctionnement ● S'assure que nos services soient démarrés ● Unit : ressource systemd qui fait une tâche, contrôle un matériel ● Gestion des dépendances 23

Slide 23

Slide 23 text

● /etc/systemd/system/ pour les units créées par l'admin du serveur (admin = auteur de l'unit) ● /usr/local/lib/systemd/system/ pour les units installées par l'admin du serveur (admin != auteur de l'unit) ● /lib/systemd/system/ pour les units installées par le package manager de l'OS Emplacement de la config 24

Slide 24

Slide 24 text

Configuration de l'unit # /etc/systemd/system/worker-messenger-forumphp.service [Unit] Description=Symfony messenger-consume %i StartLimitIntervalSec=20s StartLimitBurst=5 [Service] ExecStart=/usr/bin/php //bin/console messenger:consume async --env=prod Restart=always RestartSec=1 TimeoutSec=300 User= [Install] WantedBy=multi-user.target 25

Slide 25

Slide 25 text

La commande systemctl ● Démarrer le service : systemctl start worker-messenger-forumphp.service ● Démarrer le service au boot de la machine : systemctl enable worker-messenger-forumphp.service ● Recharger les modifs faites dans la configuration de l'unit : systemctl daemon-reload 26

Slide 26

Slide 26 text

Le worker est opérationnel ! $ systemctl status worker-messenger-forumphp.service ● worker-messenger-forumphp.service - Symfony messenger-consume async Loaded: loaded (/etc/systemd/system/worker-messenger-forumphp.service; bad; vendor preset: enabled) Active: active (running) since Wed 2022-10-05 22:31:21 CEST; 40min ago Main PID: 4125761 (php) Tasks: 1 (limit: 38293) Memory: 13.7M CPU: 160ms CGroup: /system.slice/worker-messenger-forumphp.service └─4125761 /usr/bin/php /home/loick/Work/worker-php-conf-playground/bin/console messenger:consume async --env=prod oct. 05 22:31:21 loick-PC systemd[1]: Started Symfony messenger-consume async . oct. 05 22:31:21 loick-PC php[4125761]: [OK] Consuming messages from transports "async". oct. 05 22:31:21 loick-PC php[4125761]: // The worker will automatically exit once it has received a stop oct. 05 22:31:21 loick-PC php[4125761]: // via the messenger:stop-workers command. oct. 05 22:31:21 loick-PC php[4125761]: // Quit the worker with CONTROL-C. oct. 05 22:31:21 loick-PC php[4125761]: // Re-run the command with a -vv option to see logs about consumed messages. 27

Slide 27

Slide 27 text

Protips

Slide 28

Slide 28 text

Déploiement ● Ne pas laisser les workers tourner indéfiniment --limit=10 ou --memory-limit=128M et/ou --time-limit=3600 ● Couper les workers pendant un déploiement 29 php bin/console messenger:stop-workers --env=prod # ou systemctl stop worker-messenger-forumphp.service

Slide 29

Slide 29 text

Logs ● Les logs sont gérés par journald et peuvent être consultés avec la commande journalctl : journalctl -xfeu worker-messenger-forumphp.service 30 -x: signifie “extended”, affiche plus d'information à propos du service -f: comme l'option -f de la commande tail, affiche les nouvelles lignes ajoutées -e: affiche la fin du fichier de log -u: permet de filtrer les logs pour n'afficher que ceux de l'unit demandée

Slide 30

Slide 30 text

● Faire tourner plusieurs instances du worker ● Nom de l'unit : [email protected] ● systemctl enable some_worker@{IDENTIFIER}.service ● Pas nécessaire de créer plus d'instances du service que le nombre de cœurs du CPU ● systemctl start "dummy_worker@*.service" --all Template 31

Slide 31

Slide 31 text

Retries & failures ● Par défaut, un message est réessayé 3 fois avant d'être ignoré ou envoyé dans le transport failure ● Chaque essai est retardé, au cas où l'échec est dû à une erreur temporaire ● Tout est configurable au niveau de chaque transport # config/packages/messenger.yaml framework: messenger: transports: async_priority_high: dsn: '%env(MESSENGER_TRANSPORT_DSN)%' # default configuration retry_strategy: max_retries: 3 # milliseconds delay delay: 1000 # causes the delay to be higher before each retry # e.g. 1 second delay, 2 seconds, 4 seconds multiplier: 2 max_delay: 0 # override all of this with a service that # implements RetryStrategyInterface # service: null 32

Slide 32

Slide 32 text

● Si un message est réessayé trop de fois (> max_retries), il est ignoré ● Création d'un transport pour éviter ça ● Plusieurs commandes Symfony pour visualiser et relancer les messages en erreur Failures # config/packages/messenger.yaml framework: messenger: # after retrying, messages will be sent to the "failed" transport failure_transport: failed transports: # ... other transports failed: 'doctrine://default?queue_name=failed' 33

Slide 33

Slide 33 text

The end! Merci pour votre attention 🤗 N'hésitez pas si vous avez des questions ! Une application de démo est disponible sur github https://github.com/pyrech/worker-php-conf @pyrech N'hésitez pas à laisser un feedback sur la conférence en flashant le QR code ou en allant sur https://openfeedback.io/forumphp2022/ 34

Slide 34

Slide 34 text

Sources https://jolicode.com/blog/symfony-messenger-systemd https://symfony.com/doc/current/messenger.html https://symfonycasts.com/screencast/messenger/deploy-restarting https://www.digitalocean.com/community/tutorials/how-to-use-systemctl-to-manage-systemd-services-and-units https://ege.dev/posts/systemd-vs-supervisor/ https://wiki.debian.org/fr/systemd https://seb.jambor.dev/posts/systemd-by-example-part-3-defining-services/ https://blogmotion.fr/systeme/systemd-bon-ou-mauvais-18446 https://www.computernetworkingnotes.com/linux-tutorials/systemd-units-explained-with-types-and-states.html 35