Организация сервера на примере Rsync

В данной лекции мы рассмотрим обеспечение запуска серверного ПО в Unix-подобных системах на примере программы Rsync. Начнем мы, однако, с описания работы этой программы как таковой.

Общие сведения об Rsync

Основное предназначение Rsync — эффективная односторонняя синхронизация файловых архивов через сеть. Rsync используется, например, для периодического обновления зеркал таких архивов.

Эффективность работы Rsync обеспечивается:

Rsync можно использовать и для локальной синхронизации архивов. В случае размещения копии на носителе на основе flash-памяти это позволяет, среди прочего, снизить износ (т. е. расход циклов перезаписи) устройства.

В примерах ниже мы предполагаем некоторую степень знакомства с Unix-подобными системами (в частности — Debian GNU/Linux) — хотя бы в объеме лабораторной работы «Основы взаимодействия с ОС».

Пример: рекурсивное копирование содержимого директории alice в директорию dave; при повторном копировании передается значительно (в данном случае — в 280 038 раз) меньший объем данных.
$ ls -gGR -- alice 
alice:
total 0
drwxr-xr-x 3       80 Apr 23 04:40 bob
-rw-r--r-- 1 14727168 Apr 23 04:40 onefile

alice/bob:
total 0
-rw-r--r-- 1 28023808 Apr 23 04:40 anotherfile
drwxr-xr-x 2       60 Apr 23 04:40 charlie

alice/bob/charlie:
total 0
-rw-r--r-- 1 7936000 Apr 23 04:40 thirdfile
$ rsync -v --sparse -brOtHx --suffix=.~$(date +%s)~ \
      -- alice/ dave/ 
sending incremental file list
created directory dave
./
onefile
bob/
bob/anotherfile
bob/charlie/
bob/charlie/thirdfile

sent 50,699,653 bytes  received 123 bytes  101,399,552.00 bytes/sec
total size is 50,686,976  speedup is 1.00
$ ls -gGR -- dave
dave:
total 0
drwxr-xr-x 3       80 Apr 23 04:40 bob
-rw-r--r-- 1 14727168 Apr 23 04:40 onefile

dave/bob:
total 0
-rw-r--r-- 1 28023808 Apr 23 04:40 anotherfile
drwxr-xr-x 2       60 Apr 23 04:40 charlie

dave/bob/charlie:
total 0
-rw-r--r-- 1 7936000 Apr 23 04:40 thirdfile
$ rsync -v --sparse -brOtH --suffix=.~$(date +%s)~ \
      -- alice/ dave/ 
sending incremental file list

sent 167 bytes  received 14 bytes  362.00 bytes/sec
total size is 50,686,976  speedup is 280,038.54
$ 

Все передаваемые выше файлы являются разряженными и фактически содержат лишь информацию о размере, но не какие-либо данные, заполняющие этот размер — что и объясняет total 0 в выводе ls. Опция rsync --sparse позволяет принимающей стороне выявлять такие файлы и сохранять свойство разряженности, однако протокол Rsync не поддерживает более эффективной их обработки и требует фактической передачи «несуществующего» содержимого. Отметим, что создать «пустой» разряженный файл заданного размера можно командой truncate.

Помимо прочего, Rsync позволяет указать шаблоны имен файлов, подлежащих копированию — что позволяет, например, организовать частичное зеркало или копию архива.

Пример: обновление материала на локальном HTTP-сервере по личной «рабочей копии» с указанием имен файлов для копирования.
bash$ rsync -cb -rOtHx --suffix=.~$(date +%s)~ \
          --backup-dir=.rsync-backup --exclude=\*~ \
          --include=inco-"$(date +%Y)"/{,"[algx]"/} \
          --include=inco-2020/{README,a/{bonus,report,scoring},\
basin,cry,debian,fs,g/{list,web-cat},internet,\
l/{exampled,inet,sockets},misc,ssh,web}.ru.xhtml \
          --exclude=\* \
          -- ~/private/bits/ ~/public/ 

Использование процессов и межпроцессного взаимодействия

Ради единообразия, использование Rsync всегда предполагает наличие двух процессов: клиента и сервера. Здесь возможны три основных случая.

  1. При копировании между директориями одной системы оба процесса будут просто порождены обычным системным вызовом fork (или clone.)

  2. При использовании SSH (или иного подобного протокола удаленного выполнения команд) — один из порожденных процессов будет замещен (execve) SSH-клиентом (например, ssh из состава OpenSSH), который, в свою очередь, запустит один из процессов на удаленной системе.

  3. Наконец, при использовании Rsync как сервиса Internet, сервер будет ожидать соединения на каком-либо порту. Стандартным (т. е. внесенным в соответствующий реестр IANA) для Rsync является порт 873 TCP.

    Этот случай наиболее интересен в контексте данной лекции, и мы более подробно рассмотрим его ниже.

    Такое использование Rsync является предметом соответствующего раздела лабораторной работы «Internet».

Строго говоря, возможен и четвертый случай: оба процесса выполняются на удаленных системах, обмениваясь данными через локальную. Такой вариант, очевидно, менее эффективен (любые данные передаются по сети дважды) и не поддерживается непосредственно командой rsync. Что, впрочем, при желании можно обойти, например используя Socat.

Пример: обновление материала на удаленном HTTP-сервере по SSH.
bash$ rsync -cb -rOtHx --suffix=.~$(date +%s)~ \
          --backup-dir=.rsync-backup --exclude=\*~ \
          --include=inco-"$(date +%Y)"/{,"[algx]"/}{,\*.xhtml} \
          --exclude=\* \
          -- ~/public/ www.example.com:public/ 
Пример: получение списка модулей (директорий верхнего уровня) удаленного Rsync-сервера.
$ rsync -- rsync://ftp.nluug.nl/ 
pub             /pub hierarchy
site            /site hierarchy
vol             /vol hierarchy
blender         blender

Пример: создание и обновление частичного локального зеркала NetBSD с удаленного сервера.
$ rsync -v -brOtH --suffix=.~$(date +%s)~ --exclude=\*~ \
      --include=NetBSD-9.0/{,amd64/} --include=INSTALL.html \
      --include={CHANGES,README}\* --include=LAST_MINUTE \
      --exclude=\* \
      -- rsync://ftp.nluug.nl/netbsd/ public/download/netbsd/ 
receiving incremental file list
created directory public/download/netbsd
./
README
README.export-control
NetBSD-9.0/
NetBSD-9.0/CHANGES
NetBSD-9.0/CHANGES-9.0
NetBSD-9.0/CHANGES.prev
NetBSD-9.0/LAST_MINUTE
NetBSD-9.0/README.files
NetBSD-9.0/amd64/
NetBSD-9.0/amd64/INSTALL.html

sent 316 bytes  received 1,135,867 bytes  48,348.21 bytes/sec
total size is 1,134,956  speedup is 1.00
$ rsync -v -brOtH --suffix=.~$(date +%s)~ --exclude=\*~ \
      --include=NetBSD-9.0/{,amd64/} --include=INSTALL.html \
      --include={CHANGES,README}\* --include=LAST_MINUTE \
      --exclude=\* \
      -- rsync://ftp.nluug.nl/netbsd/ public/download/netbsd/ 
receiving incremental file list

sent 143 bytes  received 298 bytes  26.73 bytes/sec
total size is 1,134,956  speedup is 2,573.60
$ 

Запуск Internet-сервера

Вспомним, что одним из простых способов организации сервера является использование управляющего процесса, владеющего ожидающим (англ. listening) гнездом и порождающего дочерние процессы по мере поступления запросов на установление соединения — причем для обработки каждого соединения создается отдельный процесс.

Таким управляющим процессом для Rsync может быть, например:

Работа Rsync в этом режиме несколько отличается от других случаев выше — когда выполняемая локально команда rsync непосредственно определяет аргументы командной строки для порождаемых Rsync-процессов (хотя бы уже по той причине, что протокол TCP сам по себе не предусматривает механизма для передачи таких аргументов.) А именно:

Рассмотрим запуск сервера Rsync на следующих примерах.

Пример файла rsyncd.conf.
### .rsyncd.conf  -*- Conf -*-

log file    = /home/private/users/jrh/.rsync.log

[alice]
    path        = /home/public/project1/alice
    comment     = Alice’s own data
    exclude     = .rsync-backup *~ .htaccess .*/ .*.*

[dave]
    path        = /home/public/users/dave
    comment     = Dave’s copy
    exclude     = .rsync-backup *~ .htaccess .*/ .*.*

### .rsyncd.conf ends here
Пример: организация сервера Rsync с использованием Socat и с использованием лишь опции --daemon.
$ setsid -- socat  TCP6:8873,fork \
      EXEC:"rsync --daemon --config=${HOME}/.rsyncd.conf" 
$ rsync --port=7873 --daemon --config="$HOME"/.rsyncd.conf 
$ 
Пример: обращение к созданным серверам.
$ rsync -- rsync://ip6-localhost:8873/ 
alice           Alice's own data
dave            Dave's copy
$ rsync -rv -- rsync://ip6-localhost:7873/alice/ 
receiving incremental file list
drwxr-xr-x             80 2020/04/23 04:40:19 .
-rw-r--r--     14,727,168 2020/04/23 04:40:19 onefile
drwxr-xr-x             80 2020/04/23 04:40:19 bob
-rw-r--r--     28,023,808 2020/04/23 04:40:19 bob/anotherfile
drwxr-xr-x             60 2020/04/23 04:40:19 bob/charlie
-rw-r--r--      7,936,000 2020/04/23 04:40:19 bob/charlie/thirdfile

sent 22 bytes  received 163 bytes  370.00 bytes/sec
total size is 50,686,976  speedup is 273,983.65
$ 
Пример файла протокола сервера Rsync.
2020/04/23 08:47:58 [10399] forward name lookup for users.example.com failed: ai_family not supported
2020/04/23 08:47:58 [10399] connect from UNKNOWN (localhost)
2020/04/23 08:48:55 [10498] rsyncd version 3.1.3 starting, listening on port 7873
2020/04/23 08:48:57 [10502] connect from localhost (::1)
2020/04/23 08:48:57 [10502] rsync on alice/ from localhost (::1)
2020/04/23 08:48:57 [10502] building file list
2020/04/23 08:48:57 [10502] sent 184 bytes  received 27 bytes  total size 50686976

Системные процессы

Вспомним, что завершающим этапом инициализации ядра (после инициализации внутренних структур, основного оборудования, корневой файловой системы, etc.) является создание процесса с идентификатором 1, или init-процесса. Задачей которого, в свою очередь, является порождение всех прочих процессов, обеспечивающих работу системы в заданной конфигурации — в том числе серверных.

Существуют различные реализации init и используемых им вспомогательных средств, а значит и различные соглашения по обеспечению запуска процессов при запуске системы. Так, например, NetBSD предусматривает для этой цели набор программ (как правило — на языке оболочки, /bin/sh) /etc/rc.d.

Для Debian классическим — и используемым, до сравнительно недавнего времени, по умолчанию — способом управления процессами при запуске и завершении работы являются программы (также на языке оболочки) /etc/init.d (близкие по сути, но отличные в реализации от программ /etc/rc.d принятых в NetBSD.)

В частности, запуск Rsync «в режиме самоуправления» обеспечивается программой /etc/init.d/rsync, которая может быть вызвана или администратором (используя, возможно, обертку service), или же служебными программами .postinst, .prerm (через invoke-rc.d) — для автоматического запуска сервера после успешной установки или обновления соответствующего пакета, или же завершения перед удалением последнего.

В комментариях заголовка файла размещена информация, читаемая низкоуровневым инструментом insserv, вызываемым в свою очередь программой update-rc.d, и определяющая порядок запуска и завершения процессов. Так, согласно этим настройкам:

Default-Start: 2 3 4 5
Rsync будет запущен при запуске системы; (некоторые системные процессы запускаются непосредственно перед завершением работы системы; для таких процессов данное поле содержит 0 6);
Required-Start: $remote_fs $syslog
… но не ранее, чем будут подключены удаленные файловые системы и запущен сервер системного протокола.

Не вдаваясь в подробности отметим, что порядок выполнения программ на деле задается содержимым директорий /etc/rcS.d, rc0.d, …, rc6.d — которым, в свою очередь, и управляют insserv и update-rc.d. В частности:

update-rc.d rsync defaults
update-rc.d rsync remove
эти две команды вызываются из программ /var/lib/dpkg/info/rsync.postinst (после успешной установки пакета rsync) и rsync.postrm (после полного удаления; англ. purge);
update-rc.d rsync enable
update-rc.d rsync disable
эти команды, в свою очередь, может использовать администратор для того, чтобы включить Rsync в процесс загрузки, или исключить из него, соответственно.

Кроме того, настройка RSYNC_ENABLE=false, имеющаяся по умолчанию в файле /etc/default/rsync, также предотвращает запуск Rsync. Другими допустимыми значениями этой переменной являются inetd (то же, но init.d/rsync не будет выдавать предупреждение о запрете запуска) и true — разрешающее запуск Rsync.

Наконец, собственно запуск и завершение процесса rsync --daemon выполняет служебная программа start-stop-daemon (вызываемая из все той же init.d/rsync.)

Необходимость использования отдельной программы вызвана тем, что в Unix-подобных системах (или, быть может, их ядрах) традиционно отсутствует какой бы то ни было механизм «регистрации» процессов — привязки их числовых (и, в достаточной мере, случайных) идентификаторов к какими бы то ни было «именам». Программа start-stop-daemon как раз и выполняет поиск процесса по его свойствам (как, например, имя исполнимого файла, идентификатор пользователя, etc.), что позволяет, с одной стороны, предотвратить повторный запуск системного процесса (чреватый конфликтом при доступе к одному и тому же обслуживаемому ресурсу), с другой — более или менее надежно завершить или перезапустить процесс, когда в том возникнет необходимость (например — после обновления соответствующего пакета.)

Следует отметить, что программа start-stop-daemon на практике все же не слишком надежна, если в системе намеренно существует несколько «одноименных» процессов. Так, например, при использовании контейнеров, выполнение команды service foo stop основной системы легко может привести к завершению системных процессов foo во всех контейнерах — а не только в основной системе.

Суперсервер Inetd

Частным, но важным с точки зрения запуска серверных программ, случаем системного процесса является суперсервер Inetd, один из вариантов которого представлен пакетом Debian openbsd-inetd.

Применение Inetd концептуально не отличается от использования socat или s6-tcpserver, за тем лишь исключением, что единственный inetd-процесс как правило создает множество ожидающих гнезд — согласно настройкам файла /etc/inetd.conf.

Пример: ожидающие гнезда процесса inetd.
# lsof -ni 
COMMAND  PID USER   FD   TYPE  NODE NAME
inetd     93 root    7u  IPv6   TCP *:discard (LISTEN)
inetd     93 root    8u  IPv6   UDP *:discard 
inetd     93 root    9u  IPv6   TCP *:ssh (LISTEN)
inetd     93 root   10u  IPv6   TCP *:rsync (LISTEN)
# 
Пример конфигурационного файла /etc/inetd.conf.
#:INTERNAL: Internal services
discard     stream  tcp6    nowait  root  internal
discard     dgram   udp6    wait    root  internal

#:STANDARD: These are standard services
ssh         stream  tcp6    nowait  root  /usr/sbin/tinysshd  tinysshd  -l -- /etc/tinyssh/key.d



#:OTHER: Other services
rsync       stream  tcp6    nowait  rsync /usr/bin/nice  rsync  -n+13 -- ionice -c 3 -- rsync --daemon

Подчеркнем, что использование отдельного процесса для обслуживания каждого клиентского соединения даже если и возможно, как правило не слишком эффективно. Практика показывает, что реализация цикла событий (например, на основе упоминавшихся уже ранее poll или select) в единственном процессе — как это сделано, например, в HTTP-сервере Lighttpd, или реализуется легковесными процессами (англ. lightweight processes) языка Erlang — зачастую обеспечивает лучшую масштабируемость сервера.

Ясно, например, что реализация сервера, в задачи которого входит обеспечивать обмен данными между клиентами — как, например, сервера видеоигры с поддержкой множества участников — при многопроцессном подходе потребует использования каких-то дополнительных средств для обеспечения взаимодействия между отдельными, обслуживающими различных клиентов, процессами. Что, в некотором смысле, сводит задачу к исходной.

Тем не менее в некоторых случаях, ради простоты и некоторой степени изоляции, обеспечиваемой использованием отдельных процессов, применение таких средств, как Inetd, s6-tcpserver, а равно socat, может быть вполне оправдано.

Домашнее задание

Для проверки владения материалом по теме лекции на обычном месте будет размещен тест; контрольный срок сдачи.