Современный веб-бэкенд, микросервисы и контейнеризированные среды (Docker, Kubernetes) практически без исключений функционируют под управлением операционных систем на базе ядра Linux. Разработчик, не понимающий базовую модель операционной системы, неизбежно сталкивается с трудноуловимыми сбоями в production: зависшими процессами, утечками дескрипторов, обрывами сетевых соединений при перезапусках сервисов и уязвимостями прав доступа.
Ядро Linux (Linux Kernel) реализует стандарты POSIX (Portable Operating System Interface), определяющие унифицированные интерфейсы взаимодействия между прикладным кодом и системными ресурсами. Ключевой концепцией архитектуры Unix/Linux является принцип: «Все есть файл» (Everything is a file). Файлами представляются не только текстовые документы на жестком диске, но и сетевые сокеты, аппаратные устройства, каналы межпроцессного взаимодействия (IPC) и даже структуры данных ядра об активных процессах.
Иерархия каталогов Linux стандартизирована спецификацией FHS (Filesystem Hierarchy Standard):
/etc — статические конфигурационные файлы хоста и системных служб (Nginx, systemd, SSH)./var — динамически изменяемые данные (журналы логов /var/log, базы данных /var/lib/postgresql, кэш /var/cache)./home — домашние каталоги пользователей с их персональными файлами и настройками окружения (~/.bashrc)./tmp — временные файлы, доступные всем процессам, очищаемые операционной системой при перезагрузке хоста./proc — виртуальная псевдофайловая система в оперативной памяти, предоставляющая прямой доступ к внутренним структурам ядра и метаданным процессов (например, /proc/1/cmdline, /proc/cpuinfo)./sys — псевдофайловая система экспорта свойств системных устройств и параметров ядра (включая структуры cgroups).В операционной системе Linux каждый исполняемый код существует как процесс (Process) — изолированная структура в памяти ядра, имеющая свой уникальный идентификатор PID (Process ID), адресное пространство виртуальной памяти, таблицу открытых файловых дескрипторов (file descriptors) и переменные окружения.
Иерархия процессов представляет собой строгое направленное дерево. Первым процессом пользовательского пространства, порождаемым ядром при загрузке системы, является процесс с PID 1 (в современных дистрибутивах — systemd, в контейнерах — основное приложение или init-менеджер вроде tini). Все остальные процессы являются прямыми или косвенными потомками PID 1, создаваемыми системным вызовом fork() (клонирование родителя) и последующим вызовом execve() (замещение исполняемого кода).
+-------------------------------------------------------------------------+
| ИЕРАРХИЯ ПРОЦЕССОВ И СИГНАЛЫ POSIX |
+-------------------------------------------------------------------------+
| |
| ЯДРО LINUX (KERNEL) |
| │ |
| PID 1: systemd / init |
| │ |
| ┌───────────────────────┴───────────────────────┐ |
| ▼ ▼ |
| PID 240: sshd PID 890: dockerd |
| │ │ |
| PID 1024: bash (сессия) PID 1420: node app |
| │ |
| PID 1850: node worker.js |
| |
| |
| ОБРАБОТКА СИГНАЛОВ: |
| |
| 1. SIGINT (Ctrl+C) ──► Приложение: перехват ──► Остановка цикла |
| 2. SIGTERM (kill) ──► Приложение: перехват ──► Graceful Shutdown |
| 3. SIGKILL (kill -9) ──► Ядро: ПРИНУДИТЕЛЬНОЕ УНИЧТОЖЕНИЕ ПРОЦЕССА |
| (Код приложения НЕ получает управление!) |
+-------------------------------------------------------------------------+Ядро дублирует родительский процесс, создавая дочерний процесс с новым PID и идентичной таблицей страниц памяти (Copy-on-Write).
Ядро заменяет адресное пространство и сегмент кода процесса новым бинарным файлом или интерпретатором (например, /usr/bin/node).
Процесс переходит в состояние R (Running/Runnable) или S (Interruptible Sleep), ожидая кванты времени CPU или события ввода-вывода (I/O).
Ядро доставляет сигнал в стек процесса. Приложение выполняет обработчик: закрывает HTTP-сервер, сбрасывает буферы и соединения с базой.
Процесс вызывает exit(0), освобождая память. Он кратковременно переходит в состояние Z (Zombie), пока родитель не прочитает его код возврата.
Команда DevOps отправила команду `kill -9 <PID>` веб-серверу Node.js. Сработает ли обработчик `process.on('SIGKILL', () => { cleanup(); })`?
SIGTERM и SIGINT в Node.js приложениях для безопасного завершения активных HTTP-запросов и закрытия транзакций БД перед остановкой контейнера.ps aux, top, htop, анализ использования памяти через /proc/meminfo и мониторинг открытых сокетов через lsof -i.600 на .env и приватные ключи SSH) и запуск сервисов от имени непривилегированных системных пользователей (node, www-data).Каждый файл и директория в файловой системе Linux привязаны к конкретному владельцу (User) и группе (Group). Права разделены на три категории:
u (Owner/User) — права владельца.g (Group) — права участников группы файла.o (Others) — права всех остальных пользователей системы.Для каждой категории определены три базовых бита прав:
r (Read, чтение) = числовой вес 4w (Write, запись/модификация) = числовой вес 2x (Execute, исполнение/вход в каталог) = числовой вес 1Сумма битов формирует восьмеричную цифру от 0 до 7:
# 1. Просмотр прав на файлы:
# Вывод формата: -rw-r--r-- 1 node appgroup 1420 Oct 19 12:00 server.js
ls -la
# 2. Установка прав 644 (Владелец: rw-, Группа: r--, Остальные: r--)
# Стандартный безопасный режим для статических файлов и исходного кода
chmod 644 server.js
# 3. Установка прав 600 (Владелец: rw-, Группа: ---, Остальные: ---)
# Обязательный режим для секретов, SSL-сертификатов и .env файлов
chmod 600 .env.production
# 4. Установка прав 755 на директории и бинарные скрипты (rwxr-xr-x)
# Для директорий флаг execute (x) необходим для перехода внутрь через cd
chmod 755 /var/www/my-app
# 5. Смена владельца и группы процесса
chown -R node:appgroup /var/www/my-appДля мониторинга процессов в операционной системе применяются стандартные утилиты:
ps aux — снимок всех запущенных процессов в системе с указанием PID, %CPU, %MEM, времени работы и статуса (STAT).kill -15 <PID> — отправка вежливого сигнала SIGTERM.kill -9 <PID> — отправка неотвратимого сигнала SIGKILL.pkill -f node — отправка сигнала всем процессам, в командной строке которых содержится шаблон node.# Поиск конкретного процесса Node.js и вывод его PID
pgrep -fl node
# Подробная информация по процессам с сортировкой по потреблению оперативной памяти
ps aux --sort=-%mem | head -n 10
# Просмотр дерева процессов с дочерними ветками
pstree -p -s $$
# Проверка того, какой процесс слушает сетевой порт 3000
lsof -i :3000
# или через современную утилиту ss:
ss -tulpn | grep 3000Веб-сервер Node.js обязан перехватывать сигналы SIGINT (посылаемый при нажатии Ctrl+C в терминале) и SIGTERM (посылаемый Docker или systemd при остановке контейнера/сервиса):
import http from 'node:http';
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ status: 'ok', timestamp: Date.now() }));
});
const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 3000;
server.listen(PORT, () => {
console.log(`[Worker ${process.pid}] Сервер запущен на порту ${PORT}`);
});
// Флаг предотвращения повторного входа в процедуру выключения
let isShuttingDown = false;
async function handleShutdown(signal: string) {
console.log(`\n[Signal] Получен системный сигнал ${signal}. Начинаем Graceful Shutdown...`);
if (isShuttingDown) {
return;
}
isShuttingDown = true;
// 1. Аварийный сторожевой таймер (Watchdog Timer)
// Если соединения не завершатся за 10 секунд, форсированно завершаем процесс
const forceExitTimeout = setTimeout(() => {
console.error('[Watchdog] Истек лимит времени штатной остановки. Принудительный выход.');
process.exit(1);
}, 10000);
forceExitTimeout.unref();
// 2. Прекращаем прием новых входящих TCP-соединений
server.close(async (err) => {
if (err) {
console.error('[Shutdown Error] Ошибка при остановке HTTP-сервера:', err);
process.exit(1);
}
try {
// 3. Закрытие активных соединений с внешними ресурсами (БД, Redis)
console.log('[Cleanup] Сброс буферов и отключение пулов базы данных...');
// await dbPool.end();
// await redisClient.quit();
console.log('[Success] Все ресурсы успешно освобождены. Завершение с кодом 0.');
process.exit(0);
} catch (cleanupError: unknown) {
const msg = cleanupError instanceof Error ? cleanupError.message : String(cleanupError);
console.error('[Cleanup Error] Ошибка при очистке ресурсов:', msg);
process.exit(1);
}
});
}
// Регистрация слушателей POSIX-сигналов
process.on('SIGTERM', () => handleShutdown('SIGTERM'));
process.on('SIGINT', () => handleShutdown('SIGINT'));Пользователь выполняет команду kill -15 PID или оркестратор инициирует остановку сервиса.
Системный вызов sys_kill(pid, sig) проверяет права инициатора на отправку сигнала целевому процессу.
Ядро находит структуру task_struct адресата и устанавливает бит сигнала в маске pending signals.
Для SIGKILL процесс мгновенно переводится в статус DEAD без переключения контекста в user-space.
При возврате процессора из режима ядра в режим пользователя (User Space) управление передается на зарегистрированный адрес обработчика.
Стек потока временно модифицируется фреймом обработчика сигнала (sigreturn trampoline).
Рантайм Node.js вызывает зарегистрированную функцию обратного вызова process.on('SIGTERM').
Приложение выполняет логику очистки соединений и инициирует системный вызов exit_group(0).
Разработчик выполнил в терминале команду `PORT=4000` (без ключевого слова export) и затем запустил сервер `node server.js`. Какое значение переменной `process.env.PORT` увидит Node.js?
При возникновении ошибки EACCES: permission denied начинающие разработчики часто выполняют команду chmod -R 777 /app.
Это критическая уязвимость безопасности: любой скомпрометированный процесс или непривилегированный пользователь получает право перезаписать бинарные файлы и исходный код вашего приложения.
Решение: Настраивайте корректного владельца через chown -R node:node /app и задавайте минимально достаточные права chmod 755 для каталогов и chmod 644 для файлов.
Попытка зарегистрировать обработчик process.on('SIGKILL', ...) в Node.js:
// ОШИБКА: Node.js выбросит ошибку или проигнорирует слушатель
process.on('SIGKILL', () => {
console.log('Попытка сохранить данные перед смертью...');
});Ядро Linux принципиально не уведомляет процесс о сигнале 9. Процесс мгновенно удаляется из планировщика задач.
Передача паролей и токенов прямо в аргументах командной строки (например, mysql -u root -pMySecretPassword или export SECRET=12345) сохраняется в истории команд оболочки ~/.bash_history в открытом текстовом виде, а также видна другим локальным пользователям в выводе ps aux.
Решение: Используйте защищенные конфигурационные файлы с правами 600 или передавайте секреты через переменные окружения из изолированных файлов .env.
Если дочерний процесс завершил работу, а родительский процесс не вызвал системный вызов wait() или waitpid(), дочерний процесс остается в таблице процессов ядра в состоянии Z (Zombie). Зомби-процессы не потребляют процессорное время или оперативную память, но занимают записи в ограниченной таблице PID ядра.
Решение: В контейнерах Docker всегда используйте легковесный init-процесс (tini или флаг docker run --init) для сбора осиротевших процессов.