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

Des workers PHP avec Symfony Messenger et systemd

Loïck Piera
October 13, 2022

Des workers PHP avec Symfony Messenger et systemd

Depuis plusieurs années, Symfony fournit tout ce qu'il faut pour créer des workers PHP asynchrones, notamment grâce à son composant Messenger. Nous verrons comment configurer ces workers pour les faire tourner en production avec systemd, ce "gestionnaire de système et de services" qui s'est imposé sur la plupart des distributions GNU/Linux. systemd permet, entre autres, de déclarer des services à exécuter et maintenir en vie. Disponible nativement sur bon nombre d'OS populaires, il sera donc un allié parfait pour simplifier la mise en place de nos workers.

Loïck Piera

October 13, 2022
Tweet

More Decks by Loïck Piera

Other Decks in Programming

Transcript

  1. 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
  2. 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
  3. 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
  4. 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
  5. 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
  6. Exemple : le message • N'importe quelle classe PHP, sérialisable

    final class CheckUrl { public function __construct( public readonly string $url, ) { } } 11
  7. 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
  8. 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
  9. 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
  10. 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
  11. 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
  12. Oui mais… • Que se passe-t-il si une erreur fatale

    intervient ? • Comment relancer le worker ? 19
  13. 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
  14. Pourquoi systemd ? • Alternative à runit / supervisord •

    Standard dans la plupart des distributions Linux • Utilisable sans être root ("user unit") 22
  15. 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
  16. • /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
  17. 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 /<chemin du projet>/bin/console messenger:consume async --env=prod Restart=always RestartSec=1 TimeoutSec=300 User=<l'utilisateur qui va lancer la commande> [Install] WantedBy=multi-user.target 25
  18. 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
  19. 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
  20. 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
  21. 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
  22. • 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
  23. 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
  24. • 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
  25. 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