Post

Зомбі, вампіри та привиди в Linux: як очистити сервер від цифрової нечисті на Хелловін

Зомбі, вампіри та привиди в Linux: як очистити сервер від цифрової нечисті на Хелловін

🇬🇧 Read this article in English


31 жовтня, ніч напередодні Дня всіх святих. Звичайні люди одягають маски відьом, ріжуть світильники Джека з гарбузів і дивляться фільми жахів.

Але системним адміністраторам та DevOps-інженерам не потрібні голлівудські жахастики — вони живуть серед справжніх монстрів щодня. У глибинах ядра Linux вештаються мертві зомбі-процеси, у контейнерах Docker відбуваються повстання нежиті, а на хмарних акаунтах безмовно блукають сервери-привиди, що висмоктують бюджет компанії з лютістю найжадібніших вампірів.

Сьогодні розберемося в анатомії серверної нечисті: чому легендарне закляття kill -9 безсиле проти зомбі, чому в Docker обов’язково треба кликати «женця душ» та як за допомогою кількох команд провести повний екзорцизм свого продакшену.

Анатомія зомбі-процесів у Linux та пастка PID 1 у 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():

  1. Дочірній процес виконує своє завдання і викликає exit(0).
  2. Ядро Linux негайно звільняє всю його оперативну пам’ять, закриває відкриті файли й сокети. Здавалося б, процес помер.
  3. АЛЕ! Ядро зобов’язане зберегти запис про мерця в системній таблиці процесів (Process Table): його ідентифікатор PID, статус завершення та код виходу.
  4. Цей запис чекає, доки батьківський процес (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

Що відбувається в цей момент?

  1. Батько вмирає.
  2. Зомбі стає сиротою (Orphan).
  3. За законами Linux, усіх сиріт у системі автоматично всиновлює головний процес — init або systemd (PID 1).
  4. А процес 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

Крім зомбі-процесів, кожен системний адміністратор рано чи пізно стикається з інфраструктурними привидами:

  1. Сервери-зомбі (Ghost Instances): Тестова віртуалка, яку інтерн підняв у хмарі AWS/Hetzner у травні 2024 року під демку, успішно захистив проєкт і пішов з компанії. Віртуалка досі молотить, ніхто не знає її призначення, але компанія платить за неї щомісяця.
  2. Диски-вампіри (Unattached Volumes): Віртуалку видалили, але прикріплений до неї NVMe диск на 500 ГБ забули позначити як Delete on Termination. Він просто висить у повітрі, наче привид, і тихо списує гроші з банківської картки.
  3. Вампірські залежності (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 та проводьте регулярний аудит своїх хмарних серверів.

Веселого Хелловіну, чистої таблиці процесів і нехай жоден мертвий процес не потурбує ваш спокійний сон цієї ночі! 🎃👻🕯️

This post is licensed under CC BY 4.0 by the author.