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:

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:

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)

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:
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:
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
Pingback:
25 października 2022 at 07:46