11. Docker в production
Цель темы: понимать границы «голого» Docker в production: почему не всегда достаточно только Docker, чем он отличается от Kubernetes, как задавать лимиты ресурсов (CPU/память), настраивать сбор логов (logging drivers), мониторить контейнеры и обеспечивать корректное завершение (graceful shutdown).
Определения терминов
Pure Docker в production
«Чистый» Docker — запуск контейнеров только через демон Docker (docker run, Docker Compose) без оркестратора. Подходит для одной машины, небольшого числа сервисов и простых сценариев. При росте числа нод, требований к отказоустойчивости, масштабированию и обновлениям без даунтайма обычно переходят на оркестратор (Kubernetes, Swarm).
Docker vs Kubernetes
Docker — runtime контейнеров и инструменты (build, run, Compose). Kubernetes — оркестратор: планирование подов на ноды, реплики, rolling update, self-healing, сервисы и балансировка, секреты, конфиги. В Kubernetes контейнеры часто запускаются через containerd/CRI-O, образы те же (OCI). Docker в production может быть «только контейнеры на одной ноде»; Kubernetes добавляет кластер, масштабирование и жизненный цикл приложений.
Resource limits (лимиты ресурсов)
Лимиты — ограничение потребления CPU и памяти контейнера через cgroups. Задаются при создании контейнера: --memory, --memory-swap, --cpus (или --cpu-quota/--cpu-period). Без лимитов один контейнер может исчерпать ресурсы хоста и повлиять на остальные. В production лимиты задают всегда.
Logging driver
Logging driver — способ сбора stdout/stderr контейнера. По умолчанию драйвер json-file: логи хранятся в файлах на хосте. Другие драйверы: syslog, journald, gcplogs, awslogs, splunk и т.д. — отправка логов во внешнюю систему. В production часто настраивают централизованный сбор (ELK, Loki, облачные логи) через соответствующий драйвер или сбор с хоста (agent читает json-file/journald).
Graceful shutdown
Корректное завершение — при остановке контейнера процесс получает SIGTERM, завершает текущие запросы, освобождает ресурсы и выходит. Оркестратор ждёт заданный таймаут (terminationGracePeriodSeconds в K8s, stop_grace_period в Compose), затем отправляет SIGKILL. Обеспечивается правильной обработкой SIGTERM в приложении или через init (tini/dumb-init), который пересылает сигнал процессу.
Почему не всегда достаточно «голого» Docker в prod
Ограничения при использовании только Docker (и Compose) на одной или нескольких машинах:
- Масштабирование — ручное запуск реплик на разных нодах, нет встроенной балансировки и обнаружения сервисов.
- Отказоустойчивость — при падении контейнера или ноды перезапуск только через restart policy на одной машине; при падении ноды сервис недоступен, пока вручную не поднять на другой.
- Обновления без даунтайма — нет встроенного rolling update; нужно самим поднимать новый контейнер, переключать трафик, останавливать старый.
- Секреты и конфигурация — нет единого API для секретов и конфигов на уровне кластера; раздача через тома или env вручную.
- Наблюдаемость — логи и метрики нужно собирать и агрегировать отдельно; оркестраторы часто интегрируются с экосистемой мониторинга.
Для одного сервера и простого стека «только Docker» может хватать. Для нескольких нод, высоких требований к доступности и обновлениям обычно вводят оркестратор (Kubernetes, в меньшей степени Swarm).
Docker vs Kubernetes (кратко)
| Критерий | Docker (Compose) | Kubernetes |
|---|---|---|
| Масштаб | Одна или несколько нод вручную | Кластер, планирование подов |
| Масштабирование | Вручную или внешние инструменты | Replicas, HPA, автомасштабирование нод |
| Обновления | Ручной порядок или скрипты | Rolling/rolling update, blue-green |
| Секреты/конфиги | Env, тома, внешние системы | Secrets, ConfigMaps |
| Сеть и сервисы | Compose networks, DNS по имени | Service, Ingress, DNS кластера |
| Наблюдаемость | Своя настройка логов и метрик | Интеграция с метриками и логами (cAdvisor, логи подов) |
Docker и Kubernetes не «конкуренты»: Docker (образы, runtime) используется внутри Kubernetes как способ упаковки и запуска контейнеров. Речь о том, когда достаточно только Docker на хосте, а когда нужен именно оркестратор.
Лимиты ресурсов (CPU и память)
Память
docker run -d --memory=512m --memory-swap=512m myapp
--memory— жёсткий лимит RAM. При превышении контейнер может быть убит (OOMKilled).--memory-swap— лимит RAM + swap. Равен--memory— отключить swap для контейнера.
В Compose:
services:
app:
image: myapp
deploy:
resources:
limits:
memory: 512M
reservations:
memory: 256M
CPU
docker run -d --cpus=0.5 myapp
# или
docker run -d --cpu-quota=50000 --cpu-period=100000 myapp
--cpus=0.5 — контейнер может использовать в среднем не более 0.5 CPU. В Compose: deploy.resources.limits.cpus: "0.5".
Практика
В production всегда задавайте --memory (и при необходимости --cpus). Это защищает хост от одного «жадного» контейнера и делает поведение предсказуемым. Резервации (reservations) в оркестраторах помогают планированию.
Logging drivers
По умолчанию: json-file
Логи пишутся в файлы на хосте (в каталоге данных контейнера). Ротация и размер задаются опциями:
docker run -d \
--log-driver json-file \
--log-opt max-size=10m \
--log-opt max-file=3 \
myapp
Отправка логов наружу
Пример с journald (логи контейнера в systemd journal хоста):
docker run -d --log-driver journald myapp
Пример с драйвером для облака (имя и опции зависят от драйвера):
docker run -d --log-driver awslogs \
--log-opt awslogs-region=eu-west-1 \
--log-opt awslogs-group=/myapp \
myapp
В production часто не меняют драйвер на стороне Docker, а ставят агент на хост (Fluentd, Promtail, Filebeat), который читает json-file или journald и отправляет в централизованную систему (ELK, Loki, облако).
Мониторинг контейнеров
Метрики с хоста
- docker stats — использование CPU, памяти, сети контейнерами в реальном времени.
- cAdvisor — сбор метрик cgroups (CPU, память, сеть, диск) по контейнерам; экспорт в Prometheus. Запускается в контейнере с доступом к
/var/run/docker.sockи к cgroup хоста. - Node exporter — метрики самой ноды (CPU, память, диск); контейнеры учитываются как процессы на ноде.
В Kubernetes метрики контейнеров и подов собирает kubelet/cAdvisor и отдаёт в Metrics API; Prometheus и др. забирают оттуда. При «голом» Docker на хосте поднимают cAdvisor и Node exporter и настраивают Prometheus.
Healthcheck
В Docker и Compose задаётся HEALTHCHECK в образе или в compose-файле. По статусу (healthy/unhealthy) можно решать перезапуск или исключение из балансировки, если перед контейнером стоит балансировщик или оркестратор. Для мониторинга сам по себе healthcheck не заменяет метрики и алерты — это лишь источник состояния «жив/не жив».
Graceful shutdown
Что нужно
- Приложение или PID 1 обрабатывает SIGTERM: завершает приём новых запросов, дообслуживает текущие, закрывает соединения и выходит.
- Таймаут со стороны того, кто останавливает контейнер (Docker, оркестратор), достаточный для завершения работы (например, 30 с). После таймаута отправляется SIGKILL.
Docker
docker stop -t 30 container_name
По умолчанию таймаут 10 с. В Compose: stop_grace_period: 30s.
Init и пересылка сигналов
Если PID 1 — приложение, которое не обрабатывает SIGTERM, контейнер будет убит по таймауту. Решение: init (tini, dumb-init) как PID 1 или docker run --init; init перешлёт SIGTERM дочернему процессу и дождётся его завершения.
В оркестраторах
В Kubernetes задаётся terminationGracePeriodSeconds у пода; перед отправкой SIGTERM может вызываться preStop hook (например, снять под с балансировщика). В Docker Swarm — stop_grace_period в сервисе.
Production
Типичная настройка: лимиты памяти и CPU у всех сервисов, логи через json-file с ротацией или через драйвер/агент в централизованную систему, метрики через cAdvisor + Prometheus, алерты по OOM и недоступности. Graceful shutdown: приложение обрабатывает SIGTERM, таймаут остановки 20–30 с, при необходимости --init.
Паттерны использования
| Паттерн | Описание |
|---|---|
| Всегда задавать лимиты | --memory и при необходимости --cpus для предсказуемости и защиты хоста. |
| Ротация логов | max-size и max-file у json-file или отправка во внешнюю систему. |
| Метрики и алерты | cAdvisor/Node exporter + Prometheus; алерты на OOM, падение контейнеров. |
| Graceful shutdown | Обработка SIGTERM в приложении или через init; достаточный stop_grace_period. |
| Оценка перехода на оркестратор | При росте нод и требований к HA/rolling update рассматривать Kubernetes. |
Антипаттерны
| Антипаттерн | Почему плохо | Что делать |
|---|---|---|
| Без лимитов в prod | Один контейнер может положить ноду. | Задавать memory (и cpus) везде. |
| Игнорировать логи | Нет ротации — диск забивается; нет централизации — сложно расследовать. | Ротация или внешний сбор логов. |
| Короткий stop timeout | Приложение не успевает завершиться, получает SIGKILL. | Увеличить stop_grace_period, проверить обработку SIGTERM. |
| «Только Docker» при нескольких нодах | Ручное масштабирование и восстановление. | Ввести оркестратор или чёткие процедуры. |
Примеры из production
Несколько сервисов на одной ноде
Docker Compose с лимитами, healthcheck и единым лог-драйвером или агентом на хосте. Обновление: по очереди останавливать сервис, поднимать новый образ, проверять health. Для нулевого даунтайма перед контейнером ставят балансировщик (nginx, HAProxy) и переключают бэкенды по health.
Переход с Docker на Kubernetes
Образы остаются те же (OCI); меняется способ запуска: поды, деплойменты, сервисы. Лимиты задаются в requests/limits; логи забирает агент с нод или sidecar; graceful shutdown — через terminationGracePeriodSeconds и обработку SIGTERM в приложении.
OOMKilled в логах
Контейнер завершился с кодом 137 (SIGKILL после OOM). Реакция: поднять лимит памяти или оптимизировать приложение; проверить утечки. Мониторинг: алерт на контейнеры с OOMKilled.