Профессиональное системное администрирование Debian Linux: от основ CLI до низкоуровневой контейнеризации

Комплексный курс для подготовки к техническим интервью и сертификации, охватывающий управление жизненным циклом системы, сетевую безопасность и автоматизацию. Программа фокусируется на глубоком понимании внутренних механизмов Debian и практических навыках администрирования.

1. Основы CLI: эффективная работа в vim и магия регулярных выражений grep

Основы CLI: эффективная работа в vim и магия регулярных выражений grep

Сервер не отвечает на пинги, SSH-соединение обрывается каждую минуту из-за потери пакетов, а графического интерфейса нет и никогда не было. В таких условиях перенос конфигурационного файла на локальную машину для редактирования в удобной IDE становится невозможным. Системный администратор остается один на один с терминалом. В этот момент текстовый редактор и утилиты поиска перестают быть просто программами — они становятся единственным интерфейсом взаимодействия с операционной системой. Способность быстро найти нужную строку в гигабайтном логе и точечно изменить конфигурацию без использования мыши отличает профессионала от новичка.

Текстовые файлы — это кровеносная система Debian Linux. Конфигурации сервисов, системные журналы, списки пользователей — всё это простой текст. Поэтому мастерство администрирования начинается с двух фундаментальных навыков: эффективной навигации и модификации текста в vim, а также извлечения паттернов с помощью регулярных выражений в grep.

Философия Vim: язык редактирования, а не просто редактор

Большинство современных редакторов работают по принципу бесконечного режима вставки: вы открываете файл и сразу печатаете. Vim использует модальную концепцию. Основное время администратор проводит не за написанием нового текста, а за чтением, навигацией и изменением существующего. Поэтому по умолчанию Vim находится в нормальном режиме (Normal mode), где клавиши клавиатуры работают не как буквы, а как команды.

Главный инсайт, позволяющий перейти от базового использования Vim (нажать i, отредактировать с помощью стрелок, нажать Esc, :wq) к профессиональному, заключается в понимании его грамматики. Vim — это язык общения с текстом.

Команды в нормальном режиме строятся по формуле: [Действие] + [Количество] + [Объект]

!Грамматика команд Vim

Действия (Глаголы)

Действия определяют, что именно нужно сделать с текстом:
  • d (delete) — удалить (и поместить в буфер обмена).
  • c (change) — удалить и сразу перейти в режим вставки.
  • y (yank) — скопировать.
  • v (visual) — выделить.
  • Объекты (Существительные)

    Объекты указывают, к чему применяется действие. Это могут быть движения (motions) или текстовые объекты (text objects).

    Движения перемещают курсор, и если применить к ним действие, оно выполнится от текущей позиции до точки назначения:

  • w (word) — начало следующего слова.
  • e (end) — конец текущего слова.
  • b (back) — начало предыдущего слова.
  • ` — конец строки.
  • Классы символов позволяют искать совпадение с любым символом из набора:

  • . (точка) — любой один символ (кроме переноса строки).
  • [abc] — любой из символов 'a', 'b' или 'c'.
  • [0-9] — любая цифра (эквивалент \d в некоторых других диалектах RegEx).
  • [^a-z] — циркумфлекс внутри скобок инвертирует класс: любой символ, кроме строчных латинских букв.
  • Квантификаторы определяют, сколько раз должен повторяться предшествующий элемент:

  • * — ноль или более раз.
  • + — один или более раз.
  • ? — ноль или один раз (делает элемент необязательным).
  • {n} — ровно n раз.
  • {n,m} — от n до m раз.
  • !Процесс сопоставления регулярного выражения

    Практическое применение grep в администрировании

    Регулярные выражения абстрактны, пока не сталкиваются с реальными задачами. Рассмотрим несколько классических сценариев.

    #### Фильтрация конфигурационных файлов Конфигурационные файлы в Linux (например, /etc/ssh/sshd_config или /etc/samba/smb.conf) часто содержат сотни строк, большинство из которых — закомментированные примеры или пустые строки. Чтобы увидеть только активные настройки, используется инвертированный поиск.

    Флаг -l (files-with-matches) заставляет grep выводить не сами совпавшие строки, а только имена файлов, в которых они найдены. Флаг -R включает рекурсивный поиск. Оболочка Bash сначала выполнит grep, получит список файлов и передаст их как аргументы для запуска vim. Внутри редактора вы сможете переключаться между этими файлами с помощью команд :next и :prev.

    Ещё более элегантный способ — использовать встроенную в Vim интеграцию с внешним grep. Находясь в Vim, вы можете выполнить команду: :grep -R "legacy.example.com" /etc/nginx/sites-available/ Vim выполнит поиск в фоне и загрузит результаты в специальную структуру — Quickfix list. После этого вы можете использовать команду :cnext для автоматического перехода к следующему файлу и конкретной строке, где было найдено совпадение, и :cprev для возврата. Это превращает Vim в полноценную среду разработки и рефакторинга конфигураций.

    Владение vim и grep на уровне понимания их внутренней логики — это не просто вопрос скорости набора текста. Это вопрос снижения когнитивной нагрузки. Когда пальцы на мышечной памяти формируют команду ci", а мозг автоматически транслирует задачу "найти IP" в grep -o -E '\b([0-9]{1,3}\.){3}[0-9]{1,3}\b'`, у администратора освобождается ресурс для решения настоящих архитектурных задач и траблшутинга сложных системных сбоев.

    10. Контейнеризация на низком уровне: работа с runc

    Контейнеризация на низком уровне: работа с runc

    В ядре Linux нет сущности под названием «контейнер». Если заглянуть в исходный код ядра, там невозможно найти системный вызов create_container. То, что индустрия привыкла называть контейнерами, является лишь оптической иллюзией, виртуозно созданной из комбинации трех независимых механизмов ядра: пространств имен (namespaces), контрольных групп (cgroups) и многослойных систем безопасности (Capabilities, Seccomp). Высокоуровневые инструменты вроде Docker или Podman прячут эту механику за удобным интерфейсом, но для профессионального администрирования и траблшутинга необходимо спуститься на уровень спецификации OCI (Open Container Initiative) и утилиты runc.

    Анатомия иллюзии: Namespaces и Cgroups

    Чтобы процесс поверил, что он работает на выделенном сервере, ядро должно ограничить его видимость и доступные ресурсы.

    Пространства имен (namespaces) отвечают за изоляцию видимости. Они определяют, какие системные ресурсы может наблюдать процесс. Исторически изоляция начиналась с файловой системы (утилита chroot), но современный Linux поддерживает изоляцию практически всех глобальных ресурсов.

    Когда процесс помещается в изолированное пространство PID (Process ID), он видит себя первым и единственным процессом в системе. Для него его идентификатор — . Однако ядро хостовой операционной системы продолжает видеть этот же процесс под его реальным номером, например, . Если процесс с внутри пространства имен завершается, ядро посылает сигнал SIGKILL всем остальным процессам в этом пространстве, так как система не может существовать без процесса инициализации.

    Помимо PID, критически важны следующие пространства:

  • Mount namespace: изолирует точки монтирования. В отличие от старого chroot, который просто менял корневую директорию, mount namespace позволяет безопасно перемонтировать /proc и /sys, чтобы утилиты вроде top или df показывали данные только текущего контейнера.
  • Network namespace: предоставляет процессу собственный сетевой стек, включая интерфейсы (обычно виртуальные veth), таблицы маршрутизации и правила Netfilter.
  • UTS namespace: позволяет установить собственные hostname и domainname.
  • User namespace: транслирует идентификаторы пользователей. Процесс может выполняться от имени (root) внутри контейнера, но ядро будет маппить его в непривилегированного пользователя, скажем, на хосте. Это мощнейший механизм безопасности.
  • !Архитектура пространств имен Linux

    Если namespaces скрывают от процесса соседей, то контрольные группы (cgroups) ограничивают его аппетит. В Debian по умолчанию используется единая иерархия cgroups v2.

    Cgroups v2 строится вокруг файловой системы /sys/fs/cgroup. Ядро предоставляет интерфейс управления через запись значений в специальные файлы. Если необходимо ограничить потребление оперативной памяти процессом и всеми его потомками, создается директория (группа) внутри /sys/fs/cgroup, куда помещается PID процесса. Затем в файл memory.max записывается лимит в байтах. Как только группа процессов превышает этот лимит, ядро активирует механизм OOM (Out Of Memory) Killer, который принудительно завершает самый ресурсоемкий процесс в группе.

    Аналогично работает квотирование процессорного времени. Файл cpu.max принимает два значения: квоту и период. Запись 50000 100000 означает, что группе разрешено использовать 50 000 микросекунд процессорного времени каждые 100 000 микросекунд (то есть жесткое ограничение в 50% одного ядра процессора).

    !Интерактивная модель ограничения ресурсов через cgroups

    Стандарт OCI и роль runc

    До 2015 года формат контейнеров диктовался исключительно компанией Docker. Чтобы избежать монополии, индустрия объединилась и создала Open Container Initiative (OCI) — открытый стандарт, описывающий формат образа и среду выполнения.

    Согласно стандарту OCI, среда выполнения (runtime) — это консольная утилита, которая принимает на вход директорию строго определенного формата (OCI bundle) и запускает из нее изолированный процесс. runc является эталонной реализацией этого стандарта, написанной на Go. Когда вы выполняете docker run, демон Docker скачивает образ, распаковывает его слои в единую директорию, генерирует конфигурационный файл и передает управление runc.

    OCI bundle состоит ровно из двух элементов:

  • Директории rootfs, содержащей корневую файловую систему будущего контейнера (бинарные файлы, библиотеки, конфигурации).
  • Файла config.json, описывающего параметры изоляции (какие namespaces активировать, какие лимиты cgroups установить, какую команду выполнить).
  • Ручная сборка контейнера в Debian

    Чтобы понять низкоуровневую механику, соберем и запустим контейнер без использования высокоуровневых демонов, опираясь только на runc и системные утилиты Debian.

    Шаг 1: Подготовка rootfs

    Контейнеру нужна файловая система. Поскольку контейнер использует ядро хоста, в rootfs не нужно помещать ядро или загрузчик — только пользовательское окружение (userspace). В Debian для создания минимальной корневой системы используется утилита debootstrap.

    Создадим директорию для OCI bundle и развернем туда минимальный Debian:

    Флаг --variant=minbase гарантирует, что будут скачаны и распакованы только самые критичные пакеты (libc, bash, coreutils, apt), без лишних утилит, что сделает размер rootfs минимальным.

    Шаг 2: Генерация и разбор config.json

    Находясь в директории /opt/mycontainer, вызовем runc для генерации базового конфигурационного файла:

    Эта команда создает файл config.json с настройками по умолчанию. Внутри этого JSON-файла скрыта вся магия изоляции. Рассмотрим ключевые секции.

    Секция process определяет, что именно будет запущено внутри контейнера:

    По умолчанию runc предлагает запустить оболочку sh от имени пользователя root () в корневой директории /.

    Секция linux.namespaces указывает ядру, какие пространства имен нужно создать для процесса (аналог флагов системного вызова clone или утилиты unshare):

    Если удалить из этого массива объект {"type": "network"}, контейнер не получит изолированного сетевого стека и будет напрямую использовать сетевые интерфейсы хоста (эквивалент флага --network host в Docker).

    Шаг 3: Настройка ограничений и безопасности

    Отредактируем config.json, чтобы добавить жесткие лимиты через cgroups. Для этого в секцию linux добавляется блок resources:

    Здесь мы ограничили оперативную память до 512 МБ (536870912 байт) и выделили половину процессорного ядра. runc самостоятельно транслирует эти JSON-директивы в операции записи в иерархию /sys/fs/cgroup.

    Особое внимание уделяется механизму pivot_root. В отличие от chroot, который уязвим к атакам выхода из директории, pivot_root меняет саму структуру монтирования ядра. Старый корень хоста перемещается во временную директорию, а rootfs контейнера становится настоящим корнем. После этого старый корень отмонтируется, физически отрезая процессу доступ к файлам хоста. runc выполняет эту операцию автоматически на основе параметра root.path в конфигурации.

    Шаг 4: Жизненный цикл контейнера

    Управление контейнером через runc разбито на четкие атомарные этапы. Это необходимо для того, чтобы высокоуровневые системы оркестрации могли внедрять свои сетевые плагины (CNI) до того, как процесс начнет выполнение.

    Создадим контейнер с именем debian-test:

    Команда выполняется мгновенно и возвращает управление. Контейнер создан, но процесс sh еще не начал работу. На этом этапе runc выполнил форк, создал пространства имен, настроил cgroups, выполнил pivot_root и загрузил бинарный файл процесс в память. Однако сам процесс приостановлен специальным механизмом ядра (через FIFO-канал), ожидая сигнала к старту.

    Проверим состояние:

    Вывод покажет статус created и номер PID процесса инициализации на хосте. Именно в этот момент, между create и start, сетевые демоны могут создать виртуальный интерфейс veth, поместить один его конец в Network namespace контейнера (используя его PID) и назначить IP-адрес.

    Запустим выполнение процесса:

    Статус меняется на running. Процесс sh начинает выполнение.

    Поскольку мы указали "terminal": true, но не привязали консоль, процесс запустится в фоне. Для интерактивного подключения к уже запущенному контейнеру используется команда exec, которая запрашивает у ядра добавление нового процесса в уже существующие пространства имен контейнера:

    Для остановки контейнера отправляется сигнал завершения:

    После остановки (статус stopped) ресурсы ядра (cgroups, namespaces) все еще существуют. Для их окончательной очистки выполняется удаление:

    Безопасность: Capabilities и Seccomp

    Даже будучи изолированным в пространствах имен, пользователь root () внутри контейнера представляет угрозу. По умолчанию ядро Linux наделяет root-пользователя абсолютной властью, которая технически разбита на набор независимых привилегий — Capabilities.

    В спецификации config.json есть секция process.capabilities, которая определяет, какие привилегии останутся у root-пользователя внутри контейнера. runc по умолчанию сбрасывает (drop) большинство критических Capabilities.

    Например, привилегия CAP_SYS_TIME позволяет изменять системное время. Если ее не удалить, root в контейнере сможет изменить аппаратные часы сервера, что сломает криптографические сертификаты и логи на всем хосте. Привилегия CAP_SYS_ADMIN (эквивалент почти полного root) позволяет монтировать файловые системы. Если контейнер скомпрометирован, наличие CAP_SYS_ADMIN часто приводит к побегу (container breakout).

    Если процесс в контейнере — это веб-сервер, которому нужно только слушать порт 80, ему достаточно оставить исключительно CAP_NET_BIND_SERVICE, сбросив все остальные привилегии.

    Второй эшелон защиты — Seccomp (Secure Computing Mode). Это механизм ядра, позволяющий фильтровать системные вызовы. В Linux существует более 300 системных вызовов, но типичному приложению требуется не более 40-50.

    В config.json профиль Seccomp описывается в секции linux.seccomp. По умолчанию runc блокирует системные вызовы вроде reboot (перезагрузка системы), kexec_load (загрузка нового ядра в память) и add_key (манипуляции с ключами ядра). Если процесс внутри контейнера попытается выполнить заблокированный системный вызов, ядро немедленно убьет этот процесс с сигналом SIGSYS, даже если у процесса есть соответствующие Capabilities.

    Для интеграции с системами принудительного контроля доступа (MAC), рассмотренными ранее, в config.json предусмотрено поле process.apparmorProfile. Передача имени загруженного профиля заставит runc переключить контекст процесса на указанный профиль AppArmor непосредственно перед выполнением команды, накладывая дополнительные ограничения на доступ к путям файловой системы.

    Понимание работы runc и структуры OCI-бандла снимает налет магии с контейнеризации. Становится очевидно, что контейнер — это не легковесная виртуальная машина, а обычный процесс Linux, вокруг которого с помощью конфигурационного JSON-файла и вызовов ядра возведены строгие барьеры видимости и потребления ресурсов. Этот фундамент позволяет осознанно подходить к настройке безопасности кластеров, отладке зависших по OOM-лимитам подов и архитектурному проектированию систем изоляции.

    2. Автоматизация на Bash: от простых команд к сложным shell-скриптам

    Автоматизация на Bash: от простых команд к сложным shell-скриптам

    Скрипт очистки старых логов успешно работал на сервере полгода, пока однажды переменная {LOG_DIR}/ превратилась в rm -rf /, и система была уничтожена за несколько секунд. Разница между набором команд в терминале и профессиональным shell-скриптом заключается не в сложности логики, а в отказоустойчивости, предсказуемости и безопасной обработке краевых случаев. Bash прощает множество ошибок при интерактивной работе, но в автоматизации эта снисходительность становится главной причиной аварий.

    Строгий режим: защитное программирование в Bash

    По умолчанию Bash продолжает выполнение скрипта, даже если команда завершилась с ошибкой, и использует пустую строку при обращении к необъявленной переменной. Для системного администрирования такое поведение неприемлемо. Фундамент любого надежного скрипта — директива set, изменяющая глобальные параметры интерпретатора.

    Конструкция set -euo pipefail (часто называемая «строгим режимом Bash») должна присутствовать в начале каждого скрипта:

  • set -e (errexit): немедленно завершает скрипт, если любая команда возвращает ненулевой код возврата (exit status).
  • set -u (nounset): вызывает ошибку и завершает скрипт при попытке использовать неинициализированную переменную. Это предотвращает катастрофы вида rm -rf /(grep "CRITICAL" /var/log/syslog || true)
  • bash

    Плохо: сложно читать, требует экранирования при вложении

    DIR_COUNT=
    ls -l \find /var/log -type d\ | wc -l

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

    DIR_COUNT=(find /var/log -type d) | wc -l) bash LOCK_FILE="/tmp/backup.lock"

    Функция очистки

    cleanup() { echo "Выполнение очистки перед выходом..." rm -f "LOCK_FILE" ]; then echo "Скрипт уже выполняется." >&2 exit 1 fi

    touch "$LOCK_FILE"

    ... основная логика резервного копирования ...

    `

    Псевдо-сигнал EXIT срабатывает всегда при завершении скрипта: успешно ли он дошел до конца, упал ли из-за set -e, или завершился по явному вызову exit. Сигналы INT (Interrupt, Ctrl+C) и TERM (Termination, стандартный сигнал команды kill) перехватываются до того, как они убьют процесс, что позволяет выполнить функцию cleanup и корректно отдать ресурсы системе. Сигнал KILL` (kill -9) перехватить невозможно — он обрабатывается ядром напрямую.

    Архитектура надежного Bash-скрипта строится на презумпции сбоя. Каждая внешняя команда может вернуть ошибку, каждая переменная может оказаться пустой, а сам процесс может быть прерван в любую миллисекунду. Использование строгого режима, правильного квотирования, потокового чтения и перехвата сигналов переводит Bash из категории инструмента для быстрых «однострочников» в полноценный язык системной автоматизации, на который можно опереться при управлении критической инфраструктурой.

    3. Управление пользователями, группами и правами доступа в Debian

    Один неосторожный запуск chmod -R 777 /var/www решает проблему с доступом веб-сервера к файлам, но одновременно открывает двери для компрометации всей системы. В Linux нет магии, блокирующей или разрешающей действия пользователя — есть только математика идентификаторов и битовые маски. Понимание того, как ядро сопоставляет UID процесса с метаданными файловой системы, отличает инженера от новичка, решающего проблемы методом перебора прав.

    Архитектура учетных записей в Debian

    В основе системы разграничения доступа лежат не текстовые имена (root, alice, nginx), а числовые идентификаторы: UID (User ID) и GID (Group ID). Ядро Linux оперирует исключительно числами. Имена существуют только для удобства администратора и транслируются в числа системными библиотеками (через NSS — Name Service Switch) при каждом обращении.

    В Debian исторически сложилось строгое разделение диапазонов UID:

  • 0: Суперпользователь (root). Ядро игнорирует любые проверки прав для этого UID.
  • 199: Системные пользователи, создаваемые при установке ОС (daemon, bin, sys).
  • 100999: Системные пользователи для демонов и сервисов, устанавливаемых пакетами (nginx, postgres, sshd). Они не имеют оболочки входа (их shell обычно /usr/sbin/nologin).
  • 1000 и выше: Обычные пользователи системы.
  • Инструментарий: useradd против adduser

    Специфика Debian, на которой часто спотыкаются администраторы с опытом работы в RHEL/CentOS, заключается в наличии двух разных утилит для создания пользователей.

    useradd — это низкоуровневый бинарный файл, часть стандартного пакета passwd. Если выполнить useradd alice, система создаст запись в /etc/passwd, но не создаст домашнюю директорию, не скопирует базовые конфигурационные файлы из /etc/skel и не запросит пароль.

    adduser — это высокоуровневый Perl-скрипт, написанный специально для Debian. Он интерактивен, автоматически создает домашнюю директорию, назначает оболочку по умолчанию (обычно /bin/bash), копирует скелетные файлы и предлагает задать пароль и GECOS (информацию о пользователе). В профессиональном администрировании Debian для заведения живых людей используется исключительно adduser, а useradd применяется в shell-скриптах автоматизации, где интерактивность недопустима.

    Анатомия системных файлов

    Информация о пользователях распределена между тремя файлами.

    Файл /etc/passwd доступен для чтения всем пользователям. Он содержит базовую информацию: alice:x:1000:1000:Alice Smith,,,:/home/alice:/bin/bash Символ x на втором месте исторически предназначался для пароля, но из соображений безопасности пароли давно вынесены в отдельный файл.

    Файл /etc/shadow доступен для чтения только пользователю root (и членам группы shadow). Именно здесь хранятся хэши паролей и параметры их устаревания.

    !Структура записи в файле shadow

    Поле хэша начинается с идентификатора алгоритма (например, ` для yescrypt — современного стандарта в Debian 12, или для SHA-512), за которым следует соль и сам хэш. Если вместо хэша стоит * или !, вход по паролю для этой учетной записи невозможен (что является нормой для системных сервисов).

    Базовая модель прав доступа (UGO)

    Каждый файл и директория в Linux имеет владельца (User) и группу-владельца (Group). Права доступа определяются для трех категорий: владельца (U), группы (G) и всех остальных (O - Others).

    Для каждой категории назначаются три базовых бита:

  • r (read) — чтение.
  • w (write) — запись.
  • x (execute) — выполнение.
  • Семантика прав для файлов и директорий

    Критически важно понимать, что биты r, w и x ведут себя по-разному в зависимости от того, применены они к файлу или к директории.

    Для файла:

  • r: позволяет прочитать содержимое файла (например, через cat).
  • w: позволяет изменять содержимое файла (через vim или перенаправление >).
  • x: позволяет ядру попытаться запустить файл как программу или скрипт.
  • Для директории:

  • r: позволяет прочитать список файлов внутри (выполнить ls). Но без бита x вы не сможете узнать метаданные этих файлов (размер, права) или обратиться к ним.
  • w: позволяет создавать, удалять и переименовывать файлы внутри этой директории. Нюанс: чтобы удалить файл, вам не нужны права на запись в сам файл — вам нужны права на запись в директорию, где он лежит.
  • x: бит поиска (search bit). Позволяет войти в директорию (выполнить cd) и обращаться к файлам внутри нее по известному имени.
  • !Калькулятор прав доступа

    Права записываются либо символьно (rwxr-xr--), либо в восьмеричной системе счисления. Восьмеричная система удобна тем, что каждый бит представляет степень двойки: чтение = 4, запись = 2, выполнение = 1. Складывая эти числа, мы получаем права для категории. Права rwxr-xr-- транслируются в для владельца (7), для группы (5) и для остальных (4). Итого: 754.

    Формирование прав: umask

    При создании нового файла пользователь не указывает права явно. Программа (например, touch или vim) запрашивает у ядра создание файла с максимально безопасными базовыми правами: 666 (rw-rw-rw-) для файлов и 777 (rwxrwxrwx) для директорий.

    Итоговые права вычисляются путем наложения маски umask. Umask — это права, которые нужно отнять (запретить) у базовых. Вычисление происходит побитово, но для упрощения можно использовать вычитание.

    Стандартный umask в Debian для обычных пользователей — 0022. Для директории: (rwxr-xr-x). Для файла: (rw-r--r--).

    Если изменить umask на 0027 (выполнив команду umask 0027), то новые файлы будут создаваться с правами (rw-r-----), полностью закрывая доступ для категории «остальные».

    Специальные биты: SUID, SGID и Sticky Bit

    Базовой модели UGO не всегда достаточно. Например, чтобы пользователь мог изменить свой пароль командой passwd, утилита должна записать новый хэш в /etc/shadow. Но этот файл доступен для записи только root. Как обычный пользователь меняет пароль?

    SUID (Set-User-ID)

    Бит SUID (числовое значение 4000) решает эту проблему. Если на исполняемом файле установлен SUID, то при запуске процесс получает права не того пользователя, который запустил команду (Real UID), а права владельца файла (Effective UID).

    !Механизм работы SUID

    Утилита /usr/bin/passwd принадлежит root и имеет права rwsr-xr-x (буква s вместо x у владельца). Когда пользователь alice запускает passwd, ядро создает процесс с rUID=1000 (alice), но eUID=0 (root). Процесс получает право писать в /etc/shadow.

    Поиск файлов с SUID-битом — первый шаг при аудите безопасности, так как любая уязвимость в таком бинарном файле ведет к немедленному получению прав root. Команда find / -perm -4000 -type f 2>/dev/null покажет все такие файлы в системе.

    SGID (Set-Group-ID)

    Бит SGID (2000) работает аналогично SUID для исполняемых файлов (процесс получает eGID группы файла), но его главная ценность раскрывается при применении к директориям.

    Если установить SGID на директорию (chmod g+s /shared), то все новые файлы и поддиректории, создаваемые внутри, будут наследовать группу этой директории, а не первичную группу создателя. Это краеугольный камень для организации общих файловых помоек.

    Например, есть группа developers и директория /var/www/project.

  • Назначаем группу: chown root:developers /var/www/project
  • Даем права на запись группе: chmod 775 /var/www/project
  • Устанавливаем SGID: chmod g+s /var/www/project (права станут rwxrwsr-x)
  • Теперь, если разработчик alice создаст файл index.html, владельцем файла будет alice, но группой — developers. Разработчик bob сможет редактировать этот файл, так как тоже состоит в группе developers.

    Sticky Bit

    Sticky Bit (1000) применяется к директориям, открытым на запись для всех (например, /tmp). Права /tmp выглядят как rwxrwxrwt (буква t на конце).

    Бит w для всех означает, что любой может создать файл в /tmp. Но, как мы помним, право писать в директорию дает право удалять из нее любые файлы. Без Sticky Bit пользователь alice могла бы удалить временные файлы пользователя bob. Sticky Bit меняет логику ядра: удалять или переименовывать файл в такой директории может только владелец файла (или root).

    Тонкая настройка: Access Control Lists (ACL)

    Модель UGO имеет жесткое ограничение: файл может принадлежать только одному пользователю и одной группе. Если требуется дать права на чтение пользователю alice, права на запись пользователю bob, а остальным запретить доступ, стандартных chmod и chown не хватит.

    В Debian по умолчанию файловые системы (ext4, xfs) монтируются с поддержкой POSIX ACL. ACL позволяет прикреплять списки доступа к конкретным inode.

    Управление осуществляется утилитами setfacl и getfacl.

    Назначение прав на чтение конкретному пользователю: setfacl -m u:alice:r-- /var/log/app.log

    Назначение прав группе analytics: setfacl -m g:analytics:r-x /opt/scripts/

    Если к файлу применен ACL, команда ls -l покажет символ + в конце строки прав: -rw-r--r--+ 1 root root 1024 Jan 1 12:00 app.log

    Важной концепцией в ACL является маска (mask). Маска определяет максимально допустимые права для всех пользователей и групп, указанных в ACL (кроме владельца файла). Если маска установлена в r--, то даже если вы через setfacl дадите пользователю alice права rwx, реально (эффективно) она получит только r--. Изменение прав группы через chmod на файле с ACL на самом деле меняет маску ACL, что часто приводит к неожиданным результатам при смешивании двух подходов управления доступом.

    Делегирование полномочий: sudo и visudo

    Работа под учетной записью root нарушает принцип наименьших привилегий и лишает систему аудита (невозможно узнать, кто из администраторов выполнил деструктивную команду). Стандарт де-факто — использование утилиты sudo (Superuser DO).

    В Debian при установке, если пароль root оставлен пустым, первый созданный пользователь автоматически добавляется в группу sudo. Члены этой группы могут выполнять любые команды от имени root, вводя свой собственный пароль.

    Конфигурация sudo хранится в /etc/sudoers и директории /etc/sudoers.d/. Редактировать основной файл напрямую через vim категорически запрещено. Синтаксическая ошибка в этом файле приведет к тому, что sudo перестанет работать, и вы потеряете возможность повысить привилегии для исправления ошибки.

    Для редактирования используется команда visudo. Она открывает файл в текстовом редакторе (по умолчанию nano или vim, в зависимости от update-alternatives), а при сохранении проверяет синтаксис. Если найдена ошибка, visudo не перезапишет файл, а предложит исправить проблему.

    Синтаксис правила в sudoers выглядит так: alice ALL=(ALL:ALL) ALL

    Разбор структуры:

  • alice: к кому применяется правило.
  • ALL=: на каких хостах действует правило (актуально при централизованном управлении конфигами, локально всегда ALL).
  • (ALL:ALL): от имени какого пользователя и какой группы можно выполнить команду.
  • ALL: какие именно команды разрешены.
  • Для тонкого делегирования можно ограничить список команд и убрать требование пароля. Например, разрешим пользователю zabbix перезапускать сервис nginx без ввода пароля: zabbix ALL=(root) NOPASSWD: /bin/systemctl restart nginx

    При использовании sudo важно помнить про переменные окружения. По умолчанию sudo сбрасывает большинство переменных окружения пользователя для безопасности. Если необходимо запустить команду с окружением целевого пользователя (например, root), используется флаг -i (sudo -i), который симулирует полноценный login shell, применяя профили из /root/.bashrc и /root/.profile.

    Взаимодействие UID, битовых масок, ACL и механизмов делегирования формирует многоуровневую защиту ОС. Ошибка на уровне прав файловой системы не может быть исправлена инструментами вышележащих слоев. Понимание того, как ядро читает биты rwx` на директориях и как SUID меняет эффективный идентификатор процесса, является фундаментом для дальнейшей работы с изоляцией процессов и безопасностью на сетевом уровне.

    4. Загрузка системы, управление процессами и приоритетами выполнения

    Загрузка системы, управление процессами и приоритетами выполнения

    Нажатие кнопки питания на сервере запускает цепную реакцию, в ходе которой мёртвое железо превращается в управляемую многозадачную среду. Понимание того, как миллисекунды инициализации оборудования переходят в загрузку ядра, а затем в запуск тысяч параллельных потоков, отличает инженера от рядового пользователя. Системный администратор должен уметь вмешиваться в этот конвейер на любом этапе: от изменения параметров загрузчика до изменения приоритета конкретного процесса, потребляющего слишком много процессорного времени.

    От UEFI до PID 1: анатомия загрузки Debian

    Процесс загрузки современного сервера под управлением Debian Linux можно разделить на четыре фундаментальные стадии. Ошибка на любой из них приводит к состоянию неработоспособности, и для диагностики необходимо точно понимать границы ответственности каждого компонента.

    !Архитектура загрузки Linux

    1. Инициализация оборудования (UEFI/BIOS)

    Материнская плата подает питание на процессор, который начинает выполнение микрокода из ПЗУ. В современных системах стандартом является UEFI (Unified Extensible Firmware Interface). В отличие от старого BIOS, UEFI умеет читать таблицы разделов GPT и понимать файловые системы (обычно FAT32). UEFI находит специальный раздел EFI System Partition (ESP), читает оттуда загрузчик и передает ему управление.

    2. Загрузчик (GRUB2)

    В Debian стандартным загрузчиком выступает GRUB2. Его главная задача — предоставить выбор ядра операционной системы и загрузить его в оперативную память. Конфигурация GRUB находится в /boot/grub/grub.cfg, но этот файл генерируется автоматически. Системный администратор вносит изменения в /etc/default/grub (например, добавляет параметры ядра, такие как quiet или nomodeset), после чего применяет их командой update-grub.

    Вместе с ядром GRUB загружает в память initramfs (Initial RAM Filesystem) — временную корневую файловую систему.

    3. Ядро и initramfs

    Ядро Linux распаковывается в памяти, инициализирует базовые структуры данных и планировщик задач. Однако ядру нужно смонтировать настоящую корневую файловую систему (/), которая может находиться на RAID-массиве, зашифрованном диске или логическом томе LVM. Ядро само по себе не содержит всех возможных драйверов для этого.

    Здесь на сцену выходит initramfs. Это минимальный набор утилит и модулей ядра, достаточный для сборки RAID, расшифровки LUKS или активации LVM. Отработав, скрипты из initramfs монтируют реальный корень, выполняют операцию pivot_root (подмену временного корня на настоящий) и передают управление первому процессу системы.

    4. Инициализация системы (systemd)

    Ядро запускает процесс с идентификатором PID 1. В Debian 8 и новее это systemd. Он отвечает за запуск всех остальных служб, монтирование дополнительных файловых систем, настройку сети и предоставление интерфейса входа.

    В systemd концепция классических уровней загрузки (runlevels) заменена на цели (targets). Цель — это логическая группировка юнитов (служб, сокетов, точек монтирования), которые должны быть запущены. Две основные цели:

  • multi-user.target — многопользовательский режим с поддержкой сети, без графического интерфейса (стандарт для серверов).
  • graphical.target — то же самое, но с запуском дисплейного менеджера (X11/Wayland).
  • Узнать текущую цель по умолчанию можно командой: systemctl get-default

    А переключить систему в режим восстановления (аналог single-user mode), где запущен только минимальный набор служб и требуется пароль root, можно так: systemctl isolate rescue.target

    Жизненный цикл и состояния процессов

    Каждая выполняющаяся программа в Linux представлена в ядре структурой task_struct. Ядро отслеживает процессы с помощью уникальных числовых идентификаторов — (Process ID). Каждый процесс, кроме PID 1, порождается другим процессом, который называется родителем и имеет свой (Parent Process ID).

    Процесс в Linux не просто «работает» или «не работает». Он постоянно перемещается между различными состояниями, определяемыми ядром.

    !Граф состояний процесса в Linux

    При анализе вывода утилит ps (например, ps aux) или top, ключевое внимание уделяется колонке STAT (или S).

  • R (Running / Runnable): Процесс либо прямо сейчас выполняется на ядре процессора, либо находится в очереди на выполнение (runqueue) и готов к работе.
  • S (Interruptible Sleep): Процесс спит, ожидая какого-то события: ввода от пользователя, пакета по сети или истечения таймера. Большинство процессов в системе находятся именно в этом состоянии. Они мгновенно просыпаются при получении аппаратного прерывания или сигнала.
  • D (Uninterruptible Sleep): Непрерываемый сон. Процесс ожидает завершения аппаратного ввода-вывода (I/O), чаще всего дискового. В этом состоянии процесс не реагирует ни на какие сигналы, даже на принудительное убийство. Если вы видите много процессов в состоянии D, это верный признак проблем с дисковой подсистемой или зависшим сетевым хранилищем (NFS).
  • T (Stopped): Процесс приостановлен. Это происходит, если процесс получает сигнал SIGSTOP (например, при нажатии Ctrl+Z в терминале).
  • Z (Zombie): Процесс-зомби. Процесс уже завершил выполнение, освободил память и закрыл файлы, но его запись в таблице процессов всё ещё существует.
  • Появление зомби — частый источник недопонимания. Зомби возникает всегда, когда процесс завершается. Ядро хранит код возврата процесса (успех или код ошибки), чтобы родительский процесс мог его прочитать с помощью системного вызова wait(). Как только родитель читает этот код, ядро удаляет зомби из таблицы. Если родительский процесс написан с ошибками и не вызывает wait(), зомби будет висеть в системе бесконечно. Зомби не потребляют CPU или RAM, они занимают лишь одну строчку в таблице процессов. Убить зомби напрямую невозможно — он уже мёртв. Единственный способ избавиться от зависших зомби — убить их родительский процесс. Тогда зомби будут переданы на усыновление процессу PID 1 (systemd), который немедленно выполнит wait() и очистит таблицу.

    Межпроцессное взаимодействие: управление через сигналы

    Системный администратор управляет запущенными процессами преимущественно с помощью сигналов. Сигнал — это асинхронное уведомление, отправляемое процессу ядром. Утилита kill вопреки своему названию предназначена не только для убийства процессов, а для отправки любых сигналов.

    Синтаксис отправки: kill -<СИГНАЛ> <PID>.

    Наиболее важные сигналы:

  • SIGTERM (15): Сигнал завершения по умолчанию (kill PID). Это вежливая просьба завершить работу. Процесс может перехватить этот сигнал, корректно закрыть соединения с базами данных, сбросить кэши на диск и только потом завершиться.
  • SIGKILL (9): Безусловное убийство (kill -9 PID). Этот сигнал перехватить или проигнорировать невозможно. Ядро немедленно уничтожает процесс. Использовать SIGKILL следует только в крайнем случае, так как это может привести к повреждению данных или появлению файлов-сирот.
  • SIGHUP (1): Исторически — сигнал обрыва терминальной линии (Hangup). В современных демонах (Nginx, Apache, SSHD) он используется как команда на перечитывание конфигурационных файлов без остановки самого процесса.
  • SIGINT (2): Прерывание с клавиатуры. Именно этот сигнал отправляется процессу, когда вы нажимаете Ctrl+C в терминале.
  • Для массового управления процессами используются утилиты killall (отправка сигнала всем процессам с указанным именем) и pkill (отправка сигнала процессам, чье имя или атрибуты совпадают с регулярным выражением).

    Например, после изменения конфигурации веб-сервера правильным действием будет не перезапуск службы, а отправка сигнала HUP: killall -HUP nginx Главный процесс Nginx перечитает конфиг, запустит новые рабочие процессы (worker processes) с новыми настройками, а старым рабочим процессам прикажет плавно завершиться после обслуживания текущих клиентов. Ни одно соединение не будет разорвано.

    Планировщик CPU: приоритеты и Niceness

    В Linux используется планировщик CFS (Completely Fair Scheduler). Его задача — справедливо распределять процессорное время между всеми процессами в состоянии R (Runnable). Однако «справедливо» не означает «поровну». Администратор может влиять на долю процессорного времени, выдаваемую задаче, изменяя её приоритет.

    В утилитах мониторинга (top, htop) вы увидите две связанные колонки: (Priority) и (Niceness). Ядро оперирует абсолютным приоритетом . Пользовательское пространство оперирует параметром (уступчивость, «вежливость»), который влияет на вычисление итогового .

    Параметр принимает значения в диапазоне .

  • : Значение по умолчанию для всех новых процессов.
  • : Максимально «вежливый» процесс. Он уступает процессорное время всем остальным и выполняется только тогда, когда CPU простаивает. Идеально для фоновых задач вроде создания резервных копий или индексации файлов.
  • : Максимально «эгоистичный» процесс с наивысшим приоритетом.
  • !Влияние параметра nice на распределение CPU

    Запуск новой команды с измененным приоритетом выполняется утилитой nice: nice -n 15 tar -czf backup.tar.gz /var/www

    Изменение приоритета уже запущенного процесса выполняется утилитой renice: renice -n -5 -p 10452

    Здесь вступает в силу строгая модель безопасности Linux. Любой пользователь может повысить значение (сделать процесс более уступчивым, снизив его приоритет) для своих собственных процессов. Но только суперпользователь (root) имеет право понизить значение (повысить приоритет), уйдя в отрицательные значения. Это защищает систему от ситуации, когда обычный пользователь захватывает все ресурсы CPU, запустив процесс с .

    Фоновые задачи и отвязка от терминала

    Когда вы запускаете команду в терминале, она становится дочерним процессом вашей оболочки Bash. Если сетевое соединение по SSH обрывается, демон SSHD завершает процесс Bash. При завершении Bash отправляет сигнал SIGHUP всем своим дочерним процессам, что приводит к их немедленному завершению.

    Если вы запустили долгий скрипт миграции базы данных и у вас моргнул интернет — скрипт прервется на середине, оставив систему в неконсистентном состоянии.

    Для управления фоновыми задачами есть несколько подходов:

    1. Управление заданиями (Job Control) Запущенный процесс можно приостановить комбинацией Ctrl+Z (отправка SIGSTOP). Процесс перейдет в состояние T. Затем его можно перевести в фон командой bg (background). Он продолжит выполнение, но освободит терминал. Вернуть процесс на передний план можно командой fg (foreground). Однако это не спасает от обрыва SSH-сессии.

    2. Утилита nohup Команда nohup (no hangup) перехватывает и игнорирует сигнал SIGHUP, а также перенаправляет стандартный вывод и вывод ошибок в файл nohup.out. nohup /opt/scripts/long_migration.sh & Амперсанд & в конце строки сразу запускает команду в фоне. Теперь можно безопасно закрывать терминал.

    3. Терминальные мультиплексоры (tmux / screen) Это стандарт де-факто для системных администраторов. tmux создает виртуальный сервер терминалов внутри вашей сессии. При обрыве связи процессы продолжают работать внутри tmux. При новом подключении по SSH вы просто выполняете tmux attach и возвращаетесь ровно в то же состояние экрана, на котором остановились.

    4. Системный подход: systemd-run Современный и наиболее надежный способ запуска разовых фоновых задач в Debian — использование systemd-run. Эта утилита создает временный (transient) юнит systemd и запускает вашу команду как полноценный системный сервис. systemd-run --unit=db-migration --remain-after-exit /opt/scripts/long_migration.sh

    В этом случае:

  • Процесс полностью отвязан от вашей сессии SSH.
  • Вы можете смотреть его логи через стандартный журнал: journalctl -u db-migration.
  • Вы можете управлять им как обычной службой: systemctl stop db-migration.
  • Процесс наследует все механизмы изоляции и лимитов ресурсов, доступные в systemd.
  • Управление процессами — это баланс между ресурсами оборудования и потребностями приложений. Понимание того, как процессы рождаются, почему они засыпают в состоянии D и как планировщик распределяет между ними такты процессора, позволяет администратору не просто наблюдать за сервером, а полностью контролировать его поведение под любой нагрузкой.

    5. Дисковая подсистема: работа с разделами и логическими томами LVM

    Дисковая подсистема: работа с разделами и логическими томами LVM

    Сервер баз данных останавливается в два часа ночи из-за ошибки No space left on device. Традиционный подход с жесткими разделами требует остановки сервисов, загрузки с LiveCD, изменения границ разделов с риском потери данных и длительного простоя. Использование менеджера логических томов позволяет добавить пространство в переполненную файловую систему одной командой, не прерывая транзакции базы данных. Управление дисковой подсистемой в Linux делится на два фундаментальных подхода: прямая работа с блочными устройствами и использование абстракций.

    Блочные устройства и традиционная разметка

    Ядро Linux взаимодействует с накопителями как с блочными устройствами. В директории /dev они представлены файлами: /dev/sda (первый SATA/SAS диск), /dev/nvme0n1 (первый NVMe накопитель), /dev/vda (виртуальный диск в KVM).

    Исторически диски делились на независимые зоны с помощью таблиц разделов.

    MBR (Master Boot Record) — устаревший стандарт, хранящий информацию о разделах в первых 512 байтах диска. Его критические ограничения: поддержка дисков объемом не более 2 ТБ и максимум 4 первичных раздела. Если требуется больше, один из разделов объявляется «расширенным» и внутри него создаются логические диски, что усложняет структуру.

    GPT (GUID Partition Table) — современный стандарт, являющийся частью спецификации UEFI. GPT использует 64-битную адресацию логических блоков (LBA), что сдвигает предел объема до зеттабайтов. GPT поддерживает до 128 разделов по умолчанию и хранит резервную копию таблицы разделов в конце диска, защищая структуру от случайного повреждения начальных секторов.

    Для работы с таблицами разделов в Debian применяются утилиты fdisk (поддерживает MBR и GPT) и gdisk (специализируется на GPT). Обе работают в интерактивном режиме. Для автоматизации в shell-скриптах используется утилита parted, позволяющая передавать команды аргументами: parted /dev/sdb mklabel gpt mkpart primary ext4 0% 100%.

    Выбор файловой системы: ext4 и xfs

    Файловая система определяет, как данные организуются внутри раздела. В контексте Debian стандартами де-факто являются ext4 и xfs. Выбор между ними критически влияет на возможности дальнейшего масштабирования.

    ext4 — проверенная временем журналируемая файловая система. Ее главное административное преимущество: она поддерживает как увеличение (resize2fs), так и уменьшение размера.

    xfs — высокопроизводительная файловая система, оптимизированная для параллельного ввода-вывода и работы с большими файлами. Однако xfs имеет жесткое архитектурное ограничение: ее можно только увеличивать (xfs_growfs). Уменьшить раздел с xfs невозможно ни при каких условиях — потребуется полное резервное копирование, пересоздание файловой системы и восстановление данных.

    Архитектура LVM: абстракция над физикой

    Logical Volume Manager (LVM) решает проблему жесткой привязки файловых систем к физическим границам дисков. LVM вводит дополнительный слой абстракции, разбивая дисковое пространство на три уровня.

    !Архитектура LVM: от физических дисков до файловых систем

  • Physical Volume (PV) — физический том. Это может быть целый диск (/dev/sdb) или традиционный раздел (/dev/sda2), инициализированный для использования в LVM. На этом этапе LVM записывает на устройство свой заголовок и разбивает пространство на блоки фиксированного размера — Physical Extents (PE). По умолчанию размер PE равен 4 МБ.
  • Volume Group (VG) — группа томов. Это пул свободного пространства, объединяющий емкость нескольких PV. Если у вас есть два диска по 1 ТБ, объединенных в одну VG, вы получаете единый пул на 2 ТБ. Группа томов оперирует экстентами (PE) и ничего не знает о физических границах дисков.
  • Logical Volume (LV) — логический том. Это конечный блочный интерфейс, который форматируется в файловую систему и монтируется в ОС. LV «нарезается» из свободных экстентов группы томов (VG). При этом экстенты одного логического тома могут физически располагаться на разных дисках.
  • Такая иерархия позволяет легко расширять логические тома: достаточно добавить новый диск в сервер, сделать его физическим томом (PV), добавить в группу томов (VG) и выделить появившиеся свободные экстенты нужному логическому тому (LV).

    Жизненный цикл логического тома

    Рассмотрим процесс создания хранилища с нуля. Допустим, к серверу подключены два новых диска: /dev/sdb и /dev/sdc, каждый по 100 ГБ. Задача — создать логический том размером 150 ГБ для базы данных.

    1. Инициализация физических томов

    Сначала диски необходимо передать под управление LVM: pvcreate /dev/sdb /dev/sdc

    Утилита pvs (или более подробная pvdisplay) покажет, что устройства готовы и разбиты на экстенты, но пока не принадлежат ни одной группе.

    2. Создание пула пространства

    Объединяем два диска в группу томов с именем data_vg: vgcreate data_vg /dev/sdb /dev/sdc

    Команда vgs покажет, что создана группа data_vg общим объемом около 200 ГБ. Теперь физические границы дисков нас не интересуют, мы оперируем пулом data_vg.

    3. Выделение логического тома

    Создаем логический том db_data размером 150 ГБ: lvcreate -n db_data -L 150G data_vg

    Флаг -n задает имя, -L — точный размер. Если нужно отдать под том всё оставшееся свободное место в группе, используется указание в экстентах (флаг -l): lvcreate -n db_data -l 100%FREE data_vg.

    После выполнения команды ядро создаст устройство в подсистеме Device Mapper. Доступ к нему можно получить по пути /dev/data_vg/db_data или /dev/mapper/data_vg-db_data. Это символические ссылки на реальное блочное устройство (например, /dev/dm-0).

    4. Форматирование и монтирование

    Логический том ведет себя как обычный раздел. Форматируем его в ext4: mkfs.ext4 /dev/data_vg/db_data

    Для постоянного монтирования при загрузке запись добавляется в /etc/fstab. При работе с обычными разделами строго рекомендуется использовать UUID (уникальные идентификаторы), так как имена вроде /dev/sda могут меняться при переподключении кабелей. Однако пути LVM (/dev/mapper/data_vg-db_data) генерируются динамически на основе метаданных LVM, хранящихся на самих дисках. Поэтому в /etc/fstab безопасно и более читаемо использовать путь Device Mapper: /dev/mapper/data_vg-db_data /var/lib/mysql ext4 defaults 0 2

    Динамическое изменение размеров

    Гибкость LVM раскрывается при необходимости изменить размер хранилища.

    Безопасное расширение (Online)

    Расширение тома — рутинная и безопасная операция, выполняемая «на горячую», без отмонтирования файловой системы.

    Допустим, в группе томов data_vg осталось свободное место, и мы хотим добавить 20 ГБ к тому db_data: lvextend -L +20G /dev/data_vg/db_data

    Блочное устройство увеличилось, но файловая система внутри него об этом не знает. Если выполнить df -h, размер останется прежним. Необходимо дать команду драйверу файловой системы занять новое пространство. Для ext4: resize2fs /dev/data_vg/db_data Для xfs: xfs_growfs /var/lib/mysql (указывается точка монтирования, а не устройство).

    В современных версиях LVM эти два шага можно объединить флагом -r (resize): lvextend -r -L +20G /dev/data_vg/db_data LVM сам определит тип файловой системы и вызовет нужную утилиту расширения.

    Опасное уменьшение (Offline)

    Уменьшение тома — крайне рискованная операция. Ошибка в порядке команд гарантированно уничтожит данные. Как упоминалось ранее, xfs уменьшить нельзя. Для ext4 процесс выглядит так:

  • Отмонтировать файловую систему (сервис должен быть остановлен):
  • umount /var/lib/mysql
  • Обязательно проверить файловую систему на ошибки:
  • e2fsck -f /dev/data_vg/db_data
  • Сначала уменьшить саму файловую систему (например, до 100 ГБ):
  • resize2fs /dev/data_vg/db_data 100G
  • Только после этого уменьшить логический том в LVM:
  • lvreduce -L 100G /dev/data_vg/db_data Здесь LVM выдаст предупреждение о возможном разрушении данных.
  • Смонтировать обратно.
  • Если уменьшить LV до того, как уменьшена файловая система, ядро «отрежет» хвост блочного устройства, на котором еще лежат данные или структуры ext4. Файловая система будет безвозвратно разрушена.

    Снапшоты и механизм Copy-on-Write

    Одной из самых мощных функций LVM являются снапшоты (снимки состояния). Снапшот позволяет мгновенно зафиксировать состояние логического тома на определенный момент времени. Это критически важно для создания консистентных резервных копий баз данных или файлов, которые постоянно изменяются.

    Снапшот в LVM не копирует все данные оригинального тома. Он использует механизм Copy-on-Write (CoW — копирование при записи).

    !Механизм Copy-on-Write при работе LVM-снапшота

    Когда создается снапшот, он занимает минимум места (только метаданные). Все запросы на чтение из снапшота перенаправляются к оригинальному тому. Однако, если какой-либо процесс пытается изменить блок данных на оригинальном томе, LVM перехватывает эту операцию. Перед тем как новые данные перезапишут старые, LVM копирует старый блок в пространство снапшота. Таким образом, оригинальный том продолжает работать и изменяться, а снапшот хранит только те блоки, которые были изменены с момента его создания, плюс ссылки на неизмененные блоки оригинала.

    Создание снапшота db_backup размером 10 ГБ для тома db_data: lvcreate -s -n db_backup -L 10G /dev/data_vg/db_data

    Параметр -L 10G означает не размер резервной копии, а размер буфера для изменений. Если с момента создания снапшота на оригинальном томе будет изменено более 10 ГБ данных, снапшот переполнится и автоматически деактивируется (перейдет в состояние dropped), так как больше не сможет гарантировать консистентность старого состояния.

    Типичный сценарий резервного копирования:

  • Выполнить сброс буферов БД на диск (например, FLUSH TABLES WITH READ LOCK в MySQL).
  • Мгновенно создать LVM-снапшот (занимает доли секунды).
  • Снять блокировку с БД (сервис продолжает работу в штатном режиме).
  • Смонтировать снапшот в отдельную директорию: mount -o ro /dev/data_vg/db_backup /mnt/backup.
  • Спокойно скопировать данные из /mnt/backup в архив (это может занять часы, но данные будут в состоянии на момент шага 2).
  • Отмонтировать и удалить снапшот: lvremove /dev/data_vg/db_backup, освобождая дисковое пространство.
  • Абстракция, предоставляемая LVM, переносит управление дисковым пространством на уровень логики, отвязывая администратора от геометрии физических накопителей. Понимание взаимодействия слоев — от аппаратного блочного устройства через подсистему Device Mapper до драйвера файловой системы — позволяет строить отказоустойчивые хранилища, способные масштабироваться без деградации сервисов.

    6. Сетевые хранилища: настройка и администрирование NFS и SMB

    Сетевые хранилища: настройка и администрирование NFS и SMB

    Системный администратор замечает, что показатель Load Average на сервере баз данных внезапно подскочил до 50, хотя процессор практически не загружен, а оперативной памяти достаточно. Попытка перезапустить зависшие процессы через kill -9 не дает никакого результата — процессы намертво "висят" в таблице задач. Причина кроется не в локальном сервере, а в том, что резервное копирование пыталось записать дамп на примонтированную сетевую шару, сервер которой ушел в незапланированную перезагрузку. Локальные процессы перешли в состояние Uninterruptible Sleep, ожидая ответа от дисковой подсистемы, которая в данный момент находится на другом конце оборванного сетевого кабеля.

    Работа с сетевыми файловыми системами кардинально отличается от работы с локальными блочными устройствами (LVM, физические диски). Сетевое хранилище привносит задержки, проблемы с аутентификацией разнородных клиентов и риск потери связности. В экосистеме Debian стандартом де-факто являются два протокола: NFS для чистого UNIX-окружения и SMB (CIFS) для гетерогенных сетей с участием Windows.

    NFS: нативная UNIX-среда и проблема доверия

    Network File System (NFS) разрабатывалась как прозрачное расширение локальной файловой системы через сеть. Современная версия протокола, актуальная для Debian — NFSv4. В отличие от третьей версии, которая использовала россыпь случайных портов и службу RPCbind, NFSv4 работает поверх единственного TCP-порта 2049, поддерживает сохранение состояния (stateful) и строгую аутентификацию.

    Фундаментальная особенность классического NFS заключается в том, что сервер по умолчанию доверяет клиенту в вопросах идентификации пользователей. Если клиентский компьютер заявляет, что файл создает пользователь с UID 1005, сервер запишет файл с владельцем UID 1005.

    Настройка сервера и псевдо-файловая система

    Установка серверной части в Debian выполняется пакетом nfs-kernel-server. Главный конфигурационный файл — /etc/exports.

    В NFSv4 применяется концепция псевдо-корневой файловой системы. Вместо того чтобы экспортировать разрозненные директории (например, /var/backups и /home/shared), администраторы создают единую точку экспорта (например, /srv/nfs4), внутрь которой монтируют нужные директории с помощью mount --bind.

    !Структура псевдо-корневой файловой системы NFSv4

    Пример подготовки структуры:

    После этого в /etc/exports описываются правила доступа:

    Разберем критические параметры экспорта:

  • fsid=0 — указывает, что эта директория является логическим корнем для клиентов NFSv4.
  • crossmnt — разрешает клиентам видеть директории, примонтированные внутри корня.
  • sync — сервер отвечает клиенту об успешной записи только после того, как данные физически сброшены на диск. Альтернатива async повышает производительность, но грозит потерей данных при сбое питания сервера.
  • no_subtree_check — отключает проверку принадлежности запрашиваемого файла к экспортируемому поддереву. Это значительно ускоряет работу и предотвращает ошибки при переименовании файлов на сервере, поэтому в современных системах этот параметр обязателен.
  • root_squash (включен по умолчанию) — важнейший механизм безопасности. Если клиентский пользователь root (UID 0) пытается создать файл на NFS-шаре, сервер принудительно понижает его права, подменяя UID на анонимный (обычно nobody, UID 65534).
  • no_root_squash — отключает эту защиту. В примере выше сервер базы данных (192.168.10.50) получает право писать бэкапы от имени root. Это потенциальная брешь в безопасности: если злоумышленник захватит клиентский сервер, он получит root-доступ к файлам на NFS-сервере.
  • Применение изменений выполняется командой exportfs -arv (перечитать конфигурацию без перезапуска службы).

    Клиентская часть и маппинг пользователей

    На клиенте устанавливается пакет nfs-common. Монтирование выполняется стандартной командой:

    Обратите внимание: клиент запрашивает путь /backups, а не абсолютный путь /srv/nfs4/backups, так как сервер презентует /srv/nfs4 как корень (fsid=0).

    Если UID пользователя на клиенте не совпадает с UID на сервере, возникает проблема доступа. Для ее решения в NFSv4 используется демон idmapd (конфиг /etc/idmapd.conf). Он перехватывает запросы и транслирует локальные UID в строковые имена вида user@domain.local, передает их по сети, а сервер транслирует их обратно в свои локальные UID. Для корректной работы параметр Domain в idmapd.conf должен совпадать на сервере и клиенте.

    Монтирование через fstab и защита от D-state

    Автоматическое монтирование сетевых ресурсов через /etc/fstab таит в себе опасность. Если сеть недоступна во время загрузки, сервер может зависнуть.

    Правильная строка в /etc/fstab для NFS выглядит так:

  • _netdev — сообщает системе инициализации (systemd), что монтирование нужно отложить до момента поднятия сетевых интерфейсов.
  • hard — определяет поведение при разрыве связи. Процессы, обращающиеся к диску, будут бесконечно блокироваться (переходить в состояние D), пока сервер не вернется. Это гарантирует консистентность данных.
  • soft — при разрыве связи ядро через некоторое время вернет процессу ошибку ввода-вывода (I/O error). Это предотвращает зависание процессов, но может привести к фатальному повреждению данных, если приложение не умеет корректно обрабатывать такие ошибки. Использовать soft для записи категорически не рекомендуется.
  • timeo и retrans — таймаут (в десятых долях секунды) и количество повторов перед тем, как NFS решит, что сервер недоступен.
  • !Симуляция зависания процесса при отключении NFS-сервера

    Если сервер "упал" окончательно, а процессы на клиенте висят в D-state из-за hard монтирования, стандартный umount /mnt/shared зависнет точно так же. Спасением является "ленивое" отмонтирование:

    Флаг -l (lazy) немедленно отсоединяет файловую систему от иерархии директорий, а флаг -f (force) принудительно обрывает сетевые соединения. Зависшие процессы получат ошибку I/O и смогут завершиться.

    SMB/CIFS: гетерогенные сети и двойной барьер прав

    Server Message Block (SMB), также известный как CIFS — родной протокол для систем Windows. В Linux его реализация обеспечивается пакетом samba. В отличие от NFS, SMB не доверяет клиенту. Сервер сам осуществляет аутентификацию каждого подключения по логину и паролю (или через Kerberos в домене Active Directory).

    Настройка Samba-сервера

    Конфигурация хранится в /etc/samba/smb.conf. Файл разделен на секцию [global] и секции отдельных ресурсов.

    Samba использует собственную базу данных паролей, так как алгоритмы хэширования в Linux (/etc/shadow) несовместимы с протоколом аутентификации NTLM, который использует SMB. Чтобы пользователь мог подключиться к шаре, он должен существовать в Linux, и ему должен быть задан пароль в Samba:

    Двойной барьер контроля доступа

    Самая частая проблема администраторов при настройке Samba — непонимание модели прав. Доступ к файлу определяется пересечением двух наборов правил:

  • Ограничения самой Samba (директивы в smb.conf).
  • Локальные права POSIX (стандартные rwx и ACL на файловой системе сервера).
  • !Схема прохождения запроса через двойной барьер прав Samba

    Если Samba разрешает запись, но локальная файловая система (ext4/xfs) запрещает ее для данного системного пользователя, доступ будет отклонен.

    Рассмотрим сложный пример. Нам нужна общая папка для отдела разработки, где все создаваемые файлы должны автоматически принадлежать группе developers и иметь права 660 (чтение и запись для владельца и группы).

    Конфигурация в smb.conf:

  • valid users = @developers — пускает только членов группы developers.
  • create mask и directory mask — работают как инвертированный umask. Они принудительно устанавливают максимальные права на новые файлы (0660) и директории (0770).
  • force group — ключевой параметр. Независимо от того, кто из разработчиков (alice или bob) создал файл, Samba запишет его на диск от имени первичной группы developers. Это решает проблему совместного редактирования, заменяя необходимость использовать SGID-бит на директории.
  • Монтирование SMB на клиенте Debian

    Для монтирования SMB-шар требуется пакет cifs-utils.

    В отличие от NFS, SMB-сервер не передает клиенту информацию о POSIX-правах и владельцах в понятном для Linux виде. Когда вы монтируете SMB-шару, локальное ядро Linux должно назначить всем файлам какого-то локального владельца и виртуальные права доступа.

    Если смонтировать шару просто через mount -t cifs //192.168.10.20/DevProject /mnt/dev -o user=alice, все файлы будут принадлежать пользователю root, и обычные процессы не смогут с ними работать.

    Правильное монтирование в /etc/fstab с подменой владельца:

    Разбор параметров:

  • credentials=/etc/smbcredentials — путь к скрытому файлу (права 600, владелец root), содержащему логин и пароль:
  • Никогда не пишите пароли открытым текстом в /etc/fstab, так как этот файл доступен для чтения всем пользователям системы.
  • uid=www-data,gid=www-data — ядро Linux на клиенте будет считать, что все файлы на этой шаре принадлежат локальному пользователю www-data. Это критически важно, если шара монтируется для веб-сервера (например, папка с медиафайлами), чтобы Nginx или Apache могли их читать.
  • file_mode и dir_mode — виртуальные права, которые клиентская ОС будет применять к этим файлам.
  • Выбор между протоколами

    При проектировании инфраструктуры выбор между NFS и SMB диктуется окружением. NFS обеспечивает максимальную производительность и минимальный оверхед при передаче данных между Linux-серверами. Сохранение POSIX-атрибутов (UID, GID, симлинки, сокеты) делает NFS идеальным для кластерных файловых систем, хранения профилей пользователей и контейнерных томов. Однако безопасность классического NFS строится на доверии к IP-адресу клиента, что недопустимо в открытых сетях без применения Kerberos (NFSv4+Krb5).

    SMB, напротив, изначально проектировался для сетей с нулевым доверием к клиенту. Он требует явной аутентификации, отлично интегрируется в корпоративные домены и является единственным адекватным выбором, если к хранилищу должны иметь доступ рабочие станции на базе Windows или macOS. Платой за эту универсальность становится повышенная нагрузка на процессор при шифровании трафика и необходимость вручную маппить права доступа при монтировании на Linux-клиентах.

    7. Сетевая безопасность: настройка брандмауэра и фильтрация трафика

    Сетевая безопасность: настройка брандмауэра и фильтрация трафика

    Свежеустановленный сервер, получивший публичный IP-адрес, подвергается первому сканированию портов в среднем через 40 секунд после загрузки сети. Через несколько минут начинаются автоматизированные атаки полного перебора на порт SSH. В условиях современного интернета отсутствие строгой фильтрации входящего трафика гарантирует компрометацию системы. Сетевой экран (брандмауэр) — это первый и главный эшелон защиты, определяющий, какие пакеты имеют право достичь приложений, а какие должны быть отброшены еще на уровне ядра операционной системы.

    Архитектура Netfilter: ядро сетевой фильтрации

    Частая ошибка начинающих администраторов — считать, что утилиты вроде iptables, ufw или nftables сами по себе фильтруют трафик. На самом деле это лишь интерфейсы пользовательского пространства (user-space). Вся реальная работа по перехвату, модификации и отбрасыванию пакетов происходит внутри ядра Linux, в подсистеме, которая называется Netfilter.

    Netfilter предоставляет набор перехватчиков (хуков) внутри сетевого стека ядра. Когда сетевой пакет проходит через ядро, он неизбежно пересекает эти хуки. Программы пользовательского уровня регистрируют свои правила на этих хуках, указывая ядру, что делать с пакетами, соответствующими определенным критериям.

    Существует пять базовых хуков Netfilter:

  • PREROUTING: Пакет только что поступил на сетевой интерфейс, до принятия ядром решения о маршрутизации (предназначен ли пакет этому серверу или его нужно переслать дальше).
  • INPUT: Решение о маршрутизации принято, и пакет предназначен локальному процессу (например, веб-серверу или SSH-демону на этом хосте).
  • FORWARD: Пакет предназначен не этому серверу, а другой машине (сервер выступает в роли маршрутизатора).
  • OUTPUT: Пакет сгенерирован локальным процессом и готовится к отправке в сеть.
  • POSTROUTING: Пакет (исходящий или транзитный) готов покинуть сетевой интерфейс.
  • !Архитектура хуков Netfilter и путь пакета

    Понимание этого пути критически важно. Если вы хотите заблокировать входящее подключение к локальному веб-серверу, правило нужно вешать на хук INPUT. Если вы хотите запретить вашим пользователям обращаться к определенному внешнему ресурсу, правило размещается в OUTPUT. Если же вы настраиваете транзит трафика между двумя сетевыми интерфейсами (например, из локальной сети в интернет), работать придется с FORWARD.

    Эволюция интерфейсов: от iptables к nftables

    Исторически стандартом де-факто для управления Netfilter была утилита iptables. Она опиралась на жесткую структуру таблиц (filter, nat, mangle) и цепочек. Однако с развитием сетей выявились серьезные архитектурные недостатки iptables:

  • Необходимость использовать разные утилиты для разных протоколов (iptables для IPv4, ip6tables для IPv6, arptables для ARP).
  • Линейный просмотр правил (при тысячах правил производительность резко падала).
  • Невозможность атомарного обновления (добавление одного правила требовало извлечения всего набора, модификации и обратной загрузки в ядро, что создавало состояние гонки).
  • Начиная с Debian 10 (Buster), стандартным инструментом фильтрации стал nftables. Это современная подсистема, которая решает проблемы предшественника. В nftables нет предопределенных таблиц и цепочек — администратор создает только те структуры, которые ему действительно нужны. Правила для IPv4 и IPv6 можно объединить в одно семейство inet.

    Для обеспечения обратной совместимости в Debian по умолчанию установлена обертка iptables-nft. Если вы вводите привычную команду iptables -A INPUT -p tcp --dport 80 -j ACCEPT, эта утилита «на лету» транслирует ее в инструкции nftables и передает ядру. Однако для профессионального администрирования современных систем необходимо писать правила на нативном синтаксисе nftables.

    Сравнение базового синтаксиса:

    | Задача | iptables (Legacy) | nftables (Modern) | |---|---|---| | Сброс всех правил | iptables -F | nft flush ruleset | | Разрешить порт 22 | iptables -A INPUT -p tcp --dport 22 -j ACCEPT | nft add rule inet filter input tcp dport 22 accept | | Заблокировать IP | iptables -A INPUT -s 192.168.1.50 -j DROP | nft add rule inet filter input ip saddr 192.168.1.50 drop |

    Stateful-фильтрация и механизм Conntrack

    Простейшие брандмауэры работают по принципу stateless (без сохранения состояния). Они анализируют каждый пакет изолированно. Если клиент отправляет HTTP-запрос (на порт 80), stateless-брандмауэр пропускает его. Но когда сервер отправляет ответ (с порта 80 на случайный порт клиента), брандмауэру требуется отдельное правило, разрешающее входящий трафик на любые высокие порты, что создает огромную дыру в безопасности.

    Современная фильтрация в Linux является stateful (с сохранением состояния). За это отвечает модуль conntrack (connection tracking). Ядро отслеживает логическое состояние каждого сетевого соединения, занося информацию о IP-адресах, портах и флагах TCP в специальную таблицу в оперативной памяти.

    !Механизм отслеживания состояний conntrack

    Conntrack выделяет четыре основных состояния пакетов:

  • NEW: Пакет начинает новое соединение (например, TCP-сегмент с флагом SYN).
  • ESTABLISHED: Пакет принадлежит уже установленному соединению, по которому трафик прошел в обоих направлениях.
  • RELATED: Пакет начинает новое соединение, но оно логически связано с уже существующим (типичный пример — FTP data channel или ICMP-сообщения об ошибках, связанные с текущей TCP-сессией).
  • INVALID: Пакет не принадлежит ни одному известному соединению и не является корректным началом нового (например, случайный TCP ACK пакет без предшествующего SYN). Такие пакеты обычно отбрасываются.
  • > Важный нюанс: conntrack отслеживает состояния даже для протоколов, не имеющих состояния по своей природе, таких как UDP или ICMP. Для UDP ядро создает псевдо-соединение: если сервер отправил UDP-пакет на определенный IP и порт, ядро временно (обычно на 30 секунд) будет считать ответные UDP-пакеты от этого IP состоянием ESTABLISHED.

    Знание механизма conntrack позволяет реализовать главное правило любого современного брандмауэра: разрешить весь трафик для установленных соединений и жестко фильтровать только новые.

    Построение набора правил nftables

    В Debian конфигурация nftables хранится в файле /etc/nftables.conf. При запуске службы systemd (юнит nftables.service) система читает этот файл и атомарно применяет правила.

    Рассмотрим структуру надежного серверного брандмауэра. Мы создадим таблицу семейства inet (обрабатывает и IPv4, и IPv6), внутри которой определим базовую цепочку для входящего трафика.

    Разбор критических решений в конфигурации

    Политика по умолчанию (policy drop). Это реализация принципа "запрещено всё, что не разрешено явно". Если пакет дошел до конца цепочки input и не совпал ни с одним правилом, он будет уничтожен. Обратите внимание, что мы используем именно drop (тихое уничтожение пакета), а не reject (отправка ответа об отказе). Для входящего трафика из интернета drop предпочтительнее, так как он замедляет сканеры портов (им приходится ждать таймаута) и не тратит ресурсы сервера на генерацию ответов.

    Порядок правил (Short-circuiting). Правило ct state established,related accept стоит в самом начале. В реальной сети 99% пакетов принадлежат уже установленным соединениям (например, передача потока данных по HTTP). Ядро проверит пакет, увидит совпадение в conntrack, применит accept и прекратит дальнейший просмотр цепочки. Это колоссально экономит ресурсы процессора. Если бы правило для порта 80 стояло первым, ядру приходилось бы для каждого пакета из гигабайтного потока проверять номер порта.

    Защита сетевых хранилищ (NFS и SMB)

    В предыдущих главах мы настраивали NFS и SMB. Эти протоколы исторически уязвимы и никогда не должны смотреть в публичный интернет. Теперь у нас есть инструмент для их изоляции.

    Добавим в цепочку input правила, разрешающие доступ к портам хранилищ только из доверенной локальной подсети (например, 10.0.0.0/24):

    Если злоумышленник попытается подключиться к порту 445 с публичного IP-адреса, пакет не совпадет с этим правилом, дойдет до конца цепочки и будет отброшен политикой drop.

    Продвинутые техники: Множества (Sets) и лимитирование

    Часто возникает задача защиты SSH от перебора паролей (brute-force). В nftables есть мощный механизм динамических множеств, который позволяет временно блокировать атакующих без использования внешних демонов вроде fail2ban.

    Вместо простого разрешения порта 22, мы можем написать правило, которое считает количество новых попыток соединения с одного IP-адреса за минуту.

    В этом примере ядро само поддерживает хэш-таблицу ssh_meter. Как только IP-адрес превышает лимит в 3 соединения за минуту, счетчик срабатывает, и пакет попадает под действие drop.

    Трансляция сетевых адресов (NAT)

    Помимо фильтрации, Netfilter выполняет функции NAT (Network Address Translation). Это критически важно, если ваш Debian-сервер выступает шлюзом для локальной сети или если вы готовитесь к работе с контейнерами.

    Самый частый сценарий — SNAT (Source NAT) или его динамическая разновидность Masquerade. Когда пакет из приватной сети (например, 192.168.1.50) идет в интернет через шлюз, маршрутизатор в интернете не знает, куда отправить ответ, так как приватные адреса не маршрутизируются глобально. Сервер должен подменить адрес отправителя в пакете на свой собственный публичный IP.

    Для этого в nftables создается отдельная таблица и цепочка, привязанная к хуку POSTROUTING:

    Когда пакет проходит через это правило, ядро подменяет Source IP, а в таблицу conntrack вносит специальную запись. Когда из интернета приходит ответ, conntrack автоматически выполняет обратную подмену (Destination NAT) и отправляет пакет исходному клиенту в локальную сеть. Без механизма отслеживания состояний реализация NAT была бы невозможна.

    Решение проблем и взаимодействие с другими системами

    При настройке брандмауэра легко отрезать себе доступ к серверу. Правилом хорошего тона перед применением новых правил является запуск команды сброса по таймеру: nft -f /etc/nftables.conf; sleep 60; nft flush ruleset. Если вы ошиблись и потеряли SSH-соединение, через минуту правила сбросятся, и доступ восстановится. Если всё работает корректно, вы просто прерываете команду sleep с помощью Ctrl+C.

    Отдельно стоит упомянуть взаимодействие брандмауэра с системами контейнеризации (например, Docker). Демон Docker напрямую управляет правилами Netfilter (исторически через iptables, создавая собственные цепочки вроде DOCKER-USER). Если вы настроите строгий drop в базовой цепочке forward, вы можете непреднамеренно сломать маршрутизацию между контейнерами или доступ контейнеров во внешнюю сеть. Понимание того, на каких хуках и с какими приоритетами работают системы виртуализации — ключ к построению комплексной инфраструктуры.

    Сетевая безопасность — это не разовая настройка, а постоянный баланс между доступностью сервисов и минимизацией поверхности атаки. Глубокое понимание того, как пакеты проходят через ядро, позволяет не просто копировать готовые конфиги, а проектировать логику защиты, адекватную архитектуре конкретного проекта.

    8. Системы принудительного контроля доступа: SELinux и AppArmor

    Системы принудительного контроля доступа и сетевой защиты

    Злоумышленник нашел уязвимость в веб-приложении и смог выполнить произвольный код от имени пользователя www-data. Стандартная модель прав доступа в Linux говорит: процесс www-data имеет право читать, писать и исполнять любые файлы, владельцем которых является www-data, а также читать файлы с правами o+r. Это означает, что взломанный веб-сервер может запустить компилятор в /tmp, скачать скрипт для майнинга, прочитать конфигурационные файлы других сайтов в /var/www, если они не изолированы строго, и даже попытаться открыть локальное сетевое соединение к базе данных. Классические права доступа (DAC) бессильны, потому что процесс действует в рамках легитимных полномочий своего пользователя. Чтобы остановить атаку на этом этапе, система должна контролировать не только права пользователя, но и конкретные действия процесса, а также жестко фильтровать сетевой трафик.

    За пределами DAC: архитектура LSM

    Классическая модель прав доступа (rwx, ACL, SUID) относится к категории Discretionary Access Control (DAC), или дискреционного контроля доступа. Фундаментальный принцип DAC: владелец объекта сам определяет права доступа к нему. Если пользователь alice создала файл, она может разрешить кому угодно его читать. Если процесс запущен от имени root, он игнорирует большинство проверок DAC.

    Mandatory Access Control (MAC), или принудительный контроль доступа, работает иначе. В MAC политики безопасности определяются централизованно системным администратором и жестко контролируются ядром. Ни один пользователь, даже владелец файла, не может изменить политику MAC для своего объекта. В правильно настроенной MAC-системе даже процесс с эффективным UID 0 (root) будет заблокирован, если его действие не разрешено явным образом в политике.

    В Linux реализация MAC опирается на фреймворк Linux Security Modules (LSM). Он предоставляет набор перехватчиков (хуков) внутри ядра. Каждый раз, когда процесс запрашивает доступ к системному ресурсу (открытие файла, создание сокета, отправка сигнала), ядро сначала проверяет стандартные права DAC. Если DAC запрещает доступ, возвращается ошибка Permission denied. Если DAC разрешает доступ, запрос передается в модуль LSM. И только если LSM дает добро, действие выполняется.

    !Процесс проходит последовательную проверку доступа через DAC, а затем через MAC.

    В экосистеме Linux исторически закрепились две основные реализации LSM: AppArmor и SELinux. В дистрибутивах семейства Debian стандартом является AppArmor.

    AppArmor: Путевой контроль доступа

    AppArmor (Application Armor) использует концепцию путевого контроля (path-based MAC). Политики безопасности привязываются не к пользователям, а к абсолютным путям исполняемых файлов. Мы ограничиваем не абстрактного пользователя www-data, а конкретный бинарный файл /usr/sbin/nginx.

    Режимы работы профилей

    Каждая программа, защищаемая AppArmor, имеет свой профиль (файлы конфигурации хранятся в /etc/apparmor.d/). Профиль может находиться в одном из трех состояний:

    | Режим | Поведение системы | Применение | | :--- | :--- | :--- | | Enforce (Принуждение) | Блокирует любое действие, не разрешенное явно. Записывает попытку нарушения в лог. | Боевая эксплуатация защищенного сервиса. | | Complain (Жалоба) | Разрешает действие, но записывает предупреждение в аудит-лог. | Отладка и создание новых профилей без остановки сервиса. | | Unconfined (Неограниченный) | Процесс игнорирует AppArmor, опираясь только на DAC. | Поведение по умолчанию для программ без профиля. |

    Синтаксис и анатомия профиля

    Рассмотрим фрагмент профиля AppArmor для вымышленного демона резервного копирования /usr/local/bin/backup-daemon:

    Права на файлы обозначаются буквами:

  • r (read) и w (write) — чтение и запись.
  • k (lock) — право накладывать блокировку на файл.
  • * — рекурсивное совпадение (все файлы и поддиректории), в отличие от , которое совпадает только с файлами строго в текущей директории.
  • Особого внимания заслуживают права на исполнение дочерних программ. Если наш демон вызывает /usr/bin/tar, AppArmor должен знать, как контролировать новый процесс:

  • ix (inherit execute): дочерний процесс унаследует текущий профиль. /usr/bin/tar будет жестко ограничен теми же правилами, что и backup-daemon.
  • px (profile execute): при запуске /usr/bin/gzip AppArmor потребует переключения на отдельный профиль, написанный специально для gzip. Если профиля нет, запуск блокируется.
  • ux (unconfined execute): запуск без ограничений (крайне небезопасно, нарушает изоляцию).
  • Правило deny обладает абсолютным приоритетом. Даже если подключенная абстракция #include <abstractions/base> разрешает чтение содержимого /etc/, строка deny /etc/shadow r гарантированно заблокирует доступ к файлу паролей.

    Интерактивное создание профиля

    Написание профиля с нуля вручную — сложная задача, так как современные программы неявно открывают сотни файлов (динамические библиотеки, локали, сокеты). В Debian для автоматизации этого процесса используется пакет apparmor-utils.

    !Пошаговая интерактивная анимация создания профиля AppArmor: от запуска aa-genprof до финального профиля в режиме Enforce с подсветкой активных элементов на каждом шаге.

    Процесс создания итеративен:

  • Выполняется команда aa-genprof /usr/local/bin/backup-daemon. Утилита создает пустой профиль-скелет, переводит его в режим Complain и ожидает действий.
  • Администратор в другом терминале запускает программу и выполняет все типичные сценарии ее работы. Ядро фиксирует доступы в системный лог.
  • Возврат в утилиту aa-genprof и запуск сканирования ("Scan"). Утилита читает логи и по каждому зафиксированному системному вызову задает вопрос: разрешить чтение этого файла? разрешить этот сетевой вызов?
  • После ответов профиль сохраняется и переводится в режим Enforce.
  • Если в процессе эксплуатации программа обновляется и начинает требовать новые доступы, используется команда aa-logprof. Она анализирует свежие отказы (сообщения DENIED в /var/log/syslog) и предлагает добавить разрешающие правила в существующий профиль.

    > Уязвимость путевого подхода (AppArmor) заключается в жестких ссылках (hardlinks). Если профиль ограничивает /usr/bin/secret-app, а злоумышленник создаст жесткую ссылку /tmp/my-app на тот же индексный дескриптор (inode), контроль может быть нарушен из-за изменения пути вызова.

    Альтернативный подход: Inode-based MAC

    Для полноты картины системному администратору необходимо понимать и альтернативный подход, применяемый в корпоративных системах на базе Red Hat (где стандартом является SELinux). Вместо путей там используется Inode-based MAC.

    Каждому объекту в системе (файлу, процессу, порту) присваивается криптографическая метка — контекст безопасности, который хранится в расширенных атрибутах файловой системы (xattr).

    !Структура контекста безопасности SELinux, состоящая из пользователя, роли, типа и уровня доступа.

    Контекст состоит из четырех полей: user:role:type:level. Главным элементом является тип (Type Enforcement). Процесс с определенным типом может взаимодействовать только с файлами разрешенных типов, независимо от того, где эти файлы находятся в файловой системе. Это исключает атаки через жесткие ссылки, но требует от администратора постоянного контроля за правильной маркировкой файлов при их перемещении.

    Сетевая изоляция: управление брандмауэром через ufw

    AppArmor защищает систему изнутри, ограничивая скомпрометированные процессы. Однако первый рубеж обороны — это сеть. Сервер не должен принимать соединения на порты, которые не предназначены для публичного доступа.

    В ядре Linux за фильтрацию сетевых пакетов отвечает подсистема Netfilter, управление которой традиционно осуществляется утилитами iptables или nftables. Их синтаксис сложен, избыточен и требует глубокого понимания цепочек маршрутизации пакетов (PREROUTING, INPUT, FORWARD, OUTPUT).

    Для упрощения администрирования в Debian и Ubuntu применяется ufw (Uncomplicated Firewall) — фронтенд, который транслирует простые человекочитаемые команды в сложные правила iptables/nftables.

    Базовая настройка и логика по умолчанию

    Золотое правило сетевой безопасности: запрещено всё, что не разрешено явно.

    Свежеустановленный ufw отключен. Перед его активацией необходимо задать базовые политики и обязательно разрешить SSH-подключения, иначе при включении брандмауэра администратор потеряет доступ к удаленному серверу.

    Политика deny incoming означает, что любой входящий пакет, не соответствующий явно заданным разрешающим правилам, будет отброшен (drop) без отправки ответа отправителю. Это делает закрытые порты невидимыми для сканеров сети. Политика allow outgoing разрешает серверу беспрепятственно скачивать обновления и обращаться к внешним API.

    Синтаксис правил: от простого к сложному

    ufw поддерживает несколько уровней детализации при написании правил.

    1. Простые правила (по порту или имени сервиса) Разрешить HTTP-трафик можно указав номер порта или имя сервиса (имена берутся из файла /etc/services): ufw allow 80 ufw allow http

    Если сервис использует протокол UDP (например, DNS-сервер), протокол указывается явно: ufw allow 53/udp

    2. Расширенные правила (IP-адреса и подсети) Часто требуется ограничить доступ к сервису, разрешив его только для определенного IP-адреса или корпоративной подсети. Например, доступ к базе данных MySQL (порт 3306) должен быть открыт только для серверов из внутренней подсети 10.0.0.0/24.

    Синтаксис расширенного правила строится по схеме: ufw [allow/deny] from <источник> to <назначение> port <порт> proto <протокол>.

    > Важно: ufw читает правила сверху вниз. Как только пакет совпадает с правилом, поиск прекращается. Если вы сначала добавите правило allow from any to any port 80, а затем deny from 192.168.1.100 to any port 80, злоумышленник всё равно получит доступ, так как сработает первое разрешающее правило.

    Для управления порядком используется нумерованный вывод: ufw status numbered

    Чтобы вставить запрещающее правило на первую позицию: ufw insert 1 deny from 192.168.1.100

    Удаление правила происходит по его номеру: ufw delete 1

    Ограничение частоты подключений (Rate Limiting)

    Открытие порта SSH (ufw allow ssh) делает сервер уязвимым для атак полного перебора паролей (brute-force). ufw имеет встроенный механизм ограничения частоты подключений — limit.

    Под капотом эта команда использует модуль recent из iptables. Логика работы следующая: если с одного IP-адреса поступает более 6 попыток инициации нового соединения в течение 30 секунд, все последующие пакеты с этого адреса будут отбрасываться. Это кардинально замедляет brute-force атаки, не мешая легитимным пользователям, которые редко подключаются чаще одного раза в несколько минут.

    Интеграция с приложениями (App Profiles)

    Многие пакеты при установке в Debian (например, Nginx, Apache, OpenSSH) автоматически создают профили для ufw. Эти профили хранятся в директории /etc/ufw/applications.d/ и содержат информацию о портах, которые использует приложение.

    Просмотреть список доступных профилей: ufw app list

    Вывод может выглядеть так:

    Вместо того чтобы вручную открывать порты 80 и 443, администратор может применить профиль: ufw allow 'Nginx Full' ufw app info 'Nginx Full' (покажет, какие именно порты открывает этот профиль).

    Логирование и диагностика

    Когда легитимный трафик блокируется, необходимо проанализировать логи брандмауэра. По умолчанию логирование в ufw отключено или работает на минимальном уровне.

    Включение логирования: ufw logging on (или ufw logging low)

    Записи о заблокированных пакетах будут отправляться в системный журнал ядра и сохраняться в /var/log/ufw.log или /var/log/syslog. Строка лога выглядит примерно так:

    [UFW BLOCK] IN=eth0 OUT= MAC=... SRC=192.168.1.50 DST=10.0.0.5 LEN=60 TOS=0x00 ... PROTO=TCP SPT=45321 DPT=8080 ...

    Здесь администратор видит интерфейс (IN=eth0), IP-адрес источника (SRC), IP-адрес назначения (DST), протокол (PROTO=TCP) и целевой порт (DPT=8080). Эта информация позволяет точно определить, какое разрешающее правило необходимо добавить.

    Комплексный подход к безопасности требует совместного использования инструментов. ufw блокирует несанкционированный доступ к сервисам извне, отсекая большую часть векторов атак на сетевом уровне. Если же легитимный сервис (например, веб-сервер, доступный через открытый порт 80) скомпрометирован через уязвимость в коде, в дело вступает AppArmor. Он не позволит взломанному процессу выйти за пределы своего профиля, прочитать системные файлы или запустить несанкционированные бинарные файлы. Понимание этих двух слоев защиты — сетевого и системного — является необходимой базой перед переходом к изучению низкоуровневой контейнеризации, где изоляция строится на тех же принципах пространств имен и модулей безопасности ядра.

    9. Виртуализация и основы управления виртуальными машинами

    Виртуализация и основы управления виртуальными машинами

    Если вы посмотрите на список процессов хостовой операционной системы с запущенной виртуальной машиной, вы не увидите магии. Вы увидите обычный процесс, потребляющий память и процессорное время, которому планировщик ядра выделяет ресурсы наравне с веб-сервером или базой данных. Парадокс аппаратной виртуализации заключается в том, что внутри этого процесса работает полноценное ядро другой операционной системы, уверенное, что оно безраздельно владеет физическим железом. Чтобы эта иллюзия стала возможной без катастрофического падения производительности, процессоры и ядро Linux используют механизмы изоляции на аппаратном уровне.

    Архитектура KVM и QEMU: как Linux становится гипервизором

    Исторически гипервизоры делили на два типа: Type 1 (bare-metal, работающие прямо поверх железа, как VMware ESXi) и Type 2 (hosted, работающие поверх обычной ОС, как VirtualBox). Встроенная в ядро Linux подсистема KVM (Kernel-based Virtual Machine) стирает эту границу. Загружая модуль kvm.ko, вы превращаете стандартное ядро Debian в полноценный гипервизор первого типа.

    Аппаратная виртуализация опирается на процессорные инструкции Intel VT-x или AMD-V. В стандартной архитектуре x86 операционная система работает на нулевом кольце защиты (Ring 0), а пользовательские приложения — на третьем (Ring 3). Гостевая ОС также требует для себя Ring 0. Чтобы избежать конфликтов, аппаратные расширения вводят новый, еще более привилегированный режим — VMX Root (часто неформально называемый Ring -1). Хостовое ядро Linux переходит в VMX Root, а гостевое ядро работает в VMX Non-Root, думая, что находится в Ring 0.

    Когда гостевая ОС пытается выполнить критическую инструкцию (например, обратиться к прерываниям или изменить таблицу страниц памяти), процессор аппаратно приостанавливает выполнение гостя и передает управление хостовому ядру. Это событие называется VM Exit.

    !Цикл выполнения KVM: VM Entry и VM Exit

    Однако ядро Linux через KVM обеспечивает только виртуализацию процессора и памяти. Гостевой системе нужны диски, сетевые адаптеры, видеокарты и USB-контроллеры. Здесь в игру вступает QEMU — эмулятор аппаратуры, работающий в пространстве пользователя (Ring 3 хоста).

    Связка работает так: процесс QEMU открывает символьное устройство /dev/kvm и через системный вызов ioctl() просит ядро запустить код гостя на физическом процессоре. Пока гость считает, KVM позволяет ему работать напрямую с железом. Как только гость пытается записать данные на виртуальный диск, происходит VM Exit, KVM перехватывает запрос и передает его процессу QEMU. QEMU транслирует этот запрос в обычную запись в файл-образ на хостовой файловой системе.

    !Архитектура стека виртуализации в Linux

    Управляющий стек libvirt и утилита virsh

    Запуск виртуальной машины чистой командой qemu-system-x86_64 требует передачи десятков параметров: от топологии процессора до MAC-адресов каждого сетевого интерфейса. Ошибка в одном символе приведет к неработоспособности сети или падению производительности. Для стандартизации управления был создан libvirt — API и демон (libvirtd), который абстрагирует сложность QEMU.

    В парадигме libvirt конфигурация каждой виртуальной машины (домена) описывается структурированным XML-файлом. Демон libvirtd читает этот XML, транслирует его в монструозную строку запуска QEMU, управляет выделением ресурсов, настраивает сетевые мосты и следит за жизненным циклом процесса.

    Основным инструментом администратора для взаимодействия с libvirtd является утилита virsh.

    Создание новой виртуальной машины редко выполняется написанием XML с нуля. Для этого используют обертку virt-install. Разберем команду развертывания веб-сервера:

    Здесь мы выделяем 2 виртуальных ядра (vCPU) и 2 ГБ оперативной памяти. Параметр --os-variant debian12 критически важен: он указывает libvirt использовать оптимальные драйверы паравиртуализации (virtio) для дисков и сети, а не эмулировать устаревшие IDE-контроллеры и сетевые карты Realtek, что снизило бы скорость ввода-вывода в разы. Флаг --graphics none отключает виртуальный монитор (VNC/Spice), а --extra-args пробрасывает вывод установщика Debian в последовательный порт, позволяя нам управлять установкой прямо из текущего терминала.

    После установки управление доменом переходит к virsh. Состояния виртуальной машины напрямую соотносятся с состояниями процессов в Linux:

  • virsh start web-server-01 — запускает процесс QEMU.
  • virsh suspend web-server-01 — приостанавливает выполнение vCPU. Процесс QEMU остается в памяти, но ядро Linux перестает выделять ему кванты времени планировщика (аналог сигнала SIGSTOP).
  • virsh resume web-server-01 — возвращает машину к жизни (аналог SIGCONT).
  • virsh shutdown web-server-01 — отправляет гостевой ОС ACPI-сигнал на мягкое выключение. Гость сам корректно завершает свои процессы и отмонтирует диски.
  • virsh destroy web-server-01 — немедленно убивает процесс QEMU на хосте (эквивалент выдергивания шнура питания или отправки SIGKILL). Использовать только при зависании гостя, так как это грозит повреждением файловой системы внутри ВМ.
  • Если требуется изменить параметры машины (например, добавить оперативной памяти), прямое редактирование файлов в /etc/libvirt/qemu/ недопустимо — демон libvirtd хранит состояние в оперативной памяти и перезапишет ваши изменения. Используется команда virsh edit web-server-01. Она открывает XML во временном файле через текстовый редактор (тот же vim), а при сохранении libvirt валидирует синтаксис. Если вы допустили ошибку в тегах, virsh не применит сломанный конфиг.

    Дисковая подсистема: мощь формата qcow2

    При создании виртуального диска администратор выбирает между форматами raw и qcow2. Формат raw — это побитовая копия блочного устройства. Он обеспечивает максимальную производительность, так как не имеет метаданных. Запись в смещение виртуального диска транслируется в запись в смещение файла на хосте. Однако raw не поддерживает снапшоты на уровне файла.

    Формат qcow2 (QEMU Copy On Write) — это стандарт де-факто для KVM. Он работает по принципу динамического выделения места (sparse file). Если вы создали диск на 100 ГБ, но гостевая ОС записала только 2 ГБ, файл qcow2 на хосте будет занимать чуть больше 2 ГБ.

    Главная суперспособность qcow2 — поддержка цепочек файлов (backing files). Это позволяет создавать мгновенные «связанные клоны» (linked clones) виртуальных машин, экономя сотни гигабайт дискового пространства.

    Механизм работает следующим образом. Создается базовый образ (base image) с установленной и настроенной ОС. Этот файл переводится в режим «только чтение». Затем для новой виртуальной машины создается пустой qcow2 файл, который ссылается на базовый образ.

    !Цепочка backing files формата qcow2

    Когда гостевая ОС пытается прочитать блок данных, QEMU проверяет верхний (дельта) файл. Если блока там нет, чтение прозрачно перенаправляется в базовый образ. Когда гость записывает данные, они всегда попадают только в верхний файл.

    Создание такого диска занимает доли секунды:

    В этой команде -b указывает путь к базовому образу, а -F задает его формат. Файл web-node-1.qcow2 изначально весит несколько сотен килобайт. Вы можете развернуть 50 виртуальных машин из одного базового образа, и они будут потреблять дисковое пространство только на те данные, которые отличаются от базы (логи, уникальные конфиги, базы данных).

    > Важное правило эксплуатации: базовый образ в цепочке запрещено модифицировать или загружать. Любое изменение битов в debian-base.qcow2 разрушит консистентность всех дельта-файлов, которые на него ссылаются.

    В высоконагруженных базах данных, где накладные расходы qcow2 на трансляцию блоков становятся заметными, администраторы пробрасывают в виртуальную машину логические тома LVM напрямую (указывая в XML путь /dev/mapper/vg0-vm_disk). В этом случае виртуальная машина работает с блочным устройством без файловой системы-посредника, а функционал снапшотов берет на себя сам LVM через механизм CoW, разобранный нами ранее.

    Сетевое взаимодействие виртуальных машин

    По умолчанию libvirt создает виртуальную сеть default, которая использует устройство virbr0. Это программный коммутатор (Linux Bridge), к которому подключаются виртуальные сетевые интерфейсы (vnet0, vnet1) запущенных машин.

    Сеть default работает в режиме NAT. Хостовая машина раздает гостям приватные IP-адреса (например, из подсети 192.168.122.0/24) через встроенный легковесный DHCP-сервер dnsmasq. Чтобы виртуальные машины получили доступ в интернет, libvirt автоматически вмешивается в работу брандмауэра ОС. Он динамически добавляет правила в Netfilter (в таблицы iptables или nftables), разрешая форвардинг трафика от интерфейса virbr0 к физическому интерфейсу хоста (например, eth0) и применяя правило MASQUERADE в цепочке POSTROUTING.

    Этот подход изолирует виртуальные машины от внешней локальной сети: они могут выходить в интернет, но компьютеры из физической сети не могут инициировать соединение с ВМ напрямую.

    Если веб-сервер внутри ВМ должен быть полноправным участником физической сети хоста (получать IP-адрес от корпоративного DHCP-сервера и быть доступным снаружи), используется режим моста (Bridged Networking). В этом случае физический интерфейс хоста (eth0) лишается собственного IP-адреса и становится портом программного коммутатора br0. Виртуальные интерфейсы машин подключаются к этому же коммутатору. Трафик из физической сети проходит через br0 напрямую в виртуальную машину, минуя NAT и правила маршрутизации хоста.

    Управление ресурсами: CPU Pinning и Memory Ballooning

    Поскольку vCPU виртуальной машины — это обычные потоки (threads) процесса QEMU, планировщик ядра Linux (CFS) постоянно перемещает их между физическими ядрами процессора (pCPU) для балансировки нагрузки.

    Каждое физическое ядро имеет собственный высокоскоростной кэш L1 и L2. Если планировщик переносит vCPU с 물리ческого ядра 0 на ядро 3, весь кэш становится невалидным (cache miss). Процессору приходится заново подтягивать данные из медленной оперативной памяти. Для обычных задач это незаметно, но для систем реального времени или высоконагруженных СУБД внутри ВМ это вызывает микрозадержки (latency).

    Для жесткой привязки виртуальных ядер к физическим используется CPU Pinning. С помощью virsh vcpupin администратор может приказать ядру Linux выполнять конкретный поток vCPU только на заданном pCPU.

    В этом примере vCPU 0 привязывается к физическому ядру 2, а vCPU 1 — к физическому ядру 3. Планировщик хоста больше не имеет права мигрировать эти потоки на другие ядра, что гарантирует максимальный процент попаданий в кэш (cache hit rate).

    С оперативной памятью ситуация иная. В отличие от процессора, память нельзя просто «отобрать» у процесса QEMU извне — гостевая ОС хранит там свои структуры данных, и внешнее вмешательство приведет к падению гостя (kernel panic). Однако гипервизору часто нужно перераспределить память между машинами при оверкоммите (когда сумма выделенной памяти всех ВМ превышает физическую память хоста).

    Для безопасного изъятия памяти используется механизм Memory Ballooning (виртуальный драйвер virtio-balloon). Драйвер загружается внутри гостевой ОС и общается с гипервизором. Когда хосту не хватает RAM, он просит драйвер-баллон «надуться». Драйвер запрашивает у гостевого ядра выделение памяти под свои нужды (гостевая ОС думает, что память заняло какое-то внутреннее приложение). Получив страницы памяти, драйвер сообщает их физические адреса гипервизору. Гипервизор KVM отбирает эти страницы на уровне хоста и отдает другой виртуальной машине.

    Когда нагрузка спадает, хост просит баллон «сдуться». Драйвер освобождает память внутри гостевой ОС, и она снова может использоваться приложениями гостя. Этот процесс требует кооперации: если гостевая ОС сама испытывает жесткую нехватку памяти (OOM), она не сможет выделить страницы для баллона, и хосту придется прибегать к своппингу, что катастрофически снизит производительность всех виртуальных машин.

    Виртуальная машина в Linux — это элегантная комбинация аппаратных возможностей процессора, изоляции на уровне ядра (KVM) и эмуляции в пользовательском пространстве (QEMU). Управляющий стек libvirt превращает управление сложными аргументами процессов в работу со структурированными объектами. Понимание того, что ВМ является обычным процессом, подчиняющимся правилам планировщика, сетевого стека и файловой системы хоста, позволяет применять к виртуализации те же инструменты отладки и тюнинга, что и к обычным демонам ОС. Эта концепция изоляции процессов без эмуляции железа получит свое радикальное развитие в технологиях контейнеризации.