• Home
  • /
  • DevOps
  • /
  • S2E6 Kurs Jenkins Continuous Deployment dla spring boot & angular.

S2E6 Kurs Jenkins Continuous Deployment dla spring boot & angular.

Z tego Kursu dowiesz się jak:

* Napisać pipeline w Jenkinsie z wykorzystaniem jenkinsfile,
* Używać groovi do pisania pipeline dla aplikacji napisanej w angular i spring,
* Zapisać hasło do bazy danych poza plikiem konfiguracyjnym i wstrzykiwać hasło przy starcie aplikacji,
* Stworzyć automatyczny proces Continuous Deployment z wykorzystaniem jenkins.

Przed przystąpieniem do tego kroku powinieneś posiadać zabezpieczony serwer z zainstalowanym Jenkinsem, opisałem jak zainstalować Jenkinsa z wykorzystaniem dockera  w artykule:

S1E4 Instalacja obrazu Jenkinsa w doker na serwerze AWS

Możesz także posłużyć się docker compose:

S2E3 Konfiguracja nginx, certbota dla docker compose

W powyższym kursie opisałem całą konfigurację docker compose potrzebną do bezbolesnego
uruchomienia Jenkinsa za reverse proxy nginx z certyfikatem ssl od let’s encypt.

1.Tworzenie pliku jenkinsfile

Plik jenkinsfile jest plikiem tekstowym, który zawiera konfigurację pipeline, dzięki temu, że jest zewnętrznym plikiem jest wersjonowany w gicie.
Jeśli korzystasz z jhipstera, generatora kodu o którym pisałem w artykule:

S2E2 Programowanie szkieletu aplikacji skrótowiec

Nie będziesz musiał pisać od początku pliku jenkinsfile.

Przejdź w miejsce w którym masz wygenerowany projekt i w konsoli wykonaj komendę:

jhipster ci-cd

Ja wykonałem tą komendę w konsoli intellij, powinieneś zobaczyć taki ekran:

Generator jenkinsfile w jhipster

Jak widzisz na powyższym zrzucie ekranu, generator umożliwia stworzenie pipeline dla różnych środowisk, dla Jenkinsa wybierz pierwszą opcję potwierdzając enterem.

Następnie będziesz poproszony czy pipeline ma budować aplikację w obrazie dokera, wybierz N

Kolejnym krokiem jest wybór, czy wysyłać status build’a do gitlaba, także wybierz N

W czwartym kroku wybierasz zgodnie z poniższym zrzutem ekranu jakie akcje są do wykonania w pipeline, akcje zaznaczasz spacją, wybrane akcje zatwierdzasz enterem, w zależności od swoich potrzeb możesz np. wybrać Snyk, analizatora zależności w projekcie, który ma za zadanie wykrywanie podatności w zależnościach. Ja nie wybrałem żadnej opcji, czyli wcisnąłem sam enter:

Widok dodatkowych opcji przy generowaniu jenkinsfile.

Gotowe, posiadasz wygenerowany Jenkinsfile z skonfigurowanym pipeline. Szczegółowo omówię je w kolejnym punkcie.

2. Omówienie zawartości pliku jenkinsfile:

Wygenerowany plik wygląda następująco:

#!/usr/bin/env groovy

node {
    stage('checkout') {
        checkout scm
    }

    stage('check java') {
        sh "java -version"
    }

    stage('clean') {
        sh "chmod +x mvnw"
        sh "./mvnw -ntp clean -P-webapp"
    }
    stage('nohttp') {
        sh "./mvnw -ntp checkstyle:check"
    }

    stage('install tools') {
        sh "./mvnw -ntp com.github.eirslett:frontend-maven-plugin:install-node-and-npm@install-node-and-npm"
    }

    stage('npm install') {
        sh "./mvnw -ntp com.github.eirslett:frontend-maven-plugin:npm"
    }
    stage('backend tests') {
        try {
            sh "./mvnw -ntp verify -P-webapp"
        } catch(err) {
            throw err
        } finally {
            junit '**/target/surefire-reports/TEST-*.xml,**/target/failsafe-reports/TEST-*.xml'
        }
    }

    stage('frontend tests') {
        try {
            sh "./mvnw -ntp com.github.eirslett:frontend-maven-plugin:npm -Dfrontend.npm.arguments='run test'"
        } catch(err) {
            throw err
        } finally {
            junit '**/target/test-results/TESTS-results-jest.xml'
        }
    }

    stage('packaging') {
        sh "./mvnw -ntp verify -P-webapp -Pprod -DskipTests"
        archiveArtifacts artifacts: '**/target/*.jar', fingerprint: true
    }
}

Jeśli będziesz miał niejasności, proszę zapoznaj się z podstawową dokumentacją:

https://www.jenkins.io/doc/book/pipeline/jenkinsfile/

UWAGA!!!!

Powyższy sktypt jest napisany w groovy, informuje cię o tym 1 linijka, pamiętaj o tym podczas szukania odpowiedzi na stackoverflow oraz w dokumentacji.

Dokumentacja pokazuje zawsze dokumentację w formie Jenkinsfile (Declarative Pipeline), a Ty potrzebujesz Jenkinsfile (Scripted Pipeline).

Zawsze przełączaj widoki klikając w link Toggle Scripted Pipeline (Advanced)

Widok przełączania dokumentacji

W tym prostym przykładzie nie zobaczysz wiele różnic, jednak przyglądając się bardziej zaawansowanym skryptom np. https://www.jenkins.io/doc/pipeline/tour/post/, różnice są spore:

Będę teraz tłumaczył poszczególne pozycje w skrypcie zaczynając od góry do dołu. Pierwsza pozycja to:

node

Node określa na której maszynie ma być wykonany build, nic nie dodajemy, build ma być wykonany na maszynie na której jest zainstalowany Jenkins.

stage('checkout') {
    checkout scm
}

W bloku stage podajesz jako parametr podajesz nazwę kroku który ma się wyświetlać:

Jako ciało funkcji podajesz instrukcje które ma wykonać Jenkins:

checkout scm

oznacza pobranie kodu z brancha który podajemy w kroku tworzenia pipeline.

stage('check java')

W tym kroku jest wykonywany skrypt powłoki:

sh "java -version"

polecenie sh wykonuje instrukcje w powłoce, w tym kroku sprawdza jaka jest zainstalowana wersja javy.

stage('clean')

Ten krok wykonuje dwie operacje w powłoce:

sh "chmod +x mvnw"

ustawa plikowi mvnw możliwość wykonywania (analogicznie jak w windows robi z niego plik exe )

sh "./mvnw -ntp clean -P-webapp"

Polecenie uruchamia plik mvnw który jest skryptem bashowym, wygenerowanym przez jhipstera i odpala mavena z parametrami na różnych środowiskach np. windows / linux.

Parametr -ntp wyłącza wyświetlanie progresu dla pobierania artefaktów.

Parametr clean usuwa pliki z poprzedniego builda.

Parametr -P-webapp ustawia profil webapp. Profil znajduje się w pliku pom.xml i opisuje kroki jakie maven powinien wykonać podczas budowania części front-endowej aplikacji.

stage('nohttp')
sh "./mvnw -ntp checkstyle:check"

Ten krok także wykonuje skrypt powłoki, parametr -ntp opisałem w pkt powyższym

checkstyle:check

uruchamia plugin https://github.com/spring-io/nohttp/tree/main/nohttp-checkstyle

Jeśli włączymy obsługę https w projekcie jhipster, ten plugin pilnuje czy wszystkie linki są dobrze wygenerowane, (sprawdza czy wszystkie linki posiadają https).

Więcej możesz doczytać w dokumentacji: https://github.com/spring-io/nohttp/tree/main/nohttp

stage('install tools') {
    sh "./mvnw -ntp com.github.eirslett:frontend-maven-plugin:install-node-and-npm@install-node-and-npm"
}

Wykonuje skrypt powłoki, który instaluje przy wykorzystaniu mavena node oraz npm

stage('npm install') {
    sh "./mvnw -ntp com.github.eirslett:frontend-maven-plugin:npm"
}

Ten krok instaluje przy wykorzystaniu pluginu do mavena zależności które są potrzebne dla front-endowej części aplikacji

stage('backend tests')
try {
    sh "./mvnw -ntp verify -P-webapp"
} catch(err) {
    throw err
} finally {
    junit '**/target/surefire-reports/TEST-*.xml,**/target/failsafe-reports/TEST-*.xml'
}

Wykorzystanie do testów bloku try catch z instrukcją finally pozwala na wykonanie instrukcji junit ’/target/surefire-reports/TEST-.xml,/target/failsafe-reports/TEST-.xml’ bez względu na to, czy wystąpi błąd podczas wykonywania wcześniejsze instrukcji:
sh „./mvnw -ntp verify -P-webapp”
Mówiąc prościej blok finally zawsze się wykona, bez względu czy ten krok się powiedzie, czy nie.

stage('frontend tests') {
try {
    sh "./mvnw -ntp com.github.eirslett:frontend-maven-plugin:npm -Dfrontend.npm.arguments='run test'"
} catch(err) {
    throw err
finally {
    junit '**/target/test-results/TESTS-results-jest.xml'
}

Adekwatnie do kroku wcześniejszego ten krok wykonuje testy front endowe i zawsze zapisuje wynik do pliku TESTS-results-jest.xml, bez względu na powodzenie kroku (zastosowano tu instukcję finally)

stage('packaging') {
    sh "./mvnw -ntp verify -P-webapp -Pprod -DskipTests"
    archiveArtifacts artifacts: '**/target/*.jar', fingerprint: true
}

Końcowy krok z wstępnie wygenerowanego pliku, który wykonuje dwie czynności, w pierwszej kolejności buduje aplikację z profilem produkcyjnym omijając testy (testy front endowe i back endowe zostały wykonane w wcześniejszym kroku i nie ma sensu ich teraz powielać)

Druga instrukcja jak sama nazwa mówi zachowuje wybudowane artefakty z podanego folderu w twoim przypadku będzie to folder '*/target/.jar’ oraz pliki z rozszerzeniem .jar

Opcja fingerprint ustawiona na true pozwala na sprawdzanie który build stworzył którego jara, oraz gdzie dalej ten jar jest wykorzystywany, więcej info w dokumentacji:

https://www.jenkins.io/doc/pipeline/steps/core/#fingerprint-record-fingerprints-of-files-to-track-usage

W tej części kursu omówiłem w całości wstępnie wygenerowany plik jenkinsfile, w dalszej części ten kurs skupi się na dodawaniu kolejnych kroków, które są potrzebne w procesie Continuous Deployment.

2.Continuous Deployment z wykorzystaniem Jenkinsa

Continuous Deployment byłby całkowicie bez sensu, gdyby zmiany, które wprowadzasz do repozytorium po automatycznym przetestowaniu nie były wgrywane automatycznie na środowisko.

Jeśli wszystko poszło zgodnie z planem, testy przeszły, zbudowała się paczka z nowszą wersją aplikacji to w tym kroku należy ją automatycznie uruchomić.

Uruchomienie nowej wersji aplikacji polega na dwóch krokach. W pierwszym musisz zatrzymać starą wersję aplikacji. Jeśli się zatrzyma, to w następnym kroku uruchamiasz nową wersję i sprawdzasz czy działa.

Do Jenkins file dodaj następujący krok odpowiedzialny za zatrzymanie aplikacji:

stage('stopping app') {
        sh "sleep 1"
        def code = sh '''curl -X POST localhost:8084/management/shutdown'''
}

W tym kroku przy wykorzystaniu curl wywołujesz metodą POST endpoint management/shutdown.

Endpoint przygotowujesz z wykorzystaniem actuator, który w łatwy sposób pozwala na zatrzymanie aplikacji. Zobacz proszę jak to jest zrobione w tym commit:

https://gitlab.com/jaktworzycaplikacje.pl/skrotowiec/-/commit/1b63d2537b59e8583f8d954b7d201b00f896f07a

Ważne, żebyś nie zapomniał zabezpieczyć operacji wyłączenia, bo każdy będzie mógł w dowolnym momencie wyłączyć twoją aplikację. Linia:

.antMatchers("/management/shutdown").access("hasIpAddress('127.0.0.1') or hasIpAddress('::1') or isAuthenticated()")

W pliku src/main/java/pl/com/skrotowiec/config/SecurityConfiguration.java pozwala na wyłączenie aplikacji z localhostu (czyli tylko z serwera na którym stoi aplikacji) lub jeśli użytkownik jest zalogowany.

Aplikacja zatrzymana, teraz pora na uruchomienie nowej wersji.

3. Dodanie Pipeline

W tym rozdziale pokażę Ci, w jaki sposób skonfigurować pipeline z użyciem jenkinsfile który przed chwilą stworzyłeś.

Pierwszym krokiem będzie wybór stworzenia nowego itemu:

Na kolejnym ekranie wpisz nazwę builda i wybierz opcję pipeline, zatwierdź wciskając ok:

Przed Tobą znajduje się ekran konfiguracji builda. Na początku przejdź do sekcji Build triggers i wybierz Poll SCM. W oknie Schedule wpisz * * * * *

Ustawiłeś w ten sposób opcję sprawdzania co minutę przez Jenkinsa zmian kodu w repozytorium. Jeśli kod się zmienił, build się rozpocznie.

Przyszedł czas na konfigurację Jenkins tak, aby pobierał projekt spring boot z repozytorium git.

W sekcji Pipeline wybierz Definition : Pipeline from SCM, następnie SCM : Git w repository URL podaj : https://gitlab.com/jaktworzycaplikacje.pl/skrotowiec.git

Po przewinięciu ekranu w dół podaj dodatkowo Branches to build : */master, Script Path : Jenkinsfile, oraz odznacz Lightweight checkout. Zapisz konfigurację.

Test pierwszego builda:

Teraz wybierz build now z lewego menu, aby manualnie uruchomić pierwsze budowanie:

Build powinien nie przejść ostatniego kroku, ponieważ aplikacja nie działa i nie ma co wyłączać.

4. Stworzenie pipeline do startu aplikacji

Teraz zajmiemy się stworzeniem builda, który będzie startował aplikację wyłącznie w momencie poprawnego zbudowania aplikacji z poprzedniego joba (Build Testowy).

Z lewego menu Jenkins wybierz New Item następnie podaj nazwę projektu, wybierz tak jak poprzednio Pipeline.

Następnie przejdź do sekcji Build Triggers i wybierz Build after other projects are built. W pojawiającym się input boxie wpisz nazwę builda: Build Testowy, na końcu upewnij się, czy wybrałeś opcję Trigger only if build is stable.

Jeśli będziesz miał komunikat, że Jenkins nie może odszukać projektu, usuń spację po końcowym przecinku.

W sekcji Pipeline w polu Script wklej następujący skrypt oraz odznacz Use Groovy Sandbox, dla tego skryptu będziesz używać prostszej notacji.

Treść skryptu:

pipeline {
    agent any
    stages {
        stage('Start Skr App') {
            environment {
                SERVICE_CREDS = credentials('skr-db-password')
            }
            steps {
                sh '''
                cd ..
                cd skr/target
                JENKINS_NODE_COOKIE=dontKillMe nohup java -jar skr-0.0.1-SNAPSHOT.jar --spring.datasource.password=$SERVICE_CREDS > /dev/null &
                '''
            }
        }
    }
}

Wyjaśnię dwie najważniejsze linie powyższego skryptu:

W linii 6 do zmiennej SERVICE_CREDS jest przypisane hasło do bazy danych, które jest pobierane przez plugin do Jenkins. Jak przechowywać hasła w Jenkins pokażę Ci w kolejnym rozdziale.

W linii 12 znajduje się komenda służąca do odpalania aplikacji:

Pierwsza część JENKINS_NODE_COOKIE=dontKillMe jest bardzo ważna. Jenkins po skończonym jobie, kończy linuxowy proces, tym samym wyłączając aplikacje. Aby uniknąć zakończenia procesu z uruchomioną aplikacją ustaw dontKillMe dla JENKINS_NODE_COOKIE.

Następnie wykonywane są polecenia bashowe, nie związane bezpośrednio z Jenkins.

nohup : nie wyłącza procesu podczas wylogowania, wyjścia z terminala itd.

Java – jar nazwa_applikacji.jar –spring.datasource.password=$SERVICE_CREDS : uruchamia aplikację z podaniem hasła do bazy danych przypisanym do zmiennej w linii 6

> /dev/null & : wszystkie komunikaty dla nohup będą przekazywane do /dev/null (czyli taki myk na nie przechowywanie komunikatów których nie potrzebujesz). Znak & oznacza uruchom proces w tle w osobnym terminalu. Ta komenda musi iść w tandemie z nohup, inaczej po wylogowaniu proces się zakończy.

5. Etap końcowy dodawanie hasła do bazy danych w Jenkins

Z lewego menu wybierz Manage Jenkins, następnie Manage Credentials:

Na kolejnym ekranie wybierz Stores scoped to Jenkins:

Następnie Global Credentials oraz z prawego menu add credentials. Pojawi się widok:

Wybierz Kind : Secret Text, Scope : Global, Secret: tutaj wpisz swoje hasło do bazy danych, ID : identyfikator który jest używany w skryptach do przekazywania hasła do aplikacji.

Pierwsze manualne uruchomienie:

Aplikacja uruchomiona, teraz gdy dodasz zmiany do brancha master, automatycznie nowa wersja aplikacji zostanie wgrana na środowisko.

One Comment

Dodaj komentarz