Root and alpine

Root and alpine Техника

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

Интерфейс dive
Интерфейс dive
Содержание
  1. Проблема
  2. An Alpine Image with Bash Runtime Configuration
  3. Building
  4. Users and Permissions
  5. Running the Container
  6. Running as appuser (the default user)
  7. Running as root
  8. Running While Passing a Known Flag
  9. Running While Passing an Unknown Option
  10. Switching User — appuser to root
  11. Switching User — root to appuser
  12. License
  13. Make Alpine Linux RootFS
  14. Requirements
  15. Usage
  16. Examples
  17. Installation Script in Heredoc
  18. Create Docker Base Image
  19. Create OCI Image
  20. License
  21. Поведение при проблемах с диском (тест)
  22. Заключительные моменты
  23. Хочется странного
  24. Оно нам действительно надо?
  25. Сравниваем базовые образы alpine и debian
  26. Установка
  27. Stage 0
  28. Stage 1
  29. Stage 2
  30. Дополнительные действия
  31. Больше пакетов
  32. Устанавливаем зону времени и обновление времени
  33. Раскладка для русского языка
  34. Сервисы для виртуализации
  35. Прописываем hostname
  36. Сборка и установка python пакетов
  37. А еще
  38. Comments
  39. reproducer
  40. workaround 1
  41. workaround 2
  42. Про Alpine
  43. Приоткроем крышку
  44. Взрываемся в препроде
  45. Обращение к не-питонистам
  46. Сравниваем в полевых условиях
  47. Баги всюду
  48. Варианты использования (use cases)
  49. Ремарка про alpine
  50. Демоны под крышкой
  51. Возможное решение
  52. Вместо заключения
  53. Итог

Проблема

Также возможно, что 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_config

Then find the given line.

#PermitRootLogin prohibit-password
PermitRootLogin yes

Then restart the SSH service.

service sshd restart

Thanks 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 
Output

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 
Output

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 
Output

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  - 
Output

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
Output

Welcome root, from 
Sourced by: -bash.

This is an interactive shell
This is a login shell

Switching User — root to appuser

Output

Welcome appuser, from 
Sourced by: -bash.

This is an interactive shell
This is a login shell

License

Make Alpine Linux RootFS

Build Status

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 \
      1

Examples

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
  1. Alpine branch (release) to install (see Alpine Releases).

  2. You can name packages to install into the chroot, in addition to base packages (see ALPINE_BASE_PKGS in alpine-make-rootfs).

  3. You may specify timezone to set (default is UTC).

  4. 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 /mnt inside the chroot and $PWD for the script is set to /mnt, so you can easily access files out of the chroot and copy them into the rootfs.

  5. Installation script may be provided also via STDIN, using a convenient heredoc syntax.
    The script passed is executed using /bin/sh -e.

  6. Note that it’s not needed to clean apk cache, 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) failed
alpine:~# 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);
    • Хорошо бы еще сохранять и восстанавливать конфигурацию межсетевого экрана.
Дополнительно:  Желательно выйти с этой страницы и не продолжать, если у вас есть avast webmail root

И на этом почти все, остальное — дело менеджера пакетов. Меньше исполняемого кода и конфигурации – меньше багов, меньше багов – меньше багов. А система все также запущена и доступна по сети. Идея выглядит неплохо, теперь посмотрим, насколько близок к ней дистрибутив 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 syslinux

0.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 table

0.4a) Раздел должен определиться. Если по какой-то причине по пути /dev/vda1 ничего нет — запускаем

rc-service sysfs restart 

Cтавим файловую систему на раздел

localhost:~# mkfs.vfat -F32 /dev/vda1

0.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/apkcache

0.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 root

1.2) В файле /etc/ssh/sshd_config cтроку #PermitRootLogin prohibit-password меняем временно на PermitRootLogin yes и перезагружаем конфигурацию sshd

service sshd reload

1.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_keys

1.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/vda2

2.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 /mnt

2.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-02

2.3) Добавляем запись в /etc/fstab. Пример добавляемой строки (измените UUID на тот который был в предыдущем пункте)

UUID=106af5fb-342c-47c3-b721-01f2f3981042 /var btrfs subvol=/var 0 0

2.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

@kost

@jumanjiman

reproducer

FROM gliderlabs/alpine:3.2

RUN adduser -D foo

USER foo
ENTRYPOINT ["sh"]
run the reproducer

$ 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
/ $ exit

@jumanjiman

workaround 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)
/ $ exit

@kost

@jumanjiman

agreed. people assume it’s safe. warning is appropriate

@andyshinn

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?

@kost

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

@andyshinn

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.

@frol

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!

@jumanjiman

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


Nov 25, 2015

@frol

@frol
frol

mentioned this issue


Nov 25, 2015

@frol

@andyshinn

@frol

@frol

@frol

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
root

We are still having a major security issue after a month since reporting the issue and a year after the first image release. Cool!

@frol

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 не слишком многолюдно:

вывод ls /boot

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

Запущенный bootloader

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

Содержимое /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.

Как видите, это не всегда эти ваши нелюбимые «портянки»:

Пример конфигурационного файла 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-сервисов и многократного дублирования функциональности в великолепно-многоуровнево-конфигурируемых-средствах на все случаи жизни.

Дополнительно:  Как исправить проблемы с микрофоном: шипит, не работает, фонит, Windows не распознает. Причины и подробные инструкции с решением
Оцените статью
Master Hi-technology
Добавить комментарий