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.
В результате обсуждения выяснилось, что можно вместо отправки сигнала 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 года
Комментарии (25)
alekciy
27.01.2018 08:39+1ротация лога не требует перезапуска nginx. Не сбивайте с толку людей.
firk
27.01.2018 10:21А ещё
Этот сервер имеет другое по сравнению с nginx расположение каталогов с файлами. Но все остальное абсолютно идентично.
Описание выглядит так, как будто это nginx с нескучными обоями. Впрочем я не в курсе, может быть так и есть. А может быть автор опять сбивает с толку людей.
apapacy Автор
27.01.2018 12:03если Вы про openresty то обояии является скриптовый движок Lua
firk
27.01.2018 15:35Обои тут это расположение файлов, которое не неотъемлемая часть программы, а все лишь вопрос того как вы её настроите. От вашей фразы создаётся впечатление, что сменить строчку с указанием расположения какого-нить access_log'а в конфиге для вас равноценно пересборке всего сервера, так же как для какого-то школьника было непосильной задачей залить новую картинку в список обоев.
tzlom
27.01.2018 13:20+2Надо вообще по другому делать.
nginx запускается не как демон (daemon off; в конфиге или nginx -g «daemon off;»), логи выводятся в stdout и stderr соответственно и готово.
Почему так правильно:
0 — При краше докер контейнера логи не будут потеряны, см. п 4.
1 — nginx написан грамотно и никаких зомби процессов оставлять он не будет, если вы из него что-нибудь запускаете, поэтому супервизор ему как таковой не нужен.
2 — Перезапуски при крашах управляются через докер (или под чем у вас кластер), там есть соответствующие параметры.
3 — Мониторинг и перезапуск правильно делать отдельным процессом, потому что рано или поздно захочется нотификаций, графиков нагрузки, вот это всё.
4 — Логи управляются через докер, там есть пачка плагинов чтобы этим рулить как надо, заводить в системы мониторинга и агрегации логов, или на крайний случай делать таки логротейт но уже логов докера (не пиная нгинкс)apapacy Автор
27.01.2018 13:49перенаправлять в стандартный вывод докера в статье на которую есть ссылка описан как один из вариантов. логи в моей конфигурации не теряются т.к вынесены в volume по поводу ротаций логов самого docker я ещё не разобрался но в репозитории docker есть многочисленные issue по ротации логов можно ли их делать без рестарта контейнера или сервиса docker?
apapacy Автор
27.01.2018 17:39Попробовал с плагинами логирования для докера. Простейши плагин для файловой системы (json) хранит все в папках с идентификатором контейнера и при пересборке контейнера удаляется. Более сложные плагины там вообще не подразумевают ротацию т.к. это уже не простое логирование в файл. Если нужно обычное файловом логирвании то как мне кажется лучше всего это делать все же через volume. Действительно для ротации нет необходимости посылать kill USR1 можно просто добавить copytruncate (хотя несколько записй лога при этому могут потеряться). Все же запуск в одном процессе может также иметь право на применение, т.к. в этом случае все настраивается совместно в одном контейнере. В то же время для действительно сложных систем с десятками и сотнями контейнеров централизованное управление логами лучше. То есть область применения этого решения малонагруженный веб-сервер когда запускается 2-3 контейнера и о них нужно забыть и в то же время иметь возможость посмотреть на логи.
nkirnos
27.01.2018 17:41Пост из разряда вредных советов. Из-за ротации логов, которые докер может может отправлять куда угодно, автор топика сделал очередной велосипед
apapacy Автор
27.01.2018 17:47Были рассмотрены два вопроса: ротация логов и взаимодействие контейнеров через unix- сокеты. Задача которую автор изначально поставил — получить ротацию файловых логов nginx. То что docker имеет возможность перенаправлять логи для обработки в (кстати не такое уж необозримое) количестио систем по обработке логов автор не подвергал сомнению.
nkirnos
27.01.2018 18:00+1Если потребовалась ротация логов сервиса, который крутится внутри контейнера, то это первый признак, что ты делаешь что-то не правильно. А добавив сюда еще взаимодействие через сокеты, я в этом убедился еще раз. В общем все проходят через то, что воспринимают контейнер докера как виртуальную машину, как только начнешь воспринимать контейнер как приложение, тогда все станет на свои места.
Попробуйте масштабировать приложение, которое будет крутиться в контейнере, который создал автор топика и вы поймете что у вас увеличивается количество телодвижений. А докер это про то, чтобы это количество не увеличивалосьapapacy Автор
27.01.2018 18:16Да, это решение не для крупномасштабных проектов. Но обычных, рядовых не 10К проектов веб-сервер + база данных многократно больше, чем сложных и высоконагруженных.
По сокетам vs 9000 порт вопрос неоднократно обсуждался и я даже не буду приводить за и против сокет vs 9000 порт. Я просто скажу что у администратора должна быть возможность выбора. Если 9000 порт работет «их коробки» во всех образах php-fpm (кстати, есть issue как раз по этому поводу на UNEXPOSE) то для работы через сокет нужны дополнительные действия. Это и было разобрано в статье. И не утверждается что сокеты это всегда лучше чем порты. Каждый выбирает то что ему подходит в кажом конкретном случае.
OnYourLips
27.01.2018 18:19+1Слишком грязно и сложно у вас получилось, явно не стоило статью про это писать. Так происходит из-за того, что вы нарушили основополагающее правило докера: не более одной однородной группы процессов на контейнер. В итоге вам пришлось делать эти уродливые костыли.
Выносите работу с логами вне докера, а супервизор — на помойку. Управление процессами, логи, мониторинг, кроны и т.д. — всё это должно быть вне образов.
Либо меняйте докер на LXD/OpenVZ.apapacy Автор
27.01.2018 18:57В Аутентичном переводе звучит «В каждом контейнере должна решаться только одна проблема».
Решение с супервизором, кстати это тоже из документации докера docs.docker.com/engine/admin/multi-service_container.
Согласен что для масштабируемых решений логирование это отдельная проблема и должна решаться централизовано.
nazarpc
Зачем вам именно на сокетах? Разницу в скорости вы скорее всего не заметите, а проблем с портативностью и настройкой добавляется знатно.
Кстати, я уже почти 3 года как сделал набор контейнеров, в котором кроме Nginx, PHP и ротации логов есть SSH, MariaDB и прочие плюшки (включая резервное копирование и восстановление): https://github.com/nazar-pc/docker-webserver
docker-compose.yml там получается гораздо проще, хотя под капотом делает многое описанное в статье.
apapacy Автор
про ротацию логUSR1 nginx можно в двух словах кто отправляет вот этот самый kill USR1?
nazarpc
Никто не отправляет. Logrotate крутится в отдельном контейнере и просто обрезает старые файлы до 0 байт. Никакого перезапуска не требуется, вот используемый конфиг: https://github.com/nazar-pc/docker-webserver/blob/master/logrotate/logrotate.conf
apapacy Автор
Но пока процесс nginx не закроет эти файлы они не будут реально уменьшены для этого сигнал USR1 являптся обязательным
nazarpc
Думаю, вы ошибаетесь.
truncate
естьtruncate
, файл будет уменьшен сразу. На сколько я понимаю,USR1
нужен если вы файл перемещаете и создаете на старом месте новый пустой, в этом случаеUSR1
заставит Nginx открыть новый файл. В случае сtruncate
мы обрезаем исходный файл и переоткрывать ничего не нужно, Nginx без проблем дописывает новые записи в тот же файл. По крайней мере так оно у меня на сервере выглядит.lega
А зачем вам ssh внутри контейнеров?
nazarpc
У меня новая версия сайта выкладывается посредством
git push
, для этого использую контейнер с SSH, там же стоит Composer. Зависит от workflow, контейнер совершенно опциональный, используйте если и когда нужно.lega
В этом случае версию можно хранить на хосте и подключать как volume — не нужен ssh и перезаписей исходного образа будет меньше.
nazarpc
Я храню данные в подключаемом data-only контейнере, по сути, то же самое, но не привязано к локальной файловой системе. Можно volume вместо контейнера подключать. Никакой перезаписи исходного образа при этом нет.