Skip to content

Latest commit

 

History

History
222 lines (163 loc) · 6.34 KB

readme-supervisor-tasks.md

File metadata and controls

222 lines (163 loc) · 6.34 KB

Background Tasks

If you need to run cron or scheduled / background tasks, it is best to place these in a dedicated container that runs supervisor. While you can add supervisor to your standard app container this does mean that your main app is running multiple different processes and containerising allows us to isolate these separate concerns.

Running as individual processes

The preferred method for running background tasks is to create separate containers for each process and as the main CMD run that console process. For example: to run the messenger:consume command you could set up the following container:

FROM somnambulist/php-ppm:8.0-latest
ENV TERM=xterm-256color

RUN apk --update add ca-certificates \
    && apk update \
    && apk upgrade \
    && apk --no-cache add -U \
    php8-pdo_pgsql \
    php8-pgsql \
    php8-pecl-amqp \
    && rm -rf /var/cache/apk/* /tmp/*

# setup custom PHP ini files
COPY config/docker/messenger/php /etc/php8/conf.d/

WORKDIR /app

COPY config/docker/messenger/docker-entrypoint.sh /docker-entrypoint.sh
RUN chmod 755 /docker-entrypoint.sh

COPY composer.* ./
COPY .env* ./

RUN composer install --no-scripts --quiet && rm -rf /tmp/*

COPY . .

CMD ["/docker-entrypoint.sh"]

The entrypoint script could be something like the following:

#!/usr/bin/env bash

set -e
cd /app

[[ -d "/app/var" ]] || mkdir -m 0777 "/app/var"
[[ -d "/app/var/cache" ]] || mkdir -m 0777 "/app/var/cache"
[[ -d "/app/var/logs" ]] || mkdir -m 0777 "/app/var/logs"
[[ -d "/app/var/tmp" ]] || mkdir -m 0777 "/app/var/tmp"

# wait for the main app container to run before starting processes
# should avoid running migrations at the same time, or remove this and the migrations
# line to avoid conflicts.
sleep 10

/app/bin/console messenger:setup-transports
/app/bin/console messenger:consume <queue_name> --memory-limit=256M

Running with Supervisor

This is a suggested approach that has its own trade offs, but does allow for a resilient supervisor process.

This is a real-world example of adding a supervisor rabbit consumer process using Messenger.

Docker Configuration

Create a new docker config for the supervisor service. This should be per environment the same as the other configurations. For example and src/Resources/docker/dev/supervisor folder and then create a new Dockerfile that contains:

FROM somnambulist/php-ppm:8.0-latest
ENV TERM=xterm-256color

RUN apk --update add ca-certificates \
    && apk update \
    && apk upgrade \
    && apk --no-cache add -U \
    php8-pdo_pgsql \
    php8-pgsql \
    php8-pecl-amqp \
    supervisor \
    && rm -rf /var/cache/apk/* /tmp/*

# setup custom PHP ini files
COPY config/docker/supervisor/custom.ini /etc/php8/conf.d/zz-custom.ini

WORKDIR /app

COPY config/docker/supervisor/docker-entrypoint.sh /docker-entrypoint.sh
RUN chmod 755 /docker-entrypoint.sh

COPY config/docker/supervisor/supervisord.conf /etc/supervisor/supervisord.conf
COPY config/docker/supervisor/conf.d /etc/supervisor/conf.d/

COPY composer.* ./
COPY .env* ./

RUN composer install --no-scripts --quiet && rm -rf /tmp/*

COPY . .

CMD ["/docker-entrypoint.sh"]

This uses the php-pm image as a base as it is very small, and adds supervisor to it. The main step is then to run supervisor instead of php-pm and copy over the config files for supervisor.

The entrypoint file does basic setup:

#!/usr/bin/env bash

set -e
cd /app

[[ -d "/app/var" ]] || mkdir -m 0777 "/app/var"
[[ -d "/app/var/cache" ]] || mkdir -m 0777 "/app/var/cache"
[[ -d "/app/var/logs" ]] || mkdir -m 0777 "/app/var/logs"
[[ -d "/app/var/tmp" ]] || mkdir -m 0777 "/app/var/tmp"

# wait for the main app container to run before starting processes
# should avoid running migrations at the same time, or remove this and the migrations
# line to avoid conflicts.
sleep 30

/app/bin/console doctrine:migrations:sync-metadata-storage
/app/bin/console doctrine:migrations:migrate --no-interaction

sleep 5

/usr/bin/supervisord -c /etc/supervisor/supervisord.conf

The docker-compose.yml then needs a new service so that supervisor is built with the project:

  supervisor:
    build:
      context: .
      dockerfile: src/Resources/docker/dev/supervisor/Dockerfile
    depends_on:
      - app
    networks:
      - backend

Supervisor Config

Now add the following supervisor config files to a conf.d folder:

For example: src/Resources/docker/dev/supervisor/conf.d/supervisord.conf

[supervisord]
nodaemon=true
loglevel=debug

[include]
files=/etc/supervisor/conf.d/*.conf

If you decide to call this main config file something else, don't forget to update the Dockerfile with the new name / location.

Now we can add the actual task configs. You should add these as one per item to run. In this example all these are grouped together into a supervisor.d folder. The basic file looks like:

[program:domain-events]
process_name=%(program_name)s_%(process_num)02d
command=php /app/bin/console messenger:consume domain_events --memory-limit=256M
autostart=true
autorestart=true
user=root
numprocs=1
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0

There are a couple of important points to note about this setup:

  • it is running as root by default, you may wish to run as a different user
  • the process name inherits the program name / proc num
  • only a single process is started
  • the output and error output are sent to stdout/err

The last point means that if you run docker-compose logs -f <supervisor-container-name> you will get all the ouput from the process in the docker logs; however this will only work when the supervisor loglevel is set to debug (in the previous config file).

The command will run the messenger:consume for the domain_events queue and run until the max memory hits 256MB at which point it will die and supervisor will restart it.

Sync'ing Source Changes

Finally, to keep source files up-to-date in the containers, a SyncIt config is needed:

    supervisor_source_files:
        source: "${PROJECT_DIR}"
        target: "docker://{docker:name=${APP_SERVICE_SUPERVISOR}:name}/app"
        options:
            sync-mode: one-way-replica
        ignore:
            - "composer.*"

Additional tasks could be added for composer files etc, otherwise the container will need rebuilding if dependencies change.