Пакеты, сервисы и журналы
Предыдущие темы научили работать с файлами, правами, процессами и потоками данных. Программы, которыми пользуется человек за компьютером, кто-то собрал, разложил по каталогам и записал сведения о них в базу. Серверные программы должны работать постоянно и запускаться после перезагрузки, а при сбое нужно знать, где записаны сообщения об ошибках. Тема отвечает на три вопроса: как устанавливать и удалять ПО, как управлять фоновыми службами и как читать журналы. Для этого используются инструменты предыдущих тем: sudo из темы 4, понятия процесса и сигнала из темы 7, перенаправления из темы 5 и просмотр текста из темы 3.
Изложение идёт от пакетов к службам: сначала APT и база dpkg, затем загрузка системы, юниты systemd и, наконец, журналы. Примеры выполнялись в Ubuntu 22.04 под WSL2, где systemd не является процессом номер 1; там, где нужна работающая служба, вывод помечен как пример вывода. Целевая среда курса — Ubuntu 24.04. В ней версии пакетов и systemd новее, а юнит-файлы пакетов лежат в /usr/lib/systemd/system (в 22.04 путь записывался как /lib/systemd/system, и он по-прежнему доступен через ссылку /lib → /usr/lib). Структура вывода в обеих версиях совпадает.
Пакеты и менеджер пакетов
Что такое пакет и зачем нужны зависимости
Пакет (англ. package) — архив с программой, библиотеками, документацией и описанием: именем, версией, архитектурой и списком зависимостей. Без пакетов пользователь сам собирал бы программы и разбирался, каких библиотек не хватает. Общее описание пакетов и менеджеров для начинающих дано в книге Шоттса 1. Пакетный менеджер решает это централизованно: мейнтейнер дистрибутива собирает программу один раз, проверяет совместимость и публикует результат в репозитории.
Пакеты связаны зависимостями (англ. dependencies). Программа curl использует библиотеку libcurl4 для сетевых запросов, а та, в свою очередь, — zlib1g для сжатия данных. Сведения о зависимостях записываются в описание пакета. Вот что сообщает APT о пакете curl в этой среде:
apt-cache depends curl
curl
Depends: libc6
Depends: libcurl4
Depends: zlib1g
Строки Depends перечисляют то, без чего curl не будет работать. Обратная задача тоже важна: при удалении библиотеки нужно знать, кто от неё зависит. Вот обратные зависимости libcurl4 среди установленных пакетов:
apt-cache rdepends --installed libcurl4
libcurl4
Reverse Depends:
curl
curl
Список повторяет curl дважды; причину дубля проверьте сами (подсказка: apt-cache show curl). Главное следствие: менеджер не позволит удалить libcurl4, если без неё сломаются другие программы.
Репозитории, индексы и подписи
Пакеты хранятся в репозиториях — серверах или зеркалах. Чтобы знать, какие пакеты есть и какой версии, репозиторий публикует индекс: текстовый список пакетов, версий и контрольных сумм. Команда apt update обновляет локальную копию индексов; сами программы она не меняет.
Индексы подписываются ключом дистрибутива, поэтому подменить пакет на зеркале невозможно без подделки подписи. Контрольные суммы пакетов из индекса сверяются с загруженными файлами. Ключи хранятся в каталогах /usr/share/keyrings и /etc/apt/keyrings. В этой среде есть ключ архивов Ubuntu и ключ репозитория Docker.
Путь пакета показан на рисунке: APT получает индекс, проверяет подпись и выбирает пакеты с учётом зависимостей; dpkg распаковывает архивы и записывает каждый установленный файл в свою базу.
APT решает задачу «что установить и в каком порядке» и знает о репозиториях. dpkg работает с одним файлом пакета и не знает о сети; APT вызывает его внутри себя. Поэтому состав уже установленного пакета показывает dpkg, а поиск по репозиторию — APT.
Команды APT
Основной интерфейс пользователя к APT — команда apt с подкомандами. Для сценариев и скриптов рекомендуются apt-get и apt-cache: их формат вывода стабилен, а apt может меняться между версиями. В таблице перечислены основные действия.
| Команда | Назначение | Нужен root |
|---|---|---|
apt update | обновить локальные индексы пакетов из репозиториев | да |
apt upgrade | установить новые версии уже установленных пакетов | да |
apt install имя | установить пакет вместе с зависимостями | да |
apt remove имя | удалить пакет, оставив его конфигурационные файлы | да |
apt purge имя | удалить пакет вместе с конфигурацией | да |
apt search слово | найти пакеты по слову в имени или описании | нет |
apt show имя | показать описание пакета из индекса | нет |
apt list --installed | список установленных пакетов | нет |
apt list --upgradable | список пакетов, для которых есть новая версия | нет |
Команды, которые изменяют систему, требуют прав root, поэтому в примерах они записаны с sudo. Вывод установки в этой среде получить нельзя, так как пароля sudo нет. Поэтому ниже показана симуляция: флаг -s выполняет проверки и сообщает, что было бы сделано, но ничего не меняет, и запускается без прав root.
apt-get -s install ncdu
NOTE: This is only a simulation!
apt-get needs root privileges for real execution.
Keep also in mind that locking is deactivated,
so don't depend on the relevance to the real current situation!
Reading package lists...
Building dependency tree...
Reading state information...
The following NEW packages will be installed:
ncdu
0 upgraded, 1 newly installed, 0 to remove and 36 not upgraded.
Inst ncdu (1.15.1-1 Ubuntu:22.04/jammy [amd64])
Conf ncdu (1.15.1-1 Ubuntu:22.04/jammy [amd64])
Строка The following NEW packages will be installed — главное место для проверки: здесь видно, какие пакеты добавятся, включая зависимости. Если в итоговой строке появляется ненулевое to remove, стоит разобраться, почему менеджер хочет что-то удалить.
apt-get -s install htop
NOTE: This is only a simulation!
apt-get needs root privileges for real execution.
Keep also in mind that locking is deactivated,
so don't depend on the relevance to the real current situation!
Reading package lists...
Building dependency tree...
Reading state information...
htop is already the newest version (3.0.5-7build2).
0 upgraded, 0 newly installed, 0 to remove and 36 not upgraded.
Поиск и просмотр описания можно выполнить без прав root. Команда apt show выводит сведения из индекса, в том числе зависимости и размер:
apt-cache policy curl
curl:
Installed: 7.81.0-1ubuntu1.29
Candidate: 7.81.0-1ubuntu1.29
Version table:
*** 7.81.0-1ubuntu1.29 500
500 http://archive.ubuntu.com/ubuntu jammy-updates/main amd64 Packages
500 http://security.ubuntu.com/ubuntu jammy-security/main amd64 Packages
100 /var/lib/dpkg/status
Вывод apt-cache policy отвечает на два вопроса: какая версия установлена (Installed) и какая будет установлена при обновлении (Candidate). Звёздочка у строки версии отмечает установленную. Число 500 — приоритет источника: чем он выше, тем предпочтительнее источник. Строка 100 /var/lib/dpkg/status означает, что это сама база установленных пакетов. Если Installed и Candidate совпадают, обновлений нет.
Обновление индексов и установленных пакетов в учебной работе выполняется отдельной командой, потому что apt update не меняет программы, а apt upgrade меняет их, но не удаляет пакеты с конфликтующими зависимостями. Перед обновлением полезно посмотреть список доступных обновлений:
apt list --upgradable
Listing...
cloud-init/jammy-updates 26.1-0ubuntu1~22.04.1 all [upgradable from: 25.3-0ubuntu1~22.04.1]
containerd.io/jammy 2.4.1-2~ubuntu.22.04~jammy amd64 [upgradable from: 2.2.4-1~ubuntu.22.04~jammy]
dnsmasq-base/jammy-updates 2.91-0ubuntu0.22.04.2 amd64 [upgradable from: 2.90-0ubuntu0.22.04.4]
Строка [upgradable from: ...] показывает текущую версию и новую. Имя пакета записано вместе с источником через косую черту: jammy-updates — это ветка репозитория, из которой придёт обновление.
Источники пакетов: sources.list и sources.list.d
Список репозиториев задаётся в файлах источников; правила их записи описаны в документации Debian 3. Классический формат — однострочный, он хранится в файле /etc/apt/sources.list и в каталоге /etc/apt/sources.list.d/. В этой среде основной файл Ubuntu содержит строки такого вида:
grep -v '^#' /etc/apt/sources.list | grep . | head -3
deb http://archive.ubuntu.com/ubuntu/ jammy main restricted
deb http://archive.ubuntu.com/ubuntu/ jammy-updates main restricted
deb http://archive.ubuntu.com/ubuntu/ jammy universe
Каждая строка описывает тип (deb — готовые пакеты, deb-src — исходные коды), адрес репозитория, кодовое имя выпуска (jammy) и компоненты (main, restricted, universe, multiverse). Компоненты отличаются лицензиями и уровнем поддержки: main поддерживается сообществом Ubuntu и Canonical, universe — сообществом. Сторонние репозитории обычно кладутся в /etc/apt/sources.list.d/ отдельными файлами. Вот файл репозитория Docker в этой среде:
cat /etc/apt/sources.list.d/docker.list
deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu jammy stable
Параметр в квадратных скобках — это опции репозитория, и в данном случае они записаны внутри строки. Опция signed-by указывает, каким ключом проверяется подпись этого репозитория, что повышает безопасность по сравнению со старым способом, когда ключи хранились в общей базе. Квадратные скобки здесь принадлежат синтаксису файла, а не разметке документа.
В Ubuntu 24.04 основной файл записан в другом формате — DEB822, где каждый репозиторий описан блоком из полей ключ: значение, а блоки разделяются пустой строкой. Это пример вывода, так как в среде Ubuntu 22.04 такого файла нет:
Types: deb
URIs: http://archive.ubuntu.com/ubuntu/
Suites: noble noble-updates noble-backports
Components: main restricted universe multiverse
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
Types: deb
URIs: http://security.ubuntu.com/ubuntu/
Suites: noble-security
Components: main restricted universe multiverse
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
Файл /etc/apt/sources.list.d/ubuntu.sources в Ubuntu 24.04 содержит именно такие блоки. Содержание полей то же, что и в однострочном формате: Types соответствует первому слову строки, URIs — адресу, Suites — кодовому имени, Components — компонентам, а Signed-By — ключу. Достаточно понимать, что оба формата описывают одно и то же, а менеджер читает оба.
Альтернативные способы установки
Наряду с APT существуют форматы, не зависящие от дистрибутива. Snap (Canonical) содержит собственные библиотеки и работает в изоляции; Flatpak решает похожую задачу для графических приложений. Они переносимы, но занимают больше места. Установка из исходного кода не записывает пакет в базу dpkg, поэтому обновлять и удалять программу придётся вручную. Для учебных задач почти всегда выбирается APT. В этой среде установлен snap, а flatpak нет (проверено командой which).
База dpkg и принадлежность файлов
База установленных пакетов
Менеджер dpkg хранит базу в каталоге /var/lib/dpkg. Каждый установленный пакет имеет запись о состоянии, версии и списке файлов. APT читает эту базу, чтобы знать, что уже установлено. Список установленных пакетов выводит dpkg -l. Ниже показаны три пакета из этой среды:
dpkg -l curl wget git
+++-==============-====================-============-=======================================================
ii curl 7.81.0-1ubuntu1.29 amd64 command line tool for transferring data with URL syntax
ii git 1:2.34.1-1ubuntu1.17 amd64 fast, scalable, distributed revision control system
ii wget 1.21.2-2ubuntu1.5 amd64 retrieves files from the web
Первая колонка — два символа состояния: первый задаёт желаемое действие (i — установить), второй — фактическое состояние (i — установлен). Для установленного пакета оба символа образуют ii. Далее идут имя, версия, архитектура и краткое описание. Если в первой колонке стоит rc, пакет удалён, но его конфигурация осталась — это состояние после apt remove без purge.
Общее число установленных пакетов можно получить, подсчитав строки с состоянием ii:
dpkg -l | awk '$1=="ii"' | wc -l
936
Конструкция awk '$1=="ii"' выбирает строки, у которых первое поле равно ii; команда wc -l считает их. Инструменты awk и wc подробно разбираются в темах 5 и 6, здесь важно лишь то, что вывод dpkg -l является обычным текстом, который можно обрабатывать конвейером.
Какие файлы принадлежат пакету
Список файлов пакета выводит dpkg -L. Это полезно, когда нужно понять, куда пакет положил конфигурацию, документацию или исполняемый файл:
dpkg -L curl | head -8
/.
/usr
/usr/bin
/usr/bin/curl
/usr/share
/usr/share/doc
/usr/share/doc/curl
/usr/share/doc/curl/copyright
Список начинается с корня /. и содержит каталоги, которые пакет создаёт или в которых размещает файлы. Обратная задача — по файлу узнать, какой пакет его установил — решается командой dpkg -S. Сначала выясним путь к программе, а затем спросим, кому он принадлежит:
dpkg -S "$(which curl)"
curl: /usr/bin/curl
Конструкция $(which curl) — подстановка команды, которая будет разобрана в теме 13; здесь она подставляет путь /usr/bin/curl в команду dpkg -S. Ответ означает, что файл принадлежит пакету curl. Эта операция полезна при диагностике: если непонятно, откуда в системе взялась утилита, dpkg -S даёт ответ за секунду.
Теперь о важной ловушке. Попробуем спросить о том же файле по другому пути. Файл ls в этой среде лежит в /usr/bin, а /bin — символическая ссылка на /usr/bin:
ls -ld /bin /usr/bin
lrwxrwxrwx 1 root root 7 Jan 7 2025 /bin -> usr/bin
drwxr-xr-x 2 root root 36864 Oct 10 14:51 /usr/bin
dpkg -S /bin/ls
echo "код возврата: $?"
dpkg -S /usr/bin/ls
echo "код возврата: $?"
coreutils: /bin/ls
код возврата: 0
dpkg-query: no path found matching pattern /usr/bin/ls
код возврата: 1
Оба пути указывают на один и тот же файл, но dpkg записал его под именем /bin/ls, и сравнение идёт буквально по строке. Это следствие слияния каталогов (англ. usrmerge): /bin, /sbin и /lib в современных дистрибутивах являются ссылками на варианты в /usr. Запрос с «неправильным» путём даёт ответ «пакет не найден», хотя файл существует. Код возврата 1 позволяет проверять результат в скриптах.
Сведения о пакете
Сведения об установленном пакете выводит dpkg -s, а описание из репозитория — apt show или apt-cache show. Первые строки вывода dpkg -s для python3-minimal выглядят так:
dpkg -s python3-minimal | head -4
Package: python3-minimal
Status: install ok installed
Priority: optional
Section: python
Строка Status подтверждает установку. Поля Depends, Installed-Size, Maintainer и Description в том же выводе описывают зависимости, размер и сопровождающего. Журнал операций dpkg находится в /var/log/dpkg.log, а история команд APT — в /var/log/apt/history.log. Оба файла текстовые, и по ним можно восстановить, когда и какой пакет был установлен:
tail -n 3 /var/log/apt/history.log
Commandline: /usr/bin/unattended-upgrade
Upgrade: snapd:amd64 (2.76+ubuntu22.04.1, 2.76.3+ubuntu22.04.1)
End-Date: 2026-10-10 14:51:42
Запись содержит даты начала и конца операции, командную строку, из-за которой она выполнилась (здесь — автоматическое обновление unattended-upgrade), и список изменённых пакетов с версиями до и после. Именно такие записи помогают разобраться, почему программа вдруг изменилась после обновления.
Загрузка системы
Этапы загрузки
Чтобы понимать, какие службы запускаются автоматически и когда, полезно представлять путь от включения питания до приглашения входа. Загрузка проходит несколько этапов (рисунок). Прошивка (англ. firmware, в современных компьютерах UEFI) находит загрузчик, в Ubuntu это GRUB. Загрузчик загружает ядро Linux и начальную файловую систему initramfs. Ядро монтирует корневую файловую систему и запускает первый процесс пользовательского пространства с номером 1 — систему инициализации (англ. init system).
Init-система запускает всё остальное: монтирование файловых систем, сетевые службы, веб-серверы и прочие программы, которые работают постоянно. В Ubuntu это systemd. Её процесс с номером 1 — родитель всех остальных процессов, о котором шла речь в теме 7. Если он завершится, ядро паникует, поэтому systemd устроен как долгоживущий менеджер.
systemd как процесс номер 1
Проверить, какая программа является процессом номер 1, можно через /proc или через ps. В этой среде результат показывает, что систему запустил не systemd, а загрузчик WSL:
ps -p 1 -o pid,comm
PID COMMAND
1 init(Ubuntu-22.
Команда ps обрезает имя по длине колонки, поэтому init(Ubuntu-22. — это полное имя процесса, которое WSL задаёт сам. В этой среде пакет systemd установлен (версия 249 входит в Ubuntu 22.04), но он не является init-системой текущей сессии:
systemctl --version | head -1
systemd 249 (249.11-0ubuntu3.22)
systemctl is-system-running
offline
Состояние offline и сообщение System has not been booted with systemd as init system (PID 1) при обращении к systemctl означают, что команда видит пакет systemd, но не может с ним работать, потому что systemd не был запущен первым процессом. Такое состояние встречается в WSL2 без настройки systemd, в контейнерах и на системах с другим init. В разделе о WSL2 ниже показано, как включить systemd. Ниже в примерах управления службами вывод помечен как пример вывода, если он получен не в живой системе.
Юниты и их виды
Всё, чем управляет systemd, описывается юнитами (англ. units). Тип объекта задаётся расширением: .service — процесс, который systemd запускает, контролирует и перезапускает; .target — группа юнитов, соответствующая режиму работы, например multi-user.target; .socket — сокет, по которому служба запускается при первом подключении; .timer — запуск по расписанию, подробно в теме 16.
Администрирование служб и их файлов подробно описано в руководстве Немета и соавторов 2. Юнит-файлы хранятся в /usr/lib/systemd/system (поставляются пакетами; в выводе Ubuntu 22.04 ниже этот каталог записан как /lib/systemd/system) и в /etc/systemd/system (файлы администратора). Одноимённый файл в /etc заменяет файл из /lib, поэтому пакет можно переопределить, не трогая его файлы. Какой пакет поставил юнит, покажет dpkg -S:
dpkg -S /lib/systemd/system/cron.service
cron: /lib/systemd/system/cron.service
Управление службами systemd
Команды systemctl
Команда systemctl — основной интерфейс к systemd; полный список её опций выводит man systemctl 5. Она отправляет запросы менеджеру systemd через системную шину и выводит ответ. В таблице перечислены команды, которые понадобятся в лабораторной работе. Все команды, кроме просмотра состояния, требуют прав root, поэтому в примерах используется sudo.
| Команда | Назначение |
|---|---|
systemctl status имя | состояние службы, последние строки журнала, главный процесс |
sudo systemctl start имя | запустить службу сейчас |
sudo systemctl stop имя | остановить службу сейчас |
sudo systemctl restart имя | остановить и запустить снова |
sudo systemctl enable имя | включить автозапуск при загрузке |
sudo systemctl disable имя | выключить автозапуск |
systemctl is-enabled имя | проверить, включён ли автозапуск |
sudo systemctl daemon-reload | перечитать юнит-файлы после их изменения |
systemctl list-units --type=service | список загруженных служб |
Важно различать start и enable. Команда start запускает службу в текущей сессии, но после перезагрузки она не вернётся, если не включён автозапуск. Команда enable ничего не запускает, она создаёт ссылку, по которой systemd запустит службу при загрузке. Это разделение похоже на различие между «запустить программу» и «добавить её в автозагрузку» в настольных системах, но в Linux оно выражено явно.
Вот как выглядит каталог, в котором systemd хранит включённые при загрузке службы. Каждая ссылка указывает на юнит-файл в /lib/systemd/system, а её существование и означает автозапуск:
ls -l /etc/systemd/system/multi-user.target.wants/ | head -4
total 0
lrwxrwxrwx 1 root root 41 Jan 7 2025 console-setup.service -> /lib/systemd/system/console-setup.service
lrwxrwxrwx 1 root root 38 Mar 2 2026 containerd.service -> /lib/systemd/system/containerd.service
lrwxrwxrwx 1 root root 32 Jan 7 2025 cron.service -> /lib/systemd/system/cron.service
Команда systemctl enable cron создала бы такую же ссылку cron.service в этом каталоге. Ссылка создаётся по секции [Install] юнит-файла, которую мы рассмотрим в разделе о собственной службе. Команда disable её удаляет. В живой системе systemctl status показывает строку Loaded (путь к юниту и признак enabled), строку Active (состояние), Main PID (главный процесс) и дерево CGroup со всеми процессами службы. Пример вывода здесь не приводится, потому что systemd в этой среде не запущен.
Состояния службы
Служба в systemd проходит состояния inactive (не запущена), activating (запускается), active (работает), deactivating (останавливается) и failed (завершилась с ошибкой и не перезапускается). Главное различие для эксплуатации — между inactive и failed: первое означает намеренную остановку, второе — сбой. Поэтому при диагностике сначала смотрят systemctl status, и только потом перезапускают службу.
Зависимости описываются в секции [Unit] директивами After, Requires и Wants. After задаёт только порядок запуска. Requires — жёсткую зависимость: если нужный юнит не запустился, зависимый не стартует. Wants — мягкую: сбой нужного юнита зависимый не останавливает. Поэтому веб-приложению, которому нужна сеть, пишут After=network.target: это задаёт очерёдность, но не гарантирует, что сеть реально работает.
Собственная служба: юнит-файл построчно
Структура юнит-файла
Юнит-файл — это текст в формате ini: секции в квадратных скобках и пары «имя = значение». Квадратные скобки здесь синтаксис самого файла, и в тексте мы их записываем только внутри кода. Рассмотрим реальный юнит-файл службы cron, поставленный пакетом. Он короткий и содержит все три основные секции:
cat /lib/systemd/system/cron.service
[Unit]
Description=Regular background program processing daemon
Documentation=man:cron(8)
After=remote-fs.target nss-user-lookup.target
[Service]
EnvironmentFile=-/etc/default/cron
ExecStart=/usr/sbin/cron -f -P $EXTRA_OPTS
IgnoreSIGPIPE=false
KillMode=process
Restart=on-failure
[Install]
WantedBy=multi-user.target
Секция [Unit] описывает юнит и его отношения с другими: название, документацию и порядок запуска. Секция [Service] содержит сведения о самом процессе: что запускать, с какими переменными окружения, как останавливать и при каком условии перезапускать. Секция [Install] нужна не во время работы службы, а при включении автозапуска командой enable: она задаёт, в какую цель добавить ссылку. Для cron это multi-user.target, то есть ссылка появится в каталоге multi-user.target.wants, который мы рассматривали выше.
Строка ExecStart=/usr/sbin/cron -f -P $EXTRA_OPTS требует пояснения. systemd не запускает программу через командную оболочку, поэтому $EXTRA_OPTS подставляет сам systemd из файла, заданного в EnvironmentFile=. Это не подстановка bash, и правила кавычек из темы 13 здесь не действуют. Такое отличие часто становится причиной ошибок: команда, которая работает в терминале, в юните может вести себя иначе, потому что в ней нет оболочки.
Синтаксис юнит-файла имеет ещё одно правило, которое легко нарушить. Комментарием считается только строка, начинающаяся с символа # или ;. Значение, записанное после знака равенства, тянется до конца строки, поэтому комментарий в конце строки попадает в значение. Если написать User=student # имя пользователя, systemd попытается запустить службу от пользователя с именем student # имя пользователя, которого не существует.
Директивы собственной службы
Для учебного приложения из лабораторной работы понадобится юнит, который запускает Python-сервер, перезапускает его при сбое и работает от обычного пользователя. В таблице перечислены директивы такого юнита, а сами значения проверены по документации systemd.service и systemd.exec.
| Директива | Секция | Значение | Назначение |
|---|---|---|---|
Description | [Unit] | строка | название, которое видно в systemctl status |
After | [Unit] | network.target | запускать после поднятия сети |
Type | [Service] | simple | главный процесс — тот, что указан в ExecStart |
User | [Service] | имя пользователя | от чьего имени работает процесс; по умолчанию root |
WorkingDirectory | [Service] | путь | рабочий каталог процесса; по умолчанию / |
ExecStart | [Service] | команда с путём | команда запуска |
Environment | [Service] | ИМЯ=значение | переменная окружения процесса |
Restart | [Service] | on-failure | перезапускать при ненулевом коде выхода и сигнале |
RestartSec | [Service] | 2 | пауза перед перезапуском в секундах |
WantedBy | [Install] | multi-user.target | цель, в которую добавляется автозапуск |
По документации systemd 4, Type=simple — значение по умолчанию при заданном ExecStart=. Без User= служба работает от root, поэтому службу, которой не нужны права суперпользователя, запускают от обычной учётной записи. По умолчанию WorkingDirectory указывает на / — частая причина ошибок при запуске скриптов по относительному пути. Поэтому в шаблоне использованы абсолютные пути и к интерпретатору, и к скрипту.
Шаблон юнит-файла для лабораторной работы находится в окружении ЛР. Для иллюстрации здесь приведено его содержимое без комментариев, которые в самом файле занимают отдельные строки:
[Unit]
Description=Учебный HTTP-сервис лабораторной работы 9
After=network.target
[Service]
Type=simple
User=student
WorkingDirectory=/home/student/lab-09/lr-env
ExecStart=/usr/bin/python3 /home/student/lab-09/lr-env/app.py
Environment=APP_PORT=8080
Restart=on-failure
RestartSec=2
[Install]
WantedBy=multi-user.target
Проверка юнит-файла
Ошибки в юнит-файле systemd выявляет systemd-analyze verify без запуска службы. Он находит неизвестные директивы и секции, но не проверяет относительный путь к скрипту: команда ExecStart=python3 app.py синтаксически верна, а app.py ищется в каталоге /. Такую ошибку видно только при работе службы или при попытке выполнить команду вручную в каталоге systemd.
cd ~/lab-09/lr-env
systemd-analyze verify templates/app.service
/lib/systemd/system/snapd.service:23: Unknown key name 'RestartMode' in section 'Service', ignoring.
Предупреждение относится не к шаблону, а к системному юниту snapd.service, который проверяется вместе с нужными зависимостями. Это важно: вывод verify может содержать сообщения о чужих файлах, и нужно отличать их по пути в начале строки. Для неисправного юнита:
systemd-analyze verify broken.service
/lib/systemd/system/snapd.service:23: Unknown key name 'RestartMode' in section 'Service', ignoring.
/home/student/lab-09/lr-env/broken.service:7: Unknown key name 'Restart' in section 'Unit', ignoring.
Вторая строка указывает файл, номер строки и проблему: директива Restart записана в секции [Unit], где её нет, и systemd её игнорирует. Такую ошибку verify видит, а вот относительный путь в ExecStart — нет (см. выше). Поэтому verify стоит запускать до установки службы, а вторую ошибку искать выполнением команды вручную.
Перезапуск при сбое
Директива Restart=on-failure задаёт перезапуск, если процесс завершился ненулевым кодом, был убит сигналом или не ответил в срок. По таблице из документации systemd.service выход с кодом 0 перезапуска не вызывает, а сигналы SIGHUP, SIGINT, SIGTERM и SIGPIPE считаются чистым завершением. Поэтому systemctl stop не приводит к перезапуску: остановку инициировал сам systemd. Перезапуск касается только аварийного завершения.
Схема цикла показана на рисунке: служба падает, systemd ждёт RestartSec секунд и запускает её снова. При частых сбоях срабатывает ограничение частоты. Окно и число попыток задаются директивами StartLimitIntervalSec и StartLimitBurst секции [Unit]; по документации systemd-system.conf значения по умолчанию — 10 секунд и 5 запусков.
Проверим поведение самого учебного приложения. Сервис app.py при запросе /crash завершается с кодом 3 через os._exit, то есть так же, как упавшая программа. Запустим его на порту 8094, поскольку порт 8080 в этой среде уже занят, и проверим ответы. Запуск выполняется от пользователя без прав root, порт выше 1024:
cd ~/lab-09/lr-env
APP_PORT=8094 python3 app.py &
sleep 1
curl -s -i http://127.0.0.1:8094/
HTTP/1.0 200 OK
Server: BaseHTTP/0.6 Python/3.10.12
Date: Mon, 12 Oct 2026 11:20:41 GMT
Content-Type: text/plain; charset=utf-8
Content-Length: 23
OK 2026-10-12 14:20:41
Конструкция & запускает процесс в фоне, а sleep 1 даёт серверу время открыть порт. Флаг -i в curl выводит заголовки ответа. Теперь обратимся к /crash и проверим код выхода процесса. Так как процесс запущен в фоне, код выхода получаем через wait:
APP_PORT=8092 python3 app.py > /tmp/app.log 2>&1 &
pid=$!
sleep 1
curl -s http://127.0.0.1:8092/crash
echo "код curl: $?"
wait $pid
echo "код выхода сервиса: $?"
cat /tmp/app.log
код curl: 52
код выхода сервиса: 3
2026-10-12 14:20:41,334 INFO сервис запущен на http://127.0.0.1:8092 (pid 32488)
2026-10-12 14:20:42,289 ERROR получен запрос /crash: аварийное завершение с кодом 3
Код curl 52 означает «пустой ответ сервера»: соединение оборвано до отправки заголовков. Код выхода 3 — ненулевой, поэтому при Restart=on-failure systemd перезапустил бы службу. Строка ERROR в журнале попала в stderr, а значит, при запуске под systemd она окажется в журнале journald. Теперь проверим реакцию на сигнал TERM, который systemd посылает при остановке:
APP_PORT=8093 python3 app.py > /tmp/app3.log 2>&1 &
pid=$!
sleep 1
kill -TERM $pid
wait $pid
echo "код выхода после SIGTERM: $?"
код выхода после SIGTERM: 143
Код 143 — это 128 плюс номер сигнала 15 (SIGTERM); так завершение по сигналу записывает оболочка. systemd хранит то же событие иначе: в журнале и в systemctl status будет code=killed, status=15/TERM, а не число 143. Согласно таблице systemd.service, SIGTERM относится к чистым завершениям, поэтому Restart=on-failure не перезапускает службу после systemctl stop. Сравнение двух кодов показывает, зачем systemd различает причины завершения: падение и остановка по команде означают разное.
Журналы системы
journald и классический syslog
Служба, которая пишет сообщения только в терминал, не годится для постоянной работы: после закрытия окна сообщения исчезают. Поэтому у systemd есть подсистема журналирования systemd-journald. Она собирает сообщения служб, ядра и самого systemd и хранит их в двоичном виде в /var/log/journal, поэтому читать журнал нужно программой journalctl, а не cat.
Классический способ — текстовые файлы в /var/log, которые ведёт демон syslog (в Ubuntu это rsyslog). В каталоге /var/log лежат привычные файлы: syslog с общими сообщениями, auth.log с входами и sudo, dpkg.log с установкой пакетов, а также подкаталоги, например apt/ для истории APT. Стандарт иерархии файловой системы (FHS) закрепляет назначение /var/log за файлами журналов 7. Список файлов в этой среде:
ls -l /var/log/syslog /var/log/dpkg.log
-rw-r--r-- 1 root root 20070 Oct 10 14:51 /var/log/dpkg.log
-rw-r----- 1 syslog adm 518399 Oct 11 03:08 /var/log/syslog
Права на журналы — частая причина сообщения «Permission denied». Решение здесь — членство в группе adm, а не запуск от root.
Текстовые файлы в /var/log просматриваются командами из темы 3, а поиск — командами из темы 6. Строка классического журнала содержит время, узел, программу и сообщение. Пример ниже получен через journalctl, потому что в этой среде журнал systemd содержит те же записи, что и текстовый syslog:
journalctl --no-pager -u libvirtd -n 3 -o short
Oct 11 03:08:59 host systemd[1]: Stopping Virtualization daemon...
Oct 11 03:08:59 host systemd[1]: libvirtd.service: Deactivated successfully.
Oct 11 03:08:59 host systemd[1]: Stopped Virtualization daemon.
Имя узла в выводе заменено на host. Каждая строка содержит время, узел, программу с номером процесса в квадратных скобках (здесь systemd[1] — сам systemd) и сообщение. Квадратные скобки являются частью журнала, а не разметкой курса. Строка Deactivated successfully соответствует состоянию, о котором шла речь выше.
journalctl: основные опции
Команда journalctl читает журнал и умеет фильтровать записи. В таблице перечислены опции, которые понадобятся в лабораторной работе.
| Опция | Назначение |
|---|---|
-u имя | только записи юнита (службы) |
-f | следить за новыми записями, как tail -f из темы 3 |
--since время, --until время | записи за период |
-p уровень | только записи уровня не ниже заданного |
-n число | последние N записей |
-b | записи текущей загрузки; -b -1 — предыдущей |
--list-boots | список загрузок, которые есть в журнале |
-o short и другие | формат вывода |
Уровни важности по убыванию серьёзности: emerg, alert, crit, err, warning, notice, info, debug. Фильтр -p err показывает все записи уровня err и выше, то есть также crit, alert и emerg. Вот примеры из журнала этой среды: список загрузок и записи об ошибках.
journalctl --list-boots --no-pager
-1 180512f1e7bb4b888c82de3940454374 Sun 2026-10-11 00:38:17 MSK—Sun 2026-10-11 01:53:49 MSK
0 2bdde8f67ea34589be21980bb572437a Sun 2026-10-11 03:04:25 MSK—Sun 2026-10-11 03:09:00 MSK
Каждая строка описывает загрузку: номер относительно текущей (0 — текущая, -1 — предыдущая), идентификатор и интервал времени. Идентификатор нужен для точного указания загрузки в -b. Для поиска ошибок за период:
journalctl --no-pager -p err --since "2026-10-11 03:00" --until "2026-10-11 03:10" -o short
Oct 11 03:07:49 host libvirtd[330]: internal error: Network is already in use by interface virbr0
Oct 11 03:07:49 host libvirtd[330]: internal error: Failed to get udev device for syspath '/sys/devices/virtual/dmi/id'
Oct 11 03:08:59 host systemd[1]: Failed unmounting /init.
Фильтр по времени и уровню позволяет быстро сузить поиск, даже если журнал содержит миллионы записей. Опция -f в живой системе работает как tail -f и выводит записи по мере их появления. В этой среде она не демонстрируется, потому что журнал не растёт; при работе с собственной службой её стоит запускать во втором окне терминала и выполнять запрос /crash в первом. Журнал службы выводится командой journalctl -u app, если юнит называется app.service.
Уровень записи, по которому работает -p, задаёт не текст сообщения, а поле приоритета. Всё, что служба пишет в stdout и stderr, journald по умолчанию записывает с уровнем info, даже если в строке стоит слово ERROR. Чтобы строка получила другой уровень, программа начинает её с префикса в угловых скобках, как ядро: <3> — err, <4> — warning, <6> — info; journald распознаёт префикс (директива SyslogLevelPrefix=yes включена по умолчанию, см. systemd.exec 4) и удаляет его из текста. Учебный app.py добавляет такой префикс, только когда запущен под systemd: это видно по переменной окружения JOURNAL_STREAM, которую systemd передаёт службе. При ручном запуске префикса нет.
Ротация файловых журналов: logrotate
Текстовый журнал, в который программа дописывает строки, растёт без предела. Служба, которая пишет в файл годами, рано или поздно заполнит диск, а искать в файле на десятки гигабайт неудобно. Поэтому файловые журналы ротируют (англ. log rotation): когда файл достигает заданного возраста или размера, его переименовывают, программе выдают новый пустой файл, старые копии сжимают, а самые старые удаляют. Журнал journald в этом смысле устроен иначе: он сам следит за общим размером и удаляет старые записи, так что отдельная утилита для него не нужна. Ротация нужна именно файлам в /var/log и собственным журналам приложений 2.
Для ротации в Ubuntu служит logrotate. Главный файл /etc/logrotate.conf содержит значения по умолчанию, а каталог /etc/logrotate.d/ — по одному файлу на пакет или службу; пакеты кладут туда свои настройки при установке. Вот главный файл без комментариев:
grep -v '^#' /etc/logrotate.conf | grep -v '^$'
weekly
su root adm
rotate 4
create
include /etc/logrotate.d
Читаем по строкам: ротировать раз в неделю, выполнять действия от имени root и группы adm (это нужно, потому что каталог /var/log принадлежит этой группе), хранить 4 старые копии, создавать новый файл после ротации, затем подключить файлы из /etc/logrotate.d. Пример настройки пакета, которая использует те же директивы:
cat /etc/logrotate.d/dpkg
/var/log/dpkg.log {
monthly
rotate 12
compress
delaycompress
missingok
notifempty
create 644 root root
}
Блок состоит из шаблона файла (или нескольких) и директив в фигурных скобках. Самые употребительные директивы собраны в таблице.
| Директива | Назначение |
|---|---|
daily, weekly, monthly | ротировать раз в день, неделю, месяц |
size 10M | ротировать, когда файл достиг размера (вместо периода) |
rotate N | сколько старых копий хранить; более старые удаляются |
compress | сжимать старые копии gzip (суффикс .gz) |
delaycompress | не сжимать самую свежую копию до следующей ротации |
missingok | не считать ошибкой отсутствие файла |
notifempty | не ротировать пустой файл |
create режим владелец группа | создать новый пустой файл с этими правами |
copytruncate | скопировать файл, а исходный обнулить на месте |
postrotate … endscript | команды, выполняемые после ротации |
Директива delaycompress нужна потому, что программа могла ещё не закрыть переименованный файл, и сжимать его сразу рискованно; поэтому копию .1 оставляют несжатой до следующего раза. Хранение складывается из rotate и частоты: weekly и rotate 4 дают примерно месяц истории.
Здесь возникает главный вопрос ротации. Ротация переименовывает файл app.log в app.log.1, но программа, которая держит файл открытым, продолжает писать в тот же дескриптор, то есть уже в app.log.1 (дескриптор указывает на объект файла, а не на имя; см. раздел об открытых файлах в теме 7). Проверим это на учебной программе, которая пишет в журнал каждые 0,2 секунды. Ротируем без дополнительных действий и считаем строки через секунду.
0 app.log
7 app.log.1
7 total
0 app.log
12 app.log.1
12 total
Новый app.log остаётся пустым, а запись продолжается в app.log.1: ротация не достигла цели. Есть два способа исправить ситуацию.
- Сообщить программе, что нужно переоткрыть файл. Это делает блок
postrotate, в котором обычно посылают сигнал, чаще всего SIGHUP (сигналы описаны в теме 7): программа закрывает старый файл и открывает файл по имени заново. Так устроены демоны вроде rsyslog и nginx, обработчик сигнала есть в самой программе. Если же обработчика нет, SIGHUP по умолчанию завершает процесс. - Использовать
copytruncate. Утилита копирует содержимое вapp.log.1и обнуляет исходный файл, не меняя его имени и дескриптора. Программе ничего не нужно знать, но между копированием и обнулением может потеряться несколько строк, а программа должна открывать файл в режиме дозаписи (в оболочке —2>>app.log, а не2>app.log), иначе после обнуления в файле появятся нули.
Оба варианта проверены на той же учебной программе: при postrotate с kill -HUP и при copytruncate новые записи оказываются в app.log, а прежние — в app.log.1.
== hup
6 app.log
5 app.log.1
== trunc
6 app.log
5 app.log.1
Конфигурацию проверяют без изменений в режиме отладки logrotate -d: он читает конфиг, печатает, что собирается сделать, но файлы не трогает. Принудительный запуск -f игнорирует расписание, а опция -s файл задаёт собственный файл состояния, в котором logrotate помнит время последней ротации каждого журнала. По умолчанию состояние лежит в /var/lib/logrotate/status и доступно только root; с собственным файлом всё работает без прав администратора, поэтому его удобно использовать для экспериментов. Создадим конфиг для журнала учебного приложения app.log, который приложение пишет через 2>>app.log.
cat > app-logrotate.conf <<CONF
$PWD/app.log {
daily
rotate 3
compress
delaycompress
missingok
notifempty
copytruncate
}
CONF
logrotate -d -f -s state app-logrotate.conf
Вывод -d приведён с сокращением, путь заменён на /home/student/lab-09/lr-env. Сначала logrotate сообщает, какой файл рассматривает, затем принимает решение и перечисляет действия.
reading config file app-logrotate.conf
Reading state from file: state
Handling 1 logs
rotating pattern: /home/student/lab-09/lr-env/app.log forced from command line (3 rotations)
empty log files are not rotated, old logs are removed
considering log /home/student/lab-09/lr-env/app.log
log needs rotating
rotating log /home/student/lab-09/lr-env/app.log, log->rotateCount is 3
copying /home/student/lab-09/lr-env/app.log to /home/student/lab-09/lr-env/app.log.1
truncating /home/student/lab-09/lr-env/app.log
Настоящая ротация выполняется той же командой без -d. Ниже каталог после трёх запусков с новой записью в журнале между ними: свежая копия .1 не сжата (delaycompress), более старые сжаты и пронумерованы.
logrotate -s state -f app-logrotate.conf
ls -l
total 24
-rw-r--r-- 1 student student 129 app-logrotate.conf
-rw-r--r-- 1 student student 0 app.log
-rw-r--r-- 1 student student 62 app.log.1
-rw-r--r-- 1 student student 88 app.log.2.gz
-rw-r--r-- 1 student student 161 app.log.3.gz
-rw-r--r-- 1 student student 3104 app.py
-rw-r----- 1 student student 74 state
Файл состояния хранит время последней ротации; поэтому без -f повторный запуск в тот же день ничего не делает. Ошибки в конфигурации -d показывает сразу, не затрагивая файлы. Например, при rotate three вместо числа:
error: bad.conf:3 bad rotation count 'three'
error: found error in /tmp/lr/app.log , skipping
Надо помнить, что logrotate не работает постоянно: его запускает по расписанию таймер systemd logrotate.timer (в Ubuntu раз в сутки, OnCalendar=daily), который стартует службу logrotate.service. Поэтому директивы hourly и size не сработают чаще, чем таймер. Таймеры systemd и cron рассмотрены в теме 16.
Размер журнала journald ограничивается собственными настройками, а не logrotate. Сколько места занимают журналы, показывает journalctl --disk-usage, а предел задают директивы SystemMaxUse= (максимум для постоянного журнала в /var/log/journal), SystemKeepFree= (сколько места оставить свободным) и RuntimeMaxUse= (для журнала в памяти, /run/log/journal) в файле /etc/systemd/journald.conf или в фрагменте каталога /etc/systemd/journald.conf.d/ 4.
journalctl --disk-usage
Archived and active journals take up 21.3M in the file system.
По умолчанию journald занимает не более 10% файловой системы (но не более 4 ГБ) и оставляет свободными 15%. Однократное освобождение места выполняют командами sudo journalctl --vacuum-size=200M (оставить не более 200 МБ), --vacuum-time=2weeks (удалить записи старше двух недель) и --vacuum-files=N (оставить N файлов). Без root команда не может удалить архивные файлы:
Failed to delete archived journal /var/log/journal/e11e623ad3004c0899baa1dc2c127f74/system@00065d855eaada6e-19d1c40eb96d6822.journal~: Permission denied
Vacuuming done, freed 0B of archived journals from /var/log/journal/e11e623ad3004c0899baa1dc2c127f74.
С правами администратора вывод заканчивается строкой вида «Vacuuming done, freed 120.0M of archived journals» (пример вывода). Постоянное ограничение задают в конфигурации, например SystemMaxUse=200M, и применяют командой sudo systemctl restart systemd-journald.
journald в WSL2
В WSL2 systemd по умолчанию отключён, поэтому journald не запускается и журнал не ведётся, даже если пакет установлен. Включение выполняется в файле /etc/wsl.conf. Документация Microsoft описывает для этого секцию [boot] с параметром systemd=true 6. Файл редактируется от root, и изменения вступают в силу после полного закрытия дистрибутива из PowerShell:
[boot]
systemd=true
Команда wsl --shutdown выполняется в Windows и останавливает все работающие дистрибутивы, поэтому её запускают, когда работа не выполняется. После перезапуска systemctl is-system-running должна показать running или degraded, а не offline. Для systemd в WSL2 нужна версия WSL не ниже 0.67.6 (проверка: wsl --version в Windows).
Если systemd в WSL2 не включается, лабораторная работа предлагает запасной путь: приложение запускается вручную, а журнал заменяется выводом в файл. Это не служба, но позволяет проверить логику приложения. Основная цель темы — понимать управление службой systemd, поэтому включение systemd рекомендуется.
Время и имя узла: timedatectl и hostnamectl
Журналы бесполезны, если время в них неверное: при разборе аварии записи разных машин сопоставляют по меткам времени. Поэтому systemd берёт под управление и базовые настройки системы. Команда timedatectl показывает и меняет время, часовой пояс и режим синхронизации, а hostnamectl — имя узла и сведения о системе. Обе обращаются к службам systemd (systemd-timedated и systemd-hostnamed) по шине D-Bus, поэтому работают только под управлением systemd.
Часы компьютера хранят время в UTC, а часовой пояс (англ. time zone) задаёт, как показывать его человеку: он выбирается ссылкой /etc/localtime на файл из базы часовых поясов /usr/share/zoneinfo. Синхронизацию с эталоном ведёт протокол NTP (англ. Network Time Protocol): в Ubuntu эту работу по умолчанию выполняет лёгкий клиент systemd-timesyncd, а на серверах с повышенными требованиями устанавливают chrony. Пример вывода timedatectl на машине с systemd (в этой среде команда не запускается, см. ниже):
Local time: Sun 2026-10-18 19:51:11 MSK
Universal time: Sun 2026-10-18 16:51:11 UTC
RTC time: Sun 2026-10-18 16:51:11
Time zone: Europe/Moscow (MSK, +0300)
System clock synchronized: yes
NTP service: active
RTC in local TZ: no
Строки читаются так: местное и всеобщее время; время аппаратных часов (RTC); часовой пояс с аббревиатурой и смещением; признак того, что часы синхронизированы; состояние службы NTP; хранятся ли аппаратные часы в местном времени (для Linux правильно no, значение yes встречается при двойной загрузке с Windows). Настройки меняет root:
| Команда | Действие |
|---|---|
timedatectl list-timezones | список часовых поясов |
sudo timedatectl set-timezone Europe/Moscow | сменить часовой пояс |
sudo timedatectl set-ntp true | включить синхронизацию по NTP |
timedatectl timesync-status | состояние systemd-timesyncd, сервер и смещение |
hostnamectl | имя узла, версия системы и ядра |
sudo hostnamectl set-hostname имя | сменить имя узла |
Пример вывода hostnamectl, показывающий поля, полезные при отладке:
Static hostname: host
Icon name: computer-vm
Chassis: vm
Machine ID: e11e623ad3004c0899baa1dc2c127f74
Boot ID: 2bdde8f67ea34589be21980bb572437a
Virtualization: wsl
Operating System: Ubuntu 22.04.5 LTS
Kernel: Linux 6.6.87.2-microsoft-standard-WSL2
Architecture: x86-64
Этот пример получен в Ubuntu 22.04 под WSL2; в Ubuntu 24.04 отличаются версии в строках Operating System и Kernel, а более новый systemd может вывести и несколько дополнительных полей.
В WSL2 без systemd обе команды завершаются ошибкой, потому что обращаться не к кому. Вот реальный вывод в этой среде:
timedatectl
System has not been booted with systemd as init system (PID 1). Can't operate.
Failed to connect to bus: Host is down
Нужные сведения доступны другими способами, которые работают и в WSL2: часовой пояс виден по файлу /etc/timezone и ссылке /etc/localtime, время — по date, имя узла — по hostname.
date; date -u
cat /etc/timezone
ls -l /etc/localtime
Sun Oct 18 19:56:03 MSK 2026
Sun Oct 18 16:56:03 UTC 2026
Europe/Moscow
lrwxrwxrwx 1 root root 33 Oct 18 13:33 /etc/localtime -> /usr/share/zoneinfo/Europe/Moscow
В WSL2 время обычно синхронизирует не NTP-клиент внутри дистрибутива, а виртуальная машина: ядро получает время от Hyper-V (в командной строке ядра есть параметр hv_utils.timesync_implicit=1), поэтому после выхода компьютера из сна часы дистрибутива могут отставать до очередной синхронизации. Если после сна время в журнале расходится с реальным, помогает команда sudo hwclock -s или перезапуск дистрибутива командой wsl --shutdown в Windows. Часовой пояс в WSL2 меняют правкой ссылки /etc/localtime (например, sudo ln -sf /usr/share/zoneinfo/Europe/Moscow /etc/localtime) или командой sudo dpkg-reconfigure tzdata. Имя узла хранится в /etc/hostname; в WSL2 его можно задать и параметром hostname секции network файла /etc/wsl.conf 6.
Подсистема аудита безопасности ядра: auditd
Отличие аудита от системных журналов
Стандартные службы журналирования (systemd-journald, rsyslog) работают в пространстве пользователя и пассивно принимают текстовые сообщения от программ. Если процесс скомпрометирован злоумышленником или администратор напрямую модифицирует бинарные файлы в обход стандартных утилит, в системный журнал ничего не попадёт.
Подсистема аудита ядра Linux Audit Framework (LAF) спроектирована для решения задач информационной безопасности и расследования инцидентов 8. Она встроена непосредственно в ядро Linux и перехватывает системные вызовы на входе и выходе из ядра ещё до того, как процесс получит результат. Демон auditd получает события ядра через специальный сокет Netlink (NETLINK_AUDIT) и асинхронно записывает их в защищённый журнал /var/log/audit/audit.log.
Конфигурация правил аудита: утилита auditctl
Правила аудита загружаются в ядро утилитой auditctl или автоматически из файлов каталога /etc/audit/rules.d/*.rules службой augenrules. Выделяют три основных класса правил:
-
Контроль целостности файлов и каталогов (File Watches):
sudo auditctl -w /etc/shadow -p wa -k shadow_access sudo auditctl -w /etc/sudoers -p wa -k sudoers_tamperФлаг
-wуказывает путь, параметр-p waзадаёт отслеживаемые действия (w— запись,a— изменение атрибутов/прав), а метка-k(key) присваивает событию идентификатор для быстрого поиска в журнале. -
Аудит системных вызовов (Syscall Rules):
sudo auditctl -a always,exit -F arch=b64 -S execve -k command_executionПравило регистрирует каждый запуск исполняемых файлов через системный вызов
execveв 64-битной архитектуре, фиксируя полный список переданных аргументов, текущий рабочий каталог, эффективный UID (euid) и исходный UID аутентификации (auid), который невозможно сбросить даже через командуsudo su. -
Защита конфигурации аудита от отключения: Команда
auditctl -e 2переводит подсистему аудита ядра в заблокированный режим (immutable mode): ни одно правило не может быть изменено или выгружено до полной перезагрузки операционной системы, что предотвращает сокрытие следов нарушителем с правами root.
Поиск и отчёты: ausearch и aureport
Прямое чтение файла /var/log/audit/audit.log затруднено из-за сложного формата записей (тип события, временная метка epoch, шестнадцатеричные аргументы). Для криминалистического анализа применяются две штатные утилиты:
ausearch— выборочный поиск событий:
Флаг# Поиск всех попыток обращения к /etc/shadow по ключу: sudo ausearch -k shadow_access -i # Поиск событий сбоев аутентификации за последние сутки: sudo ausearch -m USER_AUTH -sv no -ts yesterday-i(interpret) автоматически переводит числовые UID в имена пользователей, а шестнадцатеричные строки аргументов — в читаемый текст.aureport— сводные статистические отчёты:sudo aureport -u --failed # отчёт по неудачным попыткам входа пользователей sudo aureport -f -i # сводка обращений к файлам sudo aureport -x --summary # статистика запуска исполняемых файлов
Итоги темы
Пакеты — единица установки программного обеспечения: они описывают файлы, зависимости и подписи. APT получает индекс из репозитория, проверяет подпись, выбирает пакеты с учётом зависимостей и передаёт архивы dpkg, который распаковывает их и ведёт базу установленных файлов. Команды dpkg -L, dpkg -S и apt-cache policy показывают состав пакета, владельца файла и версии, а apt-get -s моделирует установку без прав root.
Загрузка заканчивается тем, что init-система запускает службы. В Ubuntu это systemd, работающий как процесс номер 1. Службы описываются юнитами, а команды systemctl управляют текущим запуском (start, stop) и автозапуском (enable, disable). Собственная служба оформляется юнит-файлом с секциями [Unit], [Service] и [Install]; надёжность обеспечивают Restart=on-failure, User и абсолютные пути. Ошибки синтаксиса выявляет systemd-analyze verify, а ошибки путей и рабочего каталога проявляются только при работе службы.
Журналы собирает journald, и читать их следует через journalctl, фильтруя по юниту, уровню и времени. Классические файлы в /var/log дополняют этот канал, а их права определяют, кто может их читать. Для работы служб и журналов в WSL2 нужно включить systemd в /etc/wsl.conf. Файловые журналы ротирует logrotate по конфигурации из /etc/logrotate.d; программу, которая держит журнал открытым, уведомляют сигналом в блоке postrotate или применяют copytruncate, а конфигурацию проверяют запуском logrotate -d и logrotate -f -s файл. Размер журнала journald ограничивается директивой SystemMaxUse= и командой journalctl --vacuum-size. Время, часовой пояс и имя узла находятся под управлением systemd (timedatectl, hostnamectl), а в WSL2 без systemd остаются date, hostname и файлы в /etc. Следующая тема переходит к дискам, разделам и файловым системам: блочные устройства, монтирование и файл /etc/fstab; монтирование тоже управляется systemd через юниты .mount. Таймеры systemd, которые заменяют cron и запускают logrotate, рассматриваются в теме 16.
Литература
- Shotts W. The Linux Command Line: A Complete Introduction. 2nd ed. — No Starch Press, 2019. Открытая версия: https://linuxcommand.org/tlcl.php
- Nemeth E., Snyder G., Hein T. R., Whaley B., Mackin D. UNIX and Linux System Administration Handbook. 5th ed. — Addison-Wesley, 2017.
- Debian. Debian Reference / Debian Policy: пакеты и APT. — https://www.debian.org/doc/manuals/debian-reference/
- systemd. Документация systemd.unit, systemd.service, systemd.timer, journalctl. — https://www.freedesktop.org/software/systemd/man/
- The Linux man-pages project. — https://man7.org/linux/man-pages/
- Microsoft. Документация WSL. — https://learn.microsoft.com/windows/wsl/
- Linux Foundation. Filesystem Hierarchy Standard 3.0. — 2015. https://refspecs.linuxfoundation.org/FHS_3.0/fhs-3.0.html
- Grubb S. Linux Audit Documentation and User Space Tools. — 2024. https://github.com/linux-audit/audit-documentation