From 38e03809a497632891444e901955afa1d82b25c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A4=D0=B5=D0=B4=D0=BE=D1=80=D0=BE=D0=B2=20=D0=94=D0=BC?= =?UTF-8?q?=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Tue, 9 Jul 2024 13:33:45 +0300 Subject: [PATCH] full blog --- sysadmin/Linux/Bases-systemD/bases-systemd.md | 329 ++++++++++++++++++ 1 file changed, 329 insertions(+) diff --git a/sysadmin/Linux/Bases-systemD/bases-systemd.md b/sysadmin/Linux/Bases-systemD/bases-systemd.md index 721d085..52212b3 100644 --- a/sysadmin/Linux/Bases-systemD/bases-systemd.md +++ b/sysadmin/Linux/Bases-systemD/bases-systemd.md @@ -510,3 +510,332 @@ cd /etc/systemd/system #!/bin/bash echo Hello, Habr! ``` +После выдадим права на запуск: +``` +sudo chmod +x /usr/bin/habr +``` +После этого создадим `/etc/systemd/system/habr.service` со следующим содержанием: +```bash +[Unit] +Description=SimpleService +After=default.target + +[Service] +ExecStart=/usr/bin/habr + +[Install] +WantedBy=default.target +``` +После релоадним демоны и включим наш сервис: +```bash +sudo systemctl daemon-reload +sudo systemctl enable habr +``` +Разберем файл сервиса: +### Ключи времени +* Wants, Requires, Before, After (Ожидает, Требует, Перед, После) +* WantedBy, RequiresBy (Ожидается цель/служба, Требуется цель/служба) + +### Параметры перезапуска службы +* no — никогда +* always — всегда +* on-success — успешно +* on-failure — не успешно +* on-abnormal — не нормальный +* on-abort — при прерывании +* on-watchdog — Сторожевой таймер (Watchdog timer) — аппаратно реализованная схема контроля над зависанием системы. Представляет собой таймер, который периодически сбрасывается контролируемой системой. Если сброса не произошло в течение некоторого интервала времени, происходит принудительная перезагрузка системы. + +### Список целей +* default.target — модуль по-умолчанию +* graphical.target — запуск графической подсистемы +* multi-user.target — запуск консоли +* network-online.target — ожидание подключенной сети (используется в Wants) +* network.target — сеть +* network-pre.target — запуск до настройки сети (используется в Requires) +* boot-complete.target — успешная загрузка + +### Пример сервиса (SSHD, ssh daemon) +```bash +[Unit] +Description=OpenSSH server daemon +Documentation=man:sshd(8) man:sshd_config(5) +After=network.target sshd-keygen.target +Wants=sshd-keygen.target + +[Service] +Type=notify +EnvironmentFile=-/etc/crypto-policies/back-ends/opensshserver.config +EnvironmentFile=-/etc/sysconfig/sshd +ExecStart=/usr/sbin/sshd -D $OPTIONS $CRYPTO_POLICY +ExecReload=/bin/kill -HUP $MAINPID +KillMode=process +Restart=on-failure +RestartSec=42s + +[Install] +WantedBy=multi-user.target +``` + +Объяснение ниже: + +``` +[Unit] +Description – описание юнита для большего понимания +Documentation – документация по процессу sshd +After – зависимость, т.е. в данном случае запускать юнит только после запуска network.target и sshd-keygen.target +Wants – еще одна зависимость, означает желательно. В примере Wants=sshd-keygen.target, т.е. желательно чтобы было запущено sshd-keygen.target . Желательно, но не обязательно. +[Service] +Type – типы запуска служб. Могут быть: + +simple (по умолчанию) – происходит незамедлительный запуск этой службы, с учетом того что процесс не разветвляется (fork). Не используйте simple если пользуетесь очередностью запуска. Одно исключение это активация сокета. +forking – служба считается запущенной после того, после разветвления процесса с завершением родительского процесса. Используется для запуска классических демонов исключая случаи, когда в таком поведении процесса нет необходимости. Также желательно указать PIDFile=, чтобы systemd мог отслеживать основной процесс. +oneshot – удобен для скриптов, которые выполняют одно задание и завершаются. При необходимости можно задать параметр RemainAfterExit=yes, чтобы systemd считал процесс активным даже после его завершения. +notify – идентичен параметру simple, но с оговоркой, что демон пошлет systemd сигнал о своей готовности. Эталонная реализация данного уведомления представлена в libsystemd-daemon.so. +dbus – служба считается находящейся в состоянии готовности, когда указанный параметр BusName появляется в системной шине DBus. +idle – откладывается выполнение двоичного файла службы до момента выполнения всех остальных задач. В остальном поведение аналогично simple. +Далее в разделе Service + +EnvironmentFile – файлы переменного окружения +ExecStart – полный путь к исполняемому файлу программы с параметрами запуска +ExecReload – полный пусть к исполняемому файлу программы с параметрами перезапуска программы +KillMode – указывается как будет завершен процесс. В данному случае параметр process говорит о том что будет закрыт только главный процесс +Restart – перезагрузка процесса, параметр on-failure указывает на автоматическую перезагрузку в случае отказа процесса +RestartSec – время ожидания через которое процесс должен перезагрузиться +[Install] +WantedBy – указывает на каком уровне запуска стартует сервис, параметр multi-user.target указывает на запуск в многопользовательском режиме без графики +``` +## Работа с сервисами через systemctl +systemctl — специальная утилита для взаимодействия с сервисами и systemd. + +Основные команды я вынес ниже: + +* systemctl start "служба" — запуск определенной службы +* systemctl stop "служба" — остановка определенной службы +* systemctl restart "служба" — перезапуск службы +* systemctl reload "служба" — перезагрузка конфига службы +* systemctl status "служба" — статус службы +* systemctl enable "service" — включение автозапуска службы +* systemctl disable "service" — выключение автозапуска службы +* systemctl is-enabled "service" — включен ли автозапуск службы +* systemctl poweroff — выключение системы +* systemctl suspend — перевод системы в спящий режим системы +* systemctl hibernate — гибернация системы +* systemctl reboot — перезагрузка системы +* systemctl list-units — отображает все активные юниты +* systemctl list-unit-files — отображает все доступные юнит-файлы +* systemctl show "название юнита" — отображает подробную информацию об указанном юните +* systemctl daemon-reload — перезагрузка и перечитывание всех конфигов юнитов. +* systemctl isolate "системный уровень, таргет" — переключает систему в указанный системный уровень +* systemctl mask "название юнита" — запрещает запуск данного юнита, маскирует его +* systemctl unmask "название юнита" — разрешает запуск данного юнита +* systemctl --failed — показывает все службы, которые не смогли запуститься из-за ошибки. + +## Удаленное управление systemd +Системой и менеджером служб systemd на удаленном сервере можно управлять через ssh, благодаря systemctl. + +Для этого надо просто использовать флаг `--host`: +```bash +$ systemctl --host root@hostname status nginx.service +``` +В команде выше мы проверили статус службы `nginx`. + +Благодаря этому флагу мы можем взаимодействовать с системой на удаленном сервере без прямого подключения. + +## Создаем свой скрипт для работы с systemd +Небольшой продукт нашей статьи — это скрипт на Python, целью которого является облегчение некоторых задач. + +В нашем примере мы создадим всего лишь несколько функций — более подробная функция времени загрузки системы, включение и выключение службы. + +Например, можно скомбинировать несколько похожих команд для более быстрого взаимодействия. + +Давайте попробуем создать простой скрипт, который будет анализировать время запуска системы. +Итак, давайте создадим виртуальное окружение Python и установим нужные пакеты: +```bash +python3 -m venv venv # Создаем +source venv/bin/activate # Активируем +pip3 install click # Установливаем зависимости +``` +Click — это специальная библиотека для создания CLI-приложений на python. Давайте создадим шаблон нашего приложения: +```python +# Импорт нужных нам в будущем библиотек +import os # Взаимодействие с ОС +import re # Регулярные выражения +import subprocess # Для запуска процессов +import statistics # Математически-статические вычисления +import click # Для CLI приложения + + +def is_root() -> bool: + """ + Проверяем, запущен ли скрипт от рута + + :return: True если от рута, False в ином случае + """ + return os.getuid() == 0 + + +@click.group +def main(): + """ + Группа команд + """ + pass + + +if __name__ == "__main__": + main() +``` + +Пока что у нас нету никаких команд, так давайте же создадим! Начнем с простого — получение версии systemd. + +```python +@click.command +def get_systemd_version() -> str: + """ + Получаем версию systemD + """ + version = "" + + try: + # Получаем вывод команды systemctl --version и парсим оттуда версию + text = subprocess.check_output(["systemctl", "--version"], encoding="utf8") + version = text.split(" ")[1] + except Exception as ex: + # В случае ошибки + print(f"Произошла ошибка: {ex}") + raise ex + else: + # Если все прошло гладко, возращаем версию + return version +``` +Следующий шаг — это две функции, включение и выключение службы: +```python +@click.command +@click.option("-s", "--service", required=True, help="Service name for disable") +def disable(service: str): + """ + Выключение службы. + + :param service: Имя службы + """ + # Проверяем, от рута ли мы работаем + if is_root(): + # Если да, то sudo не требуется + process = subprocess.call(["systemctl", "disable", "--now", service], + stderr=subprocess.PIPE, stdout=subprocess.PIPE) + else: + # В ином случае используем sudo + process = subprocess.call(["sudo", "systemctl", "disable", "--now", service], + stderr=subprocess.PIPE, stdout=subprocess.PIPE) + + if process.return_source != 0: + # Если команда выполнилась неудачно, то + print(f"Ошибка: {process.stderr}") + else: + # Если все успешно, то + print(process.stdout) + + +@click.command +@click.option("-s", "--service", required=True, help="Service name for enable") +def enable(service: str): + """ + Включение службы. + + :param service: Имя службы + """ + # Проверяем, от рута ли мы работаем + if is_root(): + # Если да, то просто вызываем команду + process = subprocess.call(["systemctl", "enable", "--now", service], + stderr=subprocess.PIPE, stdout=subprocess.PIPE) + else: + # Если нет, то используем команду через sudo + process = subprocess.call(["sudo", "systemctl", "enable", "--now", service], + stderr=subprocess.PIPE, stdout=subprocess.PIPE) + + if process.return_source != 0: + # Если команда выполнилась неудачно, то + print(f"Ошибка: {process.stderr}") + else: + # Если все успешно, то + print(process.stdout) +``` +А теперь займемся последней функцией — получение подробных данных о времени загрузки системы +```python +@click.command +def startuptime() -> bool: + """ + Анализ времени запуска системы и служб + """ + try: + # время запуска системы + text = subprocess.check_output(["systemd-analyze", "blame"], encoding="utf8") + except Exception as ex: + return False + + try: + # время запуска служб + text2 = subprocess.check_output(["systemd-analyze"], encoding="utf8") + except Exception as ex: + return False + + # общее время загрузки служб + total_time = text2.split("\n")[0].split(" ")[-2] + times, units, names = [], [], [] # списки для времени и названий служб + + for line in text.strip().split("\n"): + if line: + # ищем по регулярному выражению время и имя службы + match = re.match(r"\s*(\d+(?:\.\d+)?)(ms|s)\s+(\S+)", line) + + if match: + times.append(float(match.group(1))) + units.append(match.group(2)) + names.append(match.group(3)) + + # выводим службы, будет примерно так: + # 4.071s -- dev-sda2.device + # 2.631s -- iio-sensor-proxy.service + for time, unit, name in zip(times, units, names): + startup_ftime = f"{time}{unit}" + print(f"{startup_ftime:10} -- " \ + f"{name}") + + # Ищем самую медленную и быструю службу + fastest_startup_ftime = f"{times[0]}{units[0]}" + slowest_startup_ftime = f"{times[-1]}{units[-1]}" + + total_service_time = sum(times) # время запуска служб + average = sum(times) / len(times) # среднее арифмитическое времени запуска служб + median = statistics.median(times) # медиана времени запуска служба + total_unit = set(units) # название меры времени (ms или s) + + if "s" in total_unit: + total_speed = total_service_time + elif "ms" in total_unit: + total_speed = total_service_time / 1000 + + print(f"Самая медленная служба ({fastest_startup_ftime}" \ + f"): {names[0]}") + print(f"Самая быстрая служба ({slowest_startup_ftime}" \ + f"): {names[-1]}") + print(f"Среднее арифмитическое времени запуска служб: " \ + f"{average:.3f}s") + print(f"Медиана времени запуска служб: " \ + f"{median:.3f}s") + print(f"Время загрузки системы (kernel+userspace): {total_time}") + print(f"Время загрузки сервисов: " \ + f"{total_speed:.3f}{s if s in total_unit else ms}") + + return True +``` +Вот и все. Используя этот шаблон, вы можете, например, сделать установщик nginx или postgresql с последующим включением или настройкой службы. Или можно оптимизировать вашу ОС, путем анализа времени запуска системы и служб. +## Заключение +Знание написания сервисов на systemd и взаимодействия с ним — неотъемлемая часть работы любого системного администратора или даже разработчика. Именно система инициализации отвечает за загрузку всех сервисов, процессов и потоков. + +Кроме systemD, существуют другие системы инициализации — такие как OpenRC (от разработчиков Gentoo), runit, dinit. Все они также достойны жить, но вам вряд ли придется администрировать сервер, например с runit. Ибо практически все дистрибутивы основаны на systemd (Fedora, Arch, Gentoo (частично, на выбор можно установить OpenRC или systemD), Debian, RHEL, Astra Linux). И лишь часть дистрибутивов является non-systemd — такие как MX Linux (systemd там не вырезан полностью, а просто отключен), Artix Linux (arch-based), Antix Linux, devuan (debian-based) и частично Gentoo. + +Если я где-то что-то неправильно сказал, подправьте меня в комментариях. Прошу не разводить споров насчет того, зло ли systemD или нет. + +Когда-то, давным-давно, я уже писал эту статью — но решил добавить нового, полностью обдумать тему систем инициализации и создать новую статью. Надеюсь, вам понравился мой туториал! Благодарю за чтение!