На этой лекции будут затронуты некоторые принципы работы сетевых служб: их строение, пайплайн использования и модификации. В качестве системы управления сервисами выступает Systemd.
Быстрый поиск
- Управление службами и
Systemd - Связь с интерпретатором
- Запуск сетевого сервиса
- Перманентная настройка сети с помощью
systemd-networkd
Управление службами и Systemd
Разбирая задачи автоматической настройки и управления системой и, в частности, сетевыми параметрами, мы встречаемся с понятием сервиса — некоторой системы, занимающейся управлением необходимыми нам объектами и предоставляющей возможность внесения в неё параметров настройки. В некотором смысле, приложения, поддерживающие внешнюю конфигурацию, тоже могут (очень условно) называться сервисами. Такие системы работают изолированно, управляются каждая отдельно. В случае с сетевыми или, совсем широко, системными задачами возникает желание работать с некоторым универсальным управляющим сервисом, способным объединить и унифицировать управление всеми прикладными службами.
Для каждого дистрибутива Linux (да и в каждом сообществе любой операционной системы) существуют свои собственные «сервисы по управлению сервисами»: OpenRC в Gentoo, Runit в Void Linux и так далее. В данной лекции будет рассматриваться самый распространённый инициализирующий комплекс программ Systemd и, в частности, его сетевая подсистема - systemd-networkd. Несмотря на не самый простой синтаксис и логику управления сервисами, эта подсистема позволяет универсальными способами управлять прикладными службами и обеспечивать контроль работы сервисов.
Прежде чем переходить к сетевым сервисам, для начала обсудим, что из себя представляет сервис в systemd вообще. Базовым понятием в systemd выступает systemd.unit, через него описываются все действия, которые можно сделать с помощью systemd - монтирование файловой системы, подключение сокетов и т.д. Непосредственно systemd.service описывает некоторые прикладные службы, запускаемые в момент появления специальных триггеров (обращения к ним, прихода данных, изменения некоторых статусов и т.д.). Список всех существующих сервисов описан в /etc/services, там же указаны закреплённые за системными сервисами порты и имена, а также поддерживаемые протоколы передачи в сети (несмотря на их большое количество, принято указывать (и использовать) только классические протоколы TCP и UDP).
Управление сервисами осуществляется с помощью команд systemctl. Существуют команды ручного управления сервисами (start / restart / stop / status), автоматического подключения сервисов (enable / disable). Также система управления systemd поддерживает обработку зависимостей сервисов и их параллельный запуск.
При разговоре о работе с сервисами также стоит вновь упомянуть о не самой простой логике работы Systemd. Леннарт Пёттеринг, один из главных разработчиков Systemd, внёс несколько неочевидные и не всегда понятные действия в работу службы, которые далее в тексте будут помечаться карикатурой Дональда Нормана:
. И первое место, достойное такой пометки - непосредственно собственная настройка сервисов. По умолчанию systemctl умеет настраивать только существующие сервисы, а на все пользовательские для их создания отвечает:
srv
[root@srv ~]# systemctl edit new.service
No files found for new.service.
Run 'systemctl edit --force --full new.service' to create a new unit.
[root@srv ~]#
Почему логика работы устроена именно так, известно одному только Леннарту.
Вместе с понятием сервиса systemd парой возникает systemd.socket. Этот юнит описывает исключительно правила сетевого взаимодействия с указанной службой без какой-либо привязки к интерпретатору. Таким образом, в systemd, как по волшебству, разделены в соответствии с описанными выше задачами системы сетевого управления и связывания порта с интерпретатором и системы непосредственной обработки интерпретатором данных.
Связь с интерпретатором
При работе с сетевыми службами в Linux, обеспечивающими связь с интерпретатором, существует несколько вариантов их реализации.
Схема с полноценным сервисом подразумевает, что подсистема запуска сервиса контролирует лишь его запуск и останову по расписанию или запросу. А сам сервис создаёт сокет, обеспечивает обработку соединений к нему, интерпретирует потоки ввода-вывода и организовывает управление сервисом.
Другая реализация относит нас к Платоновским Демонам (собственно, термин “служба” для Linux-утилит неродной). Даймон (daemon), как его следует называть, это утилита, ожидающая обращения к ней для запуска непосредственно исполняемого сервиса. В терминах реализации сетевых служб этот способ называется сокет-активацией. В ней подсистема (в нашем случае это systemd) полностью самостоятельно занимается обслуживанием сокета, его настройкой, обработкой соединений, управлением сервисом посредством сигналов. Сам сервис же лишь интерпретирует входные и выходные потоки, уже подключённые подсистемой, и, быть может, через дополнительные утилиты или сокеты выводит доступ для управления сервисом командами. Одним из примеров служб-даймонов выступают известные из прошлых глав netcat и socat, подсистема которых обеспечивает подключение, а сами сервисы обработки потоков данных срабатывают лишь при подключении к соответствующим адресам и портам.
Для systemd существует ещё одна
-реализация связи, при которой подсистема создаёт сокет, а после передаёт его в службу. Этот способ необходим, когда непосредственное управление сокетом можно вынести от сервиса, а его наполнение (подключения и обработку соединений) он сможет обеспечить самостоятельно. Идея такой реализации лежит в ограничении прав доступа для сервисов (для создания сокета и управления им необходимы root-права, при этом сам сервис может (и зачастую должен) работать с пользовательскими правами). Однако на практике экономия от вынесения действий и отсутствия дополнительных операций по снижению прав сервиса видна лишь на крупных серверах.
Запуск сетевого сервиса
Попробуем самостоятельно организовать сетевой сервис на небольшом стенде:
~/papillon_rouge: vbintnets
srv:
eth1: intnet
client:
eth1: intnet
~/papillon_rouge:
Для начала локально попробуем запустить “сокет-активацию для бедных”. Среди работающих сетевых сервисов на виртуальных машинах всегда работает только ssh:
srv
[root@srv ~]# ss -ltp
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 128 0.0.0.0:ssh 0.0.0.0:* users:(("sshd",pid=693,fd=3))
[root@srv ~]#
Запустим с помощью socat простейший сервис, который при подключении к нему будет запускать указанный интерпретатор, например, печатать календарь:
srv
[root@srv ~]# socat TCP-LISTEN:1234 EXEC:/usr/bin/cal &
[1] 780
[root@srv ~]# ss -ltp
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 5 0.0.0.0:1234 0.0.0.0:* users:(("socat",pid=780,fd=5))
LISTEN 0 128 0.0.0.0:ssh 0.0.0.0:* users:(("sshd",pid=693,fd=3))
Обратимся к нему через netcat и посмотрим результат:
srv
[root@srv ~]# netcat localhost 1234
April 2025
Su Mo Tu We Th Fr Sa
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
[root@srv ~]# [1]+ Done socat TCP-LISTEN:1234 EXEC:/usr/bin/cal
[root@srv ~]# ss -ltp
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 128 0.0.0.0:ssh 0.0.0.0:* users:(("sshd",pid=693,fd=3))
[root@srv ~]#
Как и ожидалось, даймон socat дождался непосредственного обращения, отработал и завершился.
Аналогичным способом можно организовать сервис для сети:
srv
[root@srv ~]# autonet
[root@srv ~]# ip a sh eth1
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 08:00:27:27:4b:e8 brd ff:ff:ff:ff:ff:ff
altname enp0s8
altname enx080027274be8
inet 10.9.0.26/24 scope global eth1
valid_lft forever preferred_lft forever
[root@srv ~]# socat TCP-LISTEN:1234,reuseaddr EXEC:/usr/bin/python3
client
[root@client ~]# autonet
[root@client ~]# ip a sh eth1
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 08:00:27:3d:2d:12 brd ff:ff:ff:ff:ff:ff
altname enp0s8
altname enx0800273d2d12
inet 10.9.0.27/24 scope global eth1
valid_lft forever preferred_lft forever
[root@client ~]# netcat 10.9.0.26 1234
for i in range(5):
print(i)
0
1
2
3
4
[root@client ~]#
Таким образом, socat может выступать в роли подсистемы запуска для разных сервисов. Из важных параметров socat при таком использовании стоит упомянуть:
- Параметр
reuseaddr- в прошлых главах обсуждалось, что при работе с сокетом по окончании работы сервиса сокет на указанном порту некоторое время остаётся открытым. Для того чтобы переподключаться к тому же сокету (принудительно его отключать и подключать снова), необходим указанный параметр; - Параметр
fork- в примерахsocat-сервис обрабатывал только одно подключение. Для того чтобы обеспечить множественную работу сервиса без постоянного включения его после обращения, необходим указанный параметр;
Запуск собственных служб с помощью systemd
Теперь организуем сервис с использованием systemd.
srv
[root@srv ~]# systemctl edit --force --full cal.service
srv#SYSTEMD-EDITOR
[Unit]
Description=Calendar
[Service]
ExecStart=/usr/bin/socat TCP-LISTEN:1234,reuseaddr,fork EXEC:/usr/bin/cal
srv
[root@srv ~]# systemctl edit --force --full cal.service
Successfully installed edited file '/etc/systemd/system/cal.service'.
[root@srv ~]# systemctl start cal
[root@srv ~]# ss -ltp
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 5 0.0.0.0:1234 0.0.0.0:* users:(("socat",pid=967,fd=5))
LISTEN 0 128 0.0.0.0:ssh 0.0.0.0:* users:(("sshd",pid=693,fd=3))
[root@srv ~]#
client
[root@client ~]# netcat 10.9.0.26 1234
April 2025
Su Mo Tu We Th Fr Sa
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
[root@client ~]#
Получился простейший самостоятельный сервис, запускающий и контролирующий сокет, подключения и интерпретацию.
Теперь реализуем сервис с сокет-активацией. Для этого отдельно опишем сокет сервиса и его реализацию:
srv
[root@srv ~]# systemctl edit --force --full hexdump.socket
srv#SYSTEMD-EDITOR
[Unit]
Description=Hex Dump Socket
[Socket]
ListenStream=1234
Accept=yes
srv
[root@srv ~]# systemctl edit --force --full hexdump@.service
srv#SYSTEMD-EDITOR
[Unit]
Description=Hex Dump
[Service]
ExecStart=/usr/bin/hexdump -C
StandardInput=socket
Для сокета указывается порт подключения, а также параметр Accept, разрешающий подключение к сокету и создание параллельных обращений к сервису. В название самого сервиса при этом необходимо внести специальный символ @, который при подключении к сервису нового обращения будет заменён на идентификатор этого подключения, что позволит отличать несколько параллельно работающих подключений. Также в самом сервисе указываются потоки ввода-вывода данных (без указания потока вывода он автоматически считается равным потоку ввода), по которым определяются триггеры запуска сервиса.
srv
[root@srv ~]# systemctl edit --force --full hexdump.socket
Successfully installed edited file '/etc/systemd/system/hexdump.socket'.
[root@srv ~]# systemctl edit --force --full hexdump@.service
Successfully installed edited file '/etc/systemd/system/hexdump@.service'.
[root@srv ~]# systemctl start hexdump.socket
[root@srv ~]# ss -ltp
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 4096 0.0.0.0:1234 0.0.0.0:* users:(("systemd",pid=1,fd=101))
LISTEN 0 128 0.0.0.0:ssh 0.0.0.0:* users:(("sshd",pid=693,fd=3))
[root@srv ~]#
client
[root@client ~]# cal | netcat 10.9.0.26 1234
00000000 20 20 20 20 20 41 70 72 69 6c 20 32 30 32 35 20 | April 2025 |
00000010 20 20 20 20 0a 53 75 20 4d 6f 20 54 75 20 57 65 | .Su Mo Tu We|
00000020 20 54 68 20 46 72 20 53 61 0a 20 20 20 20 20 20 | Th Fr Sa. |
00000030 20 31 20 20 32 20 20 33 20 20 34 20 20 35 0a 20 | 1 2 3 4 5. |
00000040 36 20 20 37 20 20 38 20 20 39 20 31 30 20 31 31 |6 7 8 9 10 11|
00000050 20 31 32 0a 31 33 20 31 34 20 31 35 20 31 36 20 | 12.13 14 15 16 |
00000060 31 37 20 31 38 20 31 39 0a 32 30 20 32 31 20 32 |17 18 19.20 21 2|
00000070 32 20 32 33 20 32 34 20 32 35 20 32 36 0a 32 37 |2 23 24 25 26.27|
00000080 20 32 38 20 32 39 20 33 30 20 20 20 20 20 20 20 | 28 29 30 |
00000090 20 20 0a 20 20 20 20 20 20 20 20 20 20 20 20 20 | . |
000000a0 20 20 20 20 20 20 20 0a | .|
000000a8
[root@client ~]#
Так как наш сервис настроен на сокет-активацию, в момент отсутствия подключения он неактивен, существует лишь открытый сокет, за которым следит сам systemd:
srv
[root@srv ~]# systemctl | grep hexdump
system-hexdump.slice loaded active active Slice /system/hexdump
hexdump.socket loaded active listening Hex Dump Socket
[root@srv ~]#
client
[root@client ~]# netcat 10.9.0.26 1234
srv
[root@srv ~]# systemctl | grep hexdump
hexdump@1-10.9.0.26:1234-10.9.0.27:41290.service loaded active running Hex Dump (10.9.0.27:41290)
system-hexdump.slice loaded active active Slice /system/hexdump
hexdump.socket loaded active listening Hex Dump Socket
[root@srv ~]#
Кроме обозначения самого сервиса можно также увидеть идентификаторы текущего соединения.
Настройки параметров сервисов systemd огромны. Он позволяет задавать динамические значения, зависимости и т.д. Добавим в качестве эксперимента стартовую выходную строку:
srv
[root@srv ~]# systemctl edit hexdump@.service
srv#SYSTEMD-EDITOR
[Unit]
Description=Hex Dump
[Service]
ExecStartPre=echo "Hexdump on %H %i"
ExecStart=/usr/bin/hexdump -C
StandardInput=socket
srv
[root@srv ~]# systemctl edit --force --full hexdump@.service
Successfully installed edited file '/etc/systemd/system/hexdump@.service'.
[root@srv ~]# systemctl restart hexdump.socket
[root@srv ~]#
client
[root@client ~]# cal | netcat 10.9.0.26 1234
Hexdump on srv 2-10.9.0.26:1234-10.9.0.27:51332
00000000 20 20 20 20 20 41 70 72 69 6c 20 32 30 32 35 20 | April 2025 |
00000010 20 20 20 20 0a 53 75 20 4d 6f 20 54 75 20 57 65 | .Su Mo Tu We|
00000020 20 54 68 20 46 72 20 53 61 0a 20 20 20 20 20 20 | Th Fr Sa. |
00000030 20 31 20 20 32 20 20 33 20 20 34 20 20 35 0a 20 | 1 2 3 4 5. |
00000040 36 20 20 37 20 20 38 20 20 39 20 31 30 20 31 31 |6 7 8 9 10 11|
00000050 20 31 32 0a 31 33 20 31 34 20 31 35 20 31 36 20 | 12.13 14 15 16 |
00000060 31 37 20 31 38 20 31 39 0a 32 30 20 32 31 20 32 |17 18 19.20 21 2|
00000070 32 20 32 33 20 32 34 20 32 35 20 32 36 0a 32 37 |2 23 24 25 26.27|
00000080 20 32 38 20 32 39 20 33 30 20 20 20 20 20 20 20 | 28 29 30 |
00000090 20 20 0a 20 20 20 20 20 20 20 20 20 20 20 20 20 | . |
000000a0 20 20 20 20 20 20 20 0a | .|
000000a8
[root@client ~]#
Перманентная настройка сети с помощью systemd-networkd
С умением настраивать сервисы перейдём непосредственно к настройке сети с их помощью. При работе с systemd-networkd - сервисом настройки параметров сети - используется особая схема хранения конфигурационных файлов, так называемая .d-схема. Её особенность заключается в сборке итогового конфига из нескольких маленьких блоков в лексикографическом порядке их хранения в специальной директории - .d-каталоге /etc/systemd/network.
Настроим выход в интернет с помощью systemd-networkd. Для начала перезапустим виртуальную машину, чтобы сбросить настройки autonet. В начальный момент сервис networkd отключён: установим его активацию при каждом включении, считая и это:
srv
[root@srv ~]# networkctl
systemd-networkd is not running, output might be incomplete.
IDX LINK TYPE OPERATIONAL SETUP
1 lo loopback - unmanaged
2 eth0 ether - unmanaged
3 eth1 ether - unmanaged
4 eth2 ether - unmanaged
5 eth3 ether - unmanaged
5 links listed.
[root@srv ~]# systemctl enable --now systemd-networkd.service
Created symlink '/etc/systemd/system/dbus-org.freedesktop.network1.service' -> '/usr/lib/systemd/system
/systemd-networkd.service'.
Created symlink '/etc/systemd/system/multi-user.target.wants/systemd-networkd.service' -> '/usr/lib/sys
temd/system/systemd-networkd.service'.
Created symlink '/etc/systemd/system/sockets.target.wants/systemd-networkd.socket' -> '/usr/lib/systemd
/system/systemd-networkd.socket'.
Created symlink '/etc/systemd/system/sysinit.target.wants/systemd-network-generator.service' -> '/usr/l
ib/systemd/system/systemd-network-generator.service'.
Created symlink '/etc/systemd/system/network-online.target.wants/systemd-networkd-wait-online.service'
-> '/usr/lib/systemd/system/systemd-networkd-wait-online.service'.
[root@srv ~]# networkctl
IDX LINK TYPE OPERATIONAL SETUP
1 lo loopback carrier unmanaged
2 eth0 ether off unmanaged
3 eth1 ether off unmanaged
4 eth2 ether off unmanaged
5 eth3 ether off unmanaged
5 links listed.
[root@srv ~]#
Перейдём в .d-каталог и создадим правила настройки:
srv
[root@srv ~]# cd /etc/systemd/network/
[root@srv network]# vim 10-inet.network # расширение .network обязательно!
srv#EDITOR
[Match]
Name=eth0
[Network]
Address=10.0.2.15/24
Gateway=10.0.2.2
srv
[root@srv network]# networkctl reload
[root@srv network]# networkctl
IDX LINK TYPE OPERATIONAL SETUP
1 lo loopback carrier unmanaged
2 eth0 ether routable configured
3 eth1 ether off unmanaged
4 eth2 ether off unmanaged
5 eth3 ether off unmanaged
5 links listed.
[root@srv network]# ping -c3 -f 1.1.1.1
PING 1.1.1.1 (1.1.1.1) 56(84) bytes of data.
--- 1.1.1.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 10ms
rtt min/avg/max/mdev = 4.064/4.608/5.060/0.411 ms, ipg/ewma 4.976/4.896 ms
[root@srv network]#
Важный
-момент: в случае ошибок при настройке нигде кроме journalctl это отмечено не будет. При этом все корректные части настройки сработают. Например, при ошибке в написании:
Geteway=10.0.2.2
Случится следующее:
srv
[root@srv network]# ip a sh eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UNKNOWN group default qlen 100
0
link/ether 08:00:27:d0:e2:17 brd ff:ff:ff:ff:ff:ff
altname enp0s3
altname enx080027d0e217
inet 10.0.2.15/24 brd 10.0.2.255 scope global eth0 # Казалось бы, всё работает
valid_lft forever preferred_lft forever
[root@srv network]# ip r
10.0.2.0/24 dev eth0 proto kernel scope link src 10.0.2.15 # А дефолтного пути нет...
[root@srv network]#
Настройка статического маршрута
networkd позволяет также настраивать маршруты и ip-forwarding. С помощью него настроим маршруты между машинами:
srv
[root@srv network]# vim 50-internal.network
srv#EDITOR
[Match]
Name=eth1
[Network]
Address=10.9.0.26/24
srv
[root@srv network]# vim 50-internal.network
[root@srv network]# networkctl reload
[root@srv network]# ping -fc3 10.9.0.27
PING 10.9.0.27 (10.9.0.27) 56(84) bytes of data.
--- 10.9.0.27 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 1ms
rtt min/avg/max/mdev = 0.235/0.428/0.737/0.220 ms, ipg/ewma 0.616/0.627 ms
[root@srv network]#
Добавим статический маршрут в тот же файл для перенаправления пакетов:
srv#EDITOR
[Route]
Gateway=10.9.0.27
Destination=10.4.0.28
srv
[root@srv network]# networkctl reload
[root@srv network]# ip r
default via 10.0.2.2 dev eth0 proto static
10.0.2.0/24 dev eth0 proto kernel scope link src 10.0.2.15
10.4.0.0/24 via 10.9.0.27 dev eth1 proto static # Добавленный маршрут
10.9.0.0/24 dev eth1 proto kernel scope link src 10.9.0.26
[root@srv network]#