Перейти к содержанию

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

Что нужно

  1. Приложение или PID 1 обрабатывает SIGTERM: завершает приём новых запросов, дообслуживает текущие, закрывает соединения и выходит.
  2. Таймаут со стороны того, кто останавливает контейнер (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.


Дополнительные материалы