По мотивам моего доклада на PyCon «Контейнеризация Python без боли». На своей практике я постоянно сталкиваюсь со спорами какой базовый образ лучше использовать для проектов: alpine или debian. Аргументы есть и у той, и у другой стороны, но мне это настолько надоело, что я решил сам разобраться и наконец-то поставить точку. В конце концов «В наше время верить нельзя никому, даже себе. Но мне — можно.» (с)

- Проблема
- An Alpine Image with Bash Runtime Configuration
- Building
- Users and Permissions
- Running the Container
- Running as appuser (the default user)
- Running as root
- Running While Passing a Known Flag
- Running While Passing an Unknown Option
- Switching User — appuser to root
- Switching User — root to appuser
- License
- Make Alpine Linux RootFS
- Requirements
- Usage
- Examples
- Installation Script in Heredoc
- Create Docker Base Image
- Create OCI Image
- License
- Поведение при проблемах с диском (тест)
- Заключительные моменты
- Хочется странного
- Оно нам действительно надо?
- Сравниваем базовые образы alpine и debian
- Установка
- Stage 0
- Stage 1
- Stage 2
- Дополнительные действия
- Больше пакетов
- Устанавливаем зону времени и обновление времени
- Раскладка для русского языка
- Сервисы для виртуализации
- Прописываем hostname
- Сборка и установка python пакетов
- А еще
- Comments
- reproducer
- workaround 1
- workaround 2
- Про Alpine
- Приоткроем крышку
- Взрываемся в препроде
- Обращение к не-питонистам
- Сравниваем в полевых условиях
- Баги всюду
- Варианты использования (use cases)
- Ремарка про alpine
- Демоны под крышкой
- Возможное решение
- Вместо заключения
- Итог
Проблема
Также возможно, что NAS работает, но лагает сеть до хранилища. Если отвлечься от конкретного случая описанного выше, то представить можно и другие ситуации в отличных от вышеописанного окружениях. Например, ваш провайдер VPS внезапно начал куролесить с дисками. Или что у вас нет NAS и машины крутятся на локальном хранилище, либо у вас нет виртуалок вообще. У вас заглючил RAID-контроллер на старом сервере и начались похожие проблемы. В итоге, если вы что-то подобное не предполагали заранее — вы можете оказаться в ситуации, в которой вы ничего не сможете решить удаленно, в худшем случае придется куда-то ехать и вручную диагностировать и решать проблему. Можно привести в пример еще тысячу подобных ситуаций и как видно нет недостатка причин, по которым что-то может отказать, а вы можете потерять доступ даже элементарно для того, чтобы разобраться, что происходит.
По зову сердца и работе в Digital Design в качестве системного инженера, мне часто приходится сталкиваться с переусложненными программными продуктами и архитектурными конструкциями. Это вызывает страстное желание минимизации и упрощения всего, что попадается под руку, и приводит к восторгу от человеческих решений, просто делающих свою работу, без регистрации и смс.
Так я и познакомился с Alpine Linux.

Этот дистрибутив может вам понравиться по следующим причинам:
- Если вы любите минимализм и инструменты, ориентированные на выполнение поставленной задачи без лишних свистелок и украшений;
- Если вы заметили, что имеющиеся «мэйнстримные» дистрибутивы немного (?) раздуты и избыточны;
- Если вы захотели решить имеющуюся задачу простым способом.
Под «мэйнстримом» я подразумеваю тройку CentOS — Debian — Ubuntu (конечно же, ими мир не заканчивается), да простят меня все верующие в эти замечательные дистрибутивы. При их использовании, периодически, на границе восприятия, возникает колкая мысль – «а может быть можно проще?».
Nowadays I’m interested in docker and i see that docker offers Alpine Linux to use for those who are interested in container technologies nowadays. Once start searching about Alpine Linux distro everybody encourages you to use this distro on your projects. Official web site is said “Alpine Linux is a security-oriented, lightweight Linux distribution based on musl libc and busybox.”
Okay, let’s start sharing the knowledge that we have about the SSH in this point. By the way it was the most fastest Linux installation i have ever experienced thus far. In installation progress there was an option about SSH, and its packages. There were two ssh packages named openssh and dropbear-ssh, my choice was openssh, because i had no idea about the second one.
After the installation was done I tried to access the alpine but it didn’t work. I thought an existing security policy wouldn’t allow me to use a simple password for an SSH connection, then I changed my password to a complex one but that didn’t work either and I started looking for an answer to this problem and finally found it.
Open the given file using vi command.
vi /etc/ssh/sshd_configThen find the given line.
#PermitRootLogin prohibit-passwordPermitRootLogin yesThen restart the SSH service.
service sshd restartThanks for your time reading
An Alpine Image with Bash Runtime Configuration
This repository documents my efforts. I hope you find it useful to you.
Building
git clone https://www.github.com/neveroddoreven/alpine-bash-profile.git alpine-bash-profile alpine-bash-profile docker build -t
Users and Permissions
Running the Container
If you have not built the image, then you may also use the docker client to pull the build from docker hub. You will need to install Docker CE if you have not already done so.
docker pull neveroddoreven/alpine-bash-profile:latest
Running as appuser (the default user)
$ docker run -it --rm $ docker run -it --rm -u appuser
Welcome appuser, from Sourced by: /bin/bash. This is not an interactive shell This is not a login shell Welcome appuser, from This is not an interactive shell This is not a login shell appuser $
Running as root
$ docker run -it --rm -u root
Welcome appuser, from Sourced by: /bin/bash. This is not an interactive shell This is not a login shell Welcome appuser, from This is not an interactive shell This is not a login shell appuser
Running While Passing a Known Flag
$ docker run -it --rm --some-var
Welcome appuser, from Sourced by: /bin/bash. This is not an interactive shell This is not a login shell Welcome appuser, from This is not an interactive shell This is not a login shell Setting known flags SOME_VAR= appuser $ some var
Running While Passing an Unknown Option
$ docker run -it --rm -
Welcome appuser, from Sourced by: /bin/bash. This is not an interactive shell This is not a login shell Welcome appuser, from This is not an interactive shell This is not a login shell These unknown options were not processed - some var
Switching User — appuser to root
appuser $ sudo su - root
Welcome root, from Sourced by: -bash. This is an interactive shell This is a login shell
Switching User — root to appuser
Welcome appuser, from Sourced by: -bash. This is an interactive shell This is a login shell
License
Make Alpine Linux RootFS
This project provides a script for building customized Alpine Linux rootfs (a base image if you like) for containers.
It’s quite simple (250 LoC of shell), fast and requires minimum dependencies (just common Linux tools).
Requirements
Usage
You can copy alpine-make-rootfs into your repository or download it on demand, e.g.:
wget https://raw.githubusercontent.com/alpinelinux/alpine-make-rootfs/v0.6.1/alpine-make-rootfs \ 73948b9ee3580d6d9dc277ec2d9449d941e32818 alpine-make-rootfs sha1sum -c \ 1Examples
Installation Script in Heredoc
sudo ./alpine-make-rootfs --branch v3.8 --packages ruby ruby-bigdecimal sqlite --timezone --script-chroot example-.tar.gz - '' Copy some file from the repository root to the rootfs. install -D -m 755 examples/hello_world.rb /app/hello_world.rb Install some dev packages and gem mailcatcher. apk add --no-progress -t .make build-base ruby-dev sqlite-dev gem install --no-document mailcatcher Clean-up dev packages. (7) apk del --no-progress .make
Alpine branch (release) to install (see Alpine Releases).
You can name packages to install into the chroot, in addition to base packages (see
ALPINE_BASE_PKGSin alpine-make-rootfs).You may specify timezone to set (default is UTC).
This flag tells that the installation script will be executed inside chroot with the rootfs as new root.
Your current working directory is binded at/mntinside the chroot and$PWDfor the script is set to/mnt, so you can easily access files out of the chroot and copy them into the rootfs.Installation script may be provided also via STDIN, using a convenient heredoc syntax.
The script passed is executed using/bin/sh -e.Note that it’s not needed to clean
apkcache, this is done automatically.
Create Docker Base Image
Create your own (customized) up-to-date base image.
sudo ./alpine-make-rootfs --branch v3.8 - docker import -c - my/alpine:3.8
Create OCI Image
You can use simply shell script sloci-image to pack the generated rootfs as a single-layer OCI image.
sudo ./alpine-make-rootfs --branch v3.8 --script-chroot rootfs.tar.gz ./install.sh ./sloci-image --entrypoint /start.sh --port 80/tcp --tar rootfs.tar.gz alpine:3.8
License
This project is licensed under MIT License.
For the full text of the license, see the LICENSE file.
Поведение при проблемах с диском (тест)
Метод тестирования простой — после загрузки системы в настройках виртуалки удаляем диск и смотрим на поведение.
В строках вывода dmesg будет что-то подобное
BTRFS error (device vda2): bdev /dev/vda2 errs: wr 1, rd 0, flush 0, corrupt 0, gen 0
BTRFS error (device vda2): bdev /dev/vda2 errs: wr 2, rd 0, flush 0, corrupt 0, gen 0
BTRFS: error (device vda2) in btrfs_commit_transaction:2460: errno=-5 IO failure (Error while writing out transaction)
BTRFS info (device vda2: state E): forced readonly
BTRFS warning (device vda2: state E): Skipping commit of aborted transaction.
BTRFS: error (device vda2: state EA) in cleanup_transaction:1958: errno=-5 IO failure
FAT-fs (vda1): Directory bread(block 8184) failed
FAT-fs (vda1): Directory bread(block 8185) failed
FAT-fs (vda1): Directory bread(block 8186) failed
FAT-fs (vda1): Directory bread(block 8187) failedalpine:~# umount /var
umount: can't unmount /var: Resource busy
alpine:~# service syslog stop * WARNING: you are stopping a boot service * Stopping busybox syslog ... [ ok ]
alpine:~# umount /var
alpine:~# service syslog start * Caching service dependencies ... [ ok ] * Starting busybox syslog ...
alpine:~# apk add mtr
fetch https://mirror.yandex.ru/mirrors/alpine/v3.18/main/x86_64/APKINDEX.tar.gz
fetch https://mirror.yandex.ru/mirrors/alpine/v3.18/community/x86_64/APKINDEX.tar.gz
...
OK: 40 MiB in 81 packagesТеоретически на стадии 3 вы можете сделать не /var а отдельную дополнительную точку монтирования (cкажем /persistent), чтобы вообще не пересекаться с не особо критичными завязками на /var. Например, чтобы отмонтировать /var в нормальном режиме работы такой гибридной системе нужно как минимум остановить syslog.
Как уже было упомянуто выше, если система находится не у вас (облако, VPS, сторонние люди) — вы как минимум можете в случае инцидента понять, что сбой не у вас и получить доказательства «проблем с их стороны» для техподдержки.
Заключительные моменты
Стоит отметить несколько моментов касающихся всего вышеописанного. Статья написана с целью показать процедуру для достижения почти минимальной системы, которая загружается в RAM, но при этом еще может использовать сервисы пишущие на диск. Соответственно речь не идет о настройке дополнительно таких вещей как iptables, изменений оболочки на bash/zsh, и еще кучи вещей, которые вам с большой вероятностью необходимо сделать и настроить, если вы собираетесь использовать машину в рабочем окружении.
Процесс разбит на стадии условно — после нулевой стадии, например, можно не делать остальные, если это вас устраивает, либо сделать 2-ую до 1-ой, если не хотите настраивать ssh. Но внутри одной из стадий следует выполнять команды в предлагаемом порядке.
Позволю себе подытожить все следующим слоганом:
Критично? Перемещайтесь в RAM!
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Была ли полезна для вас статья?
Проголосовали 9 пользователей.
Воздержались 6 пользователей.
Хочется странного
От части перечисленного выше можно (попробовать) избавиться вручную, но вдруг все уже придумано за нас? В идеале, от дистрибутива серверной операционной системы общего назначения хочется видеть:
- Как ни странно, загрузчик, который дотянет нас до ядра;
- Само ядро ОС (в рассматриваемом случае — linux);
- Система инициализации, которую ядро запустит по готовности. Желательно, по простоте недалеко ушедшая от топора;
- Минимальный набор процессов, который запустит система инициализации. Ну например:
- Окончательная инициализация устройств и определение дополнительных параметров ядра;
- Обеспечение журналирования (можно с текстовыми журналами? Ну пожалуйста);
- Конфигурация сети (хорошо бы, с меньшим числом управляющих прослоек);
- Синхронизация времени (ntpd / chronyd);
- Несколько локальных консолей;
- Опционально — периодическое выполнение задач (сrond);
- Опционально — удаленный доступ к системе (sshd);
- Хорошо бы еще сохранять и восстанавливать конфигурацию межсетевого экрана.
И на этом почти все, остальное — дело менеджера пакетов. Меньше исполняемого кода и конфигурации – меньше багов, меньше багов – меньше багов. А система все также запущена и доступна по сети. Идея выглядит неплохо, теперь посмотрим, насколько близок к ней дистрибутив Alpine Linux.
Оно нам действительно надо?
$ holywar mode disable
Неужели для решения вашей небольшой задачи требуется все это:
Замечательная systemd. Система инициализации (уже не совсем), которая может произвести впечатление системы управления шаттлом?
Никто не говорит, что в управлении ею нельзя разобраться, но ее безудержный рост может начать пугать, а число концепций явно превышает минимально необходимый набор. Это все действительно необходимо для реализации простой задачи и очень нечастой перезагрузки сервера?
Несомненно, это здорово!
Можно догадываться, почему это сделано именно так, но действительно ли для моей простой задачи требуется такая цепочка?
Дублирование функциональности периодического выполнения задач как в systemd, так и в crond?
Ох уж этот Cron!
Неужели мне не хватает его классического механизма? Возможно, его синтаксис может быть не совсем очевиден, но так ли очевидны таймеры в s-d?
Сосуществование нескольких подсистем управления сетью в разных сочетаниях: классический networking / networkd / NetworkManager?
Управлять сетью надо много!
Такое сочетание, да на серверной системе, да с несколькими интерфейсами управления на все вкусы. Хотя нет, давайте добавим сюда еще и netplan, «решающий» проблему конфигурации для перечисленных подсистем. Вам свой сервис хочется завести, или часто менять орбиту за счет переконфигурирования сетевых интерфейсов?
Сервисы вида tuned и firewalld?
Как же без них?
Так ли они нужны для вашей задачи? В принципе, неплохо рассматривать firewalld как попытку сбежать от синтаксиса iptables, но в результате вы вместо одного синтаксиса будете разбираться в другом и недоумевать от размера команд firewall-cmd. И вам действительно в базовой системе нужен интерпретатор python и его процессы? Нет, я люблю python, но не в этом случае.
Локальный почтовый сервис. Вы точно будете его использовать?
Раз уж мы вспомнили про минимализм, можно очень грубо сравнить наши дистрибутивы-лидеры в их минимальном варианте установки:
- Лидером избыточности по дисковому пространству и числу пакетов оказывается Ubuntu 18.04 (2,8 ГБ дискового пространства, 342 пакета, 31 активный сервис systemd, 15 процессов при входе). Семейство systemd тут представлено в максимальном объеме — systemd, networkd, timesyncd, resolved, logind, есть dbus.
- CentOS 7.5.1804 проигрывает по диску и числу пакетов, но лидер по вероятно-избыточным сервисам (1.1 ГБ дискового пространства, 299 пакетов, 34 активных сервиса systemd, 19 процессов при входе, среди которых — NetworkManager, firewalld, tuned, postfix, polkitd, auditd, journald + rsyslogd, dbus).
- Debian 9.4.0 пытались сильно не надувать: 940 МБ, 334 пакета, 25 активных сервисов systemd, 14 процессов при входе. Само собой, тут тоже есть systemd (а также journald, timesyncd и сопутствующий dbus), но без особого фанатизма в части управления сетью.
holywar: cannot change mode to ‘disable’: Permission denied
Сравниваем базовые образы alpine и debian
Перед тем, как мы перейдём к специфике запуска python-проектов под alpine, давайте посмотрим на код базовых образов и сравним что они нам предлагают.
FROM scratch
ADD rootfs.tar.xz /
CMD ["bash"]FROM scratch
ADD alpine-minirootfs-3.17.0-x86_64.tar.gz /
CMD ["/bin/sh"]Т.е. что alpine, что debian, состоят по сути из одного слоя куда распаковывается файловая система. Давайте заглянем что же там находится. Кстати, alpine-minirootfs-3.17.0-x86_64.tar.gz весит всего 3 Mb, а rootfs.tar.xz аж 31 Mb. В распакованном виде 6.7 Mb и 122 Mb соответственно. Внушительная разница, не правда ли? За счёт чего? Сравним 2 каталога:

Разница примерно везде. Но бросается в глаза размер каталога usr — аж 100 Mb! Заглянем в него (я игнорирую все каталоги меньше 100k иначе список получится очень большой):
➜ /opt/debain/usr du -h -d 2 -t 100k .
14M ./bin
532K ./share/info
31M ./share/locale
168K ./share/keyrings
128K ./share/gcc
5,3M ./share/man
13M ./share/doc
264K ./share/common-licenses
536K ./share/perl5
344K ./share/bash-completion
124K ./share/lintian
5,0M ./share/zoneinfo
56M ./share
1,9M ./lib/locale
1,1M ./lib/apt
36M ./lib/x86_64-linux-gnu
39M ./lib
2,2M ./sbin
111M .Установка
Stage 0
0.0) Загружаемся с iso, пароль рута пустой.
Welcome to Alpine Linux 3.18
Kernel 6.1.27-2-lts on an x86_64 (/dev/ttyS0)
localhost login: root
Welcome to Alpine!
The Alpine Wiki contains a large amount of how-to guides and general
information about administrating Alpine systems.
See <https://wiki.alpinelinux.org/>.
You can setup the system with the command: setup-alpine
You may change this message by editing /etc/motd.0.1) Поднимаем сеть. Сеть на этой стадии как минимум нужна для установки пакета syslinux, который почему-то не положили в стандартный загрузочный образ. Он в свою очередь нужен для работы скрипта setup-bootable
localhost:~# setup-interfaces
Available interfaces are: eth0.
Enter '?' for help on bridges, bonding and vlans.
Which one do you want to initialize? (or '?' or 'done') [eth0]
Ip address for eth0? (or 'dhcp', 'none', '?') [dhcp]
Do you want to do any manual network configuration? (y/n) [n]
localhost:~# rc-update add networking * service networking added to runlevel default
localhost:~# rc-service networking start * Starting networking ... * lo ... [ ok ] * eth0 ...
udhcpc: started, v1.36.0
udhcpc: broadcasting discover
udhcpc: broadcasting select for 192.168.100.246, server 192.168.100.1
udhcpc: lease of 192.168.100.246 obtained from 192.168.100.1, lease time 3600 [ ok ]0.2) Настраиваем репозитории
localhost:~# setup-apkrepos
Available mirrors:
1) dl-cdn.alpinelinux.org
2) uk.alpinelinux.org
3) mirror.yandex.ru
...
72) mirror.bahnhof.net
r) Add random from the above list
f) Detect and add fastest mirror from above list
e) Edit /etc/apk/repositories with text editor
Enter mirror number (1-72) or URL to add (or r/f/e/done) [1] 3
Added mirror mirror.yandex.ru
Updating repository indexes... done.0.3) Ставим syslinux
localhost:~# apk add syslinux0.4) Cоздаем таблицу разделов MSDOS, создаем первый раздел размером в некоторую часть всего диска (в примере ниже 2G). Форматируем этот раздел в FAT32. Взводим загрузочный флаг для этого раздела. Также создаем второй раздел, который будет использоваться в дальнейшем. В примерах ниже везде устройство диска /dev/vda, в других средах это может отличаться поэтому делайте соответствующие поправки для вашего случая.
С использованием fdisk это выглядит так
Раскрыть
localhost:~# fdisk -l
Disk /dev/vda: 4096 MB, 4294967296 bytes, 8388608 sectors
8322 cylinders, 16 heads, 63 sectors/track
Units: sectors of 1 * 512 = 512 bytes
Disk /dev/vda doesn't contain a valid partition table
localhost:~# fdisk /dev/vda
Device contains neither a valid DOS partition table, nor Sun, SGI, OSF or GPT disklabel
Building a new DOS disklabel. Changes will remain in memory only,
until you decide to write them. After that the previous content
won't be recoverable.
The number of cylinders for this disk is set to 8322.
There is nothing wrong with that, but this is larger than 1024,
and could in certain setups cause problems with:
1) software that runs at boot time (e.g., old versions of LILO)
2) booting and partitioning software from other OSs (e.g., DOS FDISK, OS/2 FDISK)
Command (m for help): o
Building a new DOS disklabel. Changes will remain in memory only,
until you decide to write them. After that the previous content
won't be recoverable.
The number of cylinders for this disk is set to 8322.
There is nothing wrong with that, but this is larger than 1024,
and could in certain setups cause problems with:
1) software that runs at boot time (e.g., old versions of LILO)
2) booting and partitioning software from other OSs (e.g., DOS FDISK, OS/2 FDISK)
Command (m for help): n
Partition type p primary partition (1-4) e extended
p
Partition number (1-4): 1
First sector (63-8388607, default 63):
Using default value 63
Last sector or +size{,K,M,G,T} (63-8388607, default 8388607): +2G
Command (m for help): t
Selected partition 1
Hex code (type L to list codes): c
Changed system type of partition 1 to c (Win95 FAT32 (LBA))
Command (m for help): a
Partition number (1-4): 1
Command (m for help): p
Disk /dev/vda: 4096 MB, 4294967296 bytes, 8388608 sectors
8322 cylinders, 16 heads, 63 sectors/track
Units: sectors of 1 * 512 = 512 bytes
Device Boot StartCHS EndCHS StartLBA EndLBA Sectors Size Id Type
/dev/vda1 * 0,1,1 1023,15,63 63 4194366 4194304 2048M c Win95 FAT32 (LBA)
Command (m for help): n
Partition type p primary partition (1-4) e extended
p
Partition number (1-4): 2
First sector (4194367-8388607, default 4194367):
Using default value 4194367
Last sector or +size{,K,M,G,T} (4194367-8388607, default 8388607):
Using default value 8388607
Command (m for help): t
Partition number (1-4): 2
Hex code (type L to list codes): 83
Command (m for help): p
Disk /dev/vda: 4096 MB, 4294967296 bytes, 8388608 sectors
8322 cylinders, 16 heads, 63 sectors/track
Units: sectors of 1 * 512 = 512 bytes
Device Boot StartCHS EndCHS StartLBA EndLBA Sectors Size Id Type
/dev/vda1 * 0,1,1 1023,15,63 63 4194366 4194304 2048M c Win95 FAT32 (LBA)
/dev/vda2 1023,15,63 1023,15,63 4194367 8388607 4194241 2047M 83 Linux
Command (m for help): w
The partition table has been altered.
Calling ioctl() to re-read partition table0.4a) Раздел должен определиться. Если по какой-то причине по пути /dev/vda1 ничего нет — запускаем
rc-service sysfs restart Cтавим файловую систему на раздел
localhost:~# mkfs.vfat -F32 /dev/vda10.5) Обязательно! Монтируем раздел впервые и проверяем, что раздел монтируется без опции -t vfat в последующие разы. Все это чтобы избежать ошибок типа mounting X on Y failed: Invalid argument в капризном скрипте setup-bootable
localhost:~# mount -t vfat /dev/vda1 /mnt && umount /dev/vda1 && \
mount /dev/vda1 /mnt && umount /dev/vda1 && echo "Success"0.6) Ставим на раздел alpine linux!
localhost:~# setup-bootable /media/cdrom /dev/vda1 -v
Installing /dev/vda1 to alpine-standard-3.18.0 230508
Making /dev/vda1 bootable...0.7) Делаем сохранение конфигурации и кэша пакетов
localhost:~# mount /dev/vda1 /media/vda1
localhost:~# setup-lbu vda1
localhost:~# setup-apkcache /media/vda1/apkcache0.8) Сохраняем настройки сети и репозиториев. Отключаем iso и проверяем загрузку с диска
localhost:~# lbu ci
localhost:~# rebootПосле этой стадии система должна загружаться с диска и подключаться к сети. Фактически это diskless mode, но с загрузкой с раздела диска а не с iso. Для хранения конфигураций и кэша пакетов используется этот же раздел.
Stage 1
Эту стадию можно пропустить, если не хотите настраивать далее по ssh.
1.0) Активируем sshd
localhost:~# apk add openssh
localhost:~# rc-update add sshd * service sshd added to runlevel default
localhost:~# rc-service sshd start * Caching service dependencies ... [ ok ]
ssh-keygen: generating new host keys: RSA ECDSA ED25519 * Starting sshd ... [ ok ]1.1) Устанавливаем пароль root
localhost:~# passwd
Changing password for root
New password:
Retype password:
passwd: password for root changed by root1.2) В файле /etc/ssh/sshd_config cтроку #PermitRootLogin prohibit-password меняем временно на PermitRootLogin yes и перезагружаем конфигурацию sshd
service sshd reload1.3) Добавляем ключи root для административных целей
С вашей локальной машины запускаем
ssh-copy-id -i <pubkey> root@<alpine_ip><key> — путь к публичному ключу, например .ssh/id_rsa.pub
<alpine_ip> — aдрес машины с alpine, которую настраиваете
при запросе вводите пароль, установленный в пункте 1.1
ssh-copy-id -i .ssh/id_rsa.pub root@192.168.100.246
/run/current-system/sw/bin/ssh-copy-id: INFO: Source of key(s) to be installed: ".ssh/id_rsa.pub"
/run/current-system/sw/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/run/current-system/sw/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
root@192.168.100.246's password:
Number of key(s) added: 1
Now try logging into the machine, with: "ssh 'root@192.168.100.246'"
and check to make sure that only the key(s) you wanted were added.1.4) Прописываем папку /root/.ssh в список файлов для сохранения
localhost:~# lbu include /root/.ssh
localhost:~# lbu st
U etc/apk/protected_paths.d/lbu.list
A root
A root/.ssh
A root/.ssh/authorized_keys1.5) Возвращаем обратно конфиг, измененный в 1.2
1.6) Завершаем все сохранением
localhost:~# lbu ciПосле этой стадии дальнейшую настройку можно выполнять по ssh
Stage 2
Второй раздел диска предназначен для /var. Его можно будет использовать для прикладных задач, которые не требуют повышенной надежности, некритичны, либо слишком тяжеловесны для загрузки полностью в RAM.
2.0) Устанавливаем ФС на раздел, в данном примере btrfs. Можете использовать другую ФС на вкус
localhost:~# apk add btrfs-progs
localhost:~# modprobe btrfs
localhost:~# mkfs.btrfs /dev/vda22.1) Монтируем, создаем для /var выделенный subvolume и копируем текущее дерево каталогов /var
localhost:~# mount /dev/vda2 /mnt
localhost:~# btrfs subvolume create /mnt/var
Create subvolume '/mnt/var'
localhost:~# cp -a /var /mnt
localhost:~# umount /mnt2.2) Смотрим UUID второго раздела
localhost:~# blkid /dev/vda2
/dev/vda2: UUID="106af5fb-342c-47c3-b721-01f2f3981042" UUID_SUB="c069a505-1b6f-498f-ae71-0b5ed7d6e228" BLOCK_SIZE="4096" TYPE="btrfs" PARTUUID="5279faf3-022.3) Добавляем запись в /etc/fstab. Пример добавляемой строки (измените UUID на тот который был в предыдущем пункте)
UUID=106af5fb-342c-47c3-b721-01f2f3981042 /var btrfs subvol=/var 0 02.4) Проверяем монтирование
localhost:~# mount -a
localhost:~# mount | grep vda
/dev/vda1 on /media/vda1 type vfat (ro,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=utf8,shortname=mixed,errors=remount-ro)
/dev/vda2 on /var type btrfs (rw,relatime,space_cache=v2,subvolid=256,subvol=/var)2.5) Сохраняемся, перезагружаем, проверяем
localhost:~# lbu ci
localhost:~# rebootВ конце этой стадии у нас есть persistent раздел /var. Можно сказать, что система становится гибридной. Фактически это data disk mode без свопа, а также без множества некоторых настроек
Дополнительные действия
Больше пакетов
Часто требуется подключить community репозиторий. Для этого раскомментируем его в /etc/apk/repositories.
Устанавливаем зону времени и обновление времени
localhost:~# setup-timezone
Which timezone are you in? ('?' for list) [UTC] Europe/Moscow
localhost:~# setup-ntp
Which NTP client to run? ('busybox', 'openntpd', 'chrony' or 'none') [chrony] * service chronyd added to runlevel default * Caching service dependencies ... [ ok ] * Starting chronyd ... [ ok ]Раскладка для русского языка
Чтобы не возникло проблем с отображением символов в консоли (tty) не рекомендуется пользоваться setup-keymap и устанавливать ru ru. При работе через pty (в том числе сессии ssh) проблем такого рода нет.
Если вы хотите чтобы русские буквы отображались именно в консоли, на этот случай в сети есть полезная инструкция по настройке для русского языка (utf8) и раскладки:
https://gh0stwizard.tk/2020/03/08/ru-lang-on-alpine/
Сервисы для виртуализации
Для vmware (esx)
localhost:~# apk add open-vm-tools
localhost:~# rc-update add open-vm-tools * service open-vm-tools added to runlevel default
localhost:~# rc-service open-vm-tools start * Caching service dependencies ... [ ok ] * Starting open-vm-tools ... [ ok ]localhost:~# apk add qemu-guest-agent
localhost:~# rc-update add qemu-guest-agent * service qemu-guest-agent added to runlevel default
localhost:~# rc-service qemu-guest-agent start * Caching service dependencies ... [ ok ] * Starting QEMU Guest Agent ... [ ok ]Прописываем hostname
localhost:~# setup-hostname
Enter system hostname (fully qualified form, e.g. 'foo.example.org') [localhost] alpineПри изменения настроек и установке пакетов не забываем выполнить команду lbu ci , чтобы изменения не потерялись после перезагрузки!
После настройки ssh-доступ root для административных целей (добавленный на стадии 1) можно удалить в зависимости от вашей концепции безопасности. Описана только первоначальная настройка, далее вы можете настраивать под свои задачи. К сожалению, весь описанный процесс трудоемкий, так что делайте шаблон виртуалки или образ диска если вам нужно делать подобное неоднократно.
Сборка и установка python пакетов
Установка любого пакета python начинается с его сборки в wheel. Если он поставляется в исходниках (.tar.gz), то выполняется setup.py, потом будет сборка пакета, который уже в свою очередь и будет установлен. Так что хорошей практикой будет заливать в pypi не только исходники, но и собранные на CI/CD wheel-пакеты. Благо, это делается буквально в 2 строчки (на примере моей библиотеки для работы с Yandex Disk):
$ pip wheel --no-deps . -w dist
Processing /opt/app/YaDiskClient Preparing metadata (setup.py) ... done
Building wheels for collected packages: YaDiskClient Building wheel for YaDiskClient (setup.py) ... done Created wheel for YaDiskClient: filename=YaDiskClient-0.5.1-py3-none-any.whl size=5238
Successfully built YaDiskClient
$ twine upload dist/*
Uploading distributions to https://upload.pypi.org/legacy/
Uploading YaDiskClient-0.5.1-py3-none-any.whl
100% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 11.8/11.8 kB • 00:00 • 9.3 MB/s
View at:
https://pypi.org/project/YaDiskClient/0.5.1/dist — название пакета
version — версия пакета (обычно используется semver)
python — для какого python
abi — бинарный интерфейс (обычно abi3, повторяет python или опускается)
platform — платформа, под которую собраны бинарники
Т.е. один и тот же пакет poetry-1.1.14-py2.py3-none-any.whl будет использоваться и для python2, и для python3, причём для любой платформы. А вот cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl будет установлен только на CPython 3.9 под Linux x86_64 с более-менее современными библиотеками. Обратим внимание на manylinux_2_17_x86_64 — это строка говорит о том, что бинарники внутри скомпилированы glibc версии 2.17 под архитектуру x86_64. Важный момент! Потому что под alpine будет ставиться другой пакет — cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl. Скомпилирован он musl версии 1.1 и бинарно не совместим с manylinux. Т.е. под разные системы могут быть скачаны и распакованы разные архивы. Хорошо, если они созданы из одних исходников. Подробнее можно почитать на realpython.
Собственно, поэтому uvloop под alpine требовал компиляции — wheel под alpine просто нет на pypi. Для новых версий эту проблему починили. Т.е. теперь пакет будет скачан и распакован, компиляции не потребуется. Аналогичная проблема была и с psycopg2-binary.
Но мы возвращаемся к pydantic-1.9.1. Этот пакет собран под всё, что только можно. И при установке обычным pip выглядит нормально:
➜ ~ docker run -it python:3.9-alpine3.13 sh
/ # pip install pydantic==1.9.1
Collecting pydantic==1.9.1 Downloading pydantic-1.9.1-cp39-cp39-musllinux_1_1_x86_64.whl (12.5 MB)
|████████████████████████████████| 12.5 MB 2.1 MB/s
Collecting typing-extensions>=3.7.4.3 Downloading typing_extensions-4.4.0-py3-none-any.whl (26 kB)
Installing collected packages: typing-extensions, pydantic
Successfully installed pydantic-1.9.1 typing-extensions-4.4.0
/ # du -d 1 -h /usr/local/lib/python3.9/site-packages/
72.0K /usr/local/lib/python3.9/site-packages/__pycache__
49.5M /usr/local/lib/python3.9/site-packages/pydantic
...А еще
- Авторы дистрибутива сделали свою собственную надстройку над iptables под названием «Alpine Wall». И она не висит постоянно отдельным процессом в системе;
- Для тех, кто любит управлять сервером через веб-интерфейс, подготовлен пакет «Alpine Configuration Framework». Без PHP или Perl, но с Lua;
- Для тех, кто желает рабочего стола, есть возможность установки графической среды (хотя это может оказаться больно в начале);
- Для особых ценителей имеется «установка» Alpine в памяти с хранением конфигурации на внешнем хранилище (см. описание инструмента lbu).
Comments
reproducer
FROM gliderlabs/alpine:3.2
RUN adduser -D foo
USER foo
ENTRYPOINT ["sh"]$ docker run --rm -it reproducer
/ $ id
uid=1000(foo) gid=1000(foo)
/ $ su
/ # id
uid=0(root) gid=0(root) groups=0(root),0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)
/ # exit
/ $ exitworkaround 1
remove su and other dangerous commands.
example hardening script at https://gist.github.com/jumanjiman/f9d3db977846c163df12
workaround 2
assign passwd to root in Dockerfile
FROM gliderlabs/alpine:3.2
RUN adduser -D foo
RUN a_pass=$(echo fubar | mkpasswd) && \ echo "root:${a_pass}" | chpasswd
USER foo
ENTRYPOINT ["sh"]attempt to reproduce
$ docker run --rm -it reproducer:workaround
/ $ id
uid=1000(foo) gid=1000(foo)
/ $ su
Password:
su: incorrect password
/ $ id
uid=1000(foo) gid=1000(foo)
/ $ exitagreed. people assume it’s safe. warning is appropriate
Anyone done any debugging into what might cause this? Before just documenting some warning that could be masking a serious bug, I’d rather gather some useful information to go upstream to see why this happens.
At first glance, su is actually a symlink to bbsuid. I’m guessing that is some sort of BusyBox su. But searches come up pretty nil and I can only find a Alpine package at https://github.com/alpinelinux/aports/blob/master/main/bbsuid/APKBUILD which points to a dead git repository at http://git.alpinelinux.org/cgit/bbsuid/.
It seems like it might be a Alpine specific project so maybe we can ask them what might be going on.
A third workaround is to remove the setuid bit on /bin/bbsuid (chmod u-s /bin/bbsuid). I wonder if this bit gets set in error somewhere?
Workaround is workaround. Proper fix would be appreciated as well as security advisory.
Also, notice that there is no security contact for this image, so I abused github issues. In short, security contact would be nice in main README.md file — I’ve opened different issue for this:
#104
I posed this question in the #alpine-devel IRC channel and it reads as if it is normal behavior at the moment.
I’d highly recommend in getting involved and voicing your opinion on the mailing lists. But I’ll also be leaving this open and discussing with some other folks internally about the merits of this security issue. Security is definitely a high ticket issue, especially in container land these days.
It is not that we are ignoring issues like this. It is just that they are more upstream issues and we want to act as a friendly, good natured ambassador for the Docker community, which takes proper time and communication.
WAT?! I’ve just discovered this «feature» myself. Could you please at least patch it for Docker comunity by setting ! as a hash of a password?!
sed -ie /etc/shadow
Alpine as a standalone OS may want to allow empty password for root, so people can access it after a fresh installation, but it is not the case for Docker images!
Alpine as a standalone OS may want to allow empty password for root, so people can access it after a fresh installation, but it is not the case for Docker images!
frol
added a commit
to frol/docker-alpine
that referenced
this issue
frol
mentioned this issue
Simple one-liner that proves that nothing get fixed in the upstream:
$ docker run --rm --user nobody alpine:edge sh -c 'cat /etc/os-release ; echo ; whoami ; su -c "whoami"'
NAME="Alpine Linux"
ID=alpine
VERSION_ID=3.3.0_rc2
PRETTY_NAME="Alpine Linux v3.3"
HOME_URL="http://alpinelinux.org"
BUG_REPORT_URL="http://bugs.alpinelinux.org"
nobody
rootWe are still having a major security issue after a month since reporting the issue and a year after the first image release. Cool!
P.S. I’m still waiting for the official images get fixed also.
UPD. Official images are updated now!
Про Alpine
Чем может очаровать Alpine, особенно после CentOS? Отчаянным минимализмом!
Ну и, конечно, отсутствием необходимости сертификации «Linux Systemd Certified Voldemort».
- Понизили число используемых базовых компонентов;
- Выбрали модули поменьше и попрозрачнее;
- Упростили процесс конфигурирования системы.
- Чрезвычайно лаконичный процесс установки с использованием консольной утилиты setup-alpine;
- В качестве загрузчика взят extlinux из состава проекта syslinux;
- Небольшой инструмент сборки mkinitfs для создания временной файловой системы, используемой при загрузке;
- Система инициализации openrc с определением зависимостей между сервисами, уровнями запуска и щепоткой скриптования;
- Замена стандартной библиотеки GNU libc на более легковесную musl libc;
- Вместо пакета GNU coreutils большинство стандартных системных утилит в несколько урезанном исполнении входят в состав пакета busybox, который может быть Вам знаком по встраиваемым решениям;
- По умолчанию используется командный интерпретатор ash в составе busybox. Само собой, никто не мешает при необходимости поставить bash
, ну и systemd; - Собственный пакетный менеджер apk и собственная инфраструктура распространения пакетов.
Кроме того, авторы реализовали ряд мер, ориентированных на повышение уровня защищенности базовой системы:
Применили патчи ядра grsecurity/PaX (про их эффективность мнения расходятся, но все же);Уже нет, спасибо коллеге из комментариев. Как раз 26 июня вышла версия 3.8.0.- Собрали пакеты с использованием режимов, снижающих вероятность эксплуатации ряда возможных уязвимостей.
В итоге мы получаем систему, снабженную рядом дополнительных механизмов защиты, позволяющую решить имеющуюся задачу и занимающую около 130 МБ. В запущенной системе установлен 41 пакет и выполняется 13 пользовательских процессов, можно стучаться по ssh.
И больше ничего. Осталось добавить то, что нужно вам (да и iptables с возможностью восстановления конфигурации при старте поставьте).
Приоткроем крышку
Обратите внимание – Alpine может пригодиться как учебная площадка при ознакомлении с ОС Linux! Увидеть логику работы компонентов субъективно проще, чем пытаться охватить сходу CentOS или Ubuntu:
- Загрузчик нашей установленной системы прост, его конфигурация влезает в 12 строк:

- Да и в /boot не слишком многолюдно:

- А вот и запущенный загрузчик без модных обоев:

- Ядро загружается, подхватывает initramfs, отрабатывает собственные шаги инициализации и вызывает команду init (которая, на самом деле, тоже идет в составе busybox). Init использует файл /etc/inittab:

- И тут в явном виде прописано, что нужно запустить для инициализации системы:
- Запустить 6 процессов getty, ожидающих на 6 виртуальных консолях локального входа пользователя.
- Запустить систему инициализации openrc для поочередного достижения требуемых уровней инициализации (openrc использует не классические уровни инициализации 0-6, а собственные уровни/группы sysinit — boot — default).
Далее состояние системы зависит от конфигурации openrc, а именно:
- Переменных, заданных в файлах каталога /etc/conf.d;
- Скриптов запуска, находящихся в каталоге /etc/init.d;
- Привязки скриптов запуска к «группам инициализации»:

Осталось прочитать скрипты запуска и обработать их с учетом уровней запуска и зависимостей.
Можем на примере syslog (/etc/init.d/syslog) посмотреть, как выглядит скрипт запуска openrc.
Как видите, это не всегда эти ваши нелюбимые «портянки»:

Переменные, используемые при выполнении скрипта, определяются в соответствующем файле /etc/conf.d/syslog. В нашем случае, в файле определена переменная SYSLOGD_OPTS=»-Z».
Обратите внимание — в скрипте декларативно определены зависимости данного сервиса.
Openrc честно перебирает в заданном порядке скрипты запуска, достигает уровня «default» — и вот она, рабочая система!
Взрываемся в препроде
Со следующей проблемой я столкнулся на препроде. Баг был в библиотеке aiohttp==3.6.2 и python==3.7. Да, давно это было, но пример показательный — поведение библиотеки под alpine и debian различалось. Один сервер конкатенировал куки не через \r\n как по стандарту, а через \n. Казалось бы мелочь, но:
➜ ~ docker run --rm --net=host tyvik/py-alpine
[]
<html><body><h1>hi!</h1></body></html>
➜ ~ docker run --rm --net=host tyvik/py-debian
['uid', 'session']
<html><body><h1>hi!</h1></body></html>Исходники для проверки можно взять с github. Баг проявился потому что работал разный код. Под debian куки парсились с помощью конечного автомата, реализованного в бинарнике; под alpine работал фолбек-код на python, который парсил регуляркой. Случилось это потому что в pypi проник файл aiohttp-3.6.2-py3-none-any.whl, который подходит под все архитектуры и который был установлен как наиболее подходящий под alpine. Там исключительно python код. Под debian был установлен другой — aiohttp-3.6.2-cp37-cp37m-manylinux1_x86_64.whl вместе с бинарниками.
Обращение к не-питонистам
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Проверяете ли вы как собрался docker-образ при его написании?
собрался и ладно
размер меньше 500Mb и достаточно
2-step билд, склеиваю команды
оптимизирую и удаляю мусор по-максимуму
Проголосовал 131 пользователь.
Воздержались 23 пользователя.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Ваш базовый образ для python проектов
Проголосовали 118 пользователей.
Воздержались 35 пользователей.
Сравниваем в полевых условиях
Но вернёмся к python. Спулим 2 образа, и разница уже не столь существенна, правда?
➜ /opt docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
python 3.10-slim dae00c0316e5 12 hours ago 126MB
python 3.10-alpine 2527f31628e7 13 days ago 50.1MBДавайте соберём небольшое django-приложение. Зависимости взяты как пример из моего пет-проекта:
[tool.poetry.dependencies]
python = "^3.10"
Django = "~3.2"
django-elasticsearch-dsl = "^7.2.0"
django-enumfields = "^2.1.1"
djangorestframework = "^3.12.4"
django-elasticsearch-dsl-drf = "^0.22.1"
django-filter = "^2.4.0"
django-cors-headers = "^3.7.0"
drf-nested-routers = "^0.93.4"
gunicorn = "^20.1.0"➜ /opt docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
django debian 3e9fef9d8b54 2 seconds ago 201MB
django alpine 2f27ca4a1588 16 seconds ago 125MB
python 3.10-slim dae00c0316e5 12 hours ago 126MB
python 3.10-alpine 2527f31628e7 13 days ago 50.1MBРазница всё та же в 70Mb — предсказуемо. И если экстраполировать на какой-нибудь реальный проект, в котором docker-образ будет весить ~600Mb, то так ли важны эти 70Mb?
Ok, с простым django-приложением более-менее всё понятно. Так что я возьму другой свой пет-проект на FastAPI со следующими зависимостями:
[tool.poetry.dependencies]
python = "^3.8"
pycairo = "^1.19.1"
fastapi = "^0.54.1"
uvicorn = "^0.11.3"
aiofiles = "^0.5.0"
SQLAlchemy = {extras = ["asyncio"], version = "^1.4.29"}
alembic = "^1.7.5"
asyncpg = "^0.25.0"
elasticsearch = {extras = ["async"], version = "^7.16.2"}

ok, сам дурак — заголовочные файлы забыл. К слову установку я обернул в
RUN apk add g++ musl-dev --virtual dev \ && poetry config virtualenvs.create false \ ... && apk del devчтобы как можно честнее подсчитывать место. Но после этого меня ждало:

➜ /opt docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
fastapi debian b939da63315f 14 minutes ago 244MB
fastapi alpine 6f210c82554e 14 minutes ago 111MBРазница больше, чем в 2 раза! Серьёзно? Чувствую в этом какой-то подвох, так что посмотрим что действительно содержится в образе (прошу прощения за стаю шакалов):
Основное различие в библиотеке pydantic. Разница в 2 порядка — это уже перебор, здесь точно что-то не так! Идём в каталог и видим, что в случае debian там бинарники, а под alpine — python код. Т.е. погодите — при такой установке pydantic под alpine будет работать гораздо медленнее, чем под debain. Но, собственно, почему такое отличие вообще имеет место быть? Посмотрим на его сборку и установку.
Баги всюду
# poetry add pydantic@1.9.1
Updating dependencies
Resolving dependencies... (1.2s)
Writing lock file
Package operations: 2 installs, 0 updates, 0 removals • Installing typing-extensions (4.4.0) • Installing pydantic (1.9.1)
# du -d 1 -h /root/.cache/pypoetry/virtualenvs/-il7asoJj-py3.9/lib/python3.9/site-packages/
1.7M /root/.cache/pypoetry/virtualenvs/-il7asoJj-py3.9/lib/python3.9/site-packages/pkg_resources
876.0K /root/.cache/pypoetry/virtualenvs/-il7asoJj-py3.9/lib/python3.9/site-packages/pydantic
...Действительно! К моменту выхода статьи проблему уже пофиксили в версии 1.2.0. Заключалась она в том, что poetry просто игнорировал пакеты с тегом musllinux_1_1_x86_64 и всегда собирал из исходников. А у pydantic в setup.py:
if not any(arg in sys.argv for arg in ['clean', 'check']) and 'SKIP_CYTHON' not in os.environ: try: from Cython.Build import cythonize except ImportError: pass else: # For cython test coverage install with `make build-trace` compiler_directives = {} if 'CYTHON_TRACE' in sys.argv: compiler_directives['linetrace'] = True # Set CFLAG to all optimizations (-O3) # Any additional CFLAGS will be appended. Only the last optimization flag will have effect os.environ['CFLAGS'] = '-O3 ' + os.environ.get('CFLAGS', '') ext_modules = cythonize('pydantic/*.py', exclude=['pydantic/generics.py'], …)
setup( …Т.е. если не установлен Cython, то компиляции пропускается — будет работать код на python. Да-да, python-код можно компилировать в бинарник. Правда, не любой, с некоторыми ограничениями, но всё же.
Так что казалось бы популярный сетап alpine + poetry + FastAPI, а работать будет совсем по-другому. Вернее, дико тормозить. Да, именно эта проблема уже исправлена, но если вы взяли стандартный python:3.x-slim, вы бы о ней и не узнали, т.к. использовали те же самые пакеты, что и при разработке. Часто ли мы проверям docker-образ на то, что в действительности туда поставилось?
Варианты использования (use cases)
vpn-сервер, точка входа в корпоративную сеть
Сервер с набором утилит для администрирования инфраструктуры
Обратный прокси или балансировщик нагрузки
В некоторых особых случаях — в качестве ноды какого-либо кластера
Другие типы критичных сервисов (кроме тяжеловесных)
Мини-компьютер (Raspberry Pi и пр.)
Какое-либо устройство embedded типа
Кроме того, если у вас есть старое не особо надежное железо, на котором можно что-то крутить, но оно может отказать (либо уже отказывало, но путем шаманства восстановлено). Также подходят случаи, если у вас нет возможности или желания поддерживать дорогую инфраструктуру для организации надежного доступа (сетевые железки, шлюзы с vpn доступом и прочее) либо это нецелесообразно, а повысить надежность хочется.
Ремарка про alpine
«Small. Simple. Secure. Alpine Linux is a security-oriented, lightweight Linux distribution based on musl libc and busybox.» — цитата с официального сайта, которая целиком и полностью описывает суть дистрибутива. Проект, кстати, достаточно старый — выпускается с 2005 года, и изначально делал упор на нетребовательность к ресурсам и отсекании всего лишнего. Т.е. это был обычный дистрибутив linux но без systemd (заменили на openrc), с загрузчиком extlinux, собственным пакетным менеджером apk и busybox как замена GNU coreutils. Собственно, поэтому все утилиты в /bin и ссылаются на один и тот же файл /bin/busybox.
Но самая интересная замена — GNU libc на более легковесную musl libc. К glibc были некоторые вопросы насчёт переусложнённости и расширений. Например, isalnum() мог кинуть сегфолт. Так что alpine хорошо должен подойти под всякий embedded (поправьте, если не так). Как вы понимаете, бинарно они не совместимы, и отсюда столько боли.
Демоны под крышкой
Что же именно скрывается под скриптами запуска openrc? Как ни странно — набор задач и демонов, перечисленных ниже.
Ура, после выполнения этих шагов система готова к работе! Не забудем и про зависимости от перечисленных выше сервисов, которые были заданы в init.d файлах:
- sysfs — монтирование /sys;
- fsck — проверка и исправление файловых систем;
- root — монтирование корневой системы на запись/чтение;
- localmount — монтирование всех файловых систем, перечисленных в /etc/fstab;
- klogd — журналирование событий ядра.
Ура, никаких дополнительных сущностей (несомненно, полезных в ряде случаев, вроде PAM) — а мы в системе!
Осталось воспользоваться пакетным менеджером apk, и поискать нужные нам для нашей задачи пакеты. (Есть ли они там? Можно оценить это через веб-портал).
Возможное решение
У инцидентов подобных этим есть одна общая особенность — все сходится к тому, что важный сервис почти полностью завязан на возможность чтения или записи с диска или накопителя (будь то физический или виртуальный). Стало быть, чтобы как-то уйти от проблем связанных с этим, нужно хранить и использовать небходимый минимум в оперативной памяти без обращения к диску. Такую систему можно реализовать с помощью alpine linux, если произвести установку либо в режиме diskless mode, либо в режиме data disk mode. К сожалению, классическая cхема diskless mode обычно подразумевает что вы загружаетесь всегда с read-only носителя (iso/cdrom), а храните конфигурации и кэш пакетов на другом носителе (usb). Установка в data disk mode неудобна и плохо кастомизируется имеющимся утилитами установки alpine. Поэтому предлагается использовать гибридный режим, процесс установки которого описан в следующем разделе
Преимущества и особенности:
отказ диска не так критичен как для ВМ установленной обычным способом
все данные для загрузки находятся на разделе в состоянии read-only, поэтому как минимум с ФС не могут возникнуть проблемы из-за записи в момент отказа, что в других случаях приводит к сломанной системе, которая не загружается
система продолжает корректно работать даже, если диск недоступен
бинарники и утилиты ОС находятся на файловой системе в оперативной памяти, на хост можно зайти по ssh, использовать утилиты командной строки
большинство работающих процессов не может перестать отвечать из-за зависания I/O к диску, так как они обращаются к файловой системе в RAM (tmpfs)
скорость работы не деградирует если диск начинает тормозить
управляемость: в случае проблем угрожающего масштаба более надежный доступ (например для остановки сервисов и диагностики)
Автор не претендует на то, что подобный метод решит все проблемы. Предлагается лишь повысить надежность путем решения части проблем связанных с дисковыми отказами.
Вместо заключения
Казалось бы да, баги — с кем не бывает. Но я хочу обратить внимание, что они связаны с использованием нестандартного окружения. Не того, на котором происходит разработка. И хорошо, если это просто отнимает время на дебаг Dockerfile и установку зависимостей при сборке. Хуже, когда проблема внезапно возникает на проде, или вы вдруг узнаёте, что установилось не то, что должно было. И стоит ли сэкономленные 80Mb таких заморочек?
Alpine не плохой и не хороший. Это просто инструмент. У меня самого пара сервисов крутятся на нём, но они предельно простые. Там буквально 2 зависимости, и поэтому что-то необычное сразу бросится в глаза (например, установка из исходников). Для себя я выработал правило: в подавляющем большинстве случаев бери debian; alpine — только если действительно знаешь что делаешь.
Прошу прощения за кликбейтный заголовок. Мне хочется, чтобы к выбору базового образа подходили чуть более осознанно, учитывая как плюсы, так и минусы.
Итог
Дистрибутив Alpine не идеален, но его лаконичность меня действительно впечатлила, особенно в роли контейнера (всего 6 процессов — init, 4*getty, syslogd). Для меня он выглядит так, как должна выглядеть минимальная серверная операционная система (прости меня, CentOS!).
Кроме того, он вполне подходит на роль учебной площадки, позволяющей увидеть, из чего состоит современный дистрибутив, не погружаясь сразу в пучину whateverd-сервисов и многократного дублирования функциональности в великолепно-многоуровнево-конфигурируемых-средствах на все случаи жизни.






