Dnes si povíme o tom, co je virtualizace, k čemu je dobrá a proč jsme ji chtěli zakomponovat do našeho vývojového flow. Především si ukážeme, jak virtualizovat macOS uvnitř macOS, což byl donedávna velký oříšek.
Virtu…what? 🤔
Pro začátek je dobré si vysvětlit, co to tedy virtualizace je. Standardně na počítači běží jeden operační systém, virtualizací se dá docílit stavu, kdy uvnitř operačního systému běží další virtuální stroj, třeba s úplně jiným operačním systémem, který o tom fyzickém nemá ponětí.
Pustit si virtualizovaný systém může mít několik možných use casů – v našem případě využíváme především toho, že tak můžeme na CI kompilovat jeden projekt víckrát na jednom fyzickém stroji.
Možných využití je samozřejmě celá řada, osobně by se mi virtualizace hodila především na škole, kde některé předměty vyžadovaly instalaci spoustu boilerplate softwaru, kterého jsem se v plné míře zbavil až reinstalací systému.
Důležitý benefit virtualizace je, že virtuální stroj lze snadno rychle spouštět, ve většině případů i vytvořit a okamžitě smazat. K čemu je to dobré, si ukážeme dále. V následujících řádcích budou použity termíny host (hostitelská) a guest (hostovaná) mašina. Host mašina je váš fyzický stroj na našem schématu tedy ta růžová a guesti jsou ty bílé.
Proč to používáme? 🤨
Naše hlavní využití je pro buildy na CI. Historicky jsme na to používali dva Intel stroje, jelikož virtualizace macOS nebyla možná a některé tooly z našeho stacku neumožňovaly vícenásobné spuštění, tudíž nebylo možné, aby na jednom stroji běželo více buildů současně, přestože na to měly dostatečný výkon. Díky virtualizaci jsme schopni tento nedostatek odbourat.
Nevýhodou fyzického stroje napřímo připojeného k CI je, že na něm zůstávají různé artefakty – nainstalované aplikace v simulátoru, které mohou mít uložený nějaký svůj vnitřní stav, v simulátoru mohou zůstat viset různé systémové dialogy žádající o práva, Xcode derived data časem zabírají značnou část místa na disku. Tyto věci jsou samozřejmě řešitelné různými crony, nebo úpravou CI jobů samotných, nicméně tato řešení přidávají na složitosti setup nových zařízení, nebo přidávají overhead při udržování CI pipeline. Při běhu ve virtuálce není nic z toho třeba řešit, job doběhne, virtuálka se smaže a pro nový job se vytvoří nová přesně v initial stavu.
Co je ta virtuálka?
Zjednodušeně můžeme virtuálku považovat za takový snapshot systému, ve kterém je nainstalovaný potřebný software, v našem případě řekněme Xcode. Abych tedy na našem CI měl k dispozici vše potřebné, je potřeba vytvořit prázdnou virtuálku, nainstalovat do ní Xcode a používat tento snapshot. Tím bude mít zajištěno, že Xcode nainstaluji jednou a každá naklonovaná virtuálka jej bude mít k dispozici.
Jak virtuálku vytvořím?
Mohlo by vás napadnout, že budeme využívat Docker, nicméně virtualizace macOS uvnitř Dockeru vždy byla problematická. Pro vytvoření virtuálky budeme využívat Tart, který je postaven nad Virtualization frameworkem od Applu.
Nejjednodušším způsobem jak virtuálku vytvořit, je pomocí tart create <název VM> --from-ipsw <image macOS>
. Image macOS jsou veřejně dostupné, nicméně pro jejich seznam využíváme ipsw.me. Zde si stačí vybrat jakou verzi systému si přejete virtualizovat a příkaz odpálit. Když teď pustíme tart run <název VM>
, otevře se okno, ve kterém vám virtuálka naběhne. Zde je možné systém nainstalovat, tak jako na fyzickém zařízení. Jen takové doporučení – existuje konvence, že by Tart virtuálky měly mít přihlašovací údaje admin/admin, ale není to podmínkou. Zároveň po instalaci doporučuji povolit v systému přihlášení přes SSH a VNC.
Aktuálně existuje ve Virtualization frameworku omezení a není možné se ve virtuálce přihlásit k Apple ID. Pokud tedy chcete nainstalovat Xcode, je potřeba stáhnout XIP ve fyzickém zařízení a drag&dropnout jej do virtuálky. Aby to bylo možné, je potřeba se k zařízení připojit přes VNC, nejjednodušší je virtuálku zavřít a pustit znovu s flagem --vnc.
Nicméně vytvořit virtuálku jde i líp, než ji takto klikat, Tart má svůj plugin pro Packer, kde se definuje, jaký image se má vytvořit, co do něj nainstalovat a lze tedy vytvoření automatizovat.
Tím jak na to, se nyní nebudeme moc zabývat. Je dobré se podívat buď na náš repozitář s images, popř. na repo Cirruslabs, kteří Tart vyvíjí.
Virtuálku máme, co s ní teď?
Virtuálku je potřeba dostat na CI stroj, vzhledem k tomu, že Tart virtuálky jsou OCI-compatible, tak je možné využít jakýkoliv registr, který je také kompatibilní. Github Packages i Gitlab Package registry kompatibilní jsou. My pro naše virtuálky využíváme Github Packages. Než virtuálku pushnu, je potřeba Tartu poskytnout přihlašovací údaje ke Githubu, k tomu slouží command tart login ghcr.io (Github Packages jsou dostupné přes doménu ghcr.io). Pro přihlášení zde neslouží heslo ke Github účtu, ale je potřeba vytvořit Personal access token a ten dát Tartu jako heslo, až se na něj zeptá.
Ve chvíli, kdy je Tart přihlášený je možné virtuálku pushnout příkazem tart push <název VM> ghcr.io/<github account>/<package name>:<verze>
– pokud package neexistuje, tak bude vytvořen, pokud na to má účet práva. Verzování záleží na vás, my verzujeme náš Xcode image podle verze Xcodu a je dobré poslední verzi označovat tagem latest.
Ve chvíli, kdy je třeba remote virtuálku použít, tak se hodí příkazy tart pull <url>
a tart clone ... - pull vám virtuálku stáhne, clone ji zduplikuje, clone je možné použít jak na lokální tak na remote virtuálky a doporučuji jej používat, díky tomu vám původní image zůstane netknutý.
Bohužel jelikož disk virtuálek obsahuje celý macOS, tak virtuálky mají desítky gigabajtů, tudíž pull a push trvají dlouho i s velmi rychlým připojením. Naštěstí Tart image lokálně cachuje, takže není třeba dělat pull pořád dokola.
Zapojení do CI
Naše CI běží na self-hosted Gitlabu, připojení fyzického Maca, který bude pro každý Gitlab job vytvářet jednorázovou virtuálku není nic složitého – Tart má k dispozici Gitlab executora a jeho setup není složitý. Gitlab executor pro každý job udělá clone virtuálky, pustí v ní job a poté ji smaže. Aby nedošlo velmi brzy k vyčerpání místa na disku, tak executor automaticky promazává virtuálky, které nebyly v nedávné době použity.
Issues, které se objeví, mívají celkem snadné řešení, my jsme narazili na to, že výhoda vždy čistého stroje má nevýhodu čistého stroje - pokud začínáte každý job v čistém prostředí, tak každý job bude kompilovat závislosti přes Mint, Carthage apod. My máme Carthage vyřešený pomocí Torina už delší dobu, to pro nás nebyl velký problém. Mint byl jiný oříšek.
Řešení máme dvě - Gitlab cache, nebo je možné do virtuálky připojit složky z hostujícího macOS. Obě řešení spoléhají na nastavení environment proměnných MINT_PATH a MINT_LINK_PATH. V případě Gitlab cache je potřeba jim nastavit hodnoty uvnitř working directory vašich jobů a Gitlab se o cache postará. Toto řešení je vhodné pro nízký počet projektů v rámci Gitlabu.
Jelikož v Ackee pracujeme na velkém množství projektů paralelně, vydali jsme se druhou cestou, aby různé projekty mohly sdílet zkompilované CLI tooly. Pokud zavoláte tart run <název VM> --dir mint:/Users/<user>/.mint
tak se vám ve virtuálce na cestě /Volumes/My Shared Files/mint
objeví obsah .mint
složky z hostující mašiny. Tento parametr jde předat i Gitlab executoru, tudíž zkompilované artefakty přežijí smazání virtuálky.
Díky tomu, že veškerý tooling, který v CI pipeline je potřeba, je nainstalován uvnitř virtuálky, tak pro setup stroje na CI je potřeba nainstalovat Tart, Tart Gitlab executor, Gitlab Runner a připojit runner do Gitlabu, tím setup končí.
Jak moc je to růžové?
Rozhodně je super, že každý job začíná s čistým prostředím. CI je možné jednoduše škálovat, jelikož setup nových strojů je rychlý a snadný. Je dobré, že prostředí virtuálky je snadno přenositelné mezi různými fyzickými stroji. Velká výhoda je, že pokud na jednom fyzickém stroji běží více virtuálek, tak se neovlivňují, jelikož jsou izolované.
Výhody ovšem přináší i nějaké nevýhody - čisté prostředí může prodlužovat běh CI jobů a je potřeba vytipovat části pipeline, které je vhodné cachovat. Velikost virtuálek je značná komplikace, osobně před nasazením nového image doporučuji image na host mašině nejdřív pullnout a pak jej začít využívat, jinak bude CI dělat dlouhý pull místo odbavování jobů.
Pak se občas stane, že Apple v systémovém imagi vydá nějakou botu - v macOS 14.2 nefungovalo mountování složek přes --dir, takže bylo potřeba setrvat na starších systémech až do vydání 14.3.
Další nepříjemnout limitací je, že na jakémkoliv Macu lze spustit současně maximálně 2 virtuálky. Mělo by se jednat o limitaci, která pochází z licenčních podmínek macOS. Tedy pokud je potřeba pouštět co nejvíce jobů, tak je vhodné pořídit více slabších strojů, než nabušený Mac Pro. Každopádně virtualizace macOS je dostupné pouze pro Apple Silicon stroje, což ale dnes omezení spíše není.
S nasazením virtualizace se zkomplikoval debug CI jobů. Historicky dokud existoval na CI jeden stroj, který běžel pořád, tak nebyl problém se na něj připojit přes VNC a podívat se, co je za problém. Vzhledem k tomu, že virtuálka se po ukončení jobu smaže, tak toto není možné a je potřeba vyladit artefakty jobů tak, aby byl případný debug dostatečně přívětivý.
Občas člověk narazí na některé komplikace s tooly, které používá, my jsme narazili na nepříjemný bug v Mintu. Při implementaci cache bylo potřeba, aby linkoval binárky na cestu /Volumes/My Shared Files/mint, ale kvůli mezeře v cestě si s tím Mint neporadil. Nicméně fix byl jednoduchý a pro náš image si tedy Mint kompilujeme při jeho buildu, dokud fix nebude vydaný.
Virtualizace: vyplatila se nám?
Určitě ano, přestože výčet nepříjemností výše není úplně krátký, tak jejich řešení není složité a výhody jednoznačně převyšují. Porodní bolesti postihnout velké množství nových technologií a zde tomu nebylo jinak. Aktuálně využíváme virtualizovanou pipeline téměř rok a posledních několik měsíců funguje jak má a pouze updatujeme systém, verzi Xcodu a občas přibude nový tool do image, v principu fungování se nic nemění a to je rozhodně super! 😎