Continuous integration je volně řečeno souhrn praktik a nástrojů, při kterých vývojáři integrují (commitují) své změny často (typicky alespoň jednou za den). Každá integrace je automaticky ověřena testy a případně může vést až k automatickému nasazení nové verze aplikace (continuous delivery), v případě neúspěchu k okamžitému reportování problému.
Přínosy continuous integration jsou především zrychlení vývoje, rychlejší dodávání nových verzí a snížení chybovosti – to vše díky automatizaci co nejvíce úkonů, které je třeba dělat při vývoji, testování a nasazovaní aplikace.
Všechno by to šlo implementovat pomocí vzájemně provázaných skriptů, ale za šťastnější řešení (i když zavádí SPOF) považuji nasazení nějakého integračního serveru coby hlavního koordinátora všech operací. Integrační server toho pro nás může ale dělat ještě více – např. provázat buildy a verze aplikace s bug trackerem nebo issue trackerem (Bugzillou, Mantisem, Jirou, …). Integrační server se tak může stát místem, na kterém vidíte vše o projektu (a slovo integrační tak dostává další rozměr).
V tomto článku bych vám rád popsal, proč a jak jsem nasazoval integrační server já. Moc jsem toho o průběžné integraci předem nenastudoval, přistoupil jsem k věci celkem pragmaticky – cílem bylo vyřešit problémy, které mě trápili. A díky TeamCity jsem byl schopen do týdne rozjet prakticky vše, co jsem plánoval – tedy build Solution s více než 100 projekty (C# a C), spuštění unit testů, integračních testů, nasazení a spuštění akceptačních testů. Všechno samozřejmě plně automaticky, odpálené commitem do SubVersion.
Proč jsem do toho šel?
Až donedávna byl projekt, na kterém dělám, prakticky one-man-show. To mj. znamenalo, že všechno jsem měl pod palcem já – od konzistence (zbuildovatelnosti) zdrojových kódů v repository, přes spuštění a úspěšné projití všech testů až po nasazení (přes One-Click Publish). A všechny tyto kroky jsem dělal tehdy, když se mi zachtělo.
Tento stav s sebou nesl mnoho problémů – nejvíc mě pálilo občasné vypublikování neotestovaného kódu (protože touhle změnou jsem prostě nemohl nic rozbít), omylem vypublikovaná DLLka ve špatném formátu (x86 místo x64) a především zdržení spojené s ručním nasazením aplikace (před publikováním nové verze jsem ručně zálohoval aktuální verzi). O ruční synchronizaci aplikace a databázového schématu ani nemluvím. Poslední kapkou bylo to, že k projektu přišel další člověk a já si uvědomil, co všechno si musí nainstalovat, aby byl schopen projekt vůbec zkompilovat.
Zavedení continuous integration tak bylo lékem na všechny výše uvedené problémy.
Příprava
Jedna věc předem – je důležité si uvědomit, že „csproj“ soubory nejsou nic jiného než build scripty pro MSBuild a proto se nebojte do nich zasahovat ručně – ne vše jde totiž naklikat přes Visual Studio.
Prvním krokem k tomu, abyste mohli projekt zařadit do CI, je to, že projekt musí být po čistém checkoutu z repository zkompilovatelný jedním příkazem. Mým cílem tak bylo, aby fungovalo něco jako „MSBuild.exe MySolution.sln /T:Build /p:Configuration:Release„. To nebyl problém na mém vývojářském stroji, kde jsem měl nainstalované Visual Studio a knihovny třetích stran nakopírované na správná místa nebo v GACu. Na čistém build serveru to už ale tak růžové nebylo. Takže na co byste si měli dát pozor?
Pre a Post-build stepy
Pokud kopírujete v Pre a Post-build stepech nějaké dependence, které nelze jinak vyjádřit, tak zajistěte, aby v době volání příslušného stepu už existovaly – tedy zajistěte správné pořadí kompilace.
Já jsem řešil poměrně běžnou věc – mám nějakou nativní DLL knihovnu, kterou potřebuji zkopírovat do výstupního adresáře .NETího projektu. Takže jsem si pěkně naklikal ve Visual Studiu závislosti mezi projekty a kompilace krásně jela. Ale na build serveru byl problém. Závislost mezi .NETím a nativním projektem se totiž uloží jen do sln souboru (což není narozdíl od csproje build script!). V MSBuildu 3.5 by to bylo ok, protože tam si MSBuild vše pořádně očuchal a správně určil pořadí. To ale údajně způsobovalo u velkých projektů výkonové problémy (?) a tak byla tato fičura v MSBuildu 4.0 odstraněna a MSBuild kouká jen na závislosti uvedené přímo v projektových souborech. Visual studio vám ale nedovolí přidat do .NETího projektu referenci na ne-.NETí projekt, takže nelze jednoduše naklikat závislost .NETího projektu na ne-.NETtím – musíme referenci ručně dopsat do csproje. Jako bonus dostaneme ve Visual Studiu žlutý vykřičníček a warning, ale kompilace pomocí MSBuildu probíhá ve správném pořadí.
Knihovny třetích stran
Jak říká Martin Fowler – „Everything should be in the repository.„. Takže mějte ve verzovacím systému binárky knihoven třetích stran a z projektů na ně odkazujte pomocí relativních cest (nebojte se podívat přímo do csproje). Pozor si dávejte hlavně tehdy, když máte na vývojářském stroji nějakou knihovnu nainstalovanou v GACu – ve spojení s automatickým přidáváním referencí (jako nabízí třeba ReSharper) vám to může připravit nemilé překvapení (nareferencovat assembly z GACu).
Generované soubory
Pokud generujete soubory pomocí T4, tak tyto soubory musí být v repository. Jen je potřeba zajistit jejich přegenerování na vývojářově stroji – to mám udělané tak, že v pre-build stepu spouštím TextTransform.exe, ale jen pokud existuje soubor „%CommonProgramFiles(x86)%Microsoft sharedTextTemplating10.0TextTransform.exe“ (instaluje se až s Visual Studiem). To zajistí, že na vývojářském stroji (tj. s nainstalovaným Visual Studiem) dojde v pre-build stepu k přegenerování T4 souboru.
Pokud se rozhodnete vyřešit T4 soubory tímto způsobem, tak reference na custom assembly nedávejte přímo do tt souboru, ale předávejte je přes parametry příkazové řádky.
Testy
Připravte se na to, že pokud máte testy napsané v MSTestu, budete muset na stroj, na kterém chcete testy spouštět, instalovat Visual Studio (pokud nechcete použít nějaký alternativní test runner).
Pokud používáte v testech DeploymentItem, tak možná narazíte na jeho trošku podivné chování při zadávání relativní cesty. Osvědčilo se mi zadávat cestu relativně k umístění sln souboru a při spuštění přes MSTest.exe pak nastavit jako runconfig soubor LocalTestRun.testrunconfig (který je ve stejném adresáři jako sln soubor).
Dále myslete na to, že tam, kde budete pouštět integrační testy, budete muset mít pravděpodobně dostupné nějaké testovací verze databází apod.
Transformace configů
Před tím, než ze zkompilovaných binárek vytvoříte instalační balíčky, můžete mít potřebu upravit nějaké konfigurační soubory v aplikaci. S .NET 4 přišly web.config transformation, které toto velmi zjednoduší. Ve webovém projektu máte klasicky soubor web.config – v něm byste měli mít nastavení, které odpovídá Debug konfiguraci (protože při debugu se nevím proč transformace neprovádějí). Dále budete mít soubor web.Release.config (Visual Studio 2010 má na jeho přidání podporu), ve kterém budou transformace připravující konfiguraci pro Release konfiguraci. Pokud máte v projektu kromě Debug a Release i další konfiguraci (např. Stage), můžete mít transformace i pro tyto další konfigurace (např. web.Stage.config).
Je možné, že budete chtít transformovat i jiný soubor než web.config (my transformujeme log4net.config). To samozřejmě není problém.
Pokud nebudete mít na build serveru Visual Studio, tak si dejte pozor, abyste si vybrali to správné řešení.
Transformaci konfiguračních souborů můžete ale samozřejmě dělat jakkoliv, třeba pomocí vlastních skriptů – na tom nevidím nic špatného.
Nejen z hlediska bezpečnosti je velmi zajímavou variantou mít v některých konfiguračních položkách místo hodnot jen placeholdery, které se naplní správnými hodnotami až při nasazení. To je velice flexibilní řešení, protože umožní nasadit jeden distribuční balíček v různých prostředích (typicky různé connection stringy).
Vytvoření instalačních balíčků
Instalační/Distribuční balíčky se zásadně liší podle typu aplikace – já se zde zaměřím jen na webové aplikace, konkrétně na vytváření balíčků, které se budou dále nasazovat pomocí nástroje MSDeploy (Web Deployment Tool).
Způsob vytvoření balíčku si můžete nastavit přímo ve Visual Studiu, v Properties dané webové aplikace. Já jsem si oblíbil možnost vytvořit balíček jako soubor zip a vytvořit ho v adresáři „Deploy/Packages“ (relativně k solution).
Důležité je, že balíčky mohou být parametrizované, což umožní výše zmiňované podstrčení správných hodnot do konfiguračních souborů až při nasazení (automaticky jsou takto parametrizovány právě zmiňované connection stringy).
Pokud chcete do balíčku přidat nějaké vlastní soubory (u mě to jsou opět nativní DLL knihovny do adresáře /bin), tak to není problém – stačí je dospecifikovat v csproji.
Jak ale vytvořit balíček z MSBuildu? Zde je opět více možností. Můžete zavolat MSBuild s targetem Package („MSBuild /T:Package„) nebo můžete nechat vytvořit balíčky ihned během kompilace pomocí property DeployOnBuild („/p:DeployOnBuild“).
Nasazení
Nasazení děláme pomocí nástroje MSDeploy, který umožňuje plnohodnotné nasazení, tj. nejen zkopírování souborů, ale i vytvoření a ovládání AppPoolu, nastavení práv, migraci databáze atd. Je třeba ho mít nainstalovaný jak na stroji, ze kterého nasazujeme, tak na stroji, na který nasazujeme. Typický příkaz pro jednoduché nasazení může vypadat nějak takto:
"%ProgramFiles%IISMicrosoft Web Deploy V2MSDeploy.exe" -verb:sync -source:package=Packages/MyApplication.zip -dest:auto,computerName="https://deployTargetComputer:8172/msdeploy.axd ?site=Default Web Site",userName=%username%,password=%pwd%,authtype=basic -allowUntrusted
Parametry balíčku (např. výše zmiňované connection stringy) se dají nastavit intuitivně pomocí přepínače -setParam, příp. můžete podstrčit celý soubor s parametry.
Migrace databáze
Při nasazení aplikace je třeba ověřit, že okolní infrastruktura odpovídá tomu, na co byla aplikace stavěna. Pokud se zaměříme jen na databáze, tak bychom měli ověřit, že schéma v databázi odpovídá tomu, proti čemu chce aplikace pracovat.
To se typicky dělá tak, že aplikace ví verzi schématu (celé číslo), databáze má v nějaké metatabulce uloženu aktuální verzi schématu, a instalační skript je zodpovědný za to, že se zavolají migrační skripty, které zajistí upgrade (příp. downgrade) databáze podle potřeb aplikace, tj. sjednotí verzi schématu požadovanou aplikací a aktuální v databázi.
Celou tuto infrastrukturu si můžeme napsat sami (není to nic složitého), můžeme využít nějaké migrační knihovny, nebo může využít to, co nabízí Visual Studio a MSDeploy.
Můžeme začít!
Pokud jsme schopni zkompilovat projekt jednoduše ihned po čistém checkoutu z repository, máme na build serveru nainstalovány všechny potřebné věci (.NET 4, VS2010, MSDeploy, …), umíme generovat distribuční balíčky a máme připraveny skripty pro jejich nasazení, tak nám nic nebrání nainstalovat nějaký integrační server a jednotlivé části této výrobní linky v něm provázat.
Já jsem se rozhodl použít TeamCity a rozhodně své volby nelituji. O instalaci jsem se díky našemu QA starat nemusel, ale veškerou konfiguraci jsem pak dělal sám a poměrně rychle jsem se zorientoval. Předností je taktéž cena, která je pro menší a střední projekty nulová.
V každém projektu (já mám jen jeden projekt odpovídající celému solution) můžete mít (v Professional verzi, která je zadarmo, až 20) Build Configurations. Jednu Build Configuration bych označil za jednu výrobní linku, takže můžete mít třeba dvě BC z názvy „Stage“ a „Production“. Když budou tyto dvě BC téměř identické, je možné vyextrahovat šablonu a tyto dvě BC na ní založit. Parametrizaci pak provedete přes Build Parameters.
Každá BC v sobě obsahuje posloupnost Build Stepů. Prvním (implictním) krokem je checkout z repository. Další kroky jsou již plně na nás, ale TeamCity nám zde velmi vychází vstříc – jsou zde připravené „build step runnery“ pro spuštění MSBuildu, MSTestu, baťáku, PowerShellu, Antu, FxCopu…a dokonce přímo Solution (takže staší zadat cestu k solution, které chcete zkompilovat).
Dalším důležitým nastavením jsou triggery, které umožní nastavit, kdy se má BC spustit – kadý den v zadaný čas nebo po commitnutí do repository jsou samozřejmostí.
Hotovo
Týdnu, který jsem věnoval zavedení průběžné integrace, vůbec nelituji. Díky CI jsem zautomatizoval drtivou většinou věcí, které je třeba dělat při vývoji, testování a nasazování aplikace. Rozhodně ale nepovažuji současný stav za finální, protože je ještě spousta věcí, které by šly vylepšit nebo přidat – např. lepší správa konfiguračních souborů, automatický rollback po neúspěšném nasazení atd.
I současný stav je obrovským přínosem a proto vám doporučím začít s CI. Není potřeba začít hned dělat všechny věci, které zde popisuji – můžete je zavádět průběžně. Stačí začít třeba tím, že se vytvoří build server, který bude po každém commitu kontrolovat zbuildovatelnost solution – i to je velmi pěkný první krok.
Trocha hardwaru k našemu integračnímu serveru (aneb než se dostanu k tomu, kvůli čemu jsem to Arduino pořizoval, tak aspoň jedna fungující blbůstka): http://i54.tinypic.com/jfvog9.jpg (omluva za příšernou kvalitu fotky)
To se mi líbíTo se mi líbí
@Mormegil můžeš sharnout zdrojáky? 🙂
To se mi líbíTo se mi líbí
Nějakej takovej HW by se mi líbil – ale nechce se mi s tím patlat 🙂
To se mi líbíTo se mi líbí
Feedback na to, co mi dalo aktuálně zabrat u Package – ty properties pro MSBuild (/p:) tam nejsou sekat jako parametry buildstepu, je potřeba to dát jako Build parameter – system.DeployOnBuild s hodnotou true.
To se mi líbíTo se mi líbí
Já mám u toho Build stepu „Visual Studio (sln)“ jako hodnotu „Command line parameters“ nastaveno toto a funguje to bezvadně:
„/p:DeployOnBuild=true /p:DeployTarget=Package“
A jako „Targets“ mám nastaveno „Build“ – to taky může hrát roli.
To se mi líbíTo se mi líbí
@Aleš Roubíček: Tak ty zdrojáky pro Arduino jsou úplně triviální, protože tahle část zajišťuje jen tu HW stranu – svícení LEDkama a dělání kraválu bzučákem (při failed buildu); veškerá integrace s CruiseControlem je na straně PC, ke kterému je to přes USB připojeno – v CC.NET se využívá spouštění programu při změně stavu, plus triviální utilitka v C#, která pošle data podle příkazové řádky na COM port.
Ale někde bych to zveřejnit moh, to asi jo.
To se mi líbíTo se mi líbí