Хабрахабр

Докеризация nginx и php на сокетах с ротацией логов

В статье на Хабре обсуждался «docker way»(TM), который гласит: один контейнер — один процесс.

one process per container

Each container should have only one concern

Decoupling applications into multiple containers makes it much easier to scale horizontally and reuse containers. For instance, a web application stack might consist of three separate containers, each with its own unique image, to manage the web application, database, and an in-memory cache in a decoupled manner.

You may have heard that there should be “one process per container”. While this mantra has good intentions, it is not necessarily true that there should be only one operating system process per container. In addition to the fact that containers can now be spawned with an init process, some programs might spawn additional processes of their own accord. For instance, Celery can spawn multiple worker processes, or Apache might create a process per request. While “one process per container” is frequently a good rule of thumb, it is not a hard and fast rule. Use your best judgment to keep containers as clean and modular as possible.

If containers depend on each other, you can use Docker container networks to ensure that these containers can communicate.

Следование этому принципу при докеризации nginx чревато двумя последствиями. Настроить взаимодействие nginx и php-fpm в разных процессах через unix сокет немного сложнее, чем может показаться. И ротация логов, которая при обычной установке идет «из коробки», не может осуществляться в принципе, т.к. требует отправки сигнала USR1 nginx, для чего нужен ещё один процесс.

В результате обсуждения выяснилось, что можно вместо отправки сигнала USR1 nginx добавить опцию copytruncate в конфигурацию logrotate. Значит в контейнере нет необходимости в запуске нескольких процессов. Однако все действия по настроке запуска ротации логов по cron нужно все равно будет выполнить только не внутри контейнера, а на хосте где работет контейнер. При запуске в одном контейнере и веб-сервер, и ротации логов, отдельная настройка ротации на хосте уже не требуется.

В приведенных выше ссылках даны решения. Впрочем с первого раза все не заработало и пришлось искать причины. Поэтому кроме ссыок я привожу результаты своих опытов. Для того, чтобы можно было познакомиться со способом защиты от DDoS-атак вместо сервера nginx будет запускаться openresty (сборка nginx от Taobao со скриптовым движком Lua). Этот сервер имеет другое по сравнению с nginx расположение каталогов с файлами. Но все остальное абсолютно идентично.

Для начала создадим файл docker-compose.yml в корневом каталоге проекта:

version: "3"
services: app: build: context: ./docker/php # dockerfile: docker/php/Dockerfile args: UID: "3000" working_dir: /app nginx: build: context: ./docker/nginx # dockerfile: docker/nginx/Dockerfile args: UID: "3000" ports: - 8000:80

Мы предполагаем что сценарии создания контейнеров будут храниться в файлах docker/php/Dockerfile и docker/nginx/Dockerfile. Имя Dockerfile является именем по умолчанию, следовательно нет необходимости его явно задавать в конфигурации.

Создадим файл docker/php/Dockerfile:

FROM php:7-fpm
ARG UID
RUN addgroup --gid $UID --system app \ && adduser --uid $UID --system --disabled-login --disabled-password --gid $UID app

Загружается образ php:7-fpm и создается пользователь с идентификатором заданным параметром UID (в docker-compose.yml UID: 3000) с именем app в группе app. Это нужно чтобы задать права на чтение сокета из контйнера, где будет запущен openresty.

Для того чтобы получить ротацию логов в nginx или openresty, необходимо чтобы контейнер не завершал работу при рестарте веб-сервера, а так же чтобы в этом же контейнере был запущен cron. То есть это не будет однопроцессный контейнер, но иначе ничего не получится. Запускать несколько процессов рекомендуется через supervisor.

Создадим файл docker/nginx/Dockerfile:

FROM openresty/openresty:xenial
RUN apt-get update && apt-get install -y supervisor cron logrotate
COPY ./supervisord.conf /etc/supervisor/conf.d/supervisord.conf
COPY ./logrotate.conf /etc/logrotate.conf
COPY ./cron.d /etc/cron.d/nginx
ARG UID
RUN mkdir -p /var/log/supervisor \ && chmod 644 /etc/logrotate.conf && chown root:root /etc/logrotate.conf \ && chmod 644 /etc/cron.d/nginx && chown root:root /etc/cron.d/nginx \ && addgroup --gid $UID --system app \ && adduser --uid $UID --system --disabled-login --disabled-password --gid $UID app
ENTRYPOINT ["/usr/bin/supervisord"]

Сначала интсаллируются все необходмые программы. Затем копируются конфигурационные файлы из каталога ./docker/nginx/ во внутреннюю файловую систему контейнера. Далее некоторым из этих файлов присваиваются права 644 (в противном случае система не будет выполнять ротацию логов). И также создается пользователь и группа app с тем же самым идентификатором (UID: 3000).

Так же необходмио создать несколько конфигурационных файлов.

Файл docker/php/zz-docker.conf (имя zz-docker.conf присутсвует в конфигурации образа php:7-fpm. Это нигде не описано и может меняться. К сожалению в настоящий момент подробных описаний образов нет, и приходися их исследовать после загрузки из репозитария):

[global]
daemonize = no [www]
;listen = [::]:9000 # Don't need this
listen = /sock/docker.sock
listen.owner = app
listen.group = app
listen.mode = 0660

Параметр listen = /sock/docker.sock будет тот же что и в конфигурации nginx.

Основной конфигурационный файл nginx придется переписать т.к. в openresty он не содержит необходимых параметров, а это расположение логов, иднтификатор процесса, пользователь (app), и каталог с конфигурациями виртуалных серверов (/conf.d).

user app;
error_log /var/log/nginx/error.log debug;
pid /var/run/nginx.pid;
http { access_log /var/log/nginx/access.log; include mime.types; default_type application/octet-stream; server { listen 80; server_name localhost; location / { root html; } } include /usr/local/openresty/nginx/conf/conf.d/*;
}

Создадим виртуальный сервер с конфигурацией server.conf:

server { listen 80; server_name local; root /usr/share/nginx/html; disable_symlinks off; client_max_body_size 50M; location ~ (/assets|/favicon.ico) { try_files /build$uri $uri =404; } location / { try_files $uri /app.php$is_args$args; } location ~ \.php$ { fastcgi_pass unix:/sock/docker.sock; try_files $fastcgi_script_name =500; fastcgi_split_path_info ^(.+\.php)(/.*)$; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; }
}

К прокси обращаемся не через порт, а через сокет unix:/sock/docker.sock.

Теперь создадим файл logrotate.conf:

/var/log/nginx/*.log { size=1k missingok rotate 8 notifempty sharedscripts postrotate [ ! -f /var/run/nginx.pid ] || kill -USR1 `cat /var/run/nginx.pid` endscript
}

И задание для cron:

SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
MAILTO="" # m h dom mon dow user command
* * * * * root logrotate -v /etc/logrotate.conf
#

Размер файла size=1k и зспуск ротации каждую минуту (* * * * *) не для рабочего сервера, а тоько для того чтобы можно было при имнимальных временных заратах наблюдать ротацию логов. Команда logrotate копирует логи в архивные файлы. Но пока не будет перезапущен nginx — не произойдет реального создания нового пустого файла логов. Для того чтобы nginx открыл логи заново служит устрашающая команда kill -USR1 `cat /var/run/nginx.pid`.

И наконец конфигурация supervisor:

[supervisord]
nodaemon=true
logfile=/dev/null [program:nginx]
command=/usr/local/openresty/bin/openresty -g 'daemon off;' [program:cron]
command=cron -f

Совершенно не имеет значения где все эти конфигурационные фйлы находятся, т.к. все пути задаются в операторах COPY из Dockerfile, и в значениях volumes из docker-compose.yml. Теперь нужно набраться терпения и записать все необходимые пути в docker-compose.yml.

version: "3"
services: app: build: context: ./docker/php args: UID: "3000" working_dir: /app volumes: - ./:/app - ./html:/usr/share/nginx/html - ./docker/php/zz-docker.conf:/usr/local/etc/php-fpm.d/zz-docker.conf - ./docker/sock:/sock expose: - 9000 links: - mysql nginx: build: context: ./docker/nginx args: UID: "3000" ports: - 8000:80 volumes: - ./:/app/ - ./html/:/usr/share/nginx/html/ - ./docker/nginx/nginx.conf:/usr/local/openresty/nginx/conf/nginx.conf - ./docker/nginx/conf.d/:/usr/local/openresty/nginx/conf/conf.d/ - ./docker/nginx/log/:/var/log/nginx/ - ./lua/:/usr/share/nginx/lua/ - ./docker/sock/:/sock/ links: - app depends_on: - app

Теперь можно добавить скрипты Lua (см. статью на Хабре).

apapacy@gmail.com
27 января 2018 года

Показать больше

Похожие публикации

Кнопка «Наверх»