Alpine + Docker: поваренная книга разработчика

Автор: Евгений Голышев

Статья была опубликована в 237-м выпуске журнала Linux Format. По истечении полугода с момента публикации права на статью переходят ее автору, поэтому материал публикуется в нашем блоге.

Docker – одна из немногих технологий, которая, наделав несколько лет назад много шума, до сих пор остается с нами. Чтобы помочь разработчикам взглянуть на любимый Docker под другим углом, я достал свою поваренную книгу, смахнул с нее пыль и приготовился к тому, чтобы в рамках этой статьи поделиться своими рецептами.

Два из четырех фигурирующих здесь рецептов призваны уменьшить размер результирующих образов. Эта задача всегда была и остается актуальной для Docker’а, поэтому я начну свой небольшой сборник рецептов с рассказа об Alpine Linux, а потом перейду непосредственно к самим рецептам. Во-первых, именно Alpine является первым шагом на пути к компактным образам, а во-вторых, я хочу воспользоваться случаем и рассказать немного об этом дистрибутиве, чтобы исправить недостаток информации об Alpine на страницах этого журнала (по какой-то причине журнал до сих пор обходил этот дистрибутив стороной).

Немного об Alpine

Alpine Linux применяется в качестве основы для официальных Docker-образов; в свое время он вытеснил с этого места Ubuntu. Более того, в начале 2016-го стало известно, что Натанаэ́ль Копа [Natanael Copa], создатель дистрибутива, присоединился к команде Docker’а. При этом Alpine остается независимым дистрибутивом, подконтрольным только своему сообществу.

Вот список наиболее интересных, на мой взгляд, фактов:

  • Последняя на момент написания статьи версия дистрибутива – 3.8 – вышла 26 июня 2018 г., и ее официальный образ весит чуть больше 4 МБ.
  • Разработчики стараются придерживаться предсказуемого графика выпуска новых версий дистрибутива. Как правило, новые версии Alpine’а выходят два раза в год – в мае и декабре, но возможны и небольшие отклонения от графика. К примеру, версия 3.8 вышла с опозданием на пару месяцев.
  • Каждая версия дистрибутива поддерживается на протяжении двух лет. Так как новые версии Alpine’а выходят два раза в год, то одновременно поддерживается 3-4 версии дистрибутива.
  • В качестве системы инициализации используется OpenRC. Напомню, что стандартом де-факто сейчас является systemd.
  • Поддерживается 6 портов: x86 и x86_64, armhf и aarch64, ppc64le, s390x. Это меньше, чем у Debian, но больше, чем у Ubuntu.
Docker-образы на базе текущей и предыдущей версий Alpine’а весят чуть больше 4 МБ.

Статическая компоновка

Не вдаваясь в лишние подробности, сборку программы можно разделить на два этапа: получение объектных файлов (на каждую единицу компиляции – файл с расширением .c или .cpp – приходится по одному объектному файлу) и компоновку (или линковку, от англ. linkage). Суть компоновки заключается в создании исполняемого файла (или разделяемой библиотеки) из полученных ранее объектных файлов. Если у исполняемого файла есть зависимости в виде библиотек, то в задачи компоновщика также входит связывание [linking] исполняемого файла с этими библиотеками одним из двух способов: статически или динамически. Для статической компоновки необходимы статические библиотеки [static libraries], а для динамической – разделяемые [shared libraries].

Статическая библиотека представляет собой архив объектных файлов, который создается программой ar. Когда исполняемый файл компонуется со статической библиотекой, то составляющие данную библиотеку объектные файлы становятся частью этого исполняемого файла. Для сравнения, разделяемая библиотека представляет собой объектный файл. Когда исполняемый файл компонуется с разделяемой библиотекой, то в исполняемый файл попадает только информация об этой библиотеке. В таком случае, процесс компоновки будет осуществляться во время запуска программы динамически. (В силу ограниченного объема нашего урока, описание процесса компоновки пришлось сильно упростить, поэтому я настоятельно рекомендую обратиться к книгам «Linux API. Исчерпывающее руководство» Майкла Керриска и «Linux. Руководство программиста» Джона Фуско за более детальными подробностями по этому вопросу.)

С использованием разделяемых библиотек связан ряд преимуществ.

  • Использование разделяемых библиотек уменьшает расход дискового пространства и памяти: на диске хранится и в память загружается только одна копия разделяемой библиотеки, независимо от количества программ, которые ее используют.
  • Устранение ошибок и уязвимостей в разделяемой библиотеке не требует пересборки зависимых от нее программ (конечно, при условии, что исправления не затронули двоичную совместимость).

Все эти преимущества не имеют никакой ценности в контейнерах. Во-первых, философия Docker гласит, что на один контейнер полагается только один процесс. Таким образом, в контейнере не должно оказаться двух процессов, которые бы разделяли код одной и той же библиотеки. Во-вторых, Docker-образы, как правило, обновляются атомарно – если один из компонентов контейнера требует обновления, то пересобирается весь образ.

Всё указывает на то, что статические библиотеки подходят для Docker-образов с идеологической точки зрения. Теперь осталось разобраться, есть ли в этом какая-то практическая польза.

Использование разделяемых библиотек негативно сказывается на размере Docker-образа. В основном это связано с особенностью дистрибутивов. В дистрибутивах разделяемые библиотеки распространяются в отдельных пакетах, а с установкой любых пакетов связаны определенные накладные расходы, и чем меньше остается пакетов в образе после его сборки, тем меньше он в итоге занимает места. Таким образом, если компактность результирующего образа для вас не пустой звук, то польза от использования статических библиотек определенно есть.

К примеру, у Memcached есть две основные зависимости – пакет libevent-dev с библиотекой для ассинхронного неблокируещего ввода/вывода, и cyrus-sasl-dev с библиотекой, реализующей вторую версию SASL API. libevent-dev зачем-то тащит за собой пакет python2, который в установленном виде занимает 39 МБ. Это именно те накладные расходы, о которых я говорил выше. Таким образом, официальный Docker-образ с Memcached’ом 1.5.10, последней на момент написания статьи версией демона, весит почти 59 МБ, т. к. постоянно таскает за собой Python 2, о котором даже никто не вспоминает, когда запускает Memcached. Docker-образ со статически скомпонованным с этими библиотеками Memcached’ом, весит чуть больше 10 МБ. Предлагаю в этом убедиться на примере образа Memcached’а из проекта MMB. Для этого сначала получите исходники всего проекта, а затем (предпочтительнее) соберите образ –

$ git clone https://github.com/tolstoyevsky/mmb.git
$ cd mmb/memcached
$ IMAGE_NAME=$(grep "image: " docker-compose.yml | awk -F': ' '{print $2}')
$ docker build -t ${IMAGE_NAME} .

или вытяните его с Docker Hub’а:

$ docker pull cusdeb/memcached:1.5.10-amd64

(На самом деле, в этом образе используется еще одна нехитрая оптимизация, о которой будет рассказано сразу в следующем рецепте. Без нее, конечно, размер образа был бы чуть больше.)

Образ со статически скомпонованным Memcached’ом весит почти в 6 раз меньше официального образа.

Но за эту оптимизацию приходится платить. Дело в том, что при использовании Alpine в качестве базового образа статическая компоновка осложняется тем, что у этого дистрибутива туговато со статическими библиотеками. К примеру, как libevent-dev, так и cyrus-sasl-dev содержат только разделяемые варианты библиотек. Наличие обоих вариантов библиотек в Alpine отдается на откуп сопровождающим этих библиотек, и если сопровождающий решил, что библиотека, от которой зависит докеризуируемая вами программа, должна быть доступна только в разделяемом виде, то придется самостоятельно готовить ее статический вариант на этапе сборки самого образа, что потребует дополнительных сил на написание Dockerfile’а.

Поэтому для начала необходимо решить, что для вас имеет больший приоритет – быстрая докеризация или небольшой объем результирующего образа. Если образ предназначается для широкого круга пользователей, то лично я бы вложился в уменьшение его объема.

Докеризация только серверных частей

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

Клиенты, как правило, распространяются в дистрибутивах отдельно от серверов и, в отличие от последних, имеют минимум зависимостей и практически не требуют времени на их настройку, поэтому при решении узкоспециализированных я придерживаюсь идеи выкидывания из образа всего лишнего.

Тем не менее, если вам достаточно сделать docker exec для того, чтобы подключиться к серверу посредством клиента, запущенного в том же контейнере, что и сам сервер, то речь идет об учебной или просто несерьезной задаче, и волноваться вам не о чем. Во всех остальных случаях клиенты в Docker-образах с серверами являются лишними.

Сборка образов под ARM на машине x86

Благодаря широкому распространению одноплатных компьютеров (Raspberry Pi, Orange Pi, Banana Pi и т. д.), устройства на базе 32- и 64-битного процессора ARM, на которых можно запустить полноценную операционную систему, сейчас доступны как никогда. Более того, Docker поддерживает ARM уже достаточно давно: начиная с версии 1.10, вышедшей 4 февраля 2016 г., появилась возможность собрать клиентскую и серверную части под ARM, а с версией 1.12.1, вышедшей 18 августа того же года, официальная поддержка пришла на ARM-машины под управлением Debian Jessie, Ubuntu Trusty и Raspbian Jessie.

Тем не менее, Docker-образы не всегда удобно собирать непосредственно на одноплатниках – для экономии времени сборку лучше производить на рабочей станции или ноутбуке, которые, как правило, представляют собой машины на базе процессора x86. Один из способов этого добиться – поместить в базовый образ [base image] целевого Docker-образа двоичные файлы средства эмуляции в режиме пользователя [user mode emulation binaries].

Как в производных от Debian дистрибутивах, так и в Fedora двоичные файлы средства эмуляции в режиме пользователя содержатся в пакете qemu-user-static. Модуль ядра binfmt_misc, доступный в Linux начиная с версии 2.1.43 (которая, кстати, вышла в июне далекого теперь 1997 г.), позволяет распознавать различные форматы исполняемых файлов и ассоциировать их с произвольными приложениями. Другими словами, для определенного формата исполняемого файла можно зарегистрировать эмулятор, и при каждой попытке запустить исполняемый файл этого формата передавать его эмулятору, а не запускать на текущем процессоре. (В производных от Debian дистрибутивах также потребуется установить пакет binfmt-support, в который вынесена функция регистрации эмуляторов.) Таким образом, если предназначенный для запуска на одноплатнике образ включает эти бинарники, то на машине на базе процессора, отличного от ARM, сборка образа и запуск контейнера на его основе будут осуществляться с привлечением эмулятора, а на ARM-машинах всё будет выполняться на реальном процессоре.

Возможность прозрачного запуска собранных под ARM программ на x86-машинах доступна в ядре более 20 лет.

Предлагаю рассмотреть скрипт создания базового Docker-образа для ARM, который будет включать двоичные файлы средства эмуляции в режиме пользователя, чтобы любой образ на его основе мог быть без проблем собран и запущен на x86-машинах. Чтобы скрипт корректно отработал, в системе, где он будет запущен, должны находиться эти бинарники. В том случае, если этой системой является Fedora, установите пакет qemu-user-static, а если речь идет о Debian или Ubuntu, то qemu-user-static и binfmt-support.

А теперь перейдем к самому скрипту:

#!/bin/sh

set -e

if [ "$(id -u)" -ne "0" ]; then
  >&2 echo "This script must be run as root"
  exit 1
fi

mirror=http://dl-cdn.alpinelinux.org/alpine
alpine_ver=3.8
apk_ver=2.10.0-r3
chroot_dir=./alpine-baseimage

wget ${mirror}/v${alpine_ver}/main/armhf/apk-tools-static-${apk_ver}.apk

tar xzf apk-tools-static-${apk_ver}.apk

mkdir -p ${chroot_dir}/usr/bin

cp /usr/bin/qemu-arm-static ${chroot_dir}/usr/bin

./sbin/apk.static -X ${mirror}/v${alpine_ver}/main -U --allow-untrusted --root ${chroot_dir} --initdb add alpine-base
mknod -m 666 ${chroot_dir}/dev/full c 1 7
mknod -m 666 ${chroot_dir}/dev/ptmx c 5 2
mknod -m 644 ${chroot_dir}/dev/random c 1 8
mknod -m 644 ${chroot_dir}/dev/urandom c 1 9
mknod -m 666 ${chroot_dir}/dev/zero c 1 5
mknod -m 666 ${chroot_dir}/dev/tty c 5 0

IMAGE=$(sh -c "tar -C ${chroot_dir} -c . | docker import -")

docker tag ${IMAGE} alpine:${alpine_ver}-armhf

Обратите внимание на то, что версия утилиты apk-tools-static (в данном случае 2.10.0-r3) является плавающей – почти наверняка к выходу данной статьи в свет она изменится. Скрипту определенно следовало бы быть немного сложнее, чтобы учитывать эту особенность, но здесь он должен оставаться как можно более простым. Прежде чем переходить к его модернизации, проделайте следующее:

  • откройте http://dl-cdn.alpinelinux.org/alpine/v3.8/main/armhf/, чтобы подсмотреть текущую версию apk-tools-static;
  • обновите версию apk-tools-static в тексте скрипта;
  • запустите скрипт с правами суперпользователя (т.к. скрипту необходимо производить различные манипуляции с chroot-окружением).

В результате будет создан базовый Docker-образ alpine:3.8-armhf.

Напоследок стоит сказать, что эмулятор позволяет решить подавляющее большинство задач, но, к сожалению, не всё через него запускается или собирается. Тем не менее, случаи отказов являются большой редкостью и сильно зависят от того, насколько экзотическими являются те вещи, для запуска которых эмулятор привлекается. По моему опыту, запущенные через эмулятор FTP-сервер vsftpd и сборка watchtower ведут себя нестабильно. К счастью, на этом список известных мне проблем заканчивается.

Накладывание патчей при сборке образа

Между пакетами исходных текстов, на базе которых строятся двоичные пакеты дистрибутивов GNU/Linux, и Docker-образами есть много общего. Нередки случаи, когда докеризуемая программа собирается из исходников. Как и в случае с пакетами исходных текстов, в рамках Docker-образа, возможно, придется заняться полировкой исходных текстов целевой программы – из-за того, что разработчики ее оригинала не учитывали особенности той системы, в которой программу в данный момент хотят заставить работать. Этот рецепт посвящен распространению патчей в составе исходников Docker-образов, чтобы, как и в случае пакетов исходных текстов, внесенные в тот или иной кусок (известного) кода изменения находились у всех на виду и могли быть проанализированы и переиспользованы другими разработчиками.

В качестве примера хочу привести Docker-образ QEMU из проекта MMB: https://github.com/tolstoyevsky/mmb/tree/master/qemu. Одной из главных задач для MMB в один прекрасный момент стало предоставление пользователям компактного образа с эмулятором (а по совместительству и системой виртуализации) QEMU, поэтому в качестве основы для образа был выбран Alpine. Затем, чтобы не зависеть от версии QEMU, которая в тот или иной момент времени находится в репозитории дистрибутива, было принято решение собирать эмулятор из исходников. После этого выяснилось, что некоторые зависимости QEMU отсутствуют в Alpine, и возникла необходимость их тоже собирать из исходных текстов.

Библиотека numactl, одна из зависимостей QEMU, до версии 2.0.12 не собиралась в Alpine из-за того, что Musl, стандартная библиотека языка C, которую использует дистрибутив, нигдене объявляет макрос __GLIBC_PREREQ, фигурирующий в одном из модулей библиотеки. Таким образом, numactl оказалась зависима от Glibc. Чтобы исправить эту ситуацию, был взят на вооружение менеджер патчей quilt, тот самый, который используется в пакетах исходных текстов Debian.

К сожалению, quilt никак не может стать частью дистрибутива, вечно находясь в edge, тестируемой версии Alpine, поэтому менеджер патчей придется тоже собрать из исходников или написать примитивную альтернативу на базе программы patch и цикла for. Преимуществами использования quilt для управления патчами являются в первую очередь легкость отключения того или иного патча в серии или, при необходимости, изменение порядка их внесения.

Чтобы исправить сложившуюся с numactl ситуацию, можно воспользоваться самим quilt’ом, но для начала надо получить исходники библиотеки.

$ git clone https://github.com/numactl/numactl.git
$ cd numactl

Затем необходимо сообщить quilt’у о намерении создать новый патч:

$ quilt new adapt_to_musl.patch
$ quilt add syscall.c

syscall.c – тот самый модуль, в котором используется макрос __GLIBC_PREREQ. Теперь в него можно внести изменение, устраняющее проблему.

Моим решением на скорую руку было просто-напросто удалить __GLIBC_PREREQ из условия

#if defined(__GLIBC__) && __GLIBC_PREREQ(2, 11)

Это решает проблему и никак не влияет на логику – в случае использования Musl остаток выражения всегда будет ложным. Тем не менее, более красивым решением является добавление перед этим условием заглушки, которая выглядит следующим образом.

#ifndef __GLIBC_PREREQ
# define __GLIBC_PREREQ(x,y) 0
#endif

И наконец, надо обновить патч, чтобы он отражал сделанные только что изменения.

$ quilt refresh

В результате всех этих манипуляций в корне дерева исходных текстов библиотеки будет создана директория patches с двумя файлами – adapt_to_musl.patch и series. Второй файл является так называемой серией патчей и представляет собой список, который на данный момент состоит всего из одного-единственного патча. Посредством редактирования можно повлиять на поведение quilt.

Теперь, чтобы созданный патч был применен к numactl во время сборки образа, необходимо позаботиться о том, чтобы директория patches находилась корне исходных текстов библиотеки. Затем, вызов quilt push -a инициирует накладывание всех патчей, указанных в серии.

GNU или не GNU/Linux

Такие дистрибутивы, как Debian или Ubuntu (и многие другие), используют GNU Coreutils и GNU C Library (Glibc) в качестве Unix-подобного окружения и стандартной библиотеки языка C соответственно. Таким образом, Проект GNU, как разработчик этих компонентов, играет важную роль в дистростроении, особенно если учесть тот факт, что для сборки вышеперечисленных частей используется компиляторы C/C++ из состава коллекции компиляторов от GNU (GNU Compiler Collection, GCC). В связи с этим Debian и Ubuntu следует называть дистрибутивами GNU/Linux, т.е. дистрибутивами, в которых различные компоненты Unix-подобной операционной системы от GNU в сочетании с ядром Linux образуют прочную основу, на которой строится всё остальное.

Было даже время, когда полнофункциональную Linux-подобную операционную систему не представлялось возможным создать без привлечения арсенала GNU. Несмотря на то, что достойная альтернатива Coreutils в лице Busybox существовала с незапамятных времен, свободные компиляторы C/C++ и альтернативные стандартные библиотеки языка C не всегда дотягивали до функциональности своих GNU’шных собратьев. Но времена изменились, и сейчас Musl является достойной альтернативой Glibc, а Clang не только научился конкурировать с компиляторами C/C++ из состава GCC, но и начал в чем-то их превосходить. Таким образом, в новых операционных системах на базе ядра Linux появилась возможность без значительного ущерба для функциональности свести к минимуму использование кода от GNU, компенсировав его аналогами, распространяющимися под пермессивной лицензией (Apache 2.0, все виды BSD, MIT и т.д.) или лучше «заточенными» под встраиваемые устройства. К числу таких операционных систем принадлежит Alpine. (Однако, справедливости ради, стоит заметить, что значительная часть дистрибутива собирается не чем иным, как GCC.) Таким образом, в данном конкретном случае от GNU/Linux остается только Linux, в результате чего полное название дистрибутива – Alpine Linux. Данный подход является семантическим именованием: взглянув на одно лишь имя операционной системы, возможно не только понять, что она построена на базе ядра Linux, но еще и то, что в ней, к примеру, используется нечто отличное от Glibc. Для сравнения, полным названием Debian является Debian GNU/Linux. Глядя на это название, можно с уверенностью сказать, что концентрация распространяющегося под лицензией GPLv3 кода в базовой системе очень высока. К сожалению, не все проекты по разработке Linux-подобных операционных используют семантиче­ское именование. Отличным примером является Gentoo Linux.

Devuan: плод нелюбви к systemd

Автор: Евгений Голышев

Статья была опубликована в 237-м выпуске журнала Linux Format. По истечении полугода с момента публикации права на статью переходят ее автору, поэтому материал публикуется в нашем блоге.

Кена Томпсона [Ken Thompson] однажды спросили, что бы он изменил в UNIX, если бы проектировал систему заново, и он ответил: «Я бы написал creat с буквой e» (англ. I’d spell creat with an e). Эта шутка, на мой взгляд, как ничто другое подчеркивает, насколько бережно Unix хранит традиции: за 40 с лишним лет так никто и не добавил букву e к имени системного вызова, хотя количество опечаток, допущенных при написании кода, велико и с каждым днем продолжает расти. В конце концов, кто может осмелиться сказать, что архитектурные решения патриархов Томпсона и Ритчи ошибочны?

Unix – это удивительный мир, в котором желание к познанию чего-то нового уживается со слепой верой и следованию традициям.

И однажды в этот мир вторгся человек по имени Леннарт Пёттеринг [Lennart Poettering], со своим видением дистрибутивостроения, которое, по мистическому совпадению, в корне отличалось от того, к которому все успели привыкнуть за пару десятков лет. Всё бы ничего, но Леннарт родился с редкой способностью изменять всё вокруг, поэтому он легко начал воплощать свои задумки, а через крупнейшие дистрибутивы (Debian, Ubuntu, Red Hat Enterprise Linux, SUSE Linux Enterprise и т. д.) они стали добираться до согласных и несогласных с ним пользователей.

Леннарт полон неоднозначных идей того, каким образом дистрибутивы GNU/Linux можно сделать лучше.

Данная статья посвящена Devuan (читается как DevOne). Devuan является производным от Debian GNU/Linux дистрибутивом, основная цель которого заключается в альтернативном развитии Debian, как если бы он никогда не встретился с известным детищем Леннарта под названием systemd. Я написал эту статью главным образом по двум причинам.

Во-первых, Debian-подобные дистрибутивы – моя страсть, а Devuan, один из ярких представителей этого семейства, упоминался на страницах LXF только вскользь, поэтому я чувствую своим долгом исправить это недоразумение.

Во-вторых, я хочу помочь читателям разобраться в ситуации, сложившейся вокруг проекта systemd, а также понять, что побудило разработчиков Devuan потратить два с половиной года на то, чтобы «вытравить» systemd из Debian Jessie, получив таким образом первую стабильную версию производного дистрибутива.

В конце концов, если человек в один прекрасный момент поймет, что ненавидит systemd, то пусть хотя бы будет способен объяснить, почему.

К счастью или сожалению, материал не содержит информации, которая могла бы оказаться полезной при

  • установке, настройке и администрировании серверов под управлением Devuan;
  • администрировании серверов на базе Linux-подобных операционных систем, использующих systemd (если вы интересуетесь практиче­ской стороной systemd, то рекомендую обратиться к LXF199).

Хочу сразу отметить, что я не являюсь пользователем Devuan, предпочитая ему Debian (рабочая станция) и Ubuntu (ноутбук), и стараюсь занимать нейтральную позицию в спорах, связанных с действиями Леннарта Пёттеринга в общем и с systemd в частности. Но разочаровываться не стоит – я регулярно имею дело с Devuan при решении различных задач, одной из которых является разработка сервиса для кастомизации образов операционных систем для одноплатных компьютеров.

А теперь предлагаю начать обсуждение заявленных тем… но обо всём по порядку.

В погоне за ускорением загрузки

В конце 2000‐х несколько крупных производителей операционных систем на базе ядра Linux пришли к выводу, что необходимо ускорять время загрузки своих систем. Так как за загрузку операционной системы отвечает система инициализации, стало очевидно, что начинать нужно именно с нее. В течении долгого времени дистрибутивы GNU/Linux использовали для этих целей SysVinit, созданную Микéлем ван Смооренбургом [Miquel van Smoorenburg] и основанную на идеях оригинальной System V init. SysVinit сейчас считается классиче­ской системой инициализации и в данной статье противопоставляется всем другим.

SysVinit представляет собой систему инициализации, основанную на уровнях запуска (runlevel-based). Уровни запуска – это состояния, в которых может находиться система; система переходит из одного состояние в другое. SysVinit выполняет скрипты, ассоциированные с определенным уровнем, когда система входит в (или покидает) этот уровень. К примеру, когда система входит в уровень запуска N, то SysVinit запускает скрипты из /etc/rcN.d, где N — число от 0 до 6. Однако со временем накопилось много потребностей, которые классиче­ская система инициализации не могла удовлетворить в силу своей архитектуры (к примеру, слежение за своими дочерними процессами и их перезапуск в случае необходимости).

Недостатки SysVinit мотивировали компанию Canonical, которая является основным разработчиком Ubuntu, начать работу над альтернативной системой инициализации под названием Upstart, а достоинства launchd из Mac OS X и SMF из Solaris послужили основным источником ценных идей, которые помогли в решении поставленной задачи. Новая система инициализации оказалась эффективнее, чем SysVinit благодаря событийно-ориентированной архитектуре (event-driven architecture). Системные сервисы стало возможным запускать и останавливать на основе событий, а не уровней запуска. Данный подход предполагал генерацию событий при запуске или остановке системных сервисов, что позволило организовать привязку к ним других сервисов.

Таким образом, для определения последовательности запуска сервисов и оценки возможности их выполнения в параллельном режиме был взят на вооружение метод учета зависимостей. Работа в тандеме с ядром, которое также стало развиваться в сторону событийно-ориентированной архитектуры, благодаря чему появилась возможность асинхронной загрузки драйверов, заметно сократила время от нажатия кнопки включения до полной готовности всей системы. И при всём при этом Upstart была разработана с оглядкой на обратную совместимость с SysVinit.

Upstart дебютировала в Ubuntu 6.10 «Edgy Eft» в конце 2006 г., заменив SysVinit (однако окочательно новая система инициализации была интегрирована в дистрибутив только к выходу Ubuntu 9.10 «Karmic Koala»). Затем на Upstart обратили внимание компании Google и Red Hat, интегрировав ее в свои продукты. Так Upstart нашла применение за пределами Ubuntu и начала использоваться в ChromeOS и Red Hat Enterprise Linux 6. На новую систему инициализации даже планировал перейти Debian.

При всех своих достоинствах, Upstart оказалась всего лишь трамплином для другой, более совершенной системы инициализации. Так, в апреле 2010 г. компания Red Hat, при участии разработчиков из Novell, IBM, Intel и Nokia, поставила перед собой цель создать систему инициализации, нацеленную на более интенсивную параллелизацию выполнения сервисов на этапе загрузки. Проект, получивший название systemd, возглавил Леннарт Пёттеринг. И уже в июле того же года была выпущена первая стабильная версия проекта.

На тот момент systemd получала не больше негативной критики, чем любой другой проект, позиционирующий себя как альтернативная система инициализации. Всё шло относительно гладко, несмотря на то что systemd (почти) сразу обратила на себя внимание своим агрессивным подходом к делам, связанным с обратной совместимостью. (К примеру, если Upstart была совместима со скриптами инициализации SysVinit, то новейшая система инициализации не была завязана ни на Bash, ни на любую другую оболочку.) Ситуацию не сильно усугубил и тот факт, что именно Леннарт был тем самым разработчиком звукового сервера PulseAudio, который в начале своего пути не отличался стабильностью работы (в основном из-за аудиодрайверов, а не ошибок в коде PulseAudio).

Проекту серьезно досталось немного позднее, когда в один прекрасный день systemd перестал позиционироваться как альтернативная система инициализации, превратившись в системный менеджер, в котором система инициализации стала всего лишь одним из многочисленных компонентов. О двух таких компонентах — journald и logind — я хочу рассказать прямо сейчас.

logind представляет собой систему управления пользовательскими сеансами. До того, как за решение этой задачи взялись разработчики systemd, дистрибутивы GNU/Linux для этих целей использовали ConsoleKit. В свое время ConsoleKit поставил перед собой амбициозную цель – поддержку более одного независимого рабочего места (multi-seat), что предполагает одновременную работу на одной системе нескольких независимых графических сеансов для различных пользователей. (Функция редкая, но в некоторых случаях очень востребованная.) Тем не менее, архитектура ConsoleKit не способствовала достижению заветной цели и проект оказался в глубокой стагнации. Так, без переизобретения велосипеда непростая задача поддержки более одного независимого рабочего места была решена в рамках компонента logind. Другими словами, лучшей альтернативы в мире Linux еще не видели. Более того, решение оказалось настолько удачным, что Энди Винго (Andy Wingo), один из разработчиков дистрибутива Guix, реализовал возможность обособленного использования logind. Проект получил название elogind (по аналогии с eudev). На elogind даже перешел Devuan, который, напомню, принципиально не использует systemd; но об этом немного позже.

journald представляет собой сервис журналирования событий. Этот компонент, в свою очередь, является отличным примером того, как разработчики systemd не только решили задачу, которая давно не нуждалась в решении, но и решили ее по-своему. Двумя основными отличиями journald от различных реализация syslogd, которые много лет занимались задачами журналирования в Unix, являются использование криптографиче­ских средств для гарантирования неизменности и целостности накопленных логов и хранение этих логов в двоичном виде. Традиционно логи на Unix-серверах хранятся в текстовом виде, поэтому хранение логов в двоичном виде воспринимается как недостаток, который обесценивает любые достоинства journald, даже такие крутые, как гарантию неизменности и целостности логов. Подробнее о journald можно узнать из LXF191/192 и LXF199.

Появление Devuan

В конце 2013-го – начале 2014-го Debian в очередной раз вернулся к актуальной для себя задаче – переходу на более современную систему инициализации. В каче­стве кандидатов рассматривались Upstart и systemd, и по результатам голосования победила последняя. Следом за Debian пошла Ubuntu.

По итогам интеграции systemd в Debian дистрибутив не только перешел на новую систему инициализации, но и оказался сильно завязанным на системном менеджере, который был призван управлять всеми аспектами работы системы. В итоге, systemd был поставлен в один ряд с ядром и стандартной библиотекой языка C, для которых в дистрибутиве не предусмотрены альтернативы. Jessie стал первым выпуском Debian, который не мог функционировать без systemd. Таким образом, группа разработчиков Veteran Unix Admins, которые были не согласны с этой политикой, выпустили в рамках проекта Devuan свободную от systemd версию Jessie. Чтобы этого добиться, разработчикам потребовалось внести изменение в 381 пакет. Так появился первый выпуск Devuan, позволивший пользователям Debian использовать или не использовать systemd в зависимости от их желания.

Второй в истории лидер проекта Debian является пользователем Devaun.

В качестве системы инициализации в Devuan по умолчанию используется SysVinit, которая сейчас более-менее активно развивается. Дело в том, что версия 2.89, выпущенная 28‐го марта 2018-го года, была первой за 8 лет стагнации проекта, но с приходом Джесса Смита [Jesse Smith], который занял пустующее кресло сопровождающего проекта, ситуация резко изменилась.

Различные новостные ресурсы, освещающие достижения открытого и свободного программного обеспечения, позиционируют Devuan как борца за независимость от systemd (отчасти это объясняется тем, что самыми оперативными оказываются информационные источники, пополняемые сообществом, а профессиональные издания, как правило, чуть медленнее реагируют на события и потому постоянно догоняют первых по горячим следам). С одной стороны, это позволило быстро обратить на проект внимание, сделав Devuan звездой мира СПО. С другой стороны, это искажает оригинальные цели проекта – свободе выбора системы инициализации. Сейчас с полной уверенностью можно утверждать, что Devuan добился этой цели: во втором выпуске дистрибутива, известном под кодовым именем ASCII, который вышел 9 июня 2018 г., пользователям помимо SysVinit предлагается OpenRC, которая используется в Alpine Linux и Gentoo.

Заключение

Поляризация взглядов в сообществе пользователей и разработчиков свободного и открытого программного обеспечения является обычным делом. Одним из старейших примеров этого явления является вражда пользователей KDE с приверженцами Gnome. Другими словами, людям нужно что-то ненавидеть.

Что касается фактов, то systemd является образцом отлично написанного системного программного обеспечения, а в сообществе разработчиков царят мир, гармония и взаимоуважение. Леннарт, в свою очередь, является примером отличного лидера, который, ко всему прочему, еще и невероятно креативен – пишет много и интересно, освещая различные особенности Unix в своем блоге. Но чутье подсказывает, что где-то здесь должен быть подвох. И он действительно есть. Когда в рамках systemd решаются какие-то задачи, то разработчики, как правило, подходят к их решению по-своему и без оглядки на обратную совместимость, посягая на святая святых – традиции Unix (к примеру, я почувствовал легкое недомогание, когда первый раз прочитал новость о том, что journald предлагает хранить логи в двоичном виде). Именно здесь и начинаются конфликты интересов. Но не все решения разработчиков systemd так уже плохи. Об этом свидетельствует тот факт, что некоторые компоненты systemd уже используются в дистрибутивах, которые сознательно отказываются от systemd как от системы инициализации.

Имена доверьте авторам

Есть иногда у некоторых проектов такие имена, глядя на которые, не сразу возможно угадать их правильное произношение (под «правильным» я в данном случае подразумеваю принятое в сообществе). Обычно в таких случаях необходимо обратиться к FAQ’у или истории этих проектов. В каче­стве примера на ум приходят web-сервер Lighttpd (читается как lighty) и Nginx (читается как engine-x). (Очень забавно, когда люди с серьезным выражением лица рассказывают о некоем web-сервере под названием Энгинкс.) Но история с именами на этом не заканчивается.

Есть и такие проекты, авторы которых настаивают на строгом написании названий своих детищ. Однако все мы привыкли к тому, что слова, с которых начинаются предложения, пишутся с большой буквы. А еще мы помним, что с заглавной буквы всегда пишутся все имена собственные. Но в школе обычно не учат тому, что всем этим правилам нужно следовать только в том случае, если они не противоречат правилу написания конкретного имени. В каче­стве примеров проектов, с которыми нужно считаться с этой точки зрения, хочу привести ownCloud и, конечно, systemd. Таким образом, в каком бы месте предложения ни стояли эти имена, после какого бы знака препинания не шли – они должны сохранять авторский вариант написания.

В заключение, думаю, что нужно сказать пару слов о том, почему следует писать systemd с маленькой буквы несмотря на то, что это имя собственное. Дело в том, что проект был назван по имени основного исполняемого файла, который запускается первым при загрузке системы, становясь процессом с номером 1 и родителем всех остальных процессов. Традиционно (и тут тоже традиции!) в UNIX и C, родного для этой ОС языка, использовался нижний регистр, поэтому одна из ключевых программ в Linux-подобной операционной системе могла быть названа только systemd, а не Systemd, SystemD или как-то еще.

Debian vs. Devuan

Devuan (читается как DevOne) является производным от Debian GNU/Linux дистрибутивом. Принципиальное отличие между Debian и Devuan заключается в отсутствии привязки к systemd у последнего. Несмотря на то, что дистрибутиву уже три года, все еще остается много вопросов относительно его появления и назначения. После того как наша команда добавила возможность кастомизации Devuan сначала в Pieman, а затем в CusDeb, я наконец хочу немного рассказать об этом дистрибутиве в нашем блоге. Основным источником разногласий, которые вылились в работу над Devuan, является переход Debian и Ubuntu на использование системы инициализации systemd. Но обо всем по порядку.

Ускорение загрузки

В конце 2000-х несколько крупных производителей операционных систем на базе ядра Linux пришли к выводу, что необходимо ускорять время загрузки своих систем и, как следствие, брать на вооружение альтернативную систему инициализации. Так как именно система инициализации отвечает за загрузку операционной системы, то стало очевидно, что начинать нужно именно с нее. В течении долгого времени дистрибутивы GNU/Linux использовали для этих целей SysVinit, которая сейчас считается классической системой инициализации и в данной статье противопоставляется всем другим.

Компания Canonical, которая является основным разработчиком дистрибутива Ubuntu, начала работу над альтернативной системой инициализации под названием Upstart в середине 2000-х. Upstart дебютировала в Ubuntu 6.10 «Edgy Eft», заменив SysVinit, но окочательно новая система иницализации была интегрирована в дистрибутив только к выходу Ubuntu 9.10 «Karmic Koala». Затем на Upstart обратили внимание компании Google и Red Hat, интегрировав ее в свои продукты. Так, Upstart начала использоваться в ChromeOS и Red Hat Enterprise Linux 6. На новую систему инициализации даже планировал перейти Debian.

Upstart оказалась эффективней чем SysVinit благодаря событийно-ориентированной архитектуре. Системные сервисы стало возможным запускать и останавливать на основе событий, а не уровней запуска (runlevels), указанных в скриптах инициализации. Данный подход предполагал генерацию событий при запуске или остановке системных сервисов, что позволило организовать привязку к ним других сервисов. Таким образом, для определения последовательности запуска сервисов и оценки возможности их выполнения в параллельном режиме был взят на вооружение метод учета зависимостей. Работа в тандеме с ядром, которое также стало развиваться в сторону событийно-ориентированной архитектуры, благодаря чему появилась возможность асинхронной загрузки драйверов, заметно сократила время от нажатия кнопки включения до полной готовности всей системы.

Недостатки SysVinit мотивировали разработчиков Upstart приступить к созданию альтернативной системы инициализации, а достоинства launchd из Mac OS X и SMF из Solaris послужили основным источником ценных идей, которые помогли в решении поставленной задачи. Однако, по воле судьбы или случайному стечению обстоятельств, Upstart оказалась всего лишь трамплином для другой, более совершенной системы инициализации. Так, компания Red Hat, при участии разработчиков из Novell, IBM, Intel и Nokia, поставила перед собой цель создать систему инициализации, нацеленную на более интенсивную параллелизацию выполнения сервисов на этапе загрузки. Проект получил название systemd и отличался более радикальным подходом к решению некоторых задач. К примеру, если Upstart была совместима со скриптами инициализации SysVinit, то systemd предлагала для этих целей новый синтаксис и, таким образом, не была завязана ни на Bash, ни на любую другую оболочку. Тем не менее, на этам этапе все было хорошо. Волна критики накатила на проект немного позднее, когда стало очевидно, что акцент разработчиков немного сместился в сторону, отличную от первоначальных целей. Так, в один прекрасный момент проект под названием systemd стал позиционироваться не как «альтернативная система инициализации», а как «системный менеджер». Система инициализации в этом проекте стала одним из многочисленных компонентов, а весь проект стал нацелен на управление различными аспектами работы системы. Также стоит заметить, что немалая доля критики свалилась на systemd из-за того, что лидером проекта является не кто иной, как сам Леннарт Поттеринг (Lennart Poettering), в прошлом разработчик звукового сервера PulseAudio, который в начале своего пути не отличался стабильностью работы (в основном из-за аудиодрайверов, а не ошибок в коде PulseAudio), что и является причиной ненависти как к проекту, так и к его автору.

Появление Devuan

Наконец, я подобрался к основным мотивам, которые заставили разработчиков Devuan форкнуть Debian с целью его альтернативного развития. В конце 2013-го – начале 2014-го Debian в очередной раз вернулся к актуальной для себя задаче – переходу на более современную систему инициализации. В качестве кандидатов рассматривались Upstart и systemd, и по результатам голосования победила последняя. Следом за Debian пошла Ubuntu. Стоит заметить, что выбор стоял между системой инициализации и системным менеджером, одним из компонентов которого являлась система инициализации. По итогам интеграции systemd в Debian дистрибутив не только перешел на новую систему инициализации, но и оказался сильно завязанным на системном менеджере, который был призван управлять всеми аспектами работы системы. В итоге, systemd был поставлен в один ряд с ядром и стандартной библиотекой языка C, для которых в дистрибутиве не предусмотрены альтернативы. Jessie стал первым выпуском Debian, который не мог функционировать без systemd. Поэтом группа разработчиков Veteran Unix Admins, несогласная с этой политикой, выпустила в рамках проекта Devuan свободную от systemd версию Jessie. Для того чтобы этого добиться, разработчикам потребовалось внести изменение в 381 пакет. Так появился первый выпуск Devuan, позволивший пользователям Debian использовать или не использовать systemd в зависимости от их желания.

Заключение

Я написал эту статью в первую очередь для того, чтобы помочь нашим пользователям разобраться в причинах появления еще одного производного от Debian дистрибутива. При этом я не являюсь противником ни systemd, ни Леннарта Поттеринга, и остаюсь пользователем Debian (рабочая станция) и Ubuntu (ноутбук). Тем не менее, я считаю, что безальтернативная поставка systemd в крупнейших дистрибутивах является крайностью. В связи со всем этим я попытался сделать статью как можно более беспристрастной. Но теперь наконец я могу сказать, что я восхищаюсь разработчиками Devuan и ценю проделанную ими работу, и рад заявить, что CusDeb поддерживает этот дистрибутив.