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

Running Symfony in a Multi-Process Container

Running Symfony in a Multi-Process Container

The deployment scenario for PHP looks a little different than for other languages like Go or Ruby that have embedded webservers. The classic stack of NGINX/Apache + php-fpm doesn't perfectly fit in the container world.

Some solution providers, like DigitalOcean or certain Azure products, only allow for single containers to be deployed.

In this talk, we'll explore ways other people have solved this, like FrankenPHP and others. For production deployments, there is k8s, compose, and running everything in one multiprocess container.

These single containers could be orchestrated with systemd, supervisord, or Runit. We'll talk about the benefits and downsides of these solutions, compare them with the choice we made for our last project, s6-overlay, show off the s6-cli project I developed for this purpose and talk about recipes for running database migrations, cronjobs and a few more.

Anne-Julia Seitz

December 07, 2024
Tweet

More Decks by Anne-Julia Seitz

Other Decks in Programming

Transcript

  1. Anne-Julia Seitz • Software Developer • from Berlin • ~16

    years of PHP • Symfony since v1.2 • Organizer of the Symfony User Group in Berlin • with QOSSMIC since 2021 • QOSSMIC is now OPEN Software Consulting 2
  2. • Workshops & Trainings for Symfony & React • User

    Groups & Community Events • Supporting Teams in Symfony Projects 3
  3. dockerized dev setup # docker-compose.yaml services: app: image: php:8.4.1-fpm-alpine volumes:

    - ./app:/var/www/html nginx: image: nginx:latest ports: - "80:80" volumes: - ./app/public:/var/www/html/public depends_on: - app 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 6
  4. container: isolated process tree [root] └── dockerd (docker daemon) ├──

    containerd (container daemon) ├── docker-containerd (container manager) ├── containerd-shim (per container process) │ └── nginx (main process) │ ├── nginx (worker process) │ ├── nginx (worker process) │ └── nginx (worker process) └── containerd-shim (per container process) └── php-fpm (main process) ├── php-fpm (pool www) ├── php-fpm (pool www) └── php-fpm (pool www) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 https://docs.docker.com/engine/containers/run/ 10
  5. 11

  6. signals signal name number default action description SIGSTOP Stop 19

    Stop Stop the process; cannot be caught or ignored. SIGTERM Terminate 15 Terminate Termination signal; can be caught or ignored. SIGKILL Kill 9 Terminate (forceful) Forcefully kill the process; cannot be caught or ignored. 12
  7. 15

  8. all in one container all in one container all in

    one container all in one container all in one container 18
  9. s6-overlay S6-overlay is a container-focused process manager that offers end-to-end

    management of the container's lifecycle, from initialization to graceful shutdown. https://github.com/just-containers/s6-overlay 21
  10. one thing in a container • Containers should do one

    thing • Containers should stop when that thing stops 22
  11. key features of s6-overlay • Easy Integration • Proper PID

    1 functionality • Versatile Process Management • Dependency Control • Sequence Management • Environment Variable Templating • Log Management • Graceful Shutdown • Multi-Arch Support: Ubuntu, CentOS, Fedora, Alpine, Busybox... 23
  12. install s6-overlay FROM busybox ARG RELEASE_PATH="https://github.com/just-containers/s6-overlay/releases/download/v3.2.0.2" ADD $RELEASE_PATH/s6-overlay-noarch.tar.xz /tmp RUN

    tar -C / -Jxpf /tmp/s6-overlay-noarch.tar.xz ADD $RELEASE_PATH/s6-overlay-x86_64.tar.xz /tmp RUN tar -C / -Jxpf /tmp/s6-overlay-x86_64.tar.xz ENTRYPOINT ["/init"] 25
  13. 3 service types • oneshot: run once and exit •

    longrun: supervised by s6 • bundle: groups of related services 27
  14. oneshot type programs that will run once and exit, initialization

    tasks service definition • type: plain text file with content "oneshot" • up: plain text file containing the path to the script • dependencies.d: directory containing dependencies of a service /etc/s6-overlay/s6-rc.d ├── init-nginx │ ├── dependencies.d │ ├── type # contains "oneshot" │ └── up # contains "/etc/s6-overlay/scripts/init-nginx" └── scripts └── init-nginx 1 2 3 4 5 6 7 /etc/s6-overlay/s6-rc.d 1 ├── init-nginx 2 │ ├── dependencies.d 3 │ ├── type # contains "oneshot" 4 │ └── up # contains "/etc/s6-overlay/scripts/init-nginx" 5 └── scripts 6 └── init-nginx 7 /etc/s6-overlay/s6-rc.d ├── init-nginx 1 2 │ ├── dependencies.d 3 │ ├── type # contains "oneshot" 4 │ └── up # contains "/etc/s6-overlay/scripts/init-nginx" 5 └── scripts 6 └── init-nginx 7 │ └── up # contains "/etc/s6-overlay/scripts/init-nginx" /etc/s6-overlay/s6-rc.d 1 ├── init-nginx 2 │ ├── dependencies.d 3 │ ├── type # contains "oneshot" 4 5 └── scripts 6 └── init-nginx 7 └── scripts /etc/s6-overlay/s6-rc.d 1 ├── init-nginx 2 │ ├── dependencies.d 3 │ ├── type # contains "oneshot" 4 │ └── up # contains "/etc/s6-overlay/scripts/init-nginx" 5 6 └── init-nginx 7 └── init-nginx /etc/s6-overlay/s6-rc.d 1 ├── init-nginx 2 │ ├── dependencies.d 3 │ ├── type # contains "oneshot" 4 │ └── up # contains "/etc/s6-overlay/scripts/init-nginx" 5 └── scripts 6 7 /etc/s6-overlay/s6-rc.d ├── init-nginx │ ├── dependencies.d │ ├── type # contains "oneshot" │ └── up # contains "/etc/s6-overlay/scripts/init-nginx" └── scripts └── init-nginx 1 2 3 4 5 6 7 30
  15. longrun type daemon that will get supervised by s6 service

    definition • type: plain text file with content "longrun" • run: executable file • dependencies.d: directory containing dependencies of a service /etc/s6-overlay/s6-rc.d └── svc-nginx ├── dependencies.d ├── run └── type # contains "longrun" 1 2 3 4 5 └── svc-nginx /etc/s6-overlay/s6-rc.d 1 2 ├── dependencies.d 3 ├── run 4 └── type # contains "longrun" 5 /etc/s6-overlay/s6-rc.d └── svc-nginx ├── dependencies.d ├── run └── type # contains "longrun" 1 2 3 4 5 └── type # contains "longrun" /etc/s6-overlay/s6-rc.d 1 └── svc-nginx 2 ├── dependencies.d 3 ├── run 4 5 /etc/s6-overlay/s6-rc.d └── svc-nginx ├── dependencies.d ├── run └── type # contains "longrun" 1 2 3 4 5 31
  16. bundle type organize and manage groups of related services service

    definition • type: plain text file with content "bundle" • contents.d: services that are part of the bundle /etc/s6-overlay/s6-rc.d └── my-app ├── contents.d │ ├── svc-nginx │ └── svc-other-service └── type # contains "bundle" 1 2 3 4 5 6 32
  17. Step by step nginx & php-fpm service structure 1. service

    root directory 2. initial structure 3. php-fpm service 4. nginx service 5. run it 33
  18. add user bundle /etc/s6-overlay/s6-rc.d └── user ├── contents.d └── type

    # contains "bundle" 1 2 3 4 https://skarnet.org/software/s6-rc/ 35
  19. create php-fpm service /etc/s6-overlay/s6-rc.d/svc-php-fpm/run ├── svc-php-fpm │ ├── run │

    └── type │ └── svc-php-fpm /etc/s6-overlay/s6-rc.d 1 2 3 4 └── user 5 ├── contents.d 6 7 └── type 8 #!/command/execlineb -P /usr/local/sbin/php-fpm --nodaemonize 36
  20. create nginx service /etc/s6-overlay/s6-rc.d/svc-nginx/run: ├── svc-nginx │ ├── dependencies.d │

    │ └── svc-php-fpm │ ├── run │ └── type └── svc-nginx /etc/s6-overlay/s6-rc.d 1 2 3 4 5 6 ├── svc-php-fpm 7 │ ├── run 8 │ └── type 9 └── user 10 └── contents.d 11 12 /etc/s6-overlay/s6-rc.d ├── svc-nginx │ ├── dependencies.d │ │ └── svc-php-fpm │ ├── run │ └── type ├── svc-php-fpm │ ├── run │ └── type └── user └── contents.d └── svc-nginx 1 2 3 4 5 6 7 8 9 10 11 12 #!/command/execlineb -P nginx -g "daemon off;" 1 2 37
  21. run it! docker run --name s6-demo -d -p 80:80 s6-demo

    /var/www/html # ps PID USER TIME COMMAND 1 root 0:00 /package/admin/s6/command/s6-svscan -d4 -- /run/service 18 root 0:00 s6-supervise s6-linux-init-shutdownd 20 root 0:00 /package/admin/s6-linux-init/command/s6-linux-init-shutdownd 29 root 0:00 s6-supervise s6rc-fdholder 30 root 0:00 s6-supervise svc-php-fpm 31 root 0:00 s6-supervise svc-nginx 32 root 0:00 s6-supervise s6rc-oneshot-runner 79 root 0:00 php-fpm: master process (/usr/local/etc/php-fpm.conf) 91 root 0:00 nginx: master process nginx 109 www-data 0:00 nginx: worker process 125 www-data 0:00 php-fpm: pool www ... 1 2 3 4 5 6 7 8 9 10 11 12 13 14 1 root 0:00 /package/admin/s6/command/s6-svscan -d4 -- /run/service 30 root 0:00 s6-supervise svc-php-fpm 31 root 0:00 s6-supervise svc-nginx /var/www/html # ps 1 PID USER TIME COMMAND 2 3 18 root 0:00 s6-supervise s6-linux-init-shutdownd 4 20 root 0:00 /package/admin/s6-linux-init/command/s6-linux-init-shutdownd 5 29 root 0:00 s6-supervise s6rc-fdholder 6 7 8 32 root 0:00 s6-supervise s6rc-oneshot-runner 9 79 root 0:00 php-fpm: master process (/usr/local/etc/php-fpm.conf) 10 91 root 0:00 nginx: master process nginx 11 109 www-data 0:00 nginx: worker process 12 125 www-data 0:00 php-fpm: pool www 13 ... 14 /var/www/html # ps PID USER TIME COMMAND 1 root 0:00 /package/admin/s6/command/s6-svscan -d4 -- /run/service 18 root 0:00 s6-supervise s6-linux-init-shutdownd 20 root 0:00 /package/admin/s6-linux-init/command/s6-linux-init-shutdownd 29 root 0:00 s6-supervise s6rc-fdholder 30 root 0:00 s6-supervise svc-php-fpm 31 root 0:00 s6-supervise svc-nginx 32 root 0:00 s6-supervise s6rc-oneshot-runner 79 root 0:00 php-fpm: master process (/usr/local/etc/php-fpm.conf) 91 root 0:00 nginx: master process nginx 109 www-data 0:00 nginx: worker process 125 www-data 0:00 php-fpm: pool www ... 1 2 3 4 5 6 7 8 9 10 11 12 13 14 38
  22. graceful shutdown docker stop ├── finish /etc/s6-overlay/s6-rc.d 1 └── svc-nginx

    2 3 ├── run 4 └── type 5 #!/bin/sh # do important thing before termination echo "0" > /run/s6-linux-init-container-results/exitcode 1 2 3 4 5 39
  23. dropping privileges in execline: In sh: #!/command/execlineb -P s6-setuidgid www-data

    myservice 1 2 3 #!/bin/sh exec s6-setuidgid www-data myservice 1 2 40
  24. container environment #!/command/execlineb -P with-contenv /usr/local/sbin/php-fpm --nodaemonize 1 2 3

    with-contenv #!/command/execlineb -P 1 2 /usr/local/sbin/php-fpm --nodaemonize 3 41
  25. customizing s6-overlay behaviour • S6_BEHAVIOUR_IF_STAGE2_FAILS • S6_CMD_RECEIVE_SIGNALS • S6_CMD_WAIT_FOR_SERVICES_MAXTIME •

    S6_KEEP_ENV • S6_KILL_GRACETIME • S6_LOGGING • S6_STAGE2_HOOK • S6_VERBOSITY • ... 42
  26. migrations /etc/s6-overlay/s6-rc.d ├── init-migrations │ ├── dependencies.d │ │ └──

    svc-php-fpm │ ├── type │ └── up └── scripts └── init-migrations 1 2 3 4 5 6 7 8 #!/command/with-contenv sh s6-setuidgid www-data php /var/www/html/bin/console doctrine:migrations:migrate --no-interaction php /var/www/html/bin/console doctrine:migrations:status 1 2 3 4 5 46
  27. cronjobs with scheduler /etc/s6-overlay/s6-rc.d/svc-scheduler/run /etc/s6-overlay/s6-rc.d ├── svc-scheduler │ ├── dependencies.d

    │ │ └── svc-php-fpm │ ├── type │ └── up └── scripts └── svc-scheduler 1 2 3 4 5 6 7 8 #!/command/with-contenv sh s6-setuidgid www-data php /var/www/html/bin/console messenger:consume scheduler_default \ --time-limit=300 --limit=10 --env=`printcontenv APP_ENV` --quiet 1 2 3 4 5 47
  28. Feature flags with S6_STAGE2_HOOK # set path to feature-toggle script

    ENV S6_STAGE2_HOOK=/etc/s6-overlay/s6-hook/feature-toggle 1 2 #!/command/with-contenv sh INIT_MIGRATIONS="${FEATURE_INIT_MIGRATIONS:-false}" SVC_MESSENGER_SCHEDULER="${FEATURE_RUN_QUEUE_SCHEDULER:-false}" SVC_NGINX="${FEATURE_RUN_NGINX:-true}" for feature in "INIT_MIGRATIONS SVC_MESSENGER_SCHEDULER SVC_NGINX"; do is_enabled=$(eval echo \${$feature:-false}) feature_file=$(echo "$feature" | tr '[:upper:]' '[:lower:]' | tr '_' '-') if [ $is_enabled = false ]; then echo "feature-toggle: info: $feature is disabled. Deleting service: $feature_file" rm -f "/etc/s6-overlay/s6-rc.d/user/contents.d/$feature_file" fi done exit 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 48
  29. s6-overlay base image FROM alpine3 COPY --from=hakindazz/s6-overlay-base /s6/root / #

    install your app here ENTRYPOINT ["/init"] 1 2 3 4 5 6 https://github.com/dazz/s6-overlay-base 50
  30. s6-cli docker run -it --rm hakindazz/s6-cli help COMMANDS: create, c

    create a service remove, rm remove a service lint, l lint directories and files mermaid, m document s6 service dependencies in mermaid syntax help, h Shows a list of commands or help for one command https://github.com/dazz/s6-cli 51
  31. create a service with s6-cli docker run -it --rm -v

    ./:/etc/s6-overlay hakindazz/s6-cli create oneshot init-dependencies /etc/s6-overlay/s6-rc.d ├── init-dependencies │ ├── dependencies.d │ │ ├── base │ │ └── svc-php-fpm │ ├── type │ └── up └── scripts └── init-dependencies 1 2 3 4 5 6 7 8 9 52
  32. use s6-cli in your CI docker run -it --rm -v

    .:/etc/s6-overlay hakindazz/s6-cli lint s6-cli: lint found no issues s6-cli: lint found issues with services in /etc/s6-overlay/s6-rc.d - svc-lint-me - type file for "svc-lint-me" does not end with a newline - invalid type in svc-lint-me/type file specified 53
  33. document your setup with mermaid docker run -it --rm -v

    .:/etc/s6-overlay hakindazz/s6-cli mermaid ```mermaid graph TD; user --> init-dependencies user --> init-migrations user --> svc-nginx init-migrations --> svc-php-fpm svc-php-fpm --> init-directories svc-nginx --> init-nginx svc-nginx --> svc-php-fpm 54
  34. document your setup with mermaid docker run -it --rm -v

    .:/etc/s6-overlay hakindazz/s6-cli mermaid user init-dependencies init-migrations svc-nginx svc-php-fpm init-directories init-nginx 55
  35. References • • • • • • https://github.com/just-containers/s6-overlay https://skarnet.org/software/s6/overview.html https://serversideup.net/open-source/docker-php/docs/guide/using-s6-overlay

    https://www.tonysm.com/multiprocess-containers-with-s6-overlay/ https://github.com/dazz/s6-overlay-base https://github.com/dazz/s6-nginx-php-fpm 60