Проблема

Вы вдруг просыпаетесь от алерта — что-то случилось. Вы быстро стучите по клавиатуре и пытаетесь залогиниться по ssh куда-то, чтобы разобраться. Но вас не пускает! Хост пингуется, но зайти не получается. Вокруг вас начинают бегать, начинают вам писать или звонить, обращаются со словами "Шеф, все пропало!" или чем-то в этом же духе. Вы начинаете предполагать самое ужасное, тучи сгущаются. В результате последовательности некоторых судорожных действий выясняется, что беда с сетевым хранилищем для виртуальных машин или NAS. Оно либо перестало быть доступно, либо периодически отваливается, либо жутко тормозит.

Также возможно, что NAS работает, но лагает сеть до хранилища. Если отвлечься от конкретного случая описанного выше, то представить можно и другие ситуации в отличных от вышеописанного окружениях. Например, ваш провайдер VPS внезапно начал куролесить с дисками. Или что у вас нет NAS и машины крутятся на локальном хранилище, либо у вас нет виртуалок вообще. У вас заглючил RAID-контроллер на старом сервере и начались похожие проблемы. В итоге, если вы что-то подобное не предполагали заранее — вы можете оказаться в ситуации, в которой вы ничего не сможете решить удаленно, в худшем случае придется куда-то ехать и вручную диагностировать и решать проблему. Можно привести в пример еще тысячу подобных ситуаций и как видно нет недостатка причин, по которым что-то может отказать, а вы можете потерять доступ даже элементарно для того, чтобы разобраться, что происходит.

Возможное решение

У инцидентов подобных этим есть одна общая особенность — все сходится к тому, что важный сервис почти полностью завязан на возможность чтения или записи с диска или накопителя (будь то физический или виртуальный). Стало быть, чтобы как-то уйти от проблем связанных с этим, нужно хранить и использовать небходимый минимум в оперативной памяти без обращения к диску. Такую систему можно реализовать с помощью 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)

  • скорость работы не деградирует если диск начинает тормозить

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

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

Варианты использования (use cases)

  • Роутер, фаерволл

  • Бастион-хост, ssh-cервер

  • vpn-сервер, точка входа в корпоративную сеть

  • Сервер с набором утилит для администрирования инфраструктуры

  • Обратный прокси или балансировщик нагрузки

  • В некоторых особых случаях — в качестве ноды какого-либо кластера

  • Другие типы критичных сервисов (кроме тяжеловесных)

  • Мини-компьютер (Raspberry Pi и пр.)

  • Какое-либо устройство embedded типа

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

Установка

Требуется: машина с загрузочным носителем (виртуальная машина с подключенным iso) и доступом в интернет. Образы можно скачать здесь .

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 ]

Для kvm/qemu

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) можно удалить в зависимости от вашей концепции безопасности. Описана только первоначальная настройка, далее вы можете настраивать под свои задачи. К сожалению, весь описанный процесс трудоемкий, так что делайте шаблон виртуалки или образ диска если вам нужно делать подобное неоднократно.

Поведение при проблемах с диском (тест)

Метод тестирования простой — после загрузки системы в настройках виртуалки удаляем диск и смотрим на поведение.

В строках вывода 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

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

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!

Комментарии (5)


  1. khajiit
    01.06.2023 10:18

    Не рассматривали использование uefi и сетевой загрузки?


    1. cliver Автор
      01.06.2023 10:18
      +1

      Чисто теоретически, загрузочный раздел аналогичен загрузочному iso, там есть директория /efi/boot - вероятно его можно загружать через efi, даже не смотря на то, что там таблица msdos, а не gpt. Если пробовать натягивать систему на gpt таблицу - то стандартные скрипты alpine вероятно не помогут, надо все делать вручную, и скорее всего использовать grub2 efi.

      Насчет сетевой загрузки - особо не думал, вероятно можно реализовать что-то подобное.


  1. 13werwolf13
    01.06.2023 10:18

    nfsroot не? или iscsi..
    ну и систему можно вообще грузить с copytoram (правда лично я не пробовал).


  1. cema93
    01.06.2023 10:18

    А кто сказал что RAM бессмертна и бесконечна?


    1. khajiit
      01.06.2023 10:18

      Если логи отправляются на удаленный сервер, а ОС не хостит ничего кроме статики что может много записать — вроде баз данных — то вполне.
      Все, что выбивается за рамки этих вариантов нагрузки нужно вынести в контейнеры с корнем на сетевой ФС или iSCSI.