Я грешен: во мне есть дух соперничества. Когда я услышал, что мой друг заставил Linux загружаться с NFS, мне обязательно нужно было его превзойти. Я обязан был доказать, что могу сделать что-то сложнее, лучше, быстрее, сильнее [прим. пер.: в оригинале отсылка к композиции Daft Punk «Harder, Better, Faster, Stronger»].
Как и все хорошие проекты, этот начался с идеи.
Мой мозг вышел в астрал и выхватил оттуда нечто неуловимое, заставившее слиться воедино разрозненные концепции. В моих руках Масса обрела вес и тёмные клубящиеся цвета, грозящие гибелью каждому, кто будет вглядываться в них слишком долго.
На грани безумия мой утомлённый мозг придумал мой magnum opus: запуск Linux с рута Google Drive.
▍ Но как?
Я хотел обеспечить автономность системы, поэтому не мог использовать в качестве «помощника» вторую машину. Мой разум сразу же вспомнил FUSE — программу, работающую драйвером файловой системы в пользовательском пространстве (с поддержкой со стороны ядра).
Мне достаточно было установить программы FUSE в initramfs ядра Linux и сконфигурировать сеть. В этом ведь не должно быть ничего сложного, так?
▍ Процесс запуска Linux
Процесс запуска Linux очень забавен. Позвольте мне на секунду сделать вид, что я его понимаю1:
- Запускается прошивка (BIOS/UEFI) и загружает bootloader.
- Bootloader загружает ядро.
- Ядро распаковывает в ОЗУ временную файловую систему, у которой есть инструменты для монтирования реальной файловой системы.
- Ядро монтирует реальную файловую систему и переключает процесс на систему инициализации в новой файловой системе.
Несмотря на странность третьего этапа, на самом деле он очень полезен! Мы можем смонтировать на этом этапе файловую систему FUSE и выполнить обычный запуск.
1. Главным образом я понимаю его, потому что прочитал эту статью с Archwiki.
▍ Proof of Concept
Файловой системе initramfs требуется и поддержка сети, и двоичные файлы FUSE. К счастью, благодаря Dracut можно достаточно просто собирать собственную initramfs.
Я решил создать её поверх Arch Linux, потому что он относительно легковесен, и я знаком с его работой, в отличие от чего-то наподобие Alpine.
$ git clone https://github.com/dracutdevs/dracut $ podman run -it --name arch -v ./dracut:/dracut docker.io/archlinux:latest bash
В контейнер я установил несколько пакетов (в том числе и пакет linux
, потому что мне нужно работающее ядро), скомпилированный из исходников dracut
, а также написал простой скрипт модуля в modules.d/90fuse/module-setup.sh
:
#!/bin/bash check() { require_binaries fusermount fuseiso mkisofs || return 1 return 0 } depends() { return 0 } install() { inst_multiple fusermount fuseiso mkisofs return 0 }
Вот и всё. Это весь код, который мне нужно было написать. Окрылённый уверенностью, я рванул вперёд и собрал образ EFI.
$ ./dracut.sh --kver 6.9.6-arch1-1 \ --uefi efi_firmware/EFI/BOOT/BOOTX64.efi \ --force -l -N --no-hostonly-cmdline \ --modules "base bash fuse shutdown network" \ --add-drivers "target_core_mod target_core_file e1000" \ --kernel-cmdline "ip=dhcp rd.shell=1 console=ttyS0" $ qemu-kvm -bios ./FV/OVMF.fd -m 4G \ -drive format=raw,file=fat:rw:./efi_firmware \ -netdev user,id=network0 -device e1000,netdev=network0 -nographic ... ... dracut Warning: dracut: FATAL: No or empty root= argument dracut Warning: dracut: Refusing to continue Generating "/run/initramfs/rdsosreport.txt" You might want to save "/run/initramfs/rdsosreport.txt" to a USB stick or /boot after mounting them and attach it to a bug report. To get more debug information in the report, reboot with "rd.debug" added to the kernel command line. Dropping to debug shell. dracut:/#
Голосом хакера: мы в системе. Теперь нужно включить сеть и смонтировать тестовый рут. Я уже извлёк рут Arch Linux в локально работающий бакет S3, так что вроде особых проблем быть не должно. Достаточно вручную настроить сетевые маршруты и загрузить драйверы.
dracut:/# modprobe fuse dracut:/# modprobe e1000 dracut:/# ip link set lo up dracut:/# ip link set eth0 up dracut:/# dhclient eth0 dhcp: PREINIT eth0 up dhcp: BOUND setting up eth0 dracut:/# ip route add default via 10.0.2.2 dev eth0 proto dhcp src 10.0.2.15 dracut:/# s3fs -o url=http://192.168.2.209:9000 -o use_path_request_style fuse /sysroot dracut:/# ls /sysroot bin dev home lib64 opt root sbin sys usr boot etc lib mnt proc run srv tmp var dracut:/# switch_root /sysroot /sbin/init switch_root: failed to execute /lib/systemd/systemd: Input/output error dracut:/# ls sh: ls: command not found
Честно говоря, не знаю, на что я надеялся. Похоже, что всё просто… пропало. Я зашёл в тупик и понятия не имел, что делать. Я потратил несколько дней на изучение, исследование исходного кода switch_root
, ничего не добившись. Но потом я вспомнил о ссылке, которую мне отправил Энтони: How to shrink root filesystem without booting a livecd. По ней была команда pivot_root
, которую switch_root
, похоже, вызывает внутренним образом. Давайте попробуем её.
dracut:/# logout ... [ 430.817269] ---[ end Kernel panic - not syncing: Attempted to kill init! exitcode=0x00000100 ]--- ... dracut:/# cd /sysroot dracut:/sysroot# mkdir oldroot dracut:/sysroot# pivot_root . oldroot pivot_root: failed to change root from `.' to `oldroot': Invalid argument
Очевидно, pivot_root
не разрешается изменять руты, если переключаемый рут находится в initramfs. Не повезло. В ответе на Stack Exchange рекомендуется использовать switch_root
, что тоже не сработало. Однако моё внимание привлекла часть ответа:
initramfs — это rootfs: для rootfs нельзя ни выполнить pivot_root, ни размонтировать её. Вместо этого удалите всё из rootfs, чтобы освободить пространств (
find -xdev / -exec rm '{}' ';'
), перемонтируйте rootfs с новым рутом (cd /newmount; mount --move . /; chroot .
), подключите stdin/stdout/stderr к новому /dev/console и выполните новый init.
Возможно ли вручную переключить рут без специализированного системного вызова? Что, если я просто выполню chroot?
... dracut:/# mount --rbind /sys /sysroot/sys dracut:/# mount --rbind /dev /sysroot/dev dracut:/# mount -t proc /proc /sysroot/proc dracut:/# chroot /sysroot /sbin/init Explicit --user argument required to run as user manager.
Ага, чтобы Systemd запустился нормально, мне нужно выполнять команду chroot
как PID 1. Можно изменить скрипт инициализации initramfs, просто поместить в него мои команды запуска и заменить вызов switch_root
на exec chroot /sbin/init
.
Я поместил это в modules.d/99base/init.sh
в исходниках Dracut после загрузки правил udev и обхода предыдущих проверок переменной root
.
modprobe fuse modprobe e1000 ip link set lo up ip link set eth0 up dhclient eth0 ip route add default via 10.0.2.2 dev eth0 proto dhcp src 10.0.2.15 s3fs -o url=http://192.168.2.209:9000 -o use_path_request_style fuse /sysroot mount --rbind /sys /sysroot/sys mount --rbind /dev /sysroot/dev mount -t proc /proc /sysroot/proc
Ещё я добавил в конец exec chroot /sysroot /sbin/init
вместо команды switch_root
.
Пересобрал образ EFI, и…
Я сидел перед экраном, не веря своим глазам. Не может же всё быть так просто? Наверняка это богохульство, и дух Денниса Ритчи снизойдёт сейчас, чтобы остановить меня.
Но никто меня не остановил, поэтому я двинулся дальше.
Я залогинился как root
с совершенно безопасным паролем root
и без церемоний попал в шелл.
[root@archlinux ~]# mount s3fs on / type fuse.s3fs (rw,nosuid,nodev,relatime,user_id=0,group_id=0) ... [root@archlinux ~]#
Наконец-то Linux загрузился из бакета S3. Меня уже подмывало поделиться своим достижением с остальными, достаточно было лишь запустить программу fetch, чтобы показать её на скриншоте:
[root@archlinux ~]# pacman -Sy fastfetch :: Synchronizing package databases... core.db failed to download error: failed retrieving file 'core.db' from geo.mirror.pkgbuild.com : Could not resolve host: geo.mirror.pkgbuild.com warning: fatal error from geo.mirror.pkgbuild.com, skipping for the remainder of this transaction error: failed retrieving file 'core.db' from mirror.rackspace.com : Could not resolve host: mirror.rackspace.com warning: fatal error from mirror.rackspace.com, skipping for the remainder of this transaction error: failed retrieving file 'core.db' from mirror.leaseweb.net : Could not resolve host: mirror.leaseweb.net warning: fatal error from mirror.leaseweb.net, skipping for the remainder of this transaction error: failed to synchronize all databases (invalid url for server) [root@archlinux ~]#
Ой, похоже, DNS не работает, а у меня нет dig
и других инструментов отладки.
Постойте-ка! Файловая система рута находится на S3! Я могу просто смонтировать её куда-то ещё, где работает сеть, выполнить chroot
и установить все нужные утилиты!
Немного позанимавшись отладкой, я выяснил, что systemd-resolved отказывается запускаться, потому что Failed to connect stdout to the journal socket, ignoring: Permission denied
. Я не собираюсь пытаться отладить systemd, потому что это слишком сложно, а я ленив, поэтому вместо этого я воспользуюсь systemd Cloudflare.
[root@archlinux ~]# echo "nameserver 1.1.1.1" > /etc/resolv.conf [root@archlinux ~]# pacman -Sy fastfetch :: Synchronizing package databases... core is up to date extra is up to date ... [root@archlinux ~]# fastfetch
Меня по-прежнему никто не останавливал. Никто не вламывался в окно, и сигнализация не сработала. Можно было спокойно продолжать.
Я был готов запустить Linux с Google Drive.
▍ Дело дошло до Google
Уже существует готовый проект, который позволяет пользоваться Google Drive через FUSE: google-drive-ocamlfuse. К счастью, у меня есть аккаунт Google, которым я не пользовался многие годы! Я выполнил инструкции, не читая принял условия использования, создал все секреты oauth2, включил APIs, установил google-drive-ocamlfuse
из AUR в мою Arch Linux VM, пропатчил несколько PKGBUILD
(давно этого не делал), и на этом всё! Я примонтировал Google Drive! Смонтировав Drive и выполнив несколько очень долгих rsync
, я получил Arch Linux на Google Drive.
Шучу, конечно, никогда не бывает всё так просто. Вот неполный список проблем, с которыми я столкнулся:
- Не работают симлинки на симлинки (очень важно для того, что находится в
/usr/lib
). - Не работают жёсткие ссылки.
- Всё о-о-очень медленно.
- Относительнче симлинки вообще не работают.
- Отсутствуют повисшие симлинки (важно для того, что ссылается
/proc
и не примонтировано, или для того, что пока ещё не скопировано). - Не работают симлинки за пределами Google Drive.
- Не работают разрешения (как и атрибуты).
- Я ведь уже говорил, что всё медленное?
Учитывая количество проблем с симлинками, я уже почти был готов изменить код драйвера FUSE, чтобы он просто создавал файл, заканчивающийся на .internalsymlink
, чтобы всё это исправить (будь ты проклята, совместимость с Google Drive).
Но я поставил перед собой задачу сделать это, не исправляя ничего важного (никаких изменений в ядре и в драйвере FUSE), так что мне придётся просто смириться с этим и вручную создать все симлинки, которые не получается создать у rsync
, применив команду sed
к логам ошибок rsync
.
Тем временем я добавил в initramfs файлы токенов, сгенерированные на моём ноутбуке, двоичный файл FUSE Google Drive и сертификаты SSL, а также настроил несколько параметров2, чтобы сильно упростить себе жизнь.
2. Я задал acknowledge_abuse=true
и root_folder=fuse-root
.
... inst ./gdfuse-config /.gdfuse/default/config inst ./gdfuse-state /.gdfuse/default/state find /etc/ssl -type f -or -type l | while read file; do inst "$file"; done find /etc/ca-certificates -type f -or -type l | while read file; do inst "$file"; done ...
Здорово видеть, что, по крайней мере, работают метки времени. Теперь осталось лишь подождать мучительно медленного запуска!
chroot: /sbin/init: File not found
Вероятно, меня никто не останавливал, потому что я всё равно потерплю неудачу.
Я знаю, что файл существует, но почему же он не найден? Всё просто: Linux — это довольно странная система: если вызываемый двоичный файл зависит от библиотеки, которая не найдена, то вы получите «File not found».
dracut:/# ldd /sysroot/bin/bash linux-vdso.so.1 (0x00007e122b196000) libreadline.so.8 => /usr/lib/libreadline.so.8 (0x00007e122b01a000) libc.so.6 => /usr/lib/libc.so.6 (0x00007e122ae2e000) libncursesw.so.6 => /usr/lib/libncursesw.so.6 (0x00007e122adbf000) /lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007e122b198000)
Однако на самом деле этих симлинков не существует! Помните, выше мы говорили, что относительные симлинки не работают? Так вот, они нанесли ответный удар. Ядро ищет файлы в /sysroot
внутри /sysroot/sysroot
. К счастью, исправить это достаточно легко: нужно всего лишь связать /sysroot
с /sysroot/sysroot
без линков:
dracut:/# mkdir /sysroot/sysroot dracut:/# mount --rbind /sysroot /sysroot/sysroot
Настало время запуска!
Arch потребовалось пять минут на пересоздание кэша динамического компоновщика, ещё минута на systemd unit, а потом ничего не произошло. Запуск прекратился.
[ TIME ] Timed out waiting for device /dev/ttyS0. [DEPEND] Dependency failed for Serial Getty on ttyS0.
Наверно, нужно увеличить таймаут и перезапуститься. В /etc/systemd/system/dev-ttyS0.device
я указал следующее:
[Unit] Description=Serial device ttyS0 DefaultDependencies=no Before=sysinit.target JobTimeoutSec=infinity
К счастью, запуск занял не бесконечное количество времени.
Я уже близок к победе! Осталось увеличить ещё один таймаут. Я присвоил LOGIN_TIMEOUT
значение 0
в /etc/login.defs
Google Drive и снова попробовал выполнить вход.
К счастью, существует кэш, так что последующие операции чтения файлов стали намного быстрее.
Ура, можно надевать лавровый венок: моя химера из Linux и Google Drive ожила.
Но я пока не закончил. Никто меня не остановил, потому что все хотели, чтобы я одержал победу. Мне нужно её развить. Нужно, чтобы это заработало на реальном оборудовании.
▍ А теперь сделаем это на реальном оборудовании
К счастью, я поменял серверы и теперь у меня есть лишний ноутбук без накопителя! Идеальная жертва3 для экспериментов!
3. При работе над этим проектом ни один компьютер не пострадал (физически).
Мне пришлось внести некоторые изменения:
- Использовать подходящий драйвер Ethernet, а не стандартный
e1000
. - Не использовать последовательней дисплей.
- Изменить параметры сети, чтобы они соответствовали топологии моей домашней сети.
Мне достаточно драйвера r8169
для Ethernet-порта, а ещё добавим сюда Powerline, потому что это существенно не повлияет на производительность, а у меня нет Ethernet-кабеля, который бы можно было дотянуть до моей комнаты.
Я собрал единый файл EFI, закинул его в /BOOT/EFI
USB-накопителя, а затем подключил его в свой старый сервер. Несмотря на все усилия, я так и не разобрался, какая директива modprobe нужна для встроенной клавиатуры ноутбука, поэтому просто выполнил hid_usb
modprobe и подключил внешнюю клавиатуру для настройки сети.
Вот мой magnum opus. Моё великое творение. Этот след надолго останется на Земле после моего ухода: нативный облачный компьютер.
Здорово то, что я могу просто взять скриншот4 с Google Drive и выложить его сюда!
4. Я сделал скриншот с помощью fbgrab.
▍ Узрите — нативный облачный компьютер!
Несмотря на несерьёзность моего проекта, можно придумать ему достаточно серьёзные применения, например, запуск Linux с SSH, а может, запуск Linux из репозитория Git и отслеживание всех изменений в Git при помощи gitfs. Несмотря на посредственную полезность, его возможности бесконечны.
Если я что-то и знаю о технологиях, так это то, что современный тренд — это перенос всего в Облако. Поэтому я уже готов коммерциализировать этот проект для любой компании, желающей отказаться от своего ненадёжного аппаратного накопителя и полностью перейти в Облако. Если вас интересует Истинный Нативный Облачный Компьютинг, оставьте заявку.
К сожалению, не знаю, что делать с этим дальше. Возможно, стоит установить Nix?
Telegram-канал со скидками, розыгрышами призов и новостями IT 💻
ссылка на оригинал статьи https://habr.com/ru/articles/827422/
Добавить комментарий