full blog
parent
4e4117b121
commit
38e03809a4
|
|
@ -510,3 +510,332 @@ cd /etc/systemd/system
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
echo Hello, Habr!
|
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