Effektiv udvikling med virtualiseret continuous integration

Denne artikel beskriver, hvordan du kan arbejde mere effektivt ved at tænke virtualisering ind som en del af udviklingsmiljøet på din udviklermaskine. En af mulighederne er blandt andet at arbejde med pre-test-commit på udviklernes egne maskiner ved at installere virtualiserede continuous integration byggeservere på maskinerne.

Lokal virtualisering

Artiklen tager dig igennem konstruktionen af et setup, der gør det muligt for dig eller et andet team-member at komme igang med at kode efter at have kørt én kommando på en Windows, Linux eller Mac maskine. Efter at have kørt kommandoen er to virtuelle maskiner klar. En maskine med et IDE og alle nødvendige værktøjer og projektkode, og en maskine med et tilhørende CI setup. Du kan nu logge ind på din nye virtuelle udviklingsmaskine, starte Eclipse, og begynde at kode. Efter at have committet kode til projektet (der er testet via din virtuelle CI), kan du vælge at smide de virtuelle maskiner væk og bygge nye næste gang du har brug for det. Du kan tænke på det som at bruge en Software as a Service (SaaS) applikation som Gmail, Twitter, Facebook, Linkedin eller lignende, bortset fra at denne kører på din laptop.

Der er flere attraktive funktionaliter i det beskrevne setup, bl.a. virtualiseret Continuous Integration som beskrives mere detaljeret igennem artiklen. Det beskrevne setup er forholdsvis komplekst, men du behøver ikke bruge det hele for at få glæde af ideerne og værktøjerne.

Hvis du af den ene eller den anden grund ikke kan drage fuld nytte af de muligheder udvikling i Skyen (f.eks. PaaS) giver, kan lokal virtualisering hjælpe dig på vej.

Virtualisering og reproducerbarhed er en væsentlig del af grundlaget for Cloud-teknologiers voldsomme vækst i de senere år. De teknologier, der gør det muligt, er tilgængelige på almindelige laptops i dag. Hvis du gør brug af dem, kan du opnå nogle af de fordele Cloud-baserede udviklings-setup’s har, i udviklingsmiljøet på din udviklermaskine.

De mål der forfølges her, kan opnås ad flere veje. Artiklen er skrevet som inspiration til alternative veje som du kan bruge, hvis det giver mening i din situation.

Som eksempel vil jeg bruge en Java implementation af huskelisten over alle huskelister “Tjello”, hvor data gemmes i en MongoDB. Teamet bruger Eclipse som IDE og gradle som byggeværktøj. Projektet bruger Jenkins som CI og git som repositorie. I dette tilfælde skal udviklere have installeret Eclipse, git, Java og MongoDB i de rigtige versioner og for Eclipse’ vedkommende også den rigtige konfiguration.

Scenarier

Bendsen og Wermuth har startet “Tjello” projektet. Andre udviklere kommer ind over i kortere eller længere perioder, da Bendsen er på skiferie og Wermuth er optaget af andre opgaver.

Scenarierne nedenfor er velkendte:

  • udviklere, der ikke før har arbejdet på projektet, skal arbejde på projektet i en periode. Hver gang skal der bruges tid på at få en udviklingsplatform sat op.
  • der skiftes version af databasen, hvorefter alle udviklere skal opdatere deres lokale version.
  • udviklerne arbejder også på andre projekter, som desværre har modstridende afhængigheder (f.eks. Java- eller database-version) og derfor skal udviklernes udviklingsmiljø sættes op til at håndtere afhængigheder på tværs af projekter.
  • et eller andet holder op med at virke på din udviklingsmaskine, og du bruger timer på at få det til at virke igen.
  • CI blev sprunget over, fordi:
    • der var ikke en CI maskine tilgængelig.
    • corporate policy gjorde det for langsommeligt at lave CI på dette project.
    • den eksisterende CI infrastruktur er for langsom.
    • den eksisterende CI infrastruktur er ikke tidsvarende.
  • CI blev implementeret, men:
    • en udvikler kommer til at checke fejlbehæftiget kode ind, og blokerer derved andre.
    • den eksisterende CI infrastruktur er for langsom.
    • den eksisterende CI infrastruktur er ikke tidsvarende.

Fordele ved lokal virtualisering

Alle de ovenstående scenarier kan imødekommes med lokal virtualisering, hvor alt (OS, Desktop, IDE, værktøjer, kode, konfigurationer, database installationer, Java version) er under versionsstyring og checkes ud og bygges sammen med koden.

Et system som dette har flere fordele, f.eks.:

  • Du er ikke afhængig af at eksterne systemer skal være tilgængelige, som f.eks. en database.
  • Du har din egen infrastruktur som du kan eksperimentere med uden at påvirke andre.
  • Du kan til hver en tid checke et nyt, friskt system ud – all inclusive.
  • Din infrastruktur kører på din egen maskine – du skal altså ikke betale for (eller ansøge om) maskinkraft.
  • Du er ikke afhængig af større infrastrukturelle investeringer i f.eks. en privat PaaS.
  • Du kan versionsstyre din infrastruktur.

Dette eksempel er beskrevet som en samlet enhed, men ideerne fungerer selvstændigt. Det er f.eks. ikke nødvendigt at virtualisere hele sin Desktop for at virtualisere sin udviklingsdatabase eller sit CI setup. Læs denne artikel som et input til, hvordan du kan gøre ting anderledes, og brug det der passer i din virkelighed.

Målet med det setup, der beskrives her, er at:

skabe reproducerbart, versionsstyret udviklingsmiljø. Miljøet har indbygget CI der er centralt konfigureret og tilbyder skalerbart pre-test commit.

Beskrivelserne i resten af denne artikel er alle taget fra et fuldt fungerende system, men er her holdt på et overordnet niveau.

Teknologier

I det følgende vil der blive anvendt en række teknologier og termer, som jeg kort vil beskrive. Alle teknologier har alternativer, som du kan anvende, hvis de passer bedre for dig. Det centrale i artiklen er ideerne, ikke de specifikke værktøjer.

  • VirtualBox (Virtualiserings provider). Bruges til at starte virtuelle maskiner på din laptop. Valgt fordi det er open source og tilgængeligt på på Windows, OSX og Linux. Alternativer: VMWare, AWS, Docker, Hyper-V
  • Vagrant (Virtualiseringskonfiguration). Bruges til at konfigurere de virtuelle maskiner, f.eks sætte netværk op og installere software. Bl.a. valgt fordi det er Open Source, modent og integrerer med mange virtualiserings providers og virtualiserings provisioners.
  • Puppet og shell (Virtualiserings provisioner). Bruges til at installere software på de virtuelle maskiner. Virker i samspil med vagrant. Valgt ved at slå med en terning. Der findes mange andre fine alternativer, såsom Shell, Chef, Salt, CFEngine og Ansible
  • Git (Revisionskontrol). Bruges til at gemme (alle versioner af) alle konfigurationer, så alt er reproducerbart og kan deles med mange. Valgt fordi det er mit favorit SCM. Alternativer: mercurial, subversion.
  • Jenkins (Continuous Integration). Særligt for dette eksempel bruges Jenkins til at lave pre-test commits, dvs. sikre at fejlbehæftet kode ikke rammer andre udviklere før det er blevet testet. Bruges derudover til “normal” CI. Alternativer : Bamboo, TeamCity.
  • Ubuntu (Operativsystem). Bruges til at køre CI og Desktop. Valgt fordi det er open source og derfor kan anvendes af alle uden licensproblemer. Alternativer: Windows, Docker (kan anvendes til en del af de ting der laves her, men ikke alle. Den fulde forklaring er udenfor scope).

Setup

Den mest almindelige anvendelse af et setup som dette er situationen, hvor en ny udvikler for første gang skal arbejde med “Tjello”. Set fra helikopteren vil den nye udvikler checke “Tjello” projektet ud fra git, og derefter bygge udviklingsmiljø med de 3 kommandoer nedenfor og starte med at kode.

git clone [email protected]:tjello.git

cd tjello

vagrant up

“vagrant up” fortæller vagrant, at den skal gøre som beskrevet i vagrants konfigurationsfil, Vagrantfile. Vagrantfile kan være skrevet af dig eller den ansvarlige for projektet. Filen er en specifikation af det miljø, du ville have lavet. Filen er typisk på 50-200 linier, men kan være kortere eller længere afhængig af kompleksiteten i dit setup (i dette tilfælde er Vagrantfile på 101 linier).

I eksemplet beder vi vagrant om at starte 2 virtuelle maskiner:

  1. et udsnit af den “Vagrantfile” der definerer de 2 virtuelle maskiner
  2. output fra kommandoen “vagrant up”. Man kan skimte lidt netværksopsætning og navnene på de to maskiner der startes,”ci” og “dev”
  3. den virtuelle maskine vi lige har lavet, en Ubuntu desktop til udvikling (dev) der kører under VirtualBox. Der er automatisk lavet en bruger med korrekte rettigheder til dig.
  4. på udviklingsmaskinen er der åbnet en browser, der viser Jenkins (ci). Den viste Jenkins er blevet bygget parallelt med udviklingsmaskinen og er specialiseret til at lave pre-test commit til “Tjello”

Når 1-2-3-4 bliver sat sammen, ser det jo nemt ud, og det er heller ikke så slemt, men hvad sker der egentlig?

Til at starte med skal vi have en maskine med vagrant og virtualbox installeret. Der skal laves en “Vagrantfile” med instruktioner til vagrant (det var den på de 101 linier).

I dette tilfælde bygges de to virtuelle maskiner ovenpå to images, som jeg har lavet og uploadet til et offentligt repositorie for images. Det image, vi bygger udviklingsmaskinen ovenpå, hedder “wermuth/desktop”. “wermuth/desktop” er i praksis en frisk Ubuntu installation, som det projekt-specifikke bygges ovenpå.

Herefter lægges relevant software på maskinen, i dette tilfælde et udviklingsmiljø for Java, gradle og git. Det software, der skal installeres, beskrives i “Vagrantfile”. Installationen af software kaldes “Provisioning”, og udføres af “Provisioners”. I dette tilfælde bruges Puppet og shell.

På samme måde startes et image til CI maskinen. Der installeres samme udviklingsmiljø på CI maskinen som på udviklingsmaskinen. Derudover installeres Jenkins som “CI motor”. CI maskinen skal bruge udviklingsmiljøet til at bygge og køre den software, der er lavet på udviklingsmaskinen, på samme måde og fra samme platform, som det skal bygges og køres i andre miljøer – en væsentlig del af pointen her.

De to images, samt alle konfigurations- og kodefiler, er under versionskontrol.

Versionskontrollen har en række kendte fordele: man kan gå frem og tilbage i versioner, man kan dele kode og konfiguration osv. Derudover er der, i dette tilfælde, implementeret en ikke så almindelig (men værdifuld) funktionalitet i konfigurationen af Jenkins.

Når Jenkins boxen konstrueres, hentes Jenkins basis-software, og derefter hentes vores (dvs. din) konfiguration og puttes ned i maven på Jenkins. Den konfiguration jeg har puttet ned i maven på Jenkins, sørger for at holde Jenkins egen konfiguration synkroniseret med den version, der er i versionskontrollen. Det betyder at dem, der har kontrol over “Jenkins configuration” repositoriet centralt, kan styre konfigurationen af alle maskiner.

Lad os antage et team på 10 mand bruger dette setup. Hver mand vil kunne bruge sin lokale maskines regnekraft til at afvikle tests, og administratoren vil kunne bestemme hvilke tests, der skal afvikles. Dette er i praksis et centralt styret, distribueret, virtualiseret CI setup, baseret på eksisterende hardware (udviklernes laptops) og gratis software. Det kan køre på Windows, Mac og Linux.

Man kan overveje at implementere “pre-test commit” som en del af sin CI. Pre-test commit betyder, at udviklere kun deler kode, der har bestået kvalitetskontrollen. Ny kode vil blive kvalitetstestet før andre kan hente det, og en udvikler vil derfor ikke risikere at hente kode, der indeholder fejl (der er fanget af de nuværende tests). I det her beskrevne system er “pre-test commit” implementeret således, at koden testes på udviklerens lokale maskine, før andre udviklere kan hente den til deres maskiner.


Pre-test-commit implementationen hviler på to konventioner:

A) Alle udviklere henter den nyeste kode fra “integration” branchen.

B) Alle udviklere committer til en unik, personlig branch.

Med reference til tallene 1,2 og 3 på billedet, samt detaljer, der ikke er vist på billedet men angivet med bogstaver, vil arbejdsflowet se ud som følger:

1) Udvikler “jwermuth” henter seneste version af koden fra “integration” branchen

1.a) jwermuth koder løs og commiter til sin personlige “jwermuth” branch

2) den virtualiserede Jenkins, der kører på jwermuth’s fysiske maskine, vil teste koden.

2.a) hvis koden ikke består, bliver den ikke pushet til integrations-branchen.

3) Hvis koden er OK, vil Jenkins pushe den til “integration” branchen.

3.a) Udvikler “bendsen” kan nu hente den seneste sikre version af koden.

Opsætningen af den virtuelle CI maskine, vi lige har lavet, sørger for teknikken i baggrunden:

Den centrale konfiguration, som vi har puttet ned i maven på Jenkins, definerer et job til formålet: “pre-test commit”. Hvis man ser lidt nærmere på opstartssituationen, kan man se jobbet i Jenkins jobliste

Enhver, der har skrive-adgang til “jenkins configuration” repositoriet, vil kunne ændre disse jobs, som derefter vil blive distribueret til alle instanser. Forestil dig en situation, hvor du bliver bedt om at lave en ændring i konfigurationen af Jenkins. Det er et år siden du har gjort det – eller du har aldrig gjort det – men uanset hvad, vil du kunne bygge din egen virtuelle CI maskine, der er fuldkommen magen til alle andres, og ændre og teste og dele den nye configuration ved at gøre følgende:

1) skriv kommando “vagrant up”

2) åbne en browser på din lokale maskine (http://localhost:8080) til CI og ændre løs

3) teste dine ændringer

4) logge ind på CI maskinen med “vagrant ssh”

5) committe og pushe dine ændringer til versionstyring

Herefter vil dine ændringer blive synkroniseret med alle kørende virtuelle instanser af CI maskiner.

Konklusion og “findings”

  • Man kan i praksis basere et udviklingsteam på en platform som dette. Det vil ikke kræve nogen fælles infrastruktur ud over de anvendte repositorier.
  • Man kan i praksis skalere CI med lokal virtualisering. Den valgte CI løsning kan langt fra håndtere alle projekter, men den virker til små projekter.
  • Ja, man kunne også have anvendt Docker eller andre virtualiseringsværktøjer. Man kunne f.eks. køre den virtualiserede Jenkins maskine under Docker i stedet for med Virtualbox. Docker er herligt, men det er ikke et af de værktøjer, jeg har valgt at bruge her.
  • Det kan være belastende at udvikle i et IDE, der ligger inde i en virtuel maskine. Selvom maskinen kører hurtigt nok, har jeg f.eks. oplevet uenighed mellem host (min laptop) og guest (Ubuntu Desktop) om hvem, der skal reagere på museklik og tastetryk. Man kan også ærgre sig over, at en del af laptoppens resourcer går til virtualiseringen. Det skal hertil siges, at man ville kunne lave et setup hvor selve udviklingsmiljøet (Java, Gradle, …) kører virtualiseret, mens dit IDE kører på din fysiske laptop. Dette fjerner de nævnte performance- og museklik-problemerne.
  • Du vil gerne versionsstyre din konfiguration, helt ned til den mindste detalje. Det er superfedt, at kunne checke et helt miljø ud.
  • Der er fordele og ulemper ved virtuelle setups. Der er her beskrevet et fuldt setup, men du kan bruge de dele af det, der giver mening i din situation og smide resten væk.
  • Der findes i dag et væld af modne virtualiseringsteknologier, som bruges af store succesfulde software virksomheder. Nogle stykker af dem anvendes her, og de virker, og man kan lære at bruge dem på forholdsvis kort tid.

Jesper Wermuth

CEO | Partner

Solid erfaring som programmør, arkitekt, underviser og rådgiver. Specialiseret i automatisering af infrastruktur, virtualisering og en lang række cloud funderede teknologier. Jesper er ekspert i Docker og cloud-teknologi og har over 25 års erfaring som softwareudvikler. Han har arbejdet med stort set alle områder af IT virksomheder, fra ledelse til kabling. Han har særligt fokuseret på kvalitet i leverancer gennem automatisering, reproducerbarhed, virtualisering og skalering de seneste år.