Diskless linux přes Wi-Fi

Z thewoodcraft.org

Než se začnu věnovat mcachefs, je třeba uvést, jakým způsobem jsem se k němu dostal a proč jde o důležitou součást naší disklessové infrastruktury.

Tak rok je za námi a opět nastal čas je možné hrabat na disklessovou infrastrukturu aniž by někdo remcal. Většinou se udělá aktualizace, a pak se řeší problémy, které přinesly nové změny v distribuci. Letos mne ale čekal zcela nový úkol – rozchodit diskless přes wi-fi. Naštěstí neprobíhala žádná rekonstrukce jako loni, takže na jeho vyřešení bylo relativně dost času.

Loni to bylo hektické, ale všechno se podařilo vyřešit a protože se diskless osvědčil, přišli za mnou s nápadem, že by bylo fajn, kdyby stejný systém jako u laboratorních počítačů bylo možné použít i na turtlebotech.

"Mozkem" turtlebotů je Intel NUC, který se dodává s předinstalovaným Ubuntu, ale v laboratořích se používá disklessový Debian a ROS (Robot Operating System), jehož prostřednictvím se pracuje s roboty, se spouští v kontejneru přes singularity.

Pokud bychom turtleboty bootovali po drátě, tak by nebylo co řešit. Ovšem, co by to bylo za autonomního robota, který by za sebou tahal drát?! Jenže Wi-Fi karty nemají žádné PXE, přes které by si mohl turtlebot stáhnout linuxové jádro s ramdiskem po síti, jako u full-disklessu. Proto bylo nutné situaci vyřešit jako tzv. half-diskless.

Half-diskless

Tohle řešení jsem doposud využíval pouze pro servery, abych je mohl spouštět aniž bych potřeboval cokoliv jiného než jeden NFS server. Všechny moje servery jsou disklessové – tohle řešení používám už několik let a nemá chybu.

Jsou to virtuální stroje, které se spouští přes QEMU (nepoužívám libvirt!) a nepoužívají žádné fyzické blokové zařízení. Ovšem abych se při migraci obešel bez DHCP, které by na základě dotazu přes PXE poslalo jádro s ramdiskem, použil jsem virtuální disk, který se do virtuálního stroje "napíchne" do QEMU přes NFS jako lokální blokové zařízení, na kterém jsou uloženy pouze soubory zavaděče a jádro s ramdiskem.

Jde o relativně malý soubor (cca 200MB). Z něj se natahuje GRUB2, který přes svůj konfigurační soubor předává jádru parametry pro zavedení systému ze sítě. Po nahození oživení sítě v ramdisku se namountuje kořenový adresář (rovněž publikovaný přes NFS) a z něj se pak spustí systém.

V případě turtlebotu není nutné předávat lokální blokové zařízení přes NFS – stačí využít to, na kterém už je předinstalované Ubuntu. To se zavádí přes GRUB2, který natahuje UEFI a je rozlezlé po celém disku. Teoreticky bych ho mohl využít, a spouštět disklessový systém přes systemd-nspawn, podobně jako když dělám úpravy u disklessového systému, ale nechtěl jsem do něj šťourat víc než je nutné.

Úprava rozdělení disku

Diskový oddíl s Ubuntu tedy bylo nutné zmenšit, stávající swap posunout a pak vytvořit nový diskový oddíl, na který by se – kromě souborů spouštěných přes singularity – mohlo umístit jádro s ramdiskem.

Podle data adresářů v logu jsem zjistil, že původní instalace Ubuntu je z 1. srpna 2017, naposled aktualizovaná 26. února 2019 a na ten NUC naklonovaná 1. března 2019. Od té doby ho spustil kdosi pouze jednou – 4. března, zřejmě pro kontrolu. Do pracek se mi ten NUC dostal 5. srpna 2019.

Nabootoval jsem na něm (po drátě) náš laboratorní disklessový Debian, přes gparted zmenšil původní diskový oddíl s Ubuntu (formátovaný na ext4), posunul swap a vytvořil diskový oddíl pro Btrfs.

Na něm jsem potom vytvořil subvolume root-dir, do kterého jsem přes debootstrap nainstaloval minimální instalaci unstable Debianu + nástroje potřebné k sestavení ramdisku s podporou wi-fi a linuxové jádro 5.0.0-trunk. A soubor /boot/grub/grub.cfg předinstalovaného Ubuntu jsem upravil tak, aby primárně spouštěl systém z lokálního disku.

Wi-Fi v ramdisku

Pak jsem začal řešit klíčový úkol – nahození připojení přes wifi zabezpečenou WPA2 klíčem v prostředí ramdisku.

Postupoval jsem podle stránky na webu Marka Fargase. Všechno se zdálo ok. Jádro v ramdisku wifi zařízení rozpoznalo jako wlp1s0, ale wpa_supplicant se nepřipojil. Ovšem po nastartování systému se připojení přes wi-fi nastavilo bez problémů.

Laboroval jsem s tím několik dní. Jako testovací AP jsem totiž původně používal své Blackberry Q10, jenže na tom neumím nahodit wi-fi bez zabezpečení. Pak se ke mě přidal kolega, který má přístup na kontroler. Vyzkoušeli jsme i připojení na wi-fi bez zabezpečení. Tu se sice podařilo připojit k AP, ale přes nástroje busyboxu nešlo síť nahodit. Teprve když se do ramdisku přidalo plnotučné ip, dhclient a dhclient-script se to podařilo. Jenže připojení s ověřením přes WPA2 stále nefungovalo. Kolega tedy "protáhnul" do AP u naší kanceláře VLAN laboratoře pro turtleboty a dál jsme zkoušeli možné i nemožné. Dokonce jsem vyzkoušel i jiný Wi-Fi adaptér, ale stejně to bylo k ničemu.

První vodítko nám poskytl teprve příspěvek Sebastiana Panceace, který řešil před třemi lety podobný problém. Jemu chyběly v jádře následující moduly:

CONFIG_CRYPTO_CCM=y
CONFIG_CRYPTO_GCM=y

První modul CONFIG_CRYPTO_CCM v použitém jádře 5.0 byl, ale volba CONFIG_CRYPTO_GCM chuběla. Bylo však jasné, že ještě někde něco chybí. K řešení nám pomohl příspěvek, který napsal Marek Beliško. I ten před dvěma lety řešil podobný problém a zjistil, že ověření přes WAP nefunguje pokud jádro nemá k dispozici moduly ccm a ctr.

A jelikož oba moduly v jádře byly, zkusil jsem je přidat a ejhle! Wpa_supplicant se připojil. Pln euforie jsem tedy Markovi zavolal, abych mu za ten jeho starý diskuzní příspěvek poděkoval. Šlo totiž o dost zákeřný problém, protože nejde o moduly s přímou závislostí, takže jsem na ně nemohl přijít, ani když jsem zkoušel strace. Moje tápání bylo pochopitelně do značné míry dané i tím, že jsem doposud nic podobného neřešil.

Vyřešit připojení byl základní úkol. Ale do cíle bylo ještě daleko. Přidal jsem do /etc/initramfs-tools/scripts/nfs-top skript pro nahození wifi, vygeneroval nový ramdisk a pak zkusil nabootovat diskless.

K mému milému překvapení systém najel. Háček byl pouze v tom, že to bylo v pátek večer a během víkendu se mi vykouřilo z hlavy, že už jsem to vlastně vyřešil, protože k tomu aby to najelo nebylo nutné vůbec nic dělat. V pondělí jsem s tím zápasil celé dopoledne, než jsem přišel na to, že to funguje, pokud se v ramdisku nespustí wpa_supplicant. Jenže ten ve fázi break=premount ještě spuštěný nebyl, protože se spouštěl až ve fázi nfs-top a ve fázi break=mount už zas bylo pozdě.

Pomohl jsem si tím, že jsem si do distribučního skriptu nfs přidal vlastní breaky.

Nicméně, diskless startoval.

Problémy NFS přes Wi-Fi

Všechno jelo krásně, jenže se ukázalo, že se kvalita připojení přes Wi-Fi u mého stolu v kanceláři je silně proměnlivá a většinou se pohybuje jen mezi 30~38% (měřeno na NUCu), podle toho, jak je zrovna ta krabička natočena. AP totiž primárně nasvěcuje chodbu a 5GHz signál, který je obzvláště citlivý na průchod přes stěny ke mě leze nejspíš odrazem a ještě přes dvě půlmetrové zdi.

V laborce nic takového nebude, takže je to ideální stav pro simulaci zatížené sítě, ale pro práci v disklessovém prostředí je takové připojení téměř nepoužitelné.

Vtip našeho laboratorního disklessu je totiž v tom, že adresář, který je z NFS vyexportován jako read-only, překrývá transparentní overlay, u kterého se změny zapisují do virtuálního disku v RAM. Je to svižné, pokud je svižná síť, protože se soubory z NFS natahují do keše až když je potřebujete.

Jenže u pomalého připojení nevíte jestli se aplikace nespouští proto, že se teprve stahuje, nebo proto, že už stihla vytimeoutovat. A pokud klikáte jak zběsilí, tak vám po načtení souboru do keše vyskočí hned několik instancí najednou. Což o to, je-li připojení pomalé, dá se to přežít, protože po načtení je to už v pohodě. Jenže u brutálně pomalého připojení přes Wi-Fi, kdy se ztrácí pakety je to na průstřel hlavy. A nejhorší na tom je, že se po rebootu musí vše natahovat znova – proto jsem se rozhodl použít lokální kešování.

mcachefs – lokální kešování souborů

Řešení jako bcache, nebo dm-cache rovnou vypadly ze hry, protože NFS není blokové zařízení. Nejprve jsem zkusil použít fscache – ukázalo se však, že to je řešení na pytel, protože neudržuje keš jako kopii adresářové struktury (což jsem potřeboval kvůli překrytí overlayem). Ukládá soubory do vlastní adresářové struktury, která vychází z aktuálního id připojeného volume, které se po remountu sdíleného NFS adresáře změní, takže se nepoužije to co už bylo stažené ale všechno se tahá znova – protože se změní cesta.

Pak jsem narazil na catfs. Funguje to podobně jako overlay. Aplikace je naprogramovaná v rustu, takže jeho binárka obsahuje jeho interpreter a soubory se honí přes FUSE. Vyzkoušel jsem to a vypadalo to slibně, takže jsem to zadrátoval do ramdisku a upravil skript /etc/initramfs-tools/scripts/nfs-bottom/overlay, který sestavuje finální sendvič. Ale ouha!

Ukázalo se, že catfs nelze použít, protože do překrytého systému propagoval symlinky jako typ souboru, na který odkazoval symlink na spodní vrstvě. U jednoduché adresářové struktury, např. na datovém úložišti by to možná nevadilo, ale systemd pracuje se symlinky, takže na tomhle havarovalo přepnutí do systémového adresáře ve fázi init.

Nicméně jsem narazil na jeho stránce na mcachefs, který rovněž jede přes FUSE.

Pro mne je podstatné především to, že jsem s jeho pomocí uspokojivě vyřešil problém s lokálním kešováním.

Proč se ten blogpost jmenuje sendvič?

Tohle je pochopitelně jenom malá část mého know-how, protože systém startuje do sendviče, který může být složený z mnoha různých vrstev. A na rozdíl od původního řešení se vůbec nepracuje s parametry jádra při zavádění – u turtlebota, který nemá klávesnici by se totiž jen těžko měnily parametry, které předává grub při zavádění. Zbavil jsem se i závislosti konfigurace na typu zavaděče. Tzn. že je úplně jedno, jestli systém startuje z lokálu, nebo zda natáhne přes PXE nějaký jiný zavaděč – konfigurák je ve všech případech stejný.


U disklessového linuxu využíváme mcachefs jako transparentní vrstvu, přes kterou se kopírují soubory z NFS na lokální blokové zařízení, které funguje jako keš. Prakticky se dá ale použít k vytvoření záložní kopie vybraného adresáře, nebo v kombinaci se sdíleným úložištěm pro kešování často používaných souborů.

Následují schéma demonstruje v čem se liší základní sendvič využívající lokální kešování od klasického disklessu, kdy je overlay sestavený přímo nad NFS. Schéma demonstrující rozdíl sendviče s využitím mcachefs a lokálního blokového zařízení ve srovnání s klasickým disklessem.

Sendvič s mcachefs
Podkladový adresář, připojený přes NFS, překrývá mcachefs, který používá jako lokální keš buď adresář, nebo Btrfs subvolume na lokálním blokovém zařízení. To celé překrývá overlay, který zabraňuje tomu, aby se lokální změny ukládaly do podkladových vrstev – v tomto případě do lokální keše.
Vrstvy jsou transparentní, takže pokud se kupř. vylistuje adresář, žádné soubory se po síti nepřenáší. Na lokální keš se soubory z NFS ukládají až poté, co se natáhnou do paměti při prvním spuštění aplikace.
Pak už se spouštějí jen o chlup pomaleji, než by tomu bylo, kdyby se lokální keš nepoužívala – zpoždění způsobuje režie kterou má mcachefs, který průběžně kontroluje, zda-li je stažená verze souboru aktuální ve srovnání s tou na NFS serveru.
Upozornění Kontroluje se pouze stažený soubor. Nenačítá se znova obsah vzdáleného adresáře! Takže pokud na NFS serveru do adresáře, jehož obsah už je v paměti nějaký soubor, tak se objeví až po restartu.
Sendvič bez lokální keše
U sendviče bez lokální keše adresář sdílený přes NFS překrývá rovnou overlay.
Ani v tomto případě se při vylistování adresáře nepřenáší žádné soubory, ale protože overlay komunikuje přímo s NFS je stejná operace mnohem rychlejší, protože odpadá režie, kterou sebou přináší FUSE.
Nejdéle trvá první spuštění aplikace – v podstatě stejně dlouho jakou u sendviče, který používá mcachefs – systém musí počkat, než se aplikace natáhne do paměti. Ovšem opakované načtení už je mnohem rychlejší, protože ji overlay rovnou natahuje z pamětu a už neposílá žádný dotaz směrem k NFS serveru.

Zásadní rozdíl je ale po restartu. V obou případech se při rebootu zahodí celý obsah RAM, tedy nejenom uživatelské změny, které overlay ukládal do tmpfs ale i natažené soubory.

U sendiče, který využívá jako keš lokální blokové zařízení, se pouze ověří (porovnáním časového razítka a délky uloženého souboru s tím co je na NFS serveru), že jde o jeden a ten samý soubor a pak už se do paměti netahají data po síti, ale rovnou z lokálního blokového zařízení. Kdežto u sendviče, který lokálně uložená data nemá, se natahuje všechno znova.

PoznámkaPokud máte spolehlivou a rychlou síť, s vysokou propustností, je lokální kešování zbytečné – režie s tím spojená by jenom zdržovala komunikaci s NFS serverem. Ale pokud máte nespolehlivé připojení, jako je kupř. v situaci, kdy se používá disklessový linux přes Wi-Fi, je to výhodné. Bez toho by totiž po každém restartu následovalo martyrium spojené s nekonečným stahováním dat, které by totálně ucpalo provoz na vašem AP.
Lokální kešování přes FUSE do RAM lze využít i v situaci, kdy máte relativně hodně RAM, ale pomalé disky na NFS serveru. Nebo pokud chcete redistribuovat data z NFS serveru dál – NFS totiž za normálních okolností neumožňuje vyexportovat adresář, který je namountovaný z jiného NFS.
Poznámka Pokud chcete redistribuovat NFS adresář dál, ale nemáte zároveň dost paměti, aby se vám do ní věechny soubory vešly, můžete místo mcachefs zkusit použít [fuse_xattrs] a vyexportovat data skrze něj.