Przejdź do treści

Ciągła integracja – Pan Jenkins przybywa na ratunek

JenkinsChciałem napisać super fajny i długi artykuł o tym jakie wspaniałe rzeczy można zrobić z ciągłą integracją mając do dyspozycji potężne narzędzie jakim niewątpliwie jest Jenkins, czyli najlepszy silnik do ciągłej integracji na rynku. Niestety wszystko wskazuje na to, że chwilowo moje super moce tekstotwórcze mają urlop. W związku z tym po prostu podzielę się kilkoma luźnym spostrzeżeniami, które mogą się przydać ludziom planującym wdrożenie lub rozbudowę maszynerii do ciągłej integracji.

Zanim zaczniesz czytać dalej, powtórz trzy razy: budowanie i testowanie jest ważne. Dobre podejście do tematu – z wydzieleniem ról dla osób opiekujących się całą maszynerią przyniesie istotne korzyści dla projektu. Zmarginalizowanie tematu, lub uznanie go za ciekawostkę dla programistów sprawie, że w najlepszym wypadku zmieni się nie wiele, a w bardziej realistycznym… dojdzie do marnowania czasu i wysiłku. Wdrożenie sprawnej i rozbudowanej 'ciągłej integracji’ to inwestycja. Tak jak każda inwestycja, również i ta, przynosi zyski dopiero po pewnym czasie. I tak jak w przypadku każdej inwestycji, ludzie będą pytać „czy warto?”. Ja uważam, że warto i wygląda na to, że nie tylko ja: Who is using Jenkins?.

Tak więc do dzieła!

Po pierwsze ciągła integracja to nie jest tylko scheduler (albo cron) do budowania źródeł. Sama informacja „kompiluje się” nie daje nam zbyt wiele, bo jak wiadomo kod, który się kompiluje, może zawierać dowolną ilość poważnych błędów. Z drugiej strony są sytuacje (dla niektórych ciężkie do wyobrażenia), w których samo skompilowanie kodów źródłowych wymaga wiele wysiłku, ręcznego odpalania wielu skryptów i kilku-godzinnego oczekiwania. Jeśli masz nieszczęście pracować w takim projekcie, koniecznie zajmij się postawieniem środowiska CI jak najszybciej! Jeśli jakiś proces jest uciążliwy, to trzeba go automatyzować!

Bywa tak, że taka budowa źródeł musi odbywać się w specjalnych warunkach – na serwerze o zadanej architekturze sprzętowej i wersji systemu operacyjnego lub w obecności bazy danych w odpowiednim stanie. Systemy CI mogą tutaj pomóc na dwa sposoby! Jeśli masz już takie środowisko do budowania, możesz ograniczyć do niego dostęp – tak, aby tylko system CI mógł na nim operować (możesz bezpośrednio zainstalować Jenkinsa na takim serwerze, lub wywoływać zdalne komendy przez ssh lub jakiś inny remoting). To pomoże uchronić twój cenny skarb, jakim jest to właśnie magiczne środowisko. Ale jest też lepsze wyjście – możesz włożyć określony wysiłek i zdefiniować serię zadań w systemie CI, które będą tworzyć nowe środowisko na potrzeby buildów! Jenkins ma na przykład pluginy do systemów sterujących maszynami wirtualnymi, no i może uruchamiać przygotowane wcześniej skrypty. Jeśli bardzo zależy ci na tym, aby mieć środowisko do buildów w nienaruszonym stanie – to zautomatyzuj jego budowę i odświeżaj je często! To tyczy się również baz danych i wszystkich innych elementów, które mogą brać udział w budowaniu.

Mając zbudowaną aplikację zapewne chcemy ją przetestować – bo jak zauważyliśmy samo jej zbudowanie niewiele mówi. Oczywiście, pierwsze co zrobi nasz system CI, to uruchomienie testów jednostkowych (o ile je mamy…). Tutaj kolejna ważna uwaga – takie testy mogą zgłaszać błędy. Są projekty, w których niepowodzenie testów jednostkowych nikomu nie przeszkadza i build mimo wszystko idzie dalej. To jest antywzorzec. Jeśli masz testy jednostkowe, to dbaj o nie. Wszystkie muszą kończyć się sukcesem. Jeśli się nie kończą, to napraw problem albo napraw testy. Jeśli nie masz czasu naprawiać, to po prosty skasuj dany test! Tak będzie lepiej dla wszystkich. Cel jest taki, aby CI dało wynik: „build jest dobry” albo „build jest zły” i co więcej ten wynik ma naprawdę coś znaczyć. Jeśli regularnie będziemy dostawać wiadomość: „build jest zły”, ale wszyscy będą wiedzieć, że „to przez te 4 stare testy jednostkowe Witka, co to nikt nie wie jak je naprawić” – to cała koncepcja staje się kulawa.

Zadbane testy jednostkowe to nie wszystko. Coraz większą popularność zdobywają również automatyczne testy funkcjonalne. Jeśli je masz to super, jeśli nie… to pomyśl o tym! Do zautomatyzowania uruchomienia takich testów potrzebujemy kilku rzeczy. Jenkins na szczęście wspiera „z pudełka” systemy takie jak Selenium czy Quality Center, więc samo startowanie testów i zbieranie wyników mamy gratis. Problem jest w tym, że z reguły nasza aplikacja po zbudowaniu musi zostać gdzieś zainstalowana. Czyli musimy zadbać o środowisko testowe. To z kolei rodzi kolejne problemy: środowisko musi gdzieś być i, co więcej, musi działać. No i tu, podobnie jak w przypadku środowisk do budowania, najlepsze co możesz zrobić, to zautomatyzować proces budowania środowiska testowego – tak aby w każdej chwili dało się zaplanować na sobotnią noc totalne odtworzenie wszystkiego (w krytycznych sytuacjach łącznie z systemem operacyjnym). Stworzenie takiego automatu kosztuje. Ale mając go, otrzymujemy naprawdę potężne narzędzie. Co więcej – podobnie jak testujemy nasze oprogramowanie w ramach zadań CI, możemy również przygotować jakieś ogólne testy sprawności danego środowiska testowego i uruchamiać zadanie odbudowy środowiska w momencie, gdy te testy zakończą się wynikiem negatywnym.

Kiedy zapewnimy już sobie środowiska testowe pozostaje nam zainstalować tam aplikacje. Czasem jest to strasznie łatwe – wystarczy skopiować pliki na jakiś zdalny serwer. Są jednak przypadki (witamy w świecie Javy EE ;), kiedy proces deploymentu bywa bolesny. Na szczęście większość popularnych serwerów aplikacji JEE ma swoje wtyczki dla Jenkinsa i/lub Mavena – co jest wielką pomocą. Niestety moje doświadczenia pokazują że sama wtyczka (nawet dobrze skonfigurowana) to nieraz za mało. Kiedy deployment się nie udaje z losowych powodów… wróć do akapitu wyżej – warto odbudować środowisko od podstaw i spróbować ponownie.

Po zainstalowaniu aplikacji na serwerze (co swoją drogą samo w sobie jest jednym z testów) możemy w reszcie uruchomić automatyczne testy funkcjonalne i cieszyć się zebranymi i ładnie zwizualizowanymi wynikami. Idąc za ciosem, w naszą machinę możemy włączyć automatyczne testy wydajnościowe – tu znów Jenkins przyjdzie nam z pomocą oferując gotowe wsparcie dla JMeter i Grindera. Integracja innych podobnych narzędzi również nie powinna przysporzyć nam wielu kłopotów. Warto na początek przetestować podstawowe funkcje systemu – jak logowanie użytkowników i odwiedzenie podstawowych ekranów.

Czy to już wszystko? Nie. Ciągła integracja to przede wszystkim sztuka przekazywania informacji tak szybko, jak to możliwe. Wobec czego Twój system CI musi się komunikować z ludźmi. Wśród tych osób są dwie ważne grupy: programiści i testerzy. Programiści muszą możliwie szybko dostawać wiadomości negatywne, o tym że coś zepsuli i muszą naprawić. Z kolei testerzy muszą jak najszybciej dostawać wiadomości pozytywne – „build się udał, jest zainstalowany na serwerze: XYZ – testujcie!”. Jeśli te informacje nie będą docierać na czas lub jeśli ich pełne zrozumienie/odczytanie wymagać będzie przeklikiwania się przez długi ciąg odnośników to przegrałeś. Jenkins może słać powiadomiania pocztą e-mail (treść i lista odbiorców, a także reguły mówiąc o tym kiedy powiadamiać są konfigurowalne), ale może też rozmawiać z ludźmi na Jabberze/XMPP (Google Talk), IRCu, Twitterze, Skypie itd. lub odzywać się przez aplikację na komórkach (JenkinsMobi). W sieci można również znaleźć wiele instrukcji, jak przygotować sobie wielkie mrygające czerwone światło, które zapali się gdy coś się nie udało. Jest nawet wtyczka do syntezatora mowy, która sprawi, że nasz serwer zacznie krzyczeć kiedy build się nie uda! To wszystko nie są żarty. Jeśli możliwie wcześniej wykryjemy problem, jego wpływ będzie minimalizowany… no, a przynajmniej będziemy mieć szansę na szybką reakcję.

Tak czy inaczej, to wszystko to wciąż nie koniec. Do całego obrazka warto dołączyć system, którzy skontroluje nasz kod. Rozwiązania typu: StyleCop, Checkstyle, Findbugs, FxCop, NDepend itp. zdecydowanie pomagają. Doświadczyłem tego bardzo wyraźnie w projekcie w którym pracuje obecnie, gdzie naruszenie zasad StyleCop’a powoduje zatrzymanie builda. Dzięki temu naprawdę powstaje lepszy kod! Co więcej obecnie mamy do dyspozycji super kombajn o nazwie Sonar, który opakowuje wszystkie możliwe narzędzia kontrolujące kod źródłowy. Jeśli tylko mamy zasoby, aby analizować wyniki takiej kontroli, to nie ma na co czekać – trzeba to mieć.

Do tego warto jeszcze dodać automatyczne przebudowanie i publikację dokumentacji (o ile mamy coś takiego) – szczególnie jeśli ta dokumentacja budowana jest z kodu źródłowego. Jenkins może pokazać linki do różnych wygenerowanych i opublikowanych materiałów powstałych w ramach buildu na stronie z podsumowaniem. Znajdzie się tam na przykład link do Sonara. Możemy też dodać swoje linki – tak, aby strony w Jenkinsie zawierały komplet informacji o aktualnym stanie aplikacji.

Jak widać, powstaje nam z tego wszystkiego całkiem spora układanka. Co więcej, wyraźnie widać, że zbudowanie i dobre skontrolowanie większej aplikacji w ten sposób może zająć naprawdę sporo czasu. Jak to pogodzić z podstawowym założeniem CI, które sugeruje sprawdzanie KAŻDEJ zmiany osobno? To proste – nasze duże zadanie budowania należy rozbić na wiele małych powiązanych ze sobą kawałków. Jenkins potrafi definiować zależności między takimi małymi zadaniami. Możliwie jest również dystrybuowanie ich w klastrze – tak, aby część pracy była wykonana równolegle. Ponadto warto pomyśleć nad podzieleniem zadań na profil „light” uruchamiany możliwie często i profil pełny, uruchamiany na przykład w każdy weekend.

Aby znaleźć dokładne wskazówki jak zaimplementować wymienione wyżej pomysły warto dokładnie przejrzeć stronę z dodatkami do Jenkinsa: https://wiki.jenkins-ci.org/display/JENKINS/Plugins. Warto też pamiętać (o czym często zapominamy), że wszystko to, czego nie da się osiągnąć przez istniejący plugin Jenkinsa, można z reguły zrobić dodając ogólną akcję „wykonaj polecenie shell” do buildu, lub … pisząc własny plugin.

Jeśli masz jakieś doświadczenia z wdrożeniem CI w niestandardowy / zaawansowany sposób – podziel się w komentarzach!

PS. Fajne jest też to, że Jenkinsa można używać do rzeczy zupełnie innych niż budowanie i testowanie aplikacji. Chociażby do automatyzacji różnych czynności na serwerach testowych (lub nawet produkcyjnych) albo kontrolowania jakiegoś długotrwałego przetwarzania. Daje to nam dość solidną wartość dodaną w postaci ciągłej możliwości monitorowania postępów przez wiele osób, powiadomień wieloma kanałami oraz historii wykonań wraz z pełnymi szczegółami. Także warto sprzedać ten pomysł na przykład swoim adminom jako taki sklastrowany cron++ z elegancką konsolą web i szpanerską tekstową konsolą CLI.

PS2. Jenkins jest napisany w Javie. Ale nadaje się do projektów tworzonych w byle czym – wliczając w to C#/.NET… Doświadczenie mówi, że poradzi sobie nawet z bardzo starymi technologiami enterprise, o których nikt nic nie wie, a jedyna dokumentacja znajdowana w Google jest po koreańsku (i to nie jest żart…).

6 komentarzy do “Ciągła integracja – Pan Jenkins przybywa na ratunek”

  1. Test Joela do oceny jakości wytwarzania oprogramowania jako jeden z punktów oceny wymienia „czy możesz zbudować wersję w jednym kroku”. I podaje dlaczego jest to takie ważne:

    If the process takes any more than one step, it is prone to errors. And when you get closer to shipping, you want to have a very fast cycle of fixing the „last” bug, making the final EXEs, etc. If it takes 20 steps to compile the code, run the installation builder, etc., you’re going to go crazy and you’re going to make silly mistakes.

    (wersja angielska testu, wersja polska)

  2. Niezły artykuł – Jenkins w pigułce. Z jednym się jednak nie zgodzę:

    Jeśli nie masz czasu naprawiać, to po prosty skasuj dany test! Tak będzie lepiej dla wszystkich. Cel jest taki, aby CI dało wynik: „build jest dobry” albo „build jest zły” i co więcej ten wynik ma naprawdę coś znaczyć. Jeśli regularnie będziemy dostawać wiadomość: „build jest zły”, ale wszyscy będą wiedzieć, że „to przez te 4 stare testy jednostkowe Witka, co to nikt nie wie jak je naprawić” – to cała koncepcja staje się kulawa.

    To w dłuższej perspektywie musi prowadzić do katastrofy, bo skasowanie testu(ów) nie powoduje, że „build jest dobry” tylko zamiata problem pod dywan!
    I przede wszystkim – starajmy się naprawiać kod aplikacji a nie testy ;)

    1. Po pierwsze dzięki za komentarz :)

      Po drugie co do testów… to jednak różnie bywa. Zdecydowanie najlepiej jest jeśli takie patologiczne sytuacje nie mają miejsca.

      Z drugiej strony bywa tak, że testy są po prostu zaniedbane i jest fail nie dlatego, że mamy błąd w kodzie aplikacji, tylko dlatego, że testy nie zostały uaktualnione. Wtedy trzeba naprawić test a nie aplikację:) No a jeśli jest taka sytuacja, że wśród testów mamy zarówno świeże „dobre” testy jak i jakieś starocie, które tradycyjnie failują to najlepsze co można zrobić to wywalić te śmieci. Dlaczego? Bo przez przyzwyczajenie wszystkich, że zawsze są jakieś fail’e w testach możemy łatwo przegapić prawdziwe problemy wykrywane przez tą część testów, które mają jakikolwiek sens.

      Podsumowując – moje stanowisko jest takie, że jak najbardziej warto mieć testy i o nie dbać. Jeśli się nie dba i rzeczywistość testów i aplikacji się rozjeżdża to trzeba podjąć konkretną decyzję, albo utrzymujemy testy, albo się ich pozbywamy.

  3. Ciekawe co na tekst „nadaje się do projektów tworzonych w byle czym – wliczając w to C#/.NET…” powiedziałby Microsoft (R) ;-) ?

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Witryna wykorzystuje Akismet, aby ograniczyć spam. Dowiedz się więcej jak przetwarzane są dane komentarzy.