From fa8b76a6216b93bdce93716d071131c138310921 Mon Sep 17 00:00:00 2001 From: Ambroise Maupate Date: Thu, 16 Mar 2023 01:19:25 +0100 Subject: [PATCH] feat: Migrate from monolithic docker image to nginx + app + worker + cron containers --- Makefile | 4 + composer.json | 4 +- docker-compose.yml | 38 +++- docker/php81-fpm-alpine/Dockerfile | 36 ++++ docker/php81-fpm-alpine/crontab.txt | 12 ++ docker/php81-fpm-alpine/docker-php-entrypoint | 26 +++ docker/php81-fpm-alpine/php.ini | 33 ++++ docker/php81-fpm-alpine/wait-for-it.sh | 182 ++++++++++++++++++ 8 files changed, 328 insertions(+), 7 deletions(-) create mode 100755 docker/php81-fpm-alpine/Dockerfile create mode 100644 docker/php81-fpm-alpine/crontab.txt create mode 100755 docker/php81-fpm-alpine/docker-php-entrypoint create mode 100644 docker/php81-fpm-alpine/php.ini create mode 100755 docker/php81-fpm-alpine/wait-for-it.sh diff --git a/Makefile b/Makefile index 1c263750..955a6914 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,6 @@ test: + vendor/bin/requirements-checker vendor/bin/monorepo-builder validate vendor/bin/atoum -d ./lib/Documents/tests vendor/bin/atoum -f ./lib/EntityGenerator/tests/units/* @@ -27,6 +28,9 @@ test: php -d "memory_limit=-1" bin/console lint:twig ./lib/RoadizFontBundle/templates php -d "memory_limit=-1" bin/console lint:twig ./lib/RoadizCoreBundle/templates +requirements: + vendor/bin/requirements-checker + cache : docker-compose exec -u www-data app php bin/console cache:clear # Force workers to restart diff --git a/composer.json b/composer.json index a8c35831..1fb9e389 100644 --- a/composer.json +++ b/composer.json @@ -114,6 +114,7 @@ "symfony/property-info": "5.4.*", "symfony/proxy-manager-bridge": "5.4.*", "symfony/rate-limiter": "5.4.*", + "symfony/requirements-checker": "^2.0", "symfony/routing": "5.4.*", "symfony/runtime": "5.4.*", "symfony/security-bundle": "5.4.*", @@ -229,7 +230,8 @@ "auto-scripts": { "cache:clear": "symfony-cmd", "assets:install %PUBLIC_DIR%": "symfony-cmd", - "themes:assets:install --relative --symlink Rozier": "symfony-cmd" + "themes:assets:install --relative --symlink Rozier": "symfony-cmd", + "requirements-checker": "script" }, "post-install-cmd": [ "@auto-scripts" diff --git a/docker-compose.yml b/docker-compose.yml index 7573aed4..9b8d3751 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,5 @@ version: '3' + services: db_test: build: @@ -112,11 +113,10 @@ services: build: #context: ./docker/php74-nginx-alpine #context: ./docker/php80-nginx-alpine - context: ./docker/php81-nginx-alpine + #context: ./docker/php81-nginx-alpine + context: ./docker/php81-fpm-alpine args: USER_UID: ${USER_UID} - ports: - - ${PUBLIC_APP_PORT}:80/tcp depends_on: - db - db_test @@ -128,9 +128,6 @@ services: volumes: - ./:/var/www/html:cached networks: - frontproxynet: - aliases: - - ${APP_NAMESPACE}_app default: environment: APP_CACHE: ${APP_CACHE} @@ -138,6 +135,35 @@ services: USER_UID: ${USER_UID} DEFAULT_GATEWAY: ${DEFAULT_GATEWAY} + worker: + extends: app + deploy: + replicas: 1 + entrypoint: ["php", "/var/www/html/bin/console", "messenger:consume", "async", "--time-limit=30"] + restart: unless-stopped + + cron: + extends: app + # https://github.com/dubiousjim/dcron/issues/13#issuecomment-1406937781 + init: true + entrypoint: ["crond", "-f", "-L", "15"] + restart: unless-stopped + + nginx: + image: roadiz/nginx-alpine:latest +# ports: +# - ${PUBLIC_APP_PORT}:80/tcp + depends_on: + - app + links: + - app:app + volumes: + - ./:/var/www/html:cached + networks: + frontproxynet: + aliases: + - ${APP_NAMESPACE}_app + default: labels: - "traefik.enable=true" - "traefik.http.services.${APP_NAMESPACE}.loadbalancer.server.scheme=http" diff --git a/docker/php81-fpm-alpine/Dockerfile b/docker/php81-fpm-alpine/Dockerfile new file mode 100755 index 00000000..3430dd39 --- /dev/null +++ b/docker/php81-fpm-alpine/Dockerfile @@ -0,0 +1,36 @@ +FROM roadiz/php81-fpm-alpine +MAINTAINER Ambroise Maupate + +ARG USER_UID=1000 +ENV COMPOSER_ALLOW_SUPERUSER=1 +ENV APP_ENV=dev +ENV APP_CACHE=0 +ENV APP_FFMPEG_PATH=/usr/bin/ffmpeg +ENV MYSQL_HOST=db +ENV MYSQL_PORT=3306 + +RUN apk add --no-cache shadow make git ffmpeg \ + && usermod -u ${USER_UID} www-data \ + && groupmod -g ${USER_UID} www-data \ + && composer --version \ + && ln -s /usr/share/zoneinfo/Europe/Paris /etc/localtime \ + && "date" \ + && echo "USER_UID: ${USER_UID}\n" \ + && version=$(php -r "echo PHP_MAJOR_VERSION.PHP_MINOR_VERSION;") + +# Display errors +ADD php.ini /usr/local/etc/php/php.ini +ADD crontab.txt /crontab.txt +ADD wait-for-it.sh /wait-for-it.sh +ADD docker-php-entrypoint /usr/local/bin/docker-php-entrypoint + +VOLUME /var/www/html +WORKDIR /var/www/html + +RUN chown -R www-data:www-data /var/www/html/ + +RUN ln -s /var/www/html/bin/console /usr/local/bin/console \ + && /usr/bin/crontab -u www-data /crontab.txt \ + && chmod +x /wait-for-it.sh \ + && chmod +x /usr/local/bin/docker-php-entrypoint \ + && chown -R www-data:www-data /var/www/html/ diff --git a/docker/php81-fpm-alpine/crontab.txt b/docker/php81-fpm-alpine/crontab.txt new file mode 100644 index 00000000..4c2e5964 --- /dev/null +++ b/docker/php81-fpm-alpine/crontab.txt @@ -0,0 +1,12 @@ +# Roadiz maintenance tasks + +### Update Solr index +0 0 * * * cd /var/www/html && /usr/local/bin/php -d memory_limit=-1 bin/console solr:reindex --no-debug -n -q + +### Maintenance tasks: erase +6 months logs and keeps only 20 node-source versions +0 8 * * 1 cd /var/www/html && /usr/local/bin/php -d memory_limit=-1 bin/console documents:file:size -q +0 1 * * * cd /var/www/html && /usr/local/bin/php -d memory_limit=-1 bin/console logs:cleanup --erase -n -q +0 2 * * * cd /var/www/html && /usr/local/bin/php -d memory_limit=-1 bin/console versions:purge -c 20 -n -q +0 3 * * * cd /var/www/html && /usr/local/bin/php -d memory_limit=-1 bin/console custom-form-answer:prune -n -q +### Empty node trashcan every month +0 0 1 * * cd /var/www/html && /usr/local/bin/php -d memory_limit=-1 bin/console nodes:empty-trash -n -q diff --git a/docker/php81-fpm-alpine/docker-php-entrypoint b/docker/php81-fpm-alpine/docker-php-entrypoint new file mode 100755 index 00000000..4060a7c1 --- /dev/null +++ b/docker/php81-fpm-alpine/docker-php-entrypoint @@ -0,0 +1,26 @@ +#!/bin/sh +set -e + +# Symfony directories +/bin/chown -R www-data:www-data /var/www/html/public; +/bin/chown -R www-data:www-data /var/www/html/config; +/bin/chown -R www-data:www-data /var/www/html/var; + +# Print local env vars to .env.xxx.php file for performances and crontab jobs +#/usr/bin/sudo -u www-data -- bash -c "/usr/local/bin/composer dump-env prod" +/usr/bin/sudo -u www-data -- bash -c "/var/www/html/bin/console cache:clear -n" +/usr/bin/sudo -u www-data -- bash -c "/var/www/html/bin/console assets:install -n" +/usr/bin/sudo -u www-data -- bash -c "/var/www/html/bin/console themes:assets:install -n --relative --symlink Rozier" + +# To improve performance (i.e. avoid decrypting secrets at runtime), +# you can decrypt your secrets during deployment to the "local" vault: +#/usr/bin/sudo -u www-data -- bash -c "APP_RUNTIME_ENV=prod /var/www/html/bin/console secrets:decrypt-to-local --force" + +/wait-for-it.sh -t 0 -s ${MYSQL_HOST}:${MYSQL_PORT} + +# first arg is `-f` or `--some-option` +if [ "${1#-}" != "$1" ]; then + set -- php-fpm "$@" +fi + +exec "$@" diff --git a/docker/php81-fpm-alpine/php.ini b/docker/php81-fpm-alpine/php.ini new file mode 100644 index 00000000..8eb01de5 --- /dev/null +++ b/docker/php81-fpm-alpine/php.ini @@ -0,0 +1,33 @@ +error_reporting = E_ALL +html_errors = On + +apc.enable_cli = 0 +date.timezone = Europe/Paris +session.auto_start = Off +; Session ID cannot be passed through URLs +session.use_only_cookies = On +; Uses a secure connection (HTTPS) if possible +; session.cookie_secure = On +; Do not accept uninitialized session ID +session.use_strict_mode = On +; Do not make session cookie available to JS +session.cookie_httponly = On +short_open_tag = Off + +; http://symfony.com/doc/current/performance.html +; Configure OPcache for Maximum Performance +opcache.enable=1 +opcache.memory_consumption=256 +opcache.max_accelerated_files = 20000 +; Don't Check PHP Files Timestamps +opcache.revalidate_freq=0 +opcache.validate_timestamps=1 +opcache.fast_shutdown=1 +; Configure the PHP realpath Cache +realpath_cache_size = 4096K +realpath_cache_ttl = 600 +memory_limit = 512M +post_max_size = 128M +upload_max_filesize = 128M +expose_php = On +display_errors = On diff --git a/docker/php81-fpm-alpine/wait-for-it.sh b/docker/php81-fpm-alpine/wait-for-it.sh new file mode 100755 index 00000000..d990e0d3 --- /dev/null +++ b/docker/php81-fpm-alpine/wait-for-it.sh @@ -0,0 +1,182 @@ +#!/usr/bin/env bash +# Use this script to test if a given TCP host/port are available + +WAITFORIT_cmdname=${0##*/} + +echoerr() { if [[ $WAITFORIT_QUIET -ne 1 ]]; then echo "$@" 1>&2; fi } + +usage() +{ + cat << USAGE >&2 +Usage: + $WAITFORIT_cmdname host:port [-s] [-t timeout] [-- command args] + -h HOST | --host=HOST Host or IP under test + -p PORT | --port=PORT TCP port under test + Alternatively, you specify the host and port as host:port + -s | --strict Only execute subcommand if the test succeeds + -q | --quiet Don't output any status messages + -t TIMEOUT | --timeout=TIMEOUT + Timeout in seconds, zero for no timeout + -- COMMAND ARGS Execute command with args after the test finishes +USAGE + exit 1 +} + +wait_for() +{ + if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then + echoerr "$WAITFORIT_cmdname: waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" + else + echoerr "$WAITFORIT_cmdname: waiting for $WAITFORIT_HOST:$WAITFORIT_PORT without a timeout" + fi + WAITFORIT_start_ts=$(date +%s) + while : + do + if [[ $WAITFORIT_ISBUSY -eq 1 ]]; then + nc -z $WAITFORIT_HOST $WAITFORIT_PORT + WAITFORIT_result=$? + else + (echo -n > /dev/tcp/$WAITFORIT_HOST/$WAITFORIT_PORT) >/dev/null 2>&1 + WAITFORIT_result=$? + fi + if [[ $WAITFORIT_result -eq 0 ]]; then + WAITFORIT_end_ts=$(date +%s) + echoerr "$WAITFORIT_cmdname: $WAITFORIT_HOST:$WAITFORIT_PORT is available after $((WAITFORIT_end_ts - WAITFORIT_start_ts)) seconds" + break + fi + sleep 1 + done + return $WAITFORIT_result +} + +wait_for_wrapper() +{ + # In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692 + if [[ $WAITFORIT_QUIET -eq 1 ]]; then + timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --quiet --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & + else + timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & + fi + WAITFORIT_PID=$! + trap "kill -INT -$WAITFORIT_PID" INT + wait $WAITFORIT_PID + WAITFORIT_RESULT=$? + if [[ $WAITFORIT_RESULT -ne 0 ]]; then + echoerr "$WAITFORIT_cmdname: timeout occurred after waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" + fi + return $WAITFORIT_RESULT +} + +# process arguments +while [[ $# -gt 0 ]] +do + case "$1" in + *:* ) + WAITFORIT_hostport=(${1//:/ }) + WAITFORIT_HOST=${WAITFORIT_hostport[0]} + WAITFORIT_PORT=${WAITFORIT_hostport[1]} + shift 1 + ;; + --child) + WAITFORIT_CHILD=1 + shift 1 + ;; + -q | --quiet) + WAITFORIT_QUIET=1 + shift 1 + ;; + -s | --strict) + WAITFORIT_STRICT=1 + shift 1 + ;; + -h) + WAITFORIT_HOST="$2" + if [[ $WAITFORIT_HOST == "" ]]; then break; fi + shift 2 + ;; + --host=*) + WAITFORIT_HOST="${1#*=}" + shift 1 + ;; + -p) + WAITFORIT_PORT="$2" + if [[ $WAITFORIT_PORT == "" ]]; then break; fi + shift 2 + ;; + --port=*) + WAITFORIT_PORT="${1#*=}" + shift 1 + ;; + -t) + WAITFORIT_TIMEOUT="$2" + if [[ $WAITFORIT_TIMEOUT == "" ]]; then break; fi + shift 2 + ;; + --timeout=*) + WAITFORIT_TIMEOUT="${1#*=}" + shift 1 + ;; + --) + shift + WAITFORIT_CLI=("$@") + break + ;; + --help) + usage + ;; + *) + echoerr "Unknown argument: $1" + usage + ;; + esac +done + +if [[ "$WAITFORIT_HOST" == "" || "$WAITFORIT_PORT" == "" ]]; then + echoerr "Error: you need to provide a host and port to test." + usage +fi + +WAITFORIT_TIMEOUT=${WAITFORIT_TIMEOUT:-15} +WAITFORIT_STRICT=${WAITFORIT_STRICT:-0} +WAITFORIT_CHILD=${WAITFORIT_CHILD:-0} +WAITFORIT_QUIET=${WAITFORIT_QUIET:-0} + +# Check to see if timeout is from busybox? +WAITFORIT_TIMEOUT_PATH=$(type -p timeout) +WAITFORIT_TIMEOUT_PATH=$(realpath $WAITFORIT_TIMEOUT_PATH 2>/dev/null || readlink -f $WAITFORIT_TIMEOUT_PATH) + +WAITFORIT_BUSYTIMEFLAG="" +if [[ $WAITFORIT_TIMEOUT_PATH =~ "busybox" ]]; then + WAITFORIT_ISBUSY=1 + # Check if busybox timeout uses -t flag + # (recent Alpine versions don't support -t anymore) + if timeout &>/dev/stdout | grep -q -e '-t '; then + WAITFORIT_BUSYTIMEFLAG="-t" + fi +else + WAITFORIT_ISBUSY=0 +fi + +if [[ $WAITFORIT_CHILD -gt 0 ]]; then + wait_for + WAITFORIT_RESULT=$? + exit $WAITFORIT_RESULT +else + if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then + wait_for_wrapper + WAITFORIT_RESULT=$? + else + wait_for + WAITFORIT_RESULT=$? + fi +fi + +if [[ $WAITFORIT_CLI != "" ]]; then + if [[ $WAITFORIT_RESULT -ne 0 && $WAITFORIT_STRICT -eq 1 ]]; then + echoerr "$WAITFORIT_cmdname: strict mode, refusing to execute subprocess" + exit $WAITFORIT_RESULT + fi + exec "${WAITFORIT_CLI[@]}" +else + exit $WAITFORIT_RESULT +fi