Зомбі, вампіри та привиди в Linux: як очистити сервер від цифрової нечисті на Хелловін
🇬🇧 Read this article in English
31 жовтня, ніч напередодні Дня всіх святих. Звичайні люди одягають маски відьом, ріжуть світильники Джека з гарбузів і дивляться фільми жахів.
Але системним адміністраторам та DevOps-інженерам не потрібні голлівудські жахастики — вони живуть серед справжніх монстрів щодня. У глибинах ядра Linux вештаються мертві зомбі-процеси, у контейнерах Docker відбуваються повстання нежиті, а на хмарних акаунтах безмовно блукають сервери-привиди, що висмоктують бюджет компанії з лютістю найжадібніших вампірів.
Сьогодні розберемося в анатомії серверної нечисті: чому легендарне закляття kill -9 безсиле проти зомбі, чому в Docker обов’язково треба кликати «женця душ» та як за допомогою кількох команд провести повний екзорцизм свого продакшену.
Мал. 1. Життєвий цикл зомбі-процесу: чому kill -9 не працює та як контейнери без init накопичують мерців
1. Хто такі зомбі-процеси (<defunct>) і як вони народжуються?
Відкриваємо термінал, вводимо ps aux | grep Z або запускаємо top і з жахом бачимо:
1
2
3
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
app 4412 0.0 0.0 0 0 ? Z 10:15 0:00 [python3] <defunct>
app 4413 0.0 0.0 0 0 ? Z 10:16 0:00 [node] <defunct>
Статус Z (або підпис <defunct>) означає, що перед вами справжній Zombie Process.
Як живий процес перетворюється на нежить?
Коли програма в Linux породжує дочірній процес через системний виклик fork():
- Дочірній процес виконує своє завдання і викликає
exit(0). - Ядро Linux негайно звільняє всю його оперативну пам’ять, закриває відкриті файли й сокети. Здавалося б, процес помер.
- АЛЕ! Ядро зобов’язане зберегти запис про мерця в системній таблиці процесів (Process Table): його ідентифікатор
PID, статус завершення та код виходу. - Цей запис чекає, доки батьківський процес (Parent Process) виконає системний виклик
wait()абоwaitpid(), щоб забрати свідоцтво про смерть дитини.
Якщо ж розробник батьківського сервісу написав кривий код, забув про wait() або сам завис у нескінченному циклі — дочірній процес застрягає між світом живих і мертвих. Він уже не споживає ні байта RAM і 0% CPU, але продовжує вештатися в пам’яті ядра.
2. Головний парадокс: чому kill -9 не вбиває зомбі?
Найперша реакція будь-якого розлюченого адміна:
1
sudo kill -9 4412
Ви тиснете Enter, знову перевіряєте ps aux… а зомбі й далі нахабно дивиться на вас із термінала! Ви тиснете kill -9 десять разів — нуль реакції.
Чому?
[!CAUTION] Золоте правило системного екзорцизму:
«Ти не можеш убити те, що вже мертве!»
СигналSIGKILL(kill -9) надсилається живим процесам, щоб наказати їм негайно звільнити ресурси та померти. Але зомбі-процес вже помер. У нього немає виконуваного коду в пам’яті, щоб перехопити чи обробити сигнал.
Чим тоді небезпечні зомбі, якщо вони не їдять пам’ять?
Зомбі займають найцінніше — записи в таблиці процесів (Process IDs). За замовчуванням у Linux ліміт зазвичай становить 32 768 або 65 536 PID (перевірте: cat /proc/sys/kernel/pid_max). Якщо баг у коді наплодить тисячі зомбі, у системі закінчаться вільні PID.
Результат: сервер раптово відмовляється запускати нові завдання з фатальною помилкою:
bash: fork: retry: Resource temporarily unavailable. Ви навіть не зможете увійти по SSH, бо сервер не може створити процес сесії!
3. Справжній екзорцизм: як відправити зомбі в могилу
Щоб поховати зомбі, потрібно змусити батьківський процес виконати свій обов’язок.
Крок 1. Знаходимо батька мерця (PPID)
Використовуємо команду ps із відображенням ідентифікатора батька:
1
ps -o pid,ppid,stat,cmd -p 4412
Вивід покаже щось на кшталт:
1
2
PID PPID STAT CMD
4412 3890 Z [python3] <defunct>
Наш винуватець — батько з PPID = 3890.
Крок 2. Будимо батька сигналом SIGCHLD
Спробуйте надіслати батьківському процесу сигнал SIGCHLD — це м’яке нагадування від ядра: «Гей, перевір своїх дітей!»:
1
sudo kill -s SIGCHLD 3890
Якщо батько при тямі, він викличе wait(), забере код виходу, і зомбі миттєво щезне.
Крок 3. Радикальний метод: вбиваємо недбалого батька
Якщо батько наглухо завис і ігнорує SIGCHLD, залишається одне: вбити самого батька:
1
sudo kill -9 3890
Що відбувається в цей момент?
- Батько вмирає.
- Зомбі стає сиротою (Orphan).
- За законами Linux, усіх сиріт у системі автоматично всиновлює головний процес —
initабоsystemd(PID 1). - А процес
systemd— бездоганний «Жнець душ» (Zombie Reaper). Отримавши сироту-зомбі, він негайно викликаєwait()і назавжди очищає таблицю процесів!
4. Зомбі-апокаліпсис у Docker: пастка PID 1
Якщо у звичайному Linux systemd постійно пильнує і прибирає сиріт, то в Docker-контейнерах панує справжній хоррор.
Коли ви пишете у Dockerfile:
1
CMD ["node", "server.js"]
Ваш процес node запускається як PID 1 всередині простору імен контейнера!
Але стандартні середовища виконання (Node.js, Python, Java) не створювалися як системні менеджери процесів. Вони не перехоплюють SIGCHLD від осиротілих онуків і не чистять за ними таблицю.
Якщо ваш бекенд час від часу викликає зовнішні утиліти (конвертація зображень через imagemagick, перевірка мережі через curl чи виклики child_process), у контейнері за пару днів накопичуються сотні зомбі, доки контейнер не падає.
Як освятити контейнер: два простих порятунки
Варіант 1. Прапорець --init під час запуску (Найпростіший спосіб):
1
docker run -d --init -p 8080:8080 my-app:latest
Прапорець --init підкидає всередину контейнера крихітний спеціальний бінарник tini як PID 1. Він не споживає ресурсів, але ідеально жне всіх зомбі й прокидає сигнали завершення!
Варіант 2. Вшити tini прямо в Dockerfile:
1
2
3
4
5
6
# У Debian / Ubuntu:
RUN apt-get update && apt-get install -y tini
# Запускаємо через ініціалізатор:
ENTRYPOINT ["/usr/bin/tini", "--"]
CMD ["node", "server.js"]
5. Сервери-привиди та вампіри: прокляття Shadow IT
Крім зомбі-процесів, кожен системний адміністратор рано чи пізно стикається з інфраструктурними привидами:
- Сервери-зомбі (Ghost Instances): Тестова віртуалка, яку інтерн підняв у хмарі AWS/Hetzner у травні 2024 року під демку, успішно захистив проєкт і пішов з компанії. Віртуалка досі молотить, ніхто не знає її призначення, але компанія платить за неї щомісяця.
- Диски-вампіри (Unattached Volumes): Віртуалку видалили, але прикріплений до неї NVMe диск на 500 ГБ забули позначити як Delete on Termination. Він просто висить у повітрі, наче привид, і тихо списує гроші з банківської картки.
- Вампірські залежності (Zombie Packages): Забуті npm-пакети чи python-модулі, закинуті автором 6 років тому. Вони висять у
requirements.txt, пробиті дірками безпеки CVE й чекають, коли їх скомпрометують хакери.
Хелловінська шпаргалка: команди для виявлення нечисті
Збережіть ці закляття у свій термінальний блокнот:
1
2
3
4
5
6
7
8
9
10
11
# 1. Порахувати кількість мерців у системі прямо зараз
ps aux | awk '{print $8}' | grep -c Z
# 2. Вивести повне кладовище зомбі з їхніми батьками
ps -A -ostat,ppid,pid,cmd | grep -e '^[Zz]'
# 3. Знайти та обезголовити всіх батьків зомбі в один рядок (ОБЕРЕЖНО!)
kill -9 $(ps -A -ostat,ppid | awk '/[zZ]/ { print $2 }')
# 4. Перевірити, який відсоток PID таблиці вичерпано
echo "Використано PID: $(ls -d /proc/[0-9]* | wc -l) з $(cat /proc/sys/kernel/pid_max)"
Підсумки
Linux — це дивовижна операційна система, де навіть найстрашніші терміни (Daemons, Zombies, Orphans, Killing) насправді є елегантною математичною архітектурою Unix.
Не бійтеся зомбі — просто не забувайте викликати wait(), ставте tini у свої Dockerfile та проводьте регулярний аудит своїх хмарних серверів.
Веселого Хелловіну, чистої таблиці процесів і нехай жоден мертвий процес не потурбує ваш спокійний сон цієї ночі! 🎃👻🕯️
