View on GitHub

Linux Practic Usage

Practical materials for learning Linux

На этой лекции будут затронуты некоторые принципы работы сетевых служб: их строение, пайплайн использования и модификации. В качестве системы управления сервисами выступает Systemd.

Быстрый поиск


Управление службами и 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, внёс несколько неочевидные и не всегда понятные действия в работу службы, которые далее в тексте будут помечаться карикатурой Дональда Нормана: FrBrGeorge/MyDict/normancoffepot.png. И первое место, достойное такой пометки - непосредственно собственная настройка сервисов. По умолчанию 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 существует ещё одна FrBrGeorge/MyDict/normancoffepot.png-реализация связи, при которой подсистема создаёт сокет, а после передаёт его в службу. Этот способ необходим, когда непосредственное управление сокетом можно вынести от сервиса, а его наполнение (подключения и обработку соединений) он сможет обеспечить самостоятельно. Идея такой реализации лежит в ограничении прав доступа для сервисов (для создания сокета и управления им необходимы 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 при таком использовании стоит упомянуть:

Запуск собственных служб с помощью 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]#

Важный FrBrGeorge/MyDict/normancoffepot.png-момент: в случае ошибок при настройке нигде кроме 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]#