aA

Jenkins – zrównoleglenie, macierz oraz dynamiczne generowanie etapów w potoku

Jenkins

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:

Etapy równoległe

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:

Etapy równoległe sekwencja

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:

Macierz

 

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"
                        }
                    }
                }
            }
        }
    }
}

Etapy generyczne

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"
                            }
                        }]
                    }
                }
            }
        }
    }
}

Zrównoleglanie etapów generycznych:

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"
                                }
                            }
                        }
                    }]}
                }
            }
        }
    }
}

Zrównoleglanie etapów generycznych

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.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *