Когда приложение переносят с обычного сервера в контейнер, первое время всё выглядит одинаково: те же команды, те же логи, тот же shell. Но стоит заглянуть глубже — и выясняется, что часть привычных инструментов либо врёт, либо работает иначе. Это не баг Docker или Kubernetes, а следствие того, что контейнер — не виртуальная машина. Ниже — четыре типичных места, где окружение выдаёт себя, и что с этим делать на практике.

1. free показывает память хоста, а не контейнера

Команда free читает данные из /proc/meminfo, а этот файл в контейнере отражает память всего хоста, а не лимиты cgroup. В результате вы можете увидеть 64 ГБ свободной памяти там, где контейнеру разрешено 512 МБ. Приложение, которое принимает решения по free, рискует уйти в OOM-kill, так и не поняв, что произошло.

Что делать:

  • Смотрите лимиты через cgroup: /sys/fs/cgroup/memory.max (cgroup v2) или /sys/fs/cgroup/memory/memory.limit_in_bytes (v1).
  • В коде и скриптах не полагайтесь на free — используйте данные cgroup или метрики оркестратора.
  • Для Java задавайте -XX:MaxRAMPercentage или явный -Xmx, а не рассчитывайте на автоопределение.
  • Проверяйте поведение под нагрузкой: искусственно ограничьте память и посмотрите, как ведёт себя приложение.

Типичная ошибка — тюнить JVM или кэш «по free» на проде и удивляться, почему в контейнере всё падает раньше ожидаемого.

2. systemctl не управляет сервисами

В контейнере обычно нет systemd, поэтому systemctl либо отсутствует, либо возвращает ошибку вроде «System has not been booted with systemd». Это ломает привычные скрипты запуска и деплоя, которые на сервере работали годами.

Что делать:

  • Запускайте один процесс на контейнер — это стандартная практика, а не ограничение.
  • Для нескольких процессов используйте supervisor или s6-overlay, но сначала проверьте, нельзя ли разделить их на отдельные контейнеры.
  • Перенесите управление жизненным циклом на оркестратор: Kubernetes, systemd на хосте или docker compose.
  • Проверьте, не осталось ли в образах скриптов с systemctl — они молча не сработают.

Пример: скрипт инициализации вызывает systemctl restart nginx, а в контейнере nginx уже запущен как PID 1. Команда падает, деплой считается успешным, а конфиг не перечитан.

3. journalctl молчит, а логи уходят в stdout

В контейнере нет journald, поэтому journalctl не покажет логи приложения. Многие диагностические инструкции «посмотрите journalctl -u myapp» в контейнерной среде бесполезны. Логи при этом никуда не исчезают — они пишутся в stdout/stderr и собираются драйвером логирования.

Что делать:

  • Пишите логи в stdout/stderr, а не в файлы внутри контейнера.
  • Настройте сбор через docker logs, Loki, ELK или облачный сервис — в зависимости от инфраструктуры.
  • Добавьте структурное логирование (JSON), чтобы логи было проще фильтровать.
  • Проверьте, что приложение не буферизует вывод: иначе логи появятся с задержкой или потеряются при падении.

Ошибка — монтировать /var/log и искать там journal, которого нет. Время диагностики растёт, а причина простая: другой механизм логирования.

4. PID 1 ведёт себя не как обычный процесс

Процесс с PID 1 в контейнере получает особый статус: он должен обрабатывать сигналы и «усыновлять» зомби-процессы. Многие приложения этого не умеют. В результате SIGTERM не доходит до дочерних процессов, контейнер завершается по таймауту, а в логах появляются зомби.

Что делать:

  • Используйте --init в Docker или аналог в оркестраторе, чтобы получить минимальный init-процесс.
  • Проверьте, как приложение обрабатывает SIGTERM: корректное завершение важно для rolling update.
  • Не запускайте shell-скрипт как PID 1 без exec — сигналы не дойдут до приложения.
  • Тестируйте остановку контейнера: docker stop должен завершать процесс быстро и без потерь.

Пример: контейнер с nginx и sidecar-процессом не реагирует на SIGTERM, потому что PID 1 — shell. Kubernetes ждёт grace period и убивает контейнер по SIGKILL, обрывая соединения.

Как проверить окружение заранее

Перед переносом приложения в контейнер пройдитесь по чек-листу:

  • Убедитесь, что приложение не читает /proc/meminfo и /proc/cpuinfo для решений о ресурсах.
  • Проверьте, нет ли зависимостей от systemctl, service, journalctl.
  • Убедитесь, что логи идут в stdout/stderr.
  • Протестируйте обработку SIGTERM и поведение PID 1.
  • Ограничьте ресурсы в тестовой среде и посмотрите, как ведёт себя приложение.

Контейнер — это не «маленький сервер», а другая модель запуска. Чем раньше вы это учтёте, тем меньше сюрпризов будет на проде.