full blog

main
Федоров Дмитрий 2024-07-09 13:33:45 +03:00
parent 4e4117b121
commit 38e03809a4
1 changed files with 329 additions and 0 deletions

View File

@ -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 или нет.
Когда-то, давным-давно, я уже писал эту статью — но решил добавить нового, полностью обдумать тему систем инициализации и создать новую статью. Надеюсь, вам понравился мой туториал! Благодарю за чтение!