Jenkins w praktyce – kombinacje etapów równoległych i generycznych
Jenkins jest potężnym narzędziem pozwalającym na automatyzację procesów, które w przeciwnym razie musiałyby zostać wykonane ręcznie. Dobrze jest więc go używać i wykorzystać jego możliwości tak, by ułatwić sobie pracę. W tym artykule pokażemy, jak na różne sposoby zautomatyzować repetytywne zadania.
Jenkins jest potężnym narzędziem pozwalającym na automatyzację procesów, które w przeciwnym razie musiałyby zostać wykonane ręcznie. Dobrze jest więc go używać i wykorzystać jego możliwości tak, by ułatwić sobie pracę. W tym artykule pokażemy, jak na różne sposoby zautomatyzować repetytywne zadania.
Niektóre, na pozór proste, rozwiązania i funkcje Jenkinsa mogą sprawiać pewne trudności w przypadku jednoczesnego ich użycia. Problematyczne mogą okazać się dynamicznie generowane etapy (stage) i etapy równoległe. Dlatego dziś pokażemy, jak stworzyć kilka rodzajów potoków (pipeline) wykorzystujących różne kombinacje tych funkcjonalności, przedstawimy ich wady i zalety oraz odpowiemy na pytanie, czy w ogóle warto ich używać.
Etapy równoległe
Zacznijmy od tematu łatwiejszego, czyli od zrównoleglenia naszych etapów. Jak sama nazwa wskazuje, pozwala nam to na przeprowadzenie wielu etapów równocześnie:
pipeline {
agent any
stages {
stage("tests") {
parallel{
stage("test1"){
steps{
sh "echo test1"
}
}
stage("test2"){
steps{
sh "echo test2"
}
}
}
}
}
}
W widoku Blue Ocean prezentuje się to następująco:
Zrównoleglone w ten sposób etapy mogą działać na tym samym agencie i w tym samym obszarze roboczym (jak powyżej). Możemy też dla każdego z nich zdefiniować osobnego agenta:
pipeline {
agent none
stages {
stage("tests") {
parallel{
stage("test1"){
agent {
label "test-agent1"
}
steps{
sh "echo test1"
}
}
stage("test2"){
agent {
label "test-agent1"
}
steps{
sh "echo test2"
}
}
}
}
}
}
Mamy również możliwość ustawiania naszych etapów tak, aby niektóre z nich dalej były sekwencyjne lub umieścić wewnątrz jednego ze zrównoleglonych etapów kolejne uruchamiane jeden po drugim:
Przykłady użycia etapów równoległych:
- wykonanie wielu niezwiązanych ze sobą testów
- wykonanie tych samych scenariuszy testowych na wielu systemach równocześnie.
Wady z zalety etapów równoległych:
– zmniejsza nieznacznie przejrzystość potoków
+ znacznie skraca czas przeprowadzania różnych procesów (takich jak np. testy), jeżeli składają się one z niezależnych od siebie etapów.
Macierz
Matrix pozwala nam zdefiniować macierz etapów uruchamianych równolegle. Jest to idealne narzędzie, w przypadku gdy musimy wykonać te same operacje na wielu systemach i z różną konfiguracją.
Definiując macierz, zaczynamy od zdefiniowania osi:
matrix {
agent {label "${DISTRO}"}
axes {
axis {
name 'DISTRO'
values 'eurolinux', 'centos', 'scientific'
}
axis {
name 'BROWSER'
values 'firefox', 'chrome', 'opera'
}
}
stages {
stage("Test") {
steps {
echo "Do Build for ${DISTRO} - ${BROWSER}"
}
}
}
}
Możemy zdefiniować dowolną liczbę osi. W efekcie uzyskamy każdą możliwą kombinację zawartych w nich wartości:
Jak widać, w prosty sposób możemy uzyskać funkcjonalność, która w innym przypadku wymagałaby wielokrotnego przeklejania tekstu. Rozwiązanie to pozwala kontrolować agenta, na którym dany etap ma być uruchomiony, w zależności od wartości wybranych na osi. Umożliwia także wykluczanie wybranych kombinacji wartości przy pomocy excludes
i wiele więcej. Możliwości jest jednak wiele. W celu zapoznania się z nimi zachęcamy do skorzystania z dokumentacji ;-)
Wady i zalety macierzy:
– z powodu braku łatwego sposobu na dynamiczną zmianę nazw etapów macierz staje się mało przejrzysta, a żeby znaleźć odpowiedni wariant, musimy najechać na niego wskaźnikiem (myszką), żeby wyświetlić jego nazwę.
+ kod jest czytelny i czysty
+ pozwala łatwo dodawać kolejne etapy.
Etapy generyczne
Czasami jednak potrzebujemy więcej kontroli nad przebiegiem etapów lub jest ich tak dużo, że użycie macierzy staje się niepraktyczne. Możemy wtedy wygenerować takie etapy z listy. Wymaga to wykorzystania skryptów i prezentuje się następująco:
def OS_list = ["linux", "windows", "IOS", "android"]
pipeline {
agent eny
stages {
stage("Test all"){
steps{
script{
OS_list.each{
stage("$it"){
echo "$it"
}
}
}
}
}
}
}
Zrównoleglanie etapów generycznych
Stworzenie takiego potoku niestety nie jest już tak proste. Musimy pamiętać, że parallel
wewnątrz klamry script
zachowuje się inaczej niż ten, który występuje w zwykłym deklaratywnym potoku. W tym przypadku przyjmuje on bowiem mapę. Klucze będą wyświetlane jako nazwa rozgałęzienia, a wartościami powinny być etapy, które należy zrównoleglić. W tym miejscu polecamy poeksperymentować, jako że sposobów na stworzenie mapy w groovy’m
jest dużo, a różne kombinacje dają różne efekty. Poniżej przykład generycznych zrównoleglonych etapów:
def OS_list = ["linux", "windows", "IOS", "android"]
pipeline {
agent none
stages {
stage("Test all"){
steps{
script{
parallel OS_list.collectEntries { OS -> [ "${OS}": {
stage("$OS") {
echo "$OS"
}
}]
}
}
}
}
}
}
Takie rozwiązanie możemy dowolnie zagnieżdżać i zrównoleglać:
def OS_list = ["linux", "windows", "IOS", "android"]
def BROWSERS = ["chrome", "firefox", "opera", "edge"]
pipeline {
agent none
stages {
stage("Test all"){
steps{
script{
parallel OS_list.collectEntries { OS -> [ "${OS}": {
stage("$OS") {
BROWSERS.each{
stage("$it"){
echo "$it"
}
}
}
}]}
}
}
}
}
}
Wady i zalety etapów generycznych:
– wymaga użycia skryptów wewnątrz potoku, co może zmniejszać przejrzystość kodu
– wykorzystanie skryptów odbiera nam możliwość użycia niektórych funkcjonalności deklaratywnego potoku, takich jak np. when
– brak rozwiązania typu excludes
zmusza nas do implementacji skomplikowanych mechanizmów lub tak zwanej ifologji
+ daje ogromną dowolność
+ możemy dowolnie zagnieżdżać etapy
+ pozwala w łatwy sposób dodawać kolejne etapy.
Podsumowanie
Porównując powyższe rozwiązania, możemy zauważyć, że najczystszym rozwiązaniem, z uwagi na kod, jest użycie macierzy. Niestety to rozwiązanie nie generuje potoku który jest przejrzysty w przeglądaniu. Dynamicznie generowane etapy wyglądają dużo lepiej, ale wymagają od nas użycia skryptów, minimalnej znajomości języka groovy
oraz odbiera nam możliwość użycia niektórych funkcjonalności. Mamy nadzieję, że ten materiał pomoże wam w przyszłym projektowaniu i tworzeniu potoków jenkinsowych.