Kontinuerlig integration och Kontinuerlig Deployment med Jenkins Pipeline i Grails projects

Inledning

Programutveckling är spännande, men det är också ett yrke där du ständigt behöver utveckla och bredda din kompetens och ofta tänka utanför boxen. Förut hade mjukvaruföretagen bekymmer med att utveckla och leverera programvara snabbt och med bibehållen kvalitet. Det ledde så klart till att en stor andel projekt misslyckades. Sakta men säkert började man dock komma på metoder och processer för att upprätthålla flödet av s.k. hållbar utveckling.

För att vara konkurrenskraftig nu och/eller inom en snar framtid är det viktigt att möta efterfrågan på högkvalitativ mjukvara och implementera teknik som gör det möjligt för verksamheten att göra jobbet. Det finns många verktyg som kan hjälpa företaget att utveckla sina processer och hålla snabba kvalitativa lanseringar. Jag kommer i det här inlägget berätta om det kontinuerliga integrationsverktyget Jenkins, vilket är ett open source-verktyg som har många nyttiga och användbara tillägg. Jag kommer att visa hur man skapar en pipeline (Jenkins Declarative Pipeline) som ger en flexibel och kraftfull metod för att bygga, testa och deploya programvaror.

Mål

Skaffa allmän kunskap om kontinuerliga integrationsverktyg i mjukvaruprocessen, kunna automatisera integreringen och deployment av software pieces.

Systemkrav

Jenkins 2.73.3 (Om du använder Linux OS behöver du byta Batch skriptet mot Shell skriptet)

Grails 3. *

1.0. Kontinuerlig integration och Deployment strategi

Kontinuerlig integration är en mjukvaruutvecklingspraxis där medlemmar i ett team ofta samordnar sitt arbete. Vanligtvis integrerar varje person åtminstone på daglig basis, vilket leder till flera integreringar per dag. Varje integrering verifieras automatiskt (inklusive test) för att upptäcka eventuella integrationsfel så snabbt som möjligt (Martin Fowler, 2006).

Utvecklarna bör fokusera på utveckling och inte på implementeringsprocessen. Utvecklarens roll är att leverera kvalitetskod och programvara, när koden väl finns på plats så är utvecklaren inte ansvarig för den mer. Det låter ju bra tänker du, men hur löser man det? Det är inte en bra lösning att göra det manuellt, anledningen är att du då måste skapa en lokal miljö för att bygga och deploya mjukvaran. Om programvaran visar sig vara mer komplex så blir också byggnads- och installationsprocessen mer komplicerad. Det bästa sättet är därför att automatisera hela processen, i det här fallet kan en DevOps-ingenjör i ditt team med all säkerhet lösa processen. När koden väl finns på plats kommer den automatiskt att kompileras och deployas i alla olika miljöer med hjälp av varierande tekniker och verktyg. Utvecklarna kan då koncentrera sig på just utvecklingen.

2.0. Jenkins

Jenkins är ett välkänt verktyg för kontinuerlig integration. Verktyget har öppen källkod och gratis, det har även ett ganska stort utvecklar-Community. Med Jenkins kan du bygga ditt projekt, testa och deploya det i dina olika miljöer.

I verktyget är det möjligt att skapa olika typer av jobb eller objekt, bland dem kan du hitta “Freestyle-projektet” där du kan använda olika moduler för att bygga och deploya din programvara. “Jenkins pipeline” är ett mycket enkelt sätt att dra fördel av alla egenskaper hos Jenkins. Jenkins pipeline är dessutom mer flexibelt och anpassningsbart. I det här inlägget vill jag visa ett exempel på det.

2.1. Jenkins pipeline

Jenkins pipeline är ett Groovy-skript (Groovy är ett dynamiskt och modernt språk som översätts av Java Virtual Machine (JVM)). Pipen kan vara deklarativ eller scriptad, skillnaden är att deklarativ måste följa en viss syntax och den scriptade bör inte göra det. I det här fallet kommer vi att använda Declarative Pipeline. Declarative Pipeline har principiellt en enkel och användbar struktur där du kan dela upp ditt arbete steg för steg.

Nedan ser du en deklarativ Pipeline-struktur:

pipeline {
    agent any 
    stages {
        stage('Stage name') {
            steps{ 
            }
        } 
    }
}

Agent syntax specificerar var och hur pipen körs. Du kan lägga in många faser och var och en innehåller olika steg. I stegsyntaxen beskriver du den åtgärd du vill göra. Den deklarativa pipen är bred och innehåller många användbara syntaxer (se: https://jenkins.io/doc/book/pipeline).

Pipen kan implementeras i ett Jenkins-jobb eller som en Jenkinsfil i källan till ditt projekt, den senare är bara en fil som innehåller din deklarativa pipeline (se: https://jenkins.io/doc/book/pipeline/jenkinsfile/ ). Vidare kommer vi att använda Pipeline inom ett Jenkins-jobb.

2.1.1. Uppdatera från version control repository

I detta skede uppdaterar vi källkoden. Vi använder en Git-modul för att hantera den, den finns installerad som standard. Vi behöver bara ange vilken filial vi ska använda, webbadressen till repository och åtkomst-id. Vi kan konfigurera vår access-id i avsnittet “Credentials”.

stage(‘Pull’) {

    steps{
        git(branch: 'myBranch', 
            credentialsId: 'myId', url: 'https://github.com/myCompany/MyProject')
    }
}

2.1.2. Bygg ditt projekt

I det här skedet bygger vi projektet genom att köra ett Grails-kommando med batch modul. Genom att använd Grails i projektet så behöver vi bara köra ett kommando för att bygga det och ange var vi ska skapa vår .war-fil.

Senare måste vi arkivera vår artefakt för att få den tillgänglig för deployment eller något annat, vi använder wildcards för att ange sökvägen till artefakten.

stage('Build') {
    steps{
        bat(script: 'grails war', returnStatus: true)
        archiveArtifacts '**/build/libs/*.war'
    }
}

2.1.3. Testa ditt projekt

Grails 3. * använder Gradle som verktyg och med andra instrument, som exempelvis Ant och Maven, kan vi implementera tasks genom att anropa dem direkt eller i olika steg. När vår .war artefakt skapas kan vi testa vår mjukvara automatiskt. I det här fallet kan du köra ditt test i byggprocessen. I det här inlägget testar vi efter att ha byggt vårt projekt med en ny fas i Jenkins Pipeline.

stage('Test') { 
    when {
        expression { 
            def status = bat(returnStdout: true, script: 'grails test-app my.package.MyTestClass.myTestMethod | findstr BUILD')
            
            if(status.contains('BUILD') && status.contains('FAILED'))
                return true
               else 
                return false
        }
    }
    steps{
        error("TEST FAILED")
    }

}

I detta steg använde vi ett villkorat steg för att göra hela processen misslyckad om testet misslyckades.

2.1.4. Deploy

I det här fallet har vi Jenkins körande på samma server som vår applikationsserver och det är tillräckligt för att kopiera vår artefakt.

stage('Deploy') {
    steps {
        copyArtifacts(filter: '**/build/libs/*.war', flatten: true, projectName: 'MyProject', target: 'C:/www/MyProject', selector: specific('${BUILD_NUMBER}'))
    }
}

Med “selector: specific (‘$ {BUILD_NUMBER}’)” kommer vi att vara säkra på att kunna deploya vår artefakt (som genererats av det aktuella bygget).

2.2. Hook triggers

I avsnittet “Build Triggers” kan du se över “GitHub hook trigger för GITScm polling” options och i dina Github projektinställningar måste du lägga till en ny webhook med en hemlig nyckel som du behöver konfigurera även i Jenkins. Efter det, när Github upptäcker någon förändring i din filial, skickar den en notification till Jenkins som startar upp ditt jobb.

2.3. Ytterligare konfigurationer och tips

Om du använder Tomcat och får problem med att automatiskt deploya .war-filer så kan du lägga till nya steg i din pipeline:

stage('PreDeploy') {
    when {
        expression { 
            def status = bat(returnStdout: true, script: 'sc query Tomcat8 | findstr RUNNING || echo STOPPED')
           
            if(status.contains('STATE') && status.contains('RUNNING'))
                return true
               else 
                return false
        }
    }
    steps {
        bat(script:'net stop Tomcat8')
    } 
}

stage('Deploy') {
    steps {
        copyArtifacts(filter: '**/build/libs/*.war', flatten: true, projectName: 'MyProject', target: 'C:/www/MyProject', selector: specific('${BUILD_NUMBER}'))
    }
}

stage('PostDeploy') {
    steps {
        bat(script:'net start Tomcat8')
    }
}

Sammanfattning

Den tid du lägger på att förbättra utvecklingsprocessen, till exempel för att genomföra kontinuerlig integration och kontinuerlig deployment, är aldrig bortkastad och det kommer att säkerställa snabbare utgåvor, också ett fokus på utvecklingsprocesser och naturligtvis en nöjd kund. Jenkins pipeline erbjuder oss ett kraftfullt och enkelt sätt att göra det.

 / Carlos

 

Kommentera eller fråga gärna på mail:

info(@)visionmate.se

 

Referenser

Martin Fowler, 2006: Continuous Integration, https://www.martinfowler.com/articles/continuousIntegration.html

Alla artiklar