Hlavní navigace

Jak se píše kernel a proč by měl testy vymýšlet počítač (zápisky z LinuxDays)

6. 10. 2020
Doba čtení: 11 minut

Sdílet

 Autor: Petr Hodač
Letošní ročník konference LinuxDays byl úplně jiný. Byl sice online a probíhal jen jeden den, přesto se na něm virtuálně potkali zajímaví přednášející a zazněla poutavá témata. Víte, jak se píše vlastní kernel?

Michal Vaner: nechte si testy napsat počítačem

Zákazníci neplatí programátorům za testy, ale zajímá ho kvalitní kód. Testy jsou důležité pro mě, zákazníka nezajímají. Vývojář si ale při napsání dobrých testů může být jist, že jeho kód je v pořádku.

Spousta problémů v kódu vyplývá z toho, že programátora vůbec nenapadne, že by v dané části mohl problém vůbec být. Když jsem si někde nešetřil vstup, tak mě pravděpodobně ani nenapadlo, že bych ho měl ošetřit. Proto jsem si pro to nenapsal ani test.

Existuje proto skupina testů, kterým se říká randomizované. Při nich počítač vymýšlí testy za nás a vyzkouší i situace, které by programátora vůbec nenapadly. Počítač nageneruje náhodná vstupní data, která mohou být libovolně strukturovaná. Autor může specifikovat strategii a poté opakovaně předkládá algoritmu vstupní data. Nakonec se zkontroluje, že výstup splňuje předem dané požadavky.

Pokud test selže, pokusí se testovací framework minimalizovat vstup tak, abychom měli co nejmenší příklad dat, na kterých kód spadl. Zároveň tento vstup uloží a vyzkouší ho příště, až programátor zařídí opravu svého kódu.

Výhodou tohoto přístupu je, že znamená pro programátora méně práce. Je jednodušší nechat na počítači, aby nám vygeneroval testy než přemýšlet, kde by se mohl objevit problém. Zároveň je to dobré pro objevení přehlédnutých situací, které jsou pravděpodobně přehlédnutí i v testech.

Nevýhodou naopak je, že je takový test náročnější na výkon počítače, protože běží poměrně dlouho. Zároveň nejsou náhodné testy vhodné k nalezení okrajových případů. Naopak je to velmi vhodné na objevování algoritmických bugů.

Není možné jimi plně nahradit klasické unit testy, ty by měly být naopak na základě výsledků randomizovaného testu rozšířeny.

Existuje celá řada knihoven pro podobné testy. Pro Rust jsou to například proptest, quickcheck nebo model, Hashell má quickcheck, v C++ je RapidCheck a v Pythonu je například Hypothesis. Pokud budete hledat, určitě podobnou pomůcku pro svůj jazyk najdete.

Pro ošetření chyb ve zpracování dat se používá takzvaný fuzzing, při kterém se program krmí náhodnými daty. Obvykle jde o nestrukturovaná data, občas se připraví dostatečně validní data, která projdou hluboko do programu. Snahou je způsobit divné chování jako různé pády.

Kdybychom ale zkoušeli vkládat data úplně naslepo, zřejmě bychom na nic nepřišli, protože různých kombinací je příliš mnoho. Proto se vezme již připravený vstup a běh programu se hlídá pomocí sanitizeru. Pokud zjistí, že data prošla kódem jinudy, odloží je na hromádku zajímavých pokusů. Tímto způsobem se rozšiřuje zásoba vstupů, se kterými se program chová nestandardně.

Fuzzing je užitečný, pokud se vstup programu vystavuje do nedůvěryhodného prostředí. Je to vlastně hacker, který se snaží dlouho dostat do vašeho software. Pokud to projde, máte jistotu, že živý hacker se vám tam nedostane. Výhodou je, že se nástroje samy učí, jak se „zavrtat“ dovnitř programu.

Na druhou stranu se jedná o těžkotonážní metodu, jejíž běh může u netriviálního software může trvat dny i týdny. Dají se najít i služby, na kterých můžete svůj kód nechat běžet, pokud máte dostatečně známý open-source projekt.

Nástrojů existujte poměrně hodně, protože jde o docela starou techniku. Během přednášky byly zmíněny nástroje AFL, LibFuzzer či HongFuzz.

Vít Kabele: jak jsme si psali vlastní kernel

V rámci studentského softwarového projektu přišla potřeba napsat si vlastní jádro operačního systému. Nebyl to cíl projektu, ale jeden z kroků. Zajímavé bylo, že studenti neměli s podobnou věcí žádné zkušenosti. Tohle vám nikdo ve škole nedokáže předat. Od teorie k praxi je docela velký skok a je tam spousta překvapení.

S kódem na této úrovni už vám pomůže málokdo. Na Stack Overflow už vám nikdo nepomůže, není to JavaScript. Zdrojů je velmi málo a spoustu informací najdete jen ve specifikacích. Naštěstí nám hodně pomohl Rudolf Marek, který byl našim konzultantem.

Na začátku vyvstává otázka, jak vůbec takový kód nabootovat. Psal jsem vždycky jen běžný kód, ale nevěděl jsem, jak napsat tohle, abychom to pak mohli vůbec spustit. Běžný programátor se se zaváděním operačního systému nesetkává.

Bude potřeba bootoloader, kterému je potřeba říct, co a jak má načíst. Potřebujeme také vědět, v jakém stavu bude stroj, když nám ho bootloader předá. Naštěstí pro to existuje standard Multiboot2, který pochází z poloviny devadesátých let.

Skládá se ze dvou částí. Ta jedna říká, jak musí vypadat naše binárka, aby ji bylo možné načíst. Druhá pak říká, v jakém stavu převezmeme stroj od zavaděče.

Zmíněný standard přímo uvádí, že cílem je udělat vše co nejjednodušší pro programátora a uživatele. Většinu složité práce by měl udělat samotný zavaděč. Na té specifikaci je dobré to, že formátem binárky je běžný ELF. To je obrovské ulehčení. Sestavení kódu je tak možné provést běžným kompilátorem.

Binárka musí obsahovat hlavičku, ve které jsou uvedeny informace o architektuře a kontrolní součet. Jakmile zavaděč dostane příkaz kód zavést, prohledá daný soubor a zkontroluje tyto údaje. Pokud jsou v pořádku, načte jej do paměti.

Kromě toho je možné do hlavičky přidat další instrukce, takzvané tagy. Ty dovolují od zavaděče získat další informace, které bychom jinak získávali těžko. Můžeme tak například zjistit adresu tabulky ACPI RSBP, mapu paměti, či příkazový řádek zavaděče. Zavaděči je také možné dát odkaz na archiv, který vám zavede do paměti. Vy si jej můžete připojit a načíst z něj vlastní moduly.

Ke kompilaci jádra je možné použít běžný překladač GCC a sadu utilit Binutils. Hned druhý den psaní jsme objevili problém: chceme napsat 64bitový kernel, ale potřebujeme 32bitovou binárku ELF. Běžné utility ale nedovolují slinkovat 64bitový kód do 32bitového souboru. Je tedy potřeba linkovat nadvakrát a na začátek 64bitového kódu přidat 32bitový kód, který je pak možné přímo spustit.

Dalším nástrojem, který vývojáři používali, byl QEMU. Je to úžasně komplexní věc, se kterou jsme neměli žádný problém. V QEMU běžel samotný operační systém, v něm vlastní hypervizor a v něm opět stejný operační systém. Fungovalo to výborně.

QEMU je možné provozovat v režimu hardwarové akcelerace nebo jako emulátor. Můžete si vybrat desítky různých procesorů, všechno se chová obdivuhodně věrně. Pro vývoj je také užitečná možnost zakázat restart, abyste mohli odhalit různé problémy.

QEMU má také funkci monitor, což je interaktivní konzole umožňující vypisovat informace o běžícím stroji. My jsme nejvíce používali výpisy registrů, což je úžasné pro sledování běhu vašeho kódu. Další užitečnou věcí je sledování stránkování paměti pomocí výpisu TLB.

Pro výstup vývojáři nepoužívali klasické VGA, u které se ztrácí dlouhá historie. Místo toho využili sériovou konzoli. QEMU umí výstup vypsat na terminál. Užitečné je také propojení GDB s QEMU, kdy je možné automatizovaně sledovat stav kódu a odhalovat nejrůznější problémy. Výhodou je, že máme klasickou ELF binárku, se kterou GDB umí přímo pracovat.

Adam Blažek: jak si přizpůsobit kernel

Intel vytvořil vlastní TPU pro velmi rychlé výpočty. Připravil také miniaturní variantu pro embedded, která zvládne 3 až 4 teraoperací za sekundu a spotřebuje jen dva watty. Pro představu slušná grafická karta zvládne 10 teraoperací, ale potřebuje k tomu 150 W energie.

Takový hardware umožňuje v reálném čase udělat spoustu práce, například zpracovávat lokálně video s minimální spotřebou. Nechali jsme si vytvořit vlastní desku, která přidává vlastní procesor a další periferie.

Jako procesor byl použit ARM64 Cortex A53, ke kterému byla přidána sériová konzole (UART), LTE modem Quectel EC21, kamera Omnivision OV5645 a GPIO. Pak nás čekalo tenhle hardware rozchodit.

Pro vývojovou desku s TPU existuje Mendel Linux, což je lehký klon Debianu Buster, který obsahuje balíčky s firmwarem a ovladač pro TPU. Vše je otevřené včetně zdrojových kódů, ale na naší desce to bohužel nefunguje.

Pro start systému je potřeba nastavit bootloader, připravit jádro systému, popsat zapojení periferií pomocí device tree a dodat správné ovladače. Systém se nám zasekával někde mezi kernelem a device tree. Nebylo úplně jasné, kde je problém, ale prostě to nešlo.

Při vývoji pro takovéto embedded zařízení je nejzajímavější části kompilace, protože samotné zařízení používá jinou platformu než vývojový počítač. Je možné to řešit například cross-kompilací, emulací nebo si celé prostředí připravit v Dockeru. Díky tomu, že vše v Mendel Linuxu je v balíčcích, je možné vše upravit a znovu systém snadno sestavit. Pokud správně upravíme device tree list a všechno sestavíme, už nám to nabootuje. Sice nefungují žádné periferie, ale běží systém.

Poté zbývá zprovoznění kamery a modulu pro LTE. Dost možná to bude vyžadovat nějaké patche do jádra, aby vše fungovalo. TPU už bylo podporováno, takže je nakonec vše funkční.

Při přípravě vlastního systému je dobré vše minimalizovat a vyčistit. Čím jednodušší, tím lépe se vám s tím bude pracovat. Jakmile systém bootuje, už je třeba co nejvíce prototypovat přímo v něm. Čím méně úprav uděláte, tím méně budete mít práce s údržbou. Pokud ale stavíte vlastní desku, musíte počítat s tím, že na ní budete provozovat vlastní verzi systému.

Robert Vojčík: Kubernetes v produkci

Produkce je umístění, které splňují určité parametry. Takové parametry budou přísnější než kdekoliv jinde. Měly by například zahrnovat redundanci infrastruktury: elektřiny, konektivity, chlazení a podobně.

Nasazení v produkčním prostředí by mělo nabízet jednoduché nasazení, sběr provozních metrik a při problémech také návrat ke starší verzi (rollback). Když už sbíráme metriky, měli bychom vědět o problémech, které nastaly nebo by mohly nastat. Také je potřeba sledovat kvalitu služby podle nastavených parametrů. Třeba u API chceme mít odpověď do 50 milisekund a potřebujeme vědět, že se situace zhoršuje.

Často se zapomíná na podrobnou dokumentaci. Jde o peníze, takže by někde mělo být sepsáno, co se má udělat v případě problémů. Nesmí nastat situace, kdy by někdo v produkčním prostředí experimentoval a hledal narychlo řešení.

Při nasazování kontejnerů dochází často k nepochopení jejich principů. My jsme do toho spadli velmi rychle, řekli jsme si, že kontejner je prostě jen nová virtualizace. Pokud ale nedojde ke změně filozofie použití nové technologie, můžete se dostat do řady problémů, které mají velmi složité řešení. Ty problémy se ale neprojevovaly ve vývoji, jen jsme na ně naráželi v produkci. Splnilo to ale svou práci a zrychlilo nám to vývoj.

V roce 2015 vydali lidé z Google vlastní studii o tom, jak by se mělo přistupovat ke kontejnerům v produkci. Z toho jsme pochopili, že jsme nechytili kontejnery za správný konec. V následujícím roce se začalo hodně mluvit o Kubernetes. Velké firmy do toho začaly tlačit nejen peníze, ale i své lidi. Tím se začal vývoj akcelerovat.

V roce 2017 si pak Robert Vojčík postavil první Kubernetes cluster, sbíral s kolegy zkušenosti a školil okolí. Síť byla původně postavena na WeaveNET, což je řešení vytvořené nad IPtables. Báli jsme se, že nám to nebude výkonově stačit. Proto později přešli na kube-router, který je postavený nad BGP.

Poměrně rychle se začali přidávat další nadšenci a bylo potřeba řešit správu uživatelů. Jednotlivé projekty se nám začaly ovlivňovat, takže jsme nasadili kvóty. Obrovským tématem jsou také bezpečnostní problémy, protože Kubernetes původně ve výchozím stavu příliš bezpečnost neřešil a všechny zdroje byly dostupné všem. V poslední době se to naštěstí zlepšuje a dost se na to tlačí. Stále je potřeba tomu věnovat poměrně dost času.

Některé pokusy dokázaly sestřelit celý hardware. Rozhodli jsme se, že musíme postavit cluster pro každý tým nebo alespoň projekt. Na základě těchto zkušeností bylo rozhodnuto o stavbě nového řešení od úplného začátku.

Kubernetes sám o sobě je orchestrátor, který spoustu podstatných věcí neřeší: síť, monitoring, logování a hromadu dalších funkcí. Pro nasazení projektu bylo nakonec rozhodnuto nasadit Rancher. Pokud ještě nemáte žádné konkrétní požadavky, rozhodně vyzkoušejte Rancher. Nakonec bylo rozhodnuto použít Kubeadm obsluhovaný pomocí Puppet.

Celou konfiguraci je vhodné ukládat do Gitu, aby bylo možné kontrolovat stav a upravovat nastavení konkrétního clusteru. Chceme také jednoduše nasazovat změny na všechny clustery nebo na určité sadě.

Důležitá je také validace a testování před nasazením do produkce. S tím souvisí kontrola našich vlastních standardů, na kterých jsme se dohodli. To je důležité pro to, aby nevznikl nespravovatelný nepořádek.

Výsledkem jsou dva repozitáře: jeden základní společný pro všechny a poté další specifický pro konkrétní cluster. I když se snažíte všechno maximálně sjednotit, stejně je tam spousta věcí, které jsou rozdílné. Musíte je mít ale taky někde evidované.

Žádný z clusterů není vystaven do internetu přímo nody, ale je před nimi ještě reverzní proxy. Do internetu je tak vystavená HAProxy, která funguje jako load balancer a rozhazuje provoz na jednotlivé uzly. Přísně sledujeme, co je z internetu dostupné, aby nedošlo omylem k vystavení nějaké testovací instance.

František Petružálek: automatizovaná údržba databázových serverů

Proč vlastně vůbec automatizovat? Šetří to čas a nakonec tedy i peníze. To ovšem platí v dlouhodobém měřítku, protože nejprve je potřeba do automatizace investovat. Důležitější ale je, že to redukuje chyby a nekonzistence.

Přemýšlet nad automatizací se vyplatí, pokud se nám některý úkon opakuje. Platí pravidlo, že po třetím opakování se vyplatí přemýšlet nad zautomatizováním. Zároveň je to důležité při náročném dodržování firemních standardů, což zvyšuje spolehlivost a přehlednost provozovaných služeb.

Automatizace je sled pevně daných pravidel, která ale nedovolují vyhodnocovat složitější situace. Není to umělá inteligence. Je potřeba, aby někdo vše používal, udržoval, sledoval a vyhodnocoval. Automatizovat lze i složité procesy, ale vždy existuje hranice výhodnosti.

Při nasazování automatizace byly stanoveny následující požadavky: správa vlastní infrastruktury, open-source software a podpora pro databázové technologie. Chtěli jsme také nasadit už zaběhnutou technologii. Proto jsme zvolili Puppet.

V Puppetu používají správci oficiální i komunitní moduly. Mezi oficiální patří elasticsearch, redis či postgresql. Vždy si ale napíšeme wrapper, který se přizpůsobuje našim potřebám. Napsali si ale také vlastní moduly: mysql, mongodb, influxdb. Ty sice existují v oficiálních verzích, ale my na ně máme specifické požadavky a kdybychom si je upravovali, neměli bychom to dodnes hotové.

Cyber Security tip1

Konfigurační kód je pak možné vyzkoušet v testovacím prostředí. Vývoj probíhá v Gitu, pokud je vše v požadovaném stavu, proběhne začlenění do hlavního stromu a skript puppet-wrapper se pak automaticky postará o rozprostření napříč servery.

Při automatizaci databázových serverů je potřeba si dát pozor na operace nad perzistentními daty, nutnost zamčení automatizace při potřebě provést manuální úpravy a také je třeba psát kód precizně. Každá chyba se projeví na všech serverech a muže stát víc, než bychom chtěli.

Autor článku

Petr Krčmář pracuje jako šéfredaktor serveru Root.cz. Studoval počítače a média, takže je rozpolcen mezi dva obory. Snaží se dělat obojí, jak nejlépe umí.