PunchNJC suspends three judges, disciplines six othersThe Jerusalem PostPalestinian delegation seeks resolutions critical of Israel at UN General Assembly - report한겨레삼성바이오 부분파업 돌입…법원 요건 맞춰 전면파업은 3주 뒤 예고Daily MaverickNew wave of bomb hoaxes hits dozens of flights across EuropeZDF heuteEntdecken Sie das ZDF-NachrichtenstudioVarietyPriyanka Chopra Jonas, Russell Crowe Sci-Fi Thriller ‘Bluefly’ Adds ‘Beast’ Star Daniel MacPherson (EXCLUSIVE)سكاي نيوز عربيةهيغسيث يدافع عن خطته لإعادة هيكلة البنتاغون ويعلق عن "عزله"Seeking AlphaGEO forecasts Q4 2026 revenue $777M-$827M with adjusted EBITDA $149M-$154M as ICE activations expandDaily MailBondi Beach massacre death toll rises to 16: Survivor dies from complications resulting from their injuries almost eleven months laterSDP EspectáculosFrida Sofía acusa a Alejandra Guzmán de proteger a Enrique Guzmán pese a su denunciaNew Straits TimesSarawak polls set for Dec 12Globo EsporteCom golaço de Kaio Jorge, Cruzeiro vira sobre o Bahia e fica perto de confirmar vaga na Libertadores
The Daily Newsstand · Free, Always
Friday, November 6, 2026

Контейнер притворяется сервером: четыре места, где выдаёт себя

Translate

В моём тренажёре Prod is Down каждый игрок получает свой сломанный «сервер». На самом деле это контейнер, но игрок об этом думать не должен: он набирает те же команды, что и на настоящем дежурстве. systemctl restart nginx, journalctl -u api, free -m, top.

Из коробки половина этих команд в Docker либо не работает, либо врёт. Ниже четыре места, где контейнер выдаёт себя.

1. systemctl: PID 1 — не systemd

В обычном контейнере первым процессом запускается ваш entrypoint, а не systemd. Даже если пакет systemd стоит в образе, systemctl не с кем разговаривать:

$ systemctl restart nginx
System has not been booted with systemd as init system (PID 1). Can't operate.
Failed to connect to bus: Host is down

Вариантов три, и у каждого своя цена:

Вариант

Что даёт

Чем платите

Настоящий systemd как PID 1

Всё работает как на сервере: юниты, Restart=, journald

Нужны доступ к cgroup, tmpfs на /run, часто --privileged. Для контейнера с чужим root это плохая идея

Замена systemctl

Скрипт читает юнит-файлы и сам запускает ExecStart. Готовый пример — docker-systemctl-replacement

Подмножество systemd: что-то из юнитов будет проигнорировано

Свой скрипт запуска

Ровно то, что нужно вашим сервисам

Всё, чего в нём нет, придётся дописывать самому

В Prod is Down сервисы кейса поднимает отдельный шаг entrypoint — pd-systemd-boot, а терминал запускается последним. Первый вариант для меня отпадал сразу: в контейнере сидит незнакомый человек с sudo, и давать ему привилегированный контейнер ради настоящего systemd нельзя.

2. Сигналы и зомби: чем особенен PID 1

У первого процесса две обязанности, о которых на сервере заботится systemd, а в контейнере - никто.

сСигналы. Ядро не применяет к PID 1 действие по умолчанию. Если скрипт не установил обработчик SIGTERM, сигнал просто игнорируется. docker stop ждёт 10 секунд и добивает контейнер SIGKILL, а сервисы внутри не успевают завершиться чисто.

Зомби. Когда родитель умирает, его дети переходят к PID 1, и он должен забирать их статус выхода. Обычное приложение этого не делает, и в ps копятся процессы <defunct>. В тренажёре это вдвойне неприятно: игрок ищет в ps причину инцидента и находит мусор, который к кейсу отношения не имеет.

Лечится одним флагом: docker run -init (в compose - init: true). Первым процессом станет маленький init из комплекта Docker. Он передаст сигналы вашему entrypoint и будет собирать зомби. А в самом entrypoint последний процесс стоит запускать через exec, чтобы не оставлять лишний слой bash.

3. journalctl и логи

Нет systemd - нет и journald. Игрок набирает первую команду любого разбора и получает:

$ journalctl -u nginx
No journal files were found.

При этом у Docker свои логи, и они тоже не помогают: docker logs собирает только stdout и stderr первого процесса. Всё, что nginx или PostgreSQL пишут в свои файлы, туда не попадает. Да и игроку изнутри контейнера docker logs недоступен.

Потому решите заранее, где человек будет искать причину:

  • либо сервисы пишут в привычные файлы в /var/log, и вы проверяете, что они там правда появляются: многие образы направляют логи в stdout симлинком;

  • либо journalctl должен работать, и тогда нужен journald или замена systemctl, которая умеет отдавать логи сервиса.

Худший вариант - когда логи не пишутся никуда. На настоящем сервере такого не бывает, и человек долго ищет ошибку там, где её нет.

4. free и top показывают хост, а не лимиты

Контейнеру выдали 512 МБ и четверть ядра. Игрок набирает free -m и видит всю память сервера, а top и nprocпоказывают все его ядра. Эти утилиты читают /proc/meminfo и /proc/stat, а в обычном Docker это файлы хоста. nproc учитывает --cpuset-cpus, но не квоту.

Для тренажёра это ловушка. В кейсе про утечку памяти free покажет гигабайты свободного места, а OOM killer всё равно придёт. У контейнера свой потолок, и виден он только в cgroup:

cat /sys/fs/cgroup/memory.max       # 536870912 — настоящий лимит
cat /sys/fs/cgroup/memory.current   # сколько занято сейчас
cat /sys/fs/cgroup/cpu.max          # 25000 100000 — четверть ядра

Решений два. LXCFS (ссылка на GitHub) подменяет /proc/meminfo и компанию так, чтобы они показывали лимиты контейнера, и привычные утилиты начинают говорить правду. Или можно показывать цифры самому. Дашборд Prod is Down, например, считает CPU от лимита контейнера: процесс, съевший свою четверть ядра, выглядит как привычные 100%.

И одно предупреждение. В песочницах вроде gVisor /proc эмулируется, и часть цифр ведёт себя по-своему. Всё, что увидит человек, проверяйте в том рантайме, в котором он будет работать.

Итог

Если контейнер должен выглядеть для человека как сервер, проверьте четыре вещи:

  1. Кто у вас PID 1 и что отвечает на systemctl.

  2. Кто передаёт сигналы и собирает зомби (--init).

  3. Где человек найдёт логи сервиса и работает ли journalctl.

  4. Что показывают free, top и nproc: лимиты контейнера или хост.

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.