Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Despliegue y supervisión

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Entonces... ¿cómo funciona todo esto en producción? En realidad es un problema sencillo: en producción, tenemos que asegurarnos de alguna manera de que este comando - messenger:consume - se ejecute siempre. Como, siempre.

Algunas plataformas de alojamiento -como SymfonyCloud- te permiten hacerlo con una sencilla configuración. Básicamente dices:

¡Oye, proveedor de la nube! Por favor, asegúrate de que bin/console messenger:consume esté siempre en marcha. Si se cierra por alguna razón, inicia uno nuevo.

Si no utilizas una plataforma de alojamiento así, no pasa nada, pero tendrás que hacer un poco de trabajo para obtener el mismo resultado. Y en realidad, no es sólo que necesitemos una forma de asegurarnos de que alguien inicie este comando y luego se ejecute para siempre. En realidad no queremos que el comando se ejecute para siempre. Por muy bien que escribas tu código PHP, éste no está hecho para ejecutarse eternamente: con el tiempo, su huella de memoria aumentará demasiado y el proceso morirá. Y... ¡eso es perfecto! No queremos que nuestro proceso se ejecute para siempre. No: lo que realmente queremos es que messenger:consume se ejecute, maneje... unos cuantos mensajes... y luego se cierre. Luego, utilizaremos una herramienta diferente para asegurarnos de que cada vez que el proceso desaparezca, se reinicie.

Hola Supervisor

La herramienta que hace eso se llama supervisor. Después de instalarla, le das un comando que quieres que se ejecute siempre y se queda despierto toda la noche comiendo pizza constantemente y vigilando que ese comando se ejecute. En el momento en que deja de ejecutarse, por cualquier motivo, deja la pizza y reinicia el comando.

Así que vamos a ver cómo funciona el Supervisor y cómo podemos utilizarlo para asegurarnos de que nuestro trabajador está siempre en marcha. Como estoy utilizando un Mac, ya he instalado el Supervisor a través de Brew. Si usas Ubuntu, puedes instalarlo mediante apt. Por cierto, en realidad no necesitas instalar y configurar Supervisor en tu máquina local: sólo lo necesitas en producción. Lo instalamos para poder probar y asegurarnos de que todo funciona.

Configuración del Supervisor

Para ponerlo en marcha, necesitamos un archivo de configuración del supervisor. Busca en Google "Messenger Symfony" y abre la documentación principal. En el medio... hay un punto que habla del supervisor. Copia el archivo de configuración. Podemos ponerlo en cualquier sitio: no es necesario que viva en nuestro proyecto. Pero, a mí me gusta tenerlo en mi repo para poder guardarlo en git. En... qué tal config/, crea un nuevo archivo llamado messenger-worker.ini y pega el código dentro.

[program:messenger-consume]
command=php /path/to/your/app/bin/console messenger:consume async --time-limit=3600
user=ubuntu
numprocs=2
autostart=true
autorestart=true
process_name=%(program_name)s_%(process_num)02d

El archivo le dice al Supervisor qué comando debe ejecutar y otra información importante como el usuario con el que debe ejecutar el proceso y el número de procesos a ejecutar. Esto creará dos procesos trabajadores. Cuantos más trabajadores se ejecuten, más mensajes se podrán gestionar a la vez. Pero también, más memoria y CPU necesitarás.

Ahora, localmente, no necesito ejecutar el supervisor... porque podemos ejecutar manualmentemessenger:consume. Pero para asegurarnos de que todo esto funciona, vamos a fingir que mi ordenador es de producción y cambiar la ruta para que apunte a usar mi ruta local:/Users/weaverryan/messenger... que si vuelvo a comprobar en mi terminal... oop - me olvidé de la parte Sites/. Luego, aquí abajo, cambiaré el usuario para que sea weaverryan. De nuevo, normalmente lo establecerás con tus valores de producción.

Ah, y si te fijas bien en el comando, se está ejecutandomessenger:consume async. Asegúrate de consumir también async_priority_high. El comando también tiene una opción --time-limit=3600. Hablaremos más de esto y de otras opciones dentro de un rato, pero esto es genial: le dice al trabajador que se ejecute durante 60 minutos y luego salga, para asegurarse de que no envejece demasiado y ocupa demasiada memoria. En cuanto salga, el Supervisor lo reiniciará.

Ejecutar el Supervisor

Ahora que tenemos nuestro archivo de configuración, tenemos que asegurarnos de que el Supervisor puede verlo. Cada instalación del Supervisor tiene un archivo de configuración principal. En un Mac donde se instala a través de Brew, ese archivo se encuentra en /usr/local/etc/supervisord.ini. En Ubuntu, debería estar en /etc/supervisor/supervisord.conf.

Entonces, en algún lugar de tu archivo de configuración, encontrarás una sección include con una líneafiles. Esto significa que el Supervisor está buscando en este directorio archivos de configuración -como el nuestro- que le dirán qué hacer.

Para meter nuestro archivo de configuración en ese directorio, podemos crear un enlace simbólico:ln -s ~/Sites/messenger/config/messenger-worker.ini y luego pegar el directorio.

ln -s ~/Sites/messenger/config/messenger-worker.ini /usr/local/etc/supervisor.d/

Ya está Ahora el supervisor debería poder ver nuestro archivo de configuración. Para ejecutar el supervisor, utilizaremos algo llamado supervisorctl. Como estoy en un Mac, también necesito pasar una opción -c y apuntar al archivo de configuración que acabamos de ver. Si estás en Ubuntu, no deberías necesitar hacer esto: ya sabrá dónde buscar. Luego di reread: eso le dice al Supervisor que vuelva a leer los archivos de configuración:

supervisorctl -c /usr/local/etc/supervisord.ini reread

Por cierto, es posible que tengas que ejecutar este comando con sudo. Si lo haces, no pasa nada: ejecutará los procesos propiamente dichos como el usuario de tu archivo de configuración.

¡Genial! Ve el nuevo grupo messager-consume. Ese nombre procede de la clave que hay en la parte superior de nuestro archivo. A continuación, ejecuta el comando update... que reiniciará los procesos con la nueva configuración... si ya estuvieran en marcha... pero los nuestros aún no lo están:

supervisorctl -c /usr/local/etc/supervisord.ini update

Para iniciarlos, ejecuta start messenger-consume:*:

supervisorctl -c /usr/local/etc/supervisord.ini start messenger-consume:*

El último argumento - messenger-consume:* no es muy obvio. Cuando creas un "programa" llamado messenger-consume, se crea lo que se llama un "grupo de procesos homogéneos". Como tenemos processes=2, este grupo ejecutará dos procesos. Al decir messenger-consume:* le dice al Supervisor que inicie todos los procesos dentro de ese grupo.

Cuando lo ejecutamos... no dice nada... pero... ¡nuestros comandos de trabajador deberían estar ejecutándose ahora! Vamos a detener nuestro trabajador manual para que sólo se ejecuten los del Supervisor. Ahora,

tail -f var/log/messenger.log

Esto hará que sea realmente obvio si nuestros mensajes están siendo gestionados por esos trabajadores. Ahora, sube unas cuantas fotos, borra un par de elementos, muévete y... ¡sí! ¡Está funcionando! En realidad está funcionando casi el doble de rápido de lo normal porque tenemos el doble de trabajadores.

Y ahora podemos divertirnos un poco. En primer lugar, podemos ver los identificadores de proceso creados por el Supervisor ejecutando:

ps -A | grep messenger:consume

Tip

También puedes utilizar ps aux, que funcionará en más sistemas operativos.

Ahí están: 19915 y 19916. Matemos uno de ellos:

kill 19915

Y ejecutémoslo de nuevo:

ps -A | grep messenger:consume

¡Sí! 19916 sigue ahí, pero como hemos matado al otro, el supervisor ha iniciado un nuevo proceso para él: 19995. El supervisor es genial.

A continuación, vamos a hablar más sobre las opciones que podemos utilizar para hacer salir a los trabajadores a propósito antes de que ocupen demasiada memoria. También hablaremos de cómo reiniciar los trabajadores al desplegarlos para que vean el nuevo código y un pequeño detalle sobre cómo pueden romperse las cosas si actualizas tu clase de mensajes.

Leave a comment!

26
Login or Register to join the conversation
Tim-K Avatar
Tim-K Avatar Tim-K | posted hace 2 años | edited

In case anybody has the same problem, that the installation of SUPERVISOR is not allowed on production-server, I solved this with a workaround by using a cronjob (starting every 5 minutes) with this bash-script:


#!/bin/sh

# check with '$ which php' and add to variable
PHP_FOLDER="/usr/bin/php"

# give your root folder of your symfony-project
SYMFONY_FOLDER="$HOME/your/symfony/project"

# define the transporters that shall be treated
TRANSPORTERS="async_1 async_2"

# define timezone to avoid mismatch between php for console and php for web (or server). Different
# timezones, would delay the treatment of the messages in the queues. E.g. check timezone with
#      SERVER time: $ echo $TZ
# PHP-Console time: $ php -r 'echo date_default_timezone_get();'
TIMEZONE="Europe/Berlin"

# Show folder (main intention is to exit if folder not exists)
echo "Change folder to:"
cd "$SYMFONY_FOLDER" || exit
pwd

echo ""
echo "Stop any running Workers"
env -i "$PHP_FOLDER" -q "$SYMFONY_FOLDER"/bin/console messenger:stop-workers

echo ""
echo "Starting Worker (messenger:consume)"
env -i "$PHP_FOLDER" -d date.timezone=$TIMEZONE -q "$SYMFONY_FOLDER"/bin/console messenger:consume -vv $TRANSPORTERS  --memory-limit=100M

Happy coding!

1 Reply

Hey Tim,

Thank you for sharing your workaround with others! Good idea to start the command via CRON ;)

Cheers!

Reply
Default user avatar
Default user avatar Rémi Dck | posted hace 3 años

Hello again !
A tiny precision, wich I faced : on ubuntu server I had to precise the directory where the supervisor should find the app so my worker looks like :
[program:messenger-consume]
command=php bin/console messenger:consume async --time-limit=3600
directory=/var/www/html/my-app

One other thing about supervisor, it seems he prefer now working with .conf files and not .ini (easy to fix, but you have to think about this --> http://supervisord.org/runn...

Are aware of a trouble for the FlySystem to delete files in a handler? I can't do it on production while I'm ok on local dev (still searching btw)

1 Reply

Just in case, the correct link is: http://supervisord.org/runn...

Reply

Hey Remi,

Yeah, it makes sense. In our example we use the absolute path so it works. If you use a relative path like only "bin/console" - you have to specify the directory of your project. Thank you for some tips! Might be useful for other users.

About problem with deleting files in a handler. Hm, most probably you have different permissions, so it sounds like permission problem. Is it for all the folders? Or some of them are removed successfully but some are not?

Cheers!

Reply
Rémi W. Avatar
Hm, most probably you have different permissions, so it sounds like permission problem. Is it for all the folders? Or some of them are removed successfully but some are not?


Mmm... Not sure because my filesystem works perfectly in the controller in production... I'll explore further and keep you update (i didn't try it syncronously yet to make a differential diagnosis)

Reply

Hey @Rémi Dck

I also believe that the file deletion problem is due to permissions. I think you just have to tell supervisor to run your worker as your web user (usually named as "www"). Here is an example of how to do it but I didn't give it a try: https://stackoverflow.com/a...

Cheers!

Reply
Default user avatar
Default user avatar Rémi Dck | MolloKhan | posted hace 3 años | edited

OMG you're the best Diego. Thanks a lot !!!
I dug in and the tail is a little bit tricky (to me, i'm not a unix user...)
The first step is finding the web user, like you said and the best command for this is less /etc/passwd. For Ubuntu 18.04 seems to be www-data
Next in messenger-worker.conf change user name and add environnement targeting the home directory of www-data
Aaaand done ! Love it.

1 Reply

Awesome! Excellent job man because I wrote the name wrong, it usually is www-data not just www :p

Reply
Dang Avatar

Hello guys,
I'm using Docker for deployment and my image have supervisor installed within. Now in order to execute these three command :

supervisorctl reread
supervisorctl update
supervisorctl start messenger-consume:*

Where should I put them? In Dockerfile or a entrypoint command? They should be run after the container is up if I'm not wrong ?

Reply

Yo @Dang!

Hmm. I am FAR from an expert on Docker, especially in a production environment. However, I believe that you would have a specific (PHP) container set up for running supervisor and that supervisor would be your main CMD on that container (and you'd run supervisor with nodaemon=true). Again, this is NOT my area of expertise, so I could be misguided, but that's my impression :).

Cheers!

Reply
Marko Avatar

Hi. What is/are the possible strategy/strategies to use if the Consumer throws an exception for any reason ? Assuming that the environment is Production. Thanks.

Reply

Hey Marko,

The best strategy is to do a few more retries after some time, and if still no success - move that message into a failed messages queue where you can retry the message manually when the problem will be fixed. That's the strategy we use on SymfonyCasts, and IIRC we covered it in this course :)

Cheers!

Reply
Marko Avatar

Hello Victor! Thanks a lot for your quick answer. Does Supervisor have any role in this issue (in production) ?

Reply

Hey Marko,

Supervisor is about a different problem, it helps to restart the worker on the server, so it's a bit different. If for some reasons you mean that your worker crashes, i.e. stops on the production that you have to restart it manually again - then yes, you need that supervisor that will restart the worker for you. But the problems you described relates to different things if I understand you correctly.

Cheers!

Reply
Rainer-S Avatar
Rainer-S Avatar Rainer-S | posted hace 3 años

I really hate asking this question.
Is there an easy windows solution?

Reply

Rainer S.

Yeah that's a good question, I'm hot sure but probably this link https://github.com/alexsilv... will help you, but I think you already saw this link. Unfortunately I can't advise you some real production example for windows, also there is not so many windows servers, that's why there is not so many solutions for it!

Cheers! Hope it will help!

Reply
Michael H. Avatar
Michael H. Avatar Michael H. | posted hace 3 años

Has anyone by any chance run supervisor on an AWS EB setup? (Elastic Beanstalk, using .ebextension files)

Reply

Hey Chad,

Unfortunately, I have not run it personally. Though it looks like there's some solutions around the internet, e.g.: https://gist.github.com/vra...

I hope this helps!

Cheers!

Reply
Michael H. Avatar

Thanks, this is one of the articles I used to get things going.

Reply

Hey Chad,

Ah, great! Sorry, can't say something more about it, have never done it before. Probably someone who use it could suggest you a good tip about it.

Cheers!

Reply
Michael H. Avatar
Michael H. Avatar Michael H. | Michael H. | posted hace 3 años | edited

the following .ebextension config file is working to install supervisor and start messenger:consume, but the .env file is required to be populated with all the database credentials and such

files:
  "/tmp/messenger-consume.conf":
    mode: "000644"
    owner: root
    group: root
    content: |
      [program:messenger-consume]
      command=php /var/www/html/bin/console messenger:consume async --time-limit=1800 --memory-limit=340M --sleep=30
      user=webapp
      numprocs=1
      autostart=true
      autorestart=true
      process_name=%(program_name)s_%(process_num)02d

  "/tmp/supervisord.conf":
    mode: "000644"
    owner: root
    group: root
    content: |
      ; supervisor config file

      [unix_http_server]
      file=/var/run/supervisor.sock   ; (the path to the socket file)
      chmod=0700                       ; sockef file mode (default 0700)

      [supervisord]
      user=root
      logfile=/var/log/supervisor/supervisord.log ; (main log file;default $CWD/supervisord.log)
      pidfile=/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid)
      childlogdir=/var/log/supervisor            ; ('AUTO' child log dir, default $TEMP)

      ; the below section must remain in the config file for RPC
      ; (supervisorctl/web interface) to work, additional interfaces may be
      ; added by defining them in separate rpcinterface: sections
      [rpcinterface:supervisor]
      supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

      [supervisorctl]
      serverurl=unix:///var/run/supervisor.sock ; use a unix:// URL  for a unix socket

      ; The [include] section can just contain the "files" setting.  This
      ; setting can list multiple files (separated by whitespace or
      ; newlines).  It can also contain wildcards.  The filenames are
      ; interpreted as relative to this file.  Included files *cannot*
      ; include files themselves.

      [include]
      files = /etc/supervisor/conf.d/*.conf
      ; Change according to your configurations

commands:
  01_01_install_supervisord:
    command: |
      sudo easy_install supervisor
      sudo mkdir -p /etc/supervisor/conf.d/
      sudo mkdir -p /var/log/supervisor
      sudo touch /var/log/supervisor/supervisord.log
    ignoreErrors: true

container_commands:
  01_01_copy_supervisor_configuration_files:
    command: |
      sudo mv /tmp/supervisord.conf /etc/supervisord.conf
      sudo mv /tmp/messenger-consume.conf /etc/supervisor/conf.d/messenger-consume.conf
    ignoreErrors: true
  02_01_start_supervisord:
    command: |
      sudo /usr/local/bin/supervisord -c /etc/supervisord.conf
      sudo /usr/local/bin/supervisorctl reread
      sudo /usr/local/bin/supervisorctl update
      sudo /usr/local/bin/supervisorctl restart all
      sudo /usr/local/bin/supervisorctl start messenger-consume:*
    ignoreErrors: true```

Reply

Hey Chad,

Thank you for sharing this solution with others! I'm glad you were able to get it working.

Cheers!

Reply
Ajie62 Avatar
Ajie62 Avatar Ajie62 | posted hace 3 años | edited

Hello,

When running supervisorctl -c /usr/local/etc/supervisord.ini start messenger-consume:*, I get zsh: no matches found: messenger-consume:*. But it seems the workers are actually working, because I tried to upload an image and it worked. In the video, there's no message after you start the supervisor with messenger-consume:*. So I'm just wondering if I'm doing something wrong...

Thanks!

Reply

Hey Ajie62!

Hmm. Try this command to see what's going on:


supervisorctl -c /usr/local/etc/supervisord.ini start

I definitely don't like that "no matches found". Also try using the stop command to see if it stops anything. Here's a screenshot of what starting and stopping looks on my machine - you can see how "start" sometimes doesn't have any output (if it's already started) but sometimes does.

https://imgur.com/Pz601gU

Also, the zsh: no matches found: messenger-consume:* is odd... the error should come from supervisorctl... not from "zsh". Try surrounding messenger-consume:* with quotes. I don't know zsh well... but I'm wondering if it's somehow interpreting something differently...

Cheers!

Reply
Ajie62 Avatar
Ajie62 Avatar Ajie62 | weaverryan | posted hace 3 años | edited

Hey, thanks for you answer! The problem was oh-my-zsh. I removed it an now everything works fine. I assume I could have solved the problem and keep oh-my-zsh, but I wanted to save time and keep learning :) Thank you again, weaverryan

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

Este tutorial está construido con Symfony 4.3, pero funcionará bien en Symfony 4.4 o 5.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.1.3",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "doctrine/annotations": "^1.0", // v1.8.0
        "doctrine/doctrine-bundle": "^1.6.10", // 1.11.2
        "doctrine/doctrine-migrations-bundle": "^1.3|^2.0", // v2.0.0
        "doctrine/orm": "^2.5.11", // v2.6.3
        "intervention/image": "^2.4", // 2.4.2
        "league/flysystem-bundle": "^1.0", // 1.1.0
        "phpdocumentor/reflection-docblock": "^3.0|^4.0", // 4.3.1
        "sensio/framework-extra-bundle": "^5.3", // v5.3.1
        "symfony/console": "4.3.*", // v4.3.2
        "symfony/dotenv": "4.3.*", // v4.3.2
        "symfony/flex": "^1.9", // v1.18.7
        "symfony/framework-bundle": "4.3.*", // v4.3.2
        "symfony/messenger": "4.3.*", // v4.3.4
        "symfony/property-access": "4.3.*", // v4.3.2
        "symfony/property-info": "4.3.*", // v4.3.2
        "symfony/serializer": "4.3.*", // v4.3.2
        "symfony/validator": "4.3.*", // v4.3.2
        "symfony/webpack-encore-bundle": "^1.5", // v1.6.2
        "symfony/yaml": "4.3.*" // v4.3.2
    },
    "require-dev": {
        "easycorp/easy-log-handler": "^1.0.7", // v1.0.7
        "symfony/debug-bundle": "4.3.*", // v4.3.2
        "symfony/maker-bundle": "^1.0", // v1.12.0
        "symfony/monolog-bundle": "^3.0", // v3.4.0
        "symfony/stopwatch": "4.3.*", // v4.3.2
        "symfony/twig-bundle": "4.3.*", // v4.3.2
        "symfony/var-dumper": "4.3.*", // v4.3.2
        "symfony/web-profiler-bundle": "4.3.*" // v4.3.2
    }
}
userVoice