full blog
parent
4e4117b121
commit
38e03809a4
|
|
@ -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 или нет.
|
||||
|
||||
Когда-то, давным-давно, я уже писал эту статью — но решил добавить нового, полностью обдумать тему систем инициализации и создать новую статью. Надеюсь, вам понравился мой туториал! Благодарю за чтение!
|
||||
|
|
|
|||
Loading…
Reference in New Issue