Programiranje

Metode kontinualne integracije – Jenkins

jenktocat

Šta je kontinualna integracija?

Kontinualna integracija je praksa u softverskom inžinjeringu u kojoj se teži ka tome da se male izmene u kodu integrišu u repozitorijum često u cilju ranog otkrivanja grešaka i bržeg razvoja.

Tradicionalni pristup

Kod tradicionalnog pristupa razvoju softvera, velike celine se razvijaju odvojeno. Iako unit testovi, odnosno testovi tih celina pokazuju da kod dobro funkcioniše, prilikom integracije može doći do grešaka. Ispravljanje tih grešaka može trajati veoma dugo i dovesti do probijanja rokova i dodatnih troškova.

Ukoliko svako radi na svom delu koda duže vreme, povećava se verovatnoća da će doći do konflikta. Ako ima puno koda, potrošiće se i mnogo vremena na sređivanje konflikta.

Prednosti kontinualne integracije

Zbog navedenih mana tradicionalnog pristupa, bitno je da izmene koje se dodaju budu male. Ovako se štedi vreme i brže se može nastaviti sa radom.

Ako pretpostavimo da postoje testovi za prethodno napisan kod, kada se naprave izmene u repozitorijumu, potrebno je da se izvrši build projekta i obavi testiranje, kako bi se osiguralo da nove izmene ne kvare postojeće funkcionalnosti. Ovo nekada može da traje dosta dugo i može se desiti da se testovi ne izvrše uvek na isti način, ukoliko se izvršavaju manuelno (na primer, jedna osoba ne uradi nešto na isti način dva puta). Zbog toga je lakše da se testovi automatizuju, jer će se uvek izvršiti brže i na potpuno isti način. Za kontinualnu integraciju neophodno je da build koda i testovi budu automatski.

Prednosti kontinualne integracije su to što integracija traje kraće, greške se uočavaju ranije, debug troši manje vremena, pa ostaje više vremena za razvoj. Dalje, nema čekanja da se sazna da li kod radi, smanjuju se problemi integracije, pa se softver može razvijati brže.

Metode CI

  • Koristi se jedan repozitorijum.
  • Build se izvršava automatski.
  • Testovi se izvršavaju automatski.
  • Za svaki commit, build se vrši na mašini za integraciju.
  • Build treba da bude brz.
  • Testiranje se obavlja u kloniranom okruženju produkcije.
  • Poslednja izvršna verzija svima treba da bude lako dostupna.
  • Svi u timu imaju pregled svih dešavanja.
  • Automatski deploy.

kontinualna-integracija Faze kontinualne integracije

Kako se radi kontinualna integracija?

Programeri vrše izmene u lokalnom okruženju. Kada završe, commit-uju kod u repozitorijum. CI server prati repozitorijum i povlači nove izmene kada se dogode. CI server pokreće build i izvršava unit testove i testove integracije. CI server pravi instancu projekta spremnu za testiranje. CI server pridružuje oznaku build-u. CI server obaveštava tim o uspešnosti build-a. Ukoliko je build neuspešan, tim odmah može da počne sa rešavanjem problema. Ciklus kontinualne integracije se nastavlja, a projekat se testira.

Potrebno je da svi članovi tima prave izmene često, da ne commit-uju kod koji ne radi, da ne commit-uju kod koji nije testiran, da ne commit-uju nove izmene ukoliko build nije uspešan, i da ne napuštaju radno mesto nakon commit-a pre nego što saznaju da je build uspešan.

Dakle, kontinualna integracija podrazumeva dodavanje malih celina u repozitorijum čim se završe. Zatim se pokreće build, izvršavaju se testovi i ukoliko postoji neka greška to se saznaje veoma rano i može odmah da se ispravi.

Alati za kontinualnu integraciju

Postoje razni alati za kontinualnu integraciju kao što su Bamboo, Jenkins, Travis CI, Circle CI, Codeship, Semaphore, CruiseControl, TeamCity, GitLab CI, Drone, itd. U nastavku će se govoriti o Jenkins-u koji je jedan od najčešće korišćenih, besplatan je, ima dobru podršku i veliki broj ekstenzija.

O Jenkins-u

jenkins-logo Jenkins je open source server za automatizaciju napisan u Javi. Koristi se u softverskom inžinjeringu da pomogne timovima prilikom automatizacije procesa razvoja softvera. Uklanjanje ljudskog faktora izbacuje mogućnost nastanka greške.

Primer kontinualne integracije

Na Java projektu biće objašnjeno kako konfigurisati Jenkins da vrši automatski build i testiranje nakon što se repozitorijum izmeni. Reč je o jednostavnom kalkulatoru.

Projekat se nalazi na ovom linku (GitHub), a ispod ce biti dati i konkretni delovi koda.

Na početku imamo klasu Calculator.java sa metodom za sabiranje 2 broja i klasu sa unit testom CalculatorTest.java za njeno testiranje. U pom.xml fajlu se nalaze podaci neophodni za Maven. Za izvršavanje unit testova koristićemo JUnit.

Calculator.java

package calculator;

public class Calculator {

    public static int add(int a, int b) {
        return a + b;
    }

}

CalculatorTest.java

package calculator;

import org.junit.Assert;
import org.junit.Test;

public class CalculatorTest {

    @Test
    public void add3and7() {
        int result = Calculator.add(3, 7);
        Assert.assertEquals(result, 10);
    }

}

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>ci-demo</groupId>
    <artifactId>simple-calculator</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>Simple calculator</name>

    <dependencies>
        <!-- https://mvnrepository.com/artifact/junit/junit -->
        <dependency>
            <groupId>junit
            <artifactId>junit
            <version>4.12
            <scope>test
        </dependency>
    </dependencies>

    <properties>
        <maven.compiler.source>1.8
        <maven.compiler.target>1.8
    </properties>
</project>

Pre svega, biće prikazano kako se instalira i konfiguriše Jenkins na operativnom sistemu Ubuntu 18.04.

Instalacija Jave

Update liste paketa:

sudo apt-get update

Instalacija Jave 8:

sudo apt install openjdk-8-jdk

Proverimo da li smo uspešno instalirali Javu:

java -version

Izlaz bi trebalo da bude sličan ovom:

openjdk version "1.8.0_181"
OpenJDK Runtime Environment (build 1.8.0_181-8u181-b13-0ubuntu0.18.04.1-b13)
OpenJDK 64-Bit Server VM (build 25.181-b13, mixed mode)

U fajl

/etc/environment

dodajemo:

JAVA_HOME="/usr/lib/jvm/java-8-openjdk-amd64"

NAPOMENA: Putanja može biti drugačija. Provera JAVA_HOME promenljive:

source /etc/environment
echo $JAVA_HOME

Izlaz treba da odgovara setovanoj vrednosti.

Instalacija Git-a

Potrebno je pokrenuti komandu:

sudo apt-get install git

Instalacija Jenkins-a

wget -q -O - https://pkg.jenkins.io/debian/jenkins-ci.org.key | sudo apt-key add -
sudo sh -c 'echo deb http://pkg.jenkins.io/debian-stable binary/ > /etc/apt/sources.list.d/jenkins.list'
sudo apt-get update
sudo apt-get install jenkins

Korisnik Jenkins će biti dodat automatski i Jenkins će biti pokrenut na lokalnoj adresi na portu 8080.

Konfiguracija Jenkins-a

U browser-u treba otići na adresu:

http://localhost:8080

Pri prvom pokretanju pojaviće se pop-up sa uputstvom za autorizaciju. Komandom:

cat /var/lib/jenkins/secrets/initialAdminPassword

dobijamo lozinku koju koristimo da otključamo Jenkins. Nakon toga treba da odaberemo koje dodatke (plugins) hoćemo da instaliramo. Osim prethodno selektovanih odabraćemo GitHub integration i maven plugin. Nakon instalacije plugin-ova potrebno je napraviti administratorski nalog. Nakon kreiranja naloga, nalazimo se na početnoj strani.

Konfiguracija Git-a i Maven-a

Sa početne strane idemo na stranu Manage Jenkins > Global Tools Configuration. Za Git name unosimo

Git

a za Path to Git executable

git

Za Maven name unosimo

Maven

i čekiramo Install automatically. Treba da kliknemo da dugme apply da bi se primenile promene.

Konfiguracija e-mail-a

Sada treba da odemo na stranu Manage Jenkins > Configure system. Pronađimo sekciju E-mail Notification. Potrebno je da unesemo odgovarajuće podatke. Možemo testirati konfiguraciju e-mail-a čekiranjem opcije Test configuration by sending test e-mail a zatim klikom na dugme Test configuration. Treba da kliknemo da dugme save da sačuvamo izmene.

Vratimo se na početnu stranu. Treba da izaberemo new item.

Unosimo naziv job-a i biramo Maven za tip projekta.

Čekiramo GitHub project i unosimo URL projekta:

https://github.com/ndukic/simple-calculator

Sada za source code management treba da odaberemo Git i unesemo URL za kloniranje git projekta, u našem slučaju:

https://github.com/ndukic/simple-calculator.git

Za Build Triggers ćemo odabrati Poll SCM a za schedule ćemo uneti:

* * * * *

Ovo znači da će Jenkins na svaki minut proveravati da li je bilo promena u repozitorijumu i ako jeste, izvršiće build. Postoji i elegantniji način za ovo. Možemo odabrati Git hook trigger. Potrebno je samo da na GitHub-u napravimo novi Webhook gde ćemo za Payload URL da stavimo:

$JENKINS_BASE_URL/github-webhook/

U Build sekciji za root POM stavljamo:

pom.xml

a za Goals and options

clean install

Čekiramo E-mail Notification i unosimo adresu primaoca. Na ovu adresu će se slati izveštaji o statusu build-ova. Ukoliko hoćemo da navedemo više primalaca, adrese razdvajamo zarezom.

Na kraju treba da kliknemo na Save dugme da sačuvamo izmene.

Izvršavanje build-a

Sada kada je jobsetovan, svaki put kada dodje do izmene na repozitorijumu, Jenkins će to primetiti i pokrenuti build. Build možemo pokrenuti i ručno klikom na Build Now. Nakon konfiguracije, Jenkins će primetiti da je došlo do izmena, tj. videće da se lokalni repozitorijum, koji je prazan, razlikuje od onog na GitHub-u i odraditi build. Na slici ispod vidimo da je rezultat uspešan.

Sada ćemo proširiti klasu Calculator metodom factorial() u kome je namerno napravljena greška.

public static int factorial(int a) {
    if (a == 1) {
        return 0; // intentional mistake
    } else {
        return a * factorial(a - 1);
    }
}

Napravili smo i unit test koji očekuje tačan rezultat. U klasu CalculatorTest dodajemo:

@Test
public void factorialOf5() {
    int result = Calculator.factorial(5);
    Assert.assertEquals(result, 120);
}

Uradili smo commit novih izmena u deljeni repozitorijum. Build se okinuo i u izveštaju imamo da je 1 od 2 testa neuspešan.

Nakon debug-a, pronašli smo grešku i ispravili kod u klasi Calculator. U metodi factorial() liniju:

return 0; // intentional mistake

menjamo u:

return 1;

Nakon commit-a, ponovo se pokreće build. Ovog puta, rezultat je uspešan.

Ako proverimo e-mail, videćemo da imamo obaveštenja o ovim rezultatima.

Zaključak

Na primeru vidimo značaj kontinualne integracije, nakon male izmene, greška je odmah pronađena i brzo se može otkloniti. Da nije bilo automatskog build-a i testiranja, ova greška bi mogla da prođe neopaženo. Moglo se sakupiti više grešaka i kada bismo prilikom integracije koda primetili da nešto ne radi kako treba, bilo bi potrebno više vremena da se greška pronađe.

Značaj ove prakse je u tome što se za svaku izmenu odmah može saznati da li radi kako je očekivano.

Šta dalje?

Continuous Delivery i Continuous Deployment

Osim mehanizama za automatski build i testiranje, Jenkins se može podesiti tako da automatizuje još poslova. Na primer, ako je build uspešan (svi testovi su uspešni), kod se može dopremiti na produkciju. Ova praksa se naziva Continuous Deployment i često je usko povezana sa CI-om. Nju ne treba mešati sa pojmom Countinuous Delivery. Razlika je u tome što se ovom drugom praksom podrazumeva samo da je kod spreman za puštanje na produkciju u bilo kom trenutku.

Jenkins Pipeline

Set procesa koji se odvijaju u Jenkins-u može se podesiti pomoću Jenkins Pipeline-a. Pipeline predstavlja set plugin-ova koji omogućavaju integraciju Countinous Delivery pipeline-a.

Korisni linkovi

Continuous Integration Jenkins Pipeline
Wikipedia