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. Des workers PHP avec
    Symfony Messenger et systemd
    Loïck Piera - Forum PHP 2022 -

    View Slide

  2. 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

    View Slide

  3. Des workers en PHP ?

    View Slide

  4. 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

    View Slide

  5. 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

    View Slide

  6. 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

    View Slide

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

    View Slide

  8. Symfony Messenger

    View Slide

  9. 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

    View Slide

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

    View Slide

  11. 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

    View Slide

  12. 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

    View Slide

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

    View Slide

  14. 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

    View Slide

  15. Configuration
    ● Configuration facile avec Symfony flex :
    MESSENGER_TRANSPORT_DSN=amqp://guest:[email protected]:5672/%2f/messages
    MESSENGER_TRANSPORT_DSN=doctrine://default
    MESSENGER_TRANSPORT_DSN=redis://localhost:6379/messages
    16

    View Slide

  16. 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

    View Slide

  17. 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

    View Slide

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

    View Slide

  19. systemd

    View Slide

  20. 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

    View Slide

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

    View Slide

  22. 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

    View Slide

  23. ● /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

    View Slide

  24. 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

    View Slide

  25. 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

    View Slide

  26. 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

    View Slide

  27. Protips

    View Slide

  28. 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

    View Slide

  29. 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

    View Slide

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

    View Slide

  31. 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

    View Slide

  32. ● 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

    View Slide

  33. 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

    View Slide

  34. 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

    View Slide