DevOps w świecie monolitów: jak krok po kroku wdrażać CI/CD bez przepisywania systemu

0
5
Rate this post

Nawigacja:

Czego naprawdę szuka ktoś wchodzący z DevOps w monolit

Osoba, która próbuje wdrożyć DevOps i CI/CD w dużym monolicie, zwykle nie poluje na modne hasła. Szuka sposobu, żeby przestać bać się wdrożeń, skrócić czas reakcji na błędy i wprowadzać zmiany bez paraliżowania całej firmy. I to bez przepisywania wszystkiego na mikroserwisy oraz bez trzyletniego „programu transformacji”, który kończy się slajdami zamiast rezultatem.

DevOps w świecie monolitów to nie jest inny rodzaj DevOps, tylko inny punkt startu: więcej długu technicznego, mniej automatyzacji, więcej „magii w głowach” pojedynczych osób. Da się jednak krok po kroku dojść do sensownego CI/CD, o ile nie porywa się od razu na pełne continuous deployment i nie udaje, że problemu długich wdrożeń da się rozwiązać jednym narzędziem.

Monolit kontra DevOps – co się naprawdę gryzie, a co tylko tak wygląda

Monolit to nie tylko „jeden plik”, ale cały ekosystem

W praktyce monolit to rzadko tylko jeden artefakt. To zwykle połączenie kilku cech:

  • jeden główny proces aplikacyjny lub kilka mocno powiązanych modułów,
  • wspólna baza danych z gęstą siecią zależności (widoki, procedury, triggery),
  • duży, często wieloletni kod, który „rośnie w bok”, a nie jest świadomie projektowany,
  • proces wytwórczy oparty na ręcznych krokach i wiedzy pojedynczych osób,
  • release’y, które są wydarzeniem organizacyjnym, a nie rutyną.

Dlatego DevOps w monolicie to nie tylko pytanie o to, jak zbudować pipeline CI/CD dla jednego JAR-a czy EXE. Chodzi o zmianę sposobu, w jaki cała organizacja myśli o wytwarzaniu i wdrażaniu. I tu pojawia się zderzenie: monolit lubi stabilność i przewidywalność, a DevOps kładzie nacisk na częste zmiany, szybką informację zwrotną i automatyzację.

Główne źródła problemów: nie brak mikroserwisów, tylko brak powtarzalności

W większości monolitów największe problemy z DevOps i CI/CD wynikają z trzech obszarów:

  • brak wiarygodnych testów automatycznych – zmiana dowolnego fragmentu wymaga ręcznego klikania całej aplikacji albo „zaufania”, że jakoś będzie,
  • ręczne wdrożenia – każda wersja jest wdrażana inaczej, bo ktoś coś poprawia „na szybko” na serwerze,
  • brak powtarzalnych środowisk – test, pre-prod i produkcja są podobne tylko z nazwy; konfiguracje różnią się losowo.

To przekłada się na „święte release’y” raz na kwartał, w które angażuje się pół firmy, a i tak nikt nie ma pewności, że wszystko zadziała. CI/CD dla monolitu ma sens wtedy, gdy zaczyna rozwiązywać właśnie te problemy, a nie „wdraża najnowszy toolchain, bo tak robią w chmurze”.

Mity blokujące ruch: „najpierw przepiszmy, potem automatyzujmy”

Kilka narracji szczególnie skutecznie zabija sensowne podejście do DevOps w starych systemach:

  • „DevOps jest tylko dla mikroserwisów” – w praktyce CI/CD powstało głównie po to, żeby częściej i bezpieczniej wypuszczać nowe wersje dowolnych systemów. Monolit nie jest wyjątkiem, tylko trudniejszym przypadkiem startowym.
  • „Najpierw przepiszmy na nową architekturę, potem wdrażajmy CI/CD” – w większości organizacji oznacza to brak jakichkolwiek usprawnień przez kilka lat, a końcowy efekt i tak jest niepewny. Lepsza strategia: wdrażanie DevOps i CI/CD równolegle z utrzymaniem i stopniową refaktoryzacją.
  • „Bez 80% pokrycia testami nie ma sensu pipeline” – to klasyczny przykład paraliżu przez perfekcjonizm. Zamiast nierealnych celów sensownie jest zacząć od kilku testów dymnych obejmujących krytyczne ścieżki, a resztę rozbudowywać iteracyjnie.

Te mity często służą jako wygodne alibi dla braku ruchu: „nie opłaca się zaczynać, bo nie zrobimy tego idealnie”. Z punktu widzenia ryzyka biznesowego jest dokładnie odwrotnie – każda porcja automatyzacji, nawet niepełna, zmniejsza chaos.

Co faktycznie utrudnia CI/CD w monolicie, a co jest wymówką

Istnieją obiektywne trudności:

  • bardzo długi czas kompilacji i testów, który utrudnia częste buildy,
  • sklejone ze sobą moduły, utrudniające selektywne testowanie i wdrażanie,
  • brak wydzielonych interfejsów, przez co drobna zmiana dotyka wielu obszarów,
  • silne powiązanie z jedną, wspólną bazą danych i skomplikowanymi migracjami.

To są przeszkody, z którymi trzeba się zmierzyć technicznie i procesowo. Natomiast wymówkami są sytuacje typu:

  • „nie mamy czasu na automatyzację, bo musimy dowozić funkcje” – efekt jest taki, że dług rośnie, a kolejne wydania trwają coraz dłużej,
  • „po co pipeline, skoro mamy jedną aplikację” – monolit szczególnie korzysta na automatyzacji, bo dotknięcie jednej rzeczy wpływa na całość,
  • „bez dedykowanego teamu DevOps nie ma sensu zaczynać” – dużą część kroków da się wykonać w istniejącym zespole, jeśli są jasno rozpisane.

DevOps w monolicie to gra w ograniczanie złego wpływu istniejących ograniczeń, a nie w udawanie, że ich nie ma. Ciągła precyzyjna diagnoza: co realnie przeszkadza w kolejnym małym kroku, a co jest tylko wygodnym tłumaczeniem, przyspiesza drogę do działającego CI/CD znacznie bardziej niż kolejna prezentacja o „pełnej chmurze w trzy lata”.

Diagnoza stanu wyjściowego – zanim pojawi się pierwszy pipeline

Inwentaryzacja: kod, proces builda i ludzie-trzymacze wiedzy

Bez uczciwej inwentaryzacji DevOps w starych systemach szybko zamienia się w zgadywanie. Pierwszym krokiem nie jest wybór narzędzia CI, tylko zrozumienie, co i jak jest dziś budowane i wdrażane.

Kluczowe elementy inwentaryzacji:

  • repozytoria – gdzie znajduje się kod monolitu i powiązanych komponentów (skrypty DB, front-end, usługi pomocnicze)? Czy mamy jedno repozytorium, kilka, a może fragmenty kodu krążą po dyskach sieciowych?
  • aktualny proces builda – jak faktycznie powstaje artefakt? Czy ktoś musi uruchomić IDE i „kliknąć” build? Czy są lokalne skrypty, które nigdy nie trafiły do repozytorium?
  • skrypty i hacki – wszelkie pliki .bat, .sh, .ps1, makra, które są używane przy wdrożeniach, a oficjalnie „nie istnieją”, bo trzyma je administrator lub senior developer.
  • ludzie, którzy wiedzą jak to działa – zwykle jest 1–3 osoby, które „wiedzą, jak się to wdraża” i których nie ma w żadnej dokumentacji. To kluczowe źródło informacji na start.

Warto tę wiedzę spisać w najprostszej możliwej formie: kilka stron wiki lub prosty dokument tekstowy. Nie chodzi o idealną dokumentację, tylko o wystarczający poziom, by móc zacząć automatyzować bez wpadania w ukryte miny.

Parametry, które trzeba znać, żeby nie strzelać na ślepo

Nawet najładniejszy pipeline niewiele da, jeśli nie uwzględni realnych ograniczeń systemu. Pewien minimalny zestaw parametrów trzeba zmierzyć lub przynajmniej oszacować:

  • czas kompilacji – ile trwa pełen build na typowej maszynie? Czy liczymy to w minutach, czy w godzinach?
  • częstotliwość wydań – jak często dziś wchodzi nowa wersja na produkcję? Raz w tygodniu, raz w miesiącu, raz na kwartał?
  • czas rollbacku – w razie poważnej awarii ile czasu upływa od decyzji o wycofaniu wersji do przywrócenia poprzedniego stanu?
  • lista środowisk – ile mamy środowisk (DEV, TEST, UAT, PRE-PROD, PROD)? Jak się różnią? Które są faktycznie używane?

Te parametry nie muszą być zmierzone co do minuty. Chodzi o rzęd wielkości. Jeśli pełny build trwa 90 minut, to wiadomo, że na start pipeline musi być zbudowany tak, by nie odpalać wszystkiego przy każdej drobnej zmianie, tylko np. mieć rozdzielone fazy szybkiej weryfikacji i pełnego, nocnego builda.

Mapa zależności: bazy, kolejki, systemy zewnętrzne

Monolit rzadko żyje w próżni. Nawet jeśli „aplikacja jest jedna”, to po drodze są:

  • główna baza danych z dziesiątkami tabel i procedur,
  • kolejki (np. RabbitMQ, MSMQ, Kafka),
  • zewnętrzne systemy (ERP, CRM, bramki płatności, integracje z partnerami),
  • usługi infrastrukturalne (LDAP/AD, serwery plików, systemy raportowe).

Trzeba ustalić, które z tych elementów:

  • krytyczne – bez nich aplikacja praktycznie nie działa,
  • trudne do zasymulowania na środowiskach testowych,
  • mają skutki uboczne (np. generują faktyczne przelewy lub wysyłają maile do klientów).

To pozwala uniknąć klasycznego błędu: zbudować pipeline, który na testowym środowisku robi „prawie produkcję”, ale przy okazji wysyła masowo maile albo wystawia faktury. W kontekście pipeline dla aplikacji legacy kwestia odseparowania skutków ubocznych testów jest jednym z ważniejszych elementów bezpieczeństwa procesu.

Ocena ryzyka: co pęknie przy zmianie procesu wdrożeń

Zmiana sposobu wdrażania monolitu to operacja na żywym organizmie. Pytanie nie brzmi „czy coś pójdzie nie tak”, tylko gdzie szkody mogą być największe i jak je ograniczyć. Prosty, ale skuteczny sposób oceny ryzyka:

  • zidentyfikować obszary o największym wpływie biznesowym (np. księgowanie płatności, wystawianie faktur, logowanie użytkowników),
  • sprawdzić, które z nich są dotykane przez pipeline (np. migracje bazy danych, konfiguracja środowisk),
  • dla każdego z nich określić, jak wygląda awaria: brak działania, błędne dane, opóźnienie.

Na tej podstawie wybiera się kolejność automatyzacji. Zwykle sensowniej jest zacząć od środowisk testowych i mniej krytycznych kroków (np. build + smoke testy), a dopiero później automatyzować najbardziej ryzykowne operacje (np. migracje produkcyjnej bazy danych).

Minimalna wiedza, bez której wdrażanie CI/CD to hazard

Jeśli miałby powstać absolutnie minimalny zestaw informacji, który trzeba mieć przed budową pierwszego pipeline’u dla monolitu, wyglądałby on mniej więcej tak:

  • opis, jak dziś ręcznie buduje się artefakt (krok po kroku),
  • informacja, gdzie leżą wszystkie używane skrypty,
  • lista środowisk i sposób ich konfiguracji,
  • czas potrzebny na aktualne wdrożenie i ewentualny rollback,
  • lista krytycznych integracji z systemami zewnętrznymi.

Bez tego DevOps w starych systemach staje się „sztuką dla sztuki”: pipeline działa, ale w niewłaściwym miejscu, niewłaściwym momencie i bez zrozumienia konsekwencji. Z tak zebranym minimum można już jednak odpowiedzialnie planować kolejne kroki.

Małe kroki, nie rewolucja – strategia stopniowego wdrażania CI/CD

Dlaczego „big bang” z monolitem kończy się bólem

Próba zrobienia pełnego „continuous deployment” dla dużego monolitu w jednym skoku jest jednym z najmniej rozsądnych ruchów. Typowy scenariusz wygląda tak:

  • wybór nowego narzędzia CI/CD i zbudowanie bardzo rozbudowanego pipeline’u,
  • próba objęcia nim od razu wszystkich środowisk (łącznie z produkcją),
  • pierwsze uruchomienie – seria awarii, trudna do szybkiego odkręcenia,
  • utrata zaufania biznesu i zespołu do automatyzacji, powrót do ręcznych wdrożeń.

Monolit ma zbyt wiele powiązanych elementów, żeby wprowadzić pełną automatyzację jednym ruchem bez dużego ryzyka. Rozsądniejsze podejście to sekwencja małych zmian, z których każda poprawia sytuację, ale może być szybko wycofana, jeśli coś pójdzie źle.

Cele realistyczne: od „manualnego piekła” do sensownej automatyzacji

Implementując stopniowe wdrażanie DevOps, zamiast obiecywać continuous deployment w ciągu kilku miesięcy, lepiej określić realistyczną ścieżkę celów:

Etapy dojrzewania pipeline’u dla monolitu

Zamiast jednego wielkiego skoku bardziej przewidywalny jest podział na kilka poziomów dojrzałości. Nie chodzi o akademickie modele, tylko o konkretne pytania: co już jest powtarzalne, a gdzie nadal „czaruje się” ręcznie.

Praktyczny podział etapów może wyglądać tak:

  1. Powtarzalny build – kod da się zbudować z jednego polecenia na czystej maszynie (lokalnie lub na serwerze CI), bez klikania w IDE, bez „magicznych” ustawień użytkownika.
  2. Automatyczny build na każdym commicie – system CI reaguje na zmiany w repozytorium, buduje monolit i przynajmniej wykrywa błędy kompilacji.
  3. Podstawowe testy i statyczna analiza – do pipeline’u dochodzą szybkie testy (unit/smoke) i proste narzędzia statyczne (lint, style, podstawowy SAST).
  4. Automatyczne wdrożenie na środowisko nieprodukcyjne – np. TEST lub QA jest aktualizowane jednym przyciskiem lub automatycznie po spełnieniu warunków.
  5. Półautomatyczne wdrożenie na produkcję – pipeline wykonuje większość kroków, ale wymaga świadomej akceptacji (manual gate) i posiada jasną ścieżkę rollbacku.

Wiele zespołów próbuje przeskoczyć od razu na poziom 4 lub 5, pomijając dwa pierwsze. To jeden z najczęstszych powodów porażki: pipeline ma obsługiwać skomplikowane wdrożenie, podczas gdy zwykły „clean build” nadal wymaga lokalnych trików seniorów.

Jak wybrać pierwszy kroczek, który realnie coś zmieni

Dobry pierwszy krok ma trzy cechy: jest technicznie tani, ma niewielkie ryzyko biznesowe i daje widoczną wartość w ciągu tygodni, a nie kwartałów. Typowe kandydaty przy monolicie:

  • przeniesienie lokalnych skryptów builda do repozytorium – formalizuje proces, nie zmieniając jeszcze sposobu wdrażania,
  • zastąpienie ręcznego kopiowania plików prostym skryptem (np. PowerShell, Bash) – to jeszcze nie pełny pipeline, ale już eliminacja kilku punktów ludzkiej pomyłki,
  • dodanie automatycznego builda „na sucho” w CI – pipeline buduje aplikację, ale nie wdraża, dzięki czemu widać od razu, czy repozytorium zawiera wszystko, co potrzebne.

Zespoły często przeceniają wagę wyboru narzędzia (Jenkins, Azure DevOps, GitLab CI itp.), a niedoceniają jakości pierwszego małego celu. Narzędzie da się zmienić; pierwsze złe doświadczenie z automatyzacją – znacznie trudniej.

Ograniczanie zasięgu zmian: jeden fragment procesu naraz

Monolit jest kuszący, żeby „przy okazji” poprawić wszystko. To klasyczny błąd. Sensowniejsza jest redukcja zakresu:

  • najpierw tylko build, bez dotykania wdrożeń,
  • później wdrożenia tylko na jedno wybrane środowisko (np. TEST),
  • dopiero na końcu rozszerzanie na kolejne środowiska i produkcję.

Przy dużym monolicie nawet tak „mały” krok jak automatyzacja wdrożenia na TEST może wymagać dopracowania ścieżek, uprawnień, konfiguracji sieci. Jeśli ten etap zostanie pominięty, problemy wyjdą na produkcji, gdzie presja czasu jest największa.

Stopniowe przesuwanie odpowiedzialności z ludzi na pipeline

Zespół, który latami utrzymywał monolit „na czuja”, nie zaufa od razu maszynie. Proces zmiany odpowiedzialności dobrze rozłożyć w czasie:

  1. Faza obserwacji – pipeline powtarza kroki wykonywane ręcznie, ale „obok”, bez wpływu na środowiska (np. buduje artefakt i trafia on tylko do wewnętrznego repozytorium).
  2. Faza duplikacji – wdrożenie nadal jest robione ręcznie, ale pipeline wykonuje te same kroki równolegle na środowisku testowym. Zespół porównuje wyniki.
  3. Faza przejęcia – pipeline staje się jedyną ścieżką wdrożeń na wybranym środowisku (np. TEST), ręczne „skróty” są blokowane lub mocno utrudnione.

Dopiero gdy ta sekwencja zadziała na środowiskach nieprodukcyjnych, można rozważać podobny ruch na produkcji. W przeciwnym razie „fallback” w postaci ręcznego wdrożenia szybko stanie się z powrotem główną ścieżką.

Zardzewiałe rury i zawory starej instalacji przemysłowej w fabryce
Źródło: Pexels | Autor: Pixabay

Fundament techniczny – repozytorium, build, artefakty

Repozytorium jako źródło prawdy, nie „przydatny dodatek”

W świecie monolitów repozytorium często jest tylko miejscem, gdzie leży kod. Skrypty wdrożeniowe, konfiguracje, pliki SQL, instrukcje – wszystko to żyje osobno. Przy podejściu DevOps repo ma być źródłem prawdy o tym, jak system się buduje i wdraża.

Konkretny cel na start: wszystko, co jest używane przy buildzie i wdrożeniach, powinno znaleźć się w systemie kontroli wersji:

  • skrypty builda (np. Maven, Gradle, MSBuild, NPM, Make),
  • skrypty wdrożeniowe (PowerShell, Bash, Ansible – nawet jeśli jeszcze odpalane ręcznie),
  • szablony plików konfiguracyjnych (bez haseł i sekretów),
  • skrypty migracji baz danych (jeśli istnieją).

Wyjątkiem są dane wrażliwe (hasła, klucze, certyfikaty) – one powinny być trzymane w dedykowanych sejfach sekretów, a w repo jedynie odwołania lub zmienne środowiskowe.

Porządkowanie monorepo vs. wielorepo w praktyce

Przy monolicie często pojawia się pytanie: jedno repozytorium na wszystko czy kilka? Nie ma jednego przepisu, ale parę obserwacji się powtarza:

  • monorepo (backend, frontend, skrypty DB razem) ułatwia zbudowanie jednego pipeline’u i pilnowanie zgodności wersji, ale wymaga większej dyscypliny przy podziale modułów,
  • wielorepo pozwala rozdzielić odpowiedzialność między zespoły, ale utrudnia spójne wydania (tagowanie, wersjonowanie, zgodne migracje DB).

Jeśli system jest już monolitem logicznym, a zespół dopiero zaczyna z CI/CD, monorepo jest zwykle praktyczniejsze. Rozbijanie na kilka repozytoriów przed ugruntowaniem podstaw (build + artefakt + prosty pipeline) jest z reguły przerostem ambicji nad możliwością utrzymania.

Build powtarzalny na czystej maszynie

Kluczowy test gotowości do CI/CD jest prosty: czy na świeżej maszynie, z zainstalowanymi tylko wymaganymi narzędziami, da się zbudować monolit jednym poleceniem z dokumentacji? Typowe problemy, które wychodzą przy takim teście:

  • lokalne zależności (biblioteki zainstalowane tylko na laptopie developera),
  • odwołania do zasobów sieciowych, do których serwer CI nie ma dostępu,
  • ręczne kroki typu „przekopiuj ten plik z udziału sieciowego, zanim odpalisz build”.

Minimalny cel: zestaw kroków, które można zapisać jako skrypt i odtworzyć w identyczny sposób na dowolnej maszynie (deweloperskiej, CI, staging). Jeśli podczas budowy pipeline’u trzeba każdorazowo „dopasowywać” serwer CI do lokalnej konfiguracji developera, projekt zaczyna się od końca.

Artefakty: z jednej wersji kodu – jedna paczka

Bez jasno zdefiniowanego artefaktu nie ma sensownego CI/CD. Artefakt to wynik builda, który:

  • jest identyfikowalny – ma wersję, znany commit, z którego powstał,
  • jest powtarzalny – ten sam kod, ten sam proces builda, ten sam wynik,
  • jest niezmienny – po zbudowaniu nie jest już „poprawiany ręcznie”.

Dla monolitu artefaktem może być:

  • instalator (MSI, exe, pakiet RPM/DEB),
  • spakowany katalog z aplikacją (ZIP, tar.gz) z opisanym sposobem wdrożenia,
  • obraz kontenera (nawet jeśli aplikacja nie jest jeszcze uruchamiana w klastrze).

Ważniejsze od formatu jest to, żeby ta sama paczka była używana na wszystkich środowiskach – różnią się jedynie konfiguracją. Praktyka typu „osobny build pod TEST, osobny pod PROD” generuje błędy, których pipeline nie wyłapie.

Repozytorium artefaktów zamiast „katakumb z plikami”

W wielu organizacjach „repozytorium” wersji to katalog na serwerze plików z nazwami typu release_nowy_test_ostateczny_v2.zip. Przy pierwszej awarii trudno stwierdzić, co jest czym.

Bardziej przewidywalny model to użycie dedykowanego repozytorium artefaktów (np. Artifactory, Nexus, wbudowane registry w GitLab/Azure DevOps) lub przynajmniej jasno zorganizowanej struktury katalogów z wersjami, gdzie:

  • każdy artefakt ma wersję i odniesienie do commitu (np. w nazwie pliku lub manifestu),
  • przechowywane są także metadane: data, autor builda, wynik testów,
  • starsze artefakty są usuwane według polityki retencji, a nie „kiedyś się posprząta”.

Bez takiej struktury rollback po awarii jest loterią: zespół niby „wraca” do poprzedniej wersji, ale nie ma pewności, czy to dokładnie ta sama paczka, która była na produkcji tydzień wcześniej.

Testy w monolicie – jak zacząć, gdy testów prawie nie ma

Nie zaczynać od pełnego pokrycia – zacząć od testów krytycznych ścieżek

Przy starych monolitach bez testów łatwo wpaść w skrajności: albo deklaracja, że „wszystko trzeba przepisać z testami”, albo całkowita rezygnacja, bo „i tak się nie da”. Rozsądniejsza ścieżka to zdefiniowanie kilku krytycznych ścieżek, które muszą działać po każdym wdrożeniu, i skupienie się najpierw na nich.

Krytyczne ścieżki często obejmują:

  • logowanie i autoryzację,
  • procesy finansowe (płatności, faktury, księgowania),
  • operacje nieodwracalne (usuwanie danych, wysyłka dokumentów do klientów).

Zwykle nie da się ich od razu pokryć eleganckimi testami jednostkowymi – ale można zacząć od prostych testów integracyjnych lub end-to-end, które przynajmniej sprawdzą, czy główne scenariusze działają.

Smoke testy jako pierwszy filtr w pipeline

Przy braku rozbudowanego zestawu testów automatycznych sensownym pierwszym filtrzem są smoke testy – kilka prostych sprawdzeń po wdrożeniu na środowisko testowe, np.:

  • czy aplikacja się uruchamia i odpowiada na endpoint zdrowia,
  • czy można zalogować się testowym kontem,
  • czy kluczowa strona lub API nie zwraca błędu 500.

Takie testy można napisać w dowolnym narzędziu (Selenium, Cypress, RestAssured, prosty skrypt curl + asercje). Ważne, żeby pipeline był w stanie je uruchomić i przerwać proces, jeśli podstawy nie działają. To nie zastąpi pełnej regresji, ale znacząco redukuje ryzyko oczywistych wpadek.

Testy jednostkowe w legacy: nie wszędzie, tylko tam, gdzie dotykasz kodu

Próba objęcia testami jednostkowymi całego starego monolitu rzadko kończy się powodzeniem. Zwykle lepiej zastosować zasadę „testujemy tam, gdzie zmieniamy”: każde nowe lub modyfikowane miejsce w kodzie dostaje chociaż minimalny zestaw testów.

Aby miało to sens w pipeline’ach, potrzebne są dwie rzeczy:

  • proste narzędzie do uruchamiania testów (Maven Surefire, NUnit, JUnit, xUnit itp.),
  • podział testów na szybkie (unit) i wolne (integracyjne, e2e), żeby nie blokować codziennych buildów.

Pipeline powinien uruchamiać szybkie testy przy każdym commitcie, a wolniejsze np. raz na noc lub przy przygotowaniu wersji kandydującej do wydania. To kompromis między jakością a czasem oczekiwania na wynik.

Manualna regresja – jak ją włączyć w proces, zamiast udawać, że jej nie ma

W wielu organizacjach główną barierą automatyzacji jest rozbudowana ręczna regresja wykonywana przez QA. Zamiast próbować ją wyeliminować z dnia na dzień, rozsądniej jest włączyć ją świadomie do procesu:

  • pipeline buduje artefakt i wdraża na środowisko regresyjne,
  • QA dostaje jasny sygnał: „ta wersja przeszła podstawowe testy automatyczne, możesz zacząć regresję”,
  • wyniki manualnych testów są zapisywane i powiązane z konkretną wersją artefaktu.

W ten sposób ręczna regresja nie znika, ale przestaje być „czarną skrzynką” i może być stopniowo zastępowana przez testy automatyczne w obszarach, gdzie powtarzalność jest największa.

Minimalne metryki testowe, które pomagają, a nie paraliżują

Przy legacy-monolicie łatwo wpaść w pułapkę „magicznej liczby pokrycia kodu”. Sam procent coverage rzadko cokolwiek rozwiązuje – da się go sztucznie podbić, pisząc testy bez realnej wartości. Rozsądniejszy zestaw na początek to kilka prostych metryk, które da się policzyć automatycznie:

  • liczba testów uruchomionych w pipeline (unit + integracyjne) vs. liczba testów pominiętych,
  • czas wykonania testów – żeby widzieć, kiedy zestaw zaczyna „puchnąć” ponad rozsądny limit,
  • trend pokrycia na poziomie modułów, a nie całego monolitu (które moduły realnie zyskują testy),
  • flaky tests – ile testów w ostatnim tygodniu/msc raz przechodzi, raz nie, bez zmian w kodzie.

Celem nie jest osiągnięcie konkretnej liczby, tylko obserwacja kierunku: czy z wersji na wersję testów przybywa i czy są stabilne. Jeżeli po kilku sprintach metryki pokazują ciągły wzrost liczby testów i spadek awarii na produkcji, to dużo bardziej wiarygodny sygnał niż „osiągnęliśmy 70% coverage”.

Dobrym kompromisem jest wprowadzenie zasady typu: „nowy kod nie ma mniejszego pokrycia niż stary”. Nie wymusza to rewolucji w całym systemie, ale zatrzymuje dalsze pogarszanie jakości.

Automatyzacja wdrożeń monolitu – od skryptów do powtarzalnego release’u

Od ręcznych kroków do skryptów – katalogowanie rzeczywistości

Przy starych monolitach procedura wdrożenia bywa kombinacją dokumentu Word, wiedzy w głowach administratorów i kilku nieudokumentowanych trików. Zanim pojawi się „prawdziwy” deployment z pipeline’u, trzeba to wszystko nazwać i zapisać.

Praktyczny start to spis kroków wykonywanych przy wdrożeniu, nawet jeśli są absurdalne. Typowy zestaw obejmuje:

  • zatrzymanie usług lub planowane okno serwisowe,
  • backup bazy danych i plików aplikacji,
  • kopiowanie nowej wersji binariów/artefaktu,
  • zmiany konfiguracji specyficzne dla środowiska,
  • migracje schematu bazy lub danych,
  • uruchomienie aplikacji i podstawowe sanity checki.

Na tym etapie nie chodzi o elegancję, tylko o kompletność. Często dopiero przy takim spisie wychodzą na jaw „tajne” kroki, bez których wdrożenie się wykłada, np. ręczne czyszczenie jakiegoś katalogu lub restart zewnętrznej usługi.

Skrypt wdrożeniowy jako jedyne źródło prawdy

Gdy lista kroków wygląda sensownie, kolejnym krokiem jest przepisanie ich do skryptu wdrożeniowego. Technologia jest wtórna (Bash, PowerShell, Ansible, Makefile) – istotne jest, żeby:

  • skrypt dało się uruchomić bez interakcji (bez klikania i potwierdzeń w środku),
  • każdy krok miał jasny log i komunikaty błędów,
  • skrypt był w repozytorium, a nie w prywatnym katalogu admina.

Częsta pułapka: tworzenie „pseudoskryptu”, który opisuje, co trzeba kliknąć w GUI narzędzia serwerowego. Taki „skrypt” nie nadaje się do CI/CD, bo nie da się go odpalić nienadzorowanie. Jeśli monolit wymaga interakcji z GUI (np. instalator MSI z wizardem), warto od razu szukać trybu cichej instalacji lub sposobu zbudowania instalatora, który da się uruchomić bez okienek.

Parametryzacja środowisk – jedna ścieżka wdrożeniowa, różne konfiguracje

Kluczowym celem przy automatyzacji wdrożeń jest jedna ścieżka wdrożeniowa dla wszystkich środowisk. Rozwijanie osobnych skryptów dla DEV, TEST i PROD kończy się rozjazdem i niemożnością przewidzenia, co tak naprawdę pójdzie na produkcję.

Bezpieczniejszy wzorzec to jeden skrypt wdrożenia, który przyjmuje parametry środowiskowe lub korzysta z osobnych plików konfiguracyjnych. Zwykle sprowadza się to do:

  • osobnych plików z konfiguracją (endpointy, connection stringi, feature flagi),
  • zmiennych środowiskowych,
  • integracji z sejfem sekretów dla haseł i kluczy.

Jeżeli różnice między środowiskami zaczynają dotyczyć samej logiki (np. inny sposób liczenia prowizji na TEST niż na PROD „bo tak się łatwiej testuje”), automatyzacja wdrożeń przestaje mieć sens – pipeline nie jest w stanie zasymulować warunków produkcyjnych.

Migracje baz danych jako część procesu, nie osobny rytuał

Monolit zwykle ma jedną, rozbudowaną bazę danych, której zmiany budzą najwięcej obaw. Typowe antywzorce:

  • migracje wykonywane ręcznie z pliku SQL w Management Studio / psql,
  • brak wersjonowania schematu – wiadomo, że „coś się zmieniło”, ale nie wiadomo kiedy i gdzie,
  • przechowywanie skryptów migracyjnych poza repozytorium, np. w mailach lub katalogach domowych.

Bardziej przewidywalne podejście to narzędzie do migracji schematu (Flyway, Liquibase, EF Migrations, Flywaydb dla .NET/Javy, inne). Nawet jeśli część migracji nadal powstaje jako ręcznie pisany SQL, to:

  • każda zmiana ma numer wersji i jest powiązana z konkretnym releasem,
  • migracje są uruchamiane z tego samego pipeline’u lub skryptu wdrożeniowego,
  • można odtworzyć stan bazy na dowolnym środowisku na konkretną wersję aplikacji.

Przy bardzo wrażliwych systemach dobrą praktyką są migracje „bezpieczne wstecznie”, czyli takie, które pozwalają chwilowo działać starej i nowej wersji równolegle (np. dodanie nowej kolumny, a dopiero w kolejnym wydaniu usunięcie starej). To spowalnia tempo zmian, ale znacząco zmniejsza ryzyko niedziałającego rollbacku.

Rollback, który naprawdę da się wykonać

W wielu firmach słowo „rollback” istnieje tylko w procedurach. W praktyce nikt go nie testuje, a po kilku wersjach złożone migracje bazy i zmiany konfiguracji uniemożliwiają realny powrót.

Jeżeli monolit ma być wdrażany automatycznie, rollback musi być tak samo przemyślany jak deploy. Kilka zasad, które rzadko zawodzą:

  • backup przed migracją – automatyczny, z jasnym miejscem przechowywania i sposobem przywrócenia,
  • niewielkie paczki zmian – krótsze migracje, mniejsze wersje, łatwiejszy powrót,
  • test rollbacku na niższych środowiskach – choćby raz na kilka wydań.

Pełny „downgrade” bazy do poprzedniego schematu bywa trudny, dlatego część zespołów wybiera model „rollforward only” – zamiast wracać do starej wersji, szybko wprowadzają kolejną, korygującą. Taki wariant jest realny tylko wtedy, gdy pipeline jest na tyle szybki i stabilny, że umożliwia wypuszczenie poprawki w krótkim czasie. Przy powolnych i ręcznych wdrożeniach to raczej pobożne życzenie niż strategia.

Stopniowe włączanie pipeline’u w proces release’ów

Monolit z ręcznym wdrażaniem rzadko od razu przeskakuje na pełne „push to prod z pipeline’u”. Bezpieczniejsza ścieżka to powolne wypieranie ręcznych kroków przez automatyzację:

  1. Pipeline jako narzędzie pomocnicze – buduje artefakt, ale wdrożenie na TEST/PROD nadal jest manualne (np. administrator pobiera paczkę z repozytorium artefaktów).
  2. Automatyczne wdrożenie na środowiska niższe – DEV/TEST/QA, z opcją ręcznego wdrożenia na PROD.
  3. Pipeline z manualnym „guzikiem” na PROD – cały proces jest zaszyty w pipeline’ie, ale wymaga akceptacji (manual gate) przed produkcją.
  4. Automatyczne wdrożenie na PROD dla wybranych typów zmian – np. tylko poprawki hotfixowe o małym zakresie, po przejściu kompletu testów.

Na każdym etapie dobrze jest porównać, ile czasu i błędów generuje stara ścieżka vs. nowa. Dopiero gdy zespół zobaczy, że pipeline rzeczywiście zmniejsza liczbę incydentów, pojawia się zaufanie niezbędne do dalszej automatyzacji.

Blue-green, canary i inne „chmurne” wzorce w świecie monolitu

Zaawansowane strategie wdrożeń (blue-green, canary) kojarzą się z mikroserwisami, ale część z nich da się zastosować także dla monolitów – pod warunkiem, że infrastruktura na to pozwala.

W praktyce wygląda to często bardziej siermiężnie niż w katalogach marketingowych:

  • blue-green – dwie maszyny / dwa zestawy serwerów z monolitem, przełączanie odbywa się na poziomie load balancera lub DNS; wymaga to podwojenia zasobów, co nie zawsze jest akceptowalne,
  • canary – wdrożenie nowej wersji tylko na części węzłów (np. 1 z 4 serwerów aplikacyjnych), obserwacja metryk i dopiero potem rozszerzenie; dla monolitu kluczowa jest kompatybilność z jedną bazą danych.

Dla wielu organizacji realnym celem na początek jest prostszy wariant: wdrożenie poza godzinami szczytu + szybki rollback. Gotowość monolitu do bardziej wyrafinowanych strategii zwykle pojawia się dopiero wtedy, gdy sama aplikacja i baza zaczynają być stopniowo rozszczepiane.

Monitoring i logi jako warunek sensownej automatyzacji

Automatyczne wdrożenia bez dobrego monitoringu to proszenie się o długie, nerwowe wieczory. Pipeline może powiedzieć „deploy zakończony sukcesem”, a użytkownicy i tak będą mieli problemy, których nikt nie wychwyci na czas.

Przy monolicie potrzebne jest przynajmniej kilka elementów:

  • metryki techniczne – CPU, pamięć, liczba requestów, czasy odpowiedzi, błędy 5xx,
  • logi skorelowane z wersją – w logach powinien pojawiać się identyfikator wersji/artefaktu,
  • proste alerty – np. nagły wzrost błędów 500 lub spadek liczby udanych logowań.

Bez tego trudno ocenić, czy konkretne wdrożenie pogorszyło sytuację, czy problemy mają inne źródło. Dobrą praktyką jest włączenie do pipeline’u kroku, który zapisze w systemie monitoringu informację o nowej wersji (np. event z numerem releasu). Pozwala to później zestawić timeline: „tu było wdrożenie, tu zaczęły rosnąć błędy”.

Utrwalanie wiedzy o procesie – runbooki zamiast legend

Na koniec aspekt często lekceważony: dokumentacja operacyjna. Przy ręcznych wdrożeniach wiedza bywa w głowie jednego admina. Przy automatyzacji potrzebne są runbooki – krótkie, rzeczowe instrukcje „co zrobić, gdy…”.

Nie chodzi o tworzenie tomów dokumentacji, tylko o konkretne scenariusze:

  • co zrobić, gdy pipeline deploymentu przerwie się w połowie,
  • jak ręcznie dokończyć lub odkręcić konkretne wydanie,
  • jak zweryfikować, że środowisko po wdrożeniu jest w poprawnym stanie.

Takie runbooki dobrze jest trzymać w tym samym repozytorium co skrypty, a nie na osobnych dyskach sieciowych. Z czasem część kroków z runbooków da się zastąpić automatami i testami, ale dopóki proces wdrożenia monolitu nie jest banalny, spisane procedury bywają jedyną linią obrony przed chaosem w stresujących momentach.

Najważniejsze punkty

  • DevOps w monolicie to nie „inna odmiana DevOps”, tylko start z gorszej pozycji: więcej długu technicznego, ręcznych kroków i wiedzy w głowach pojedynczych osób, więc celem jest najpierw ograniczenie ryzyka wdrożeń, a dopiero potem finezyjne narzędzia.
  • Największym problemem nie jest brak mikroserwisów, lecz brak powtarzalności: niespójne środowiska, ręczne wdrożenia i słabe testy automatyczne powodują „święte release’y” raz na kwartał, w które angażuje się pół firmy.
  • Popularne narracje typu „najpierw przepisywanie, potem CI/CD” czy „bez 80% pokrycia testami nie ma sensu pipeline” zwykle pełnią rolę alibi dla braku ruchu; realnie bardziej opłaca się rozwijać automatyzację małymi krokami równolegle z utrzymaniem.
  • Monolit szczególnie zyskuje na automatyzacji, bo każda zmiana potencjalnie wpływa na całość systemu; argument „mamy tylko jedną aplikację, po co pipeline” jest wymówką, nie analizą ryzyka.
  • Istnieją twarde ograniczenia (długi build, sklejone moduły, wspólna baza, trudne migracje) i z nimi trzeba mierzyć się technicznie, natomiast brak czasu, brak „teamu DevOps” czy wiara w cudowne narzędzie to głównie blokery mentalne.
  • Sensowny start to uczciwa inwentaryzacja: gdzie jest kod, jak dokładnie wygląda obecny build i wdrożenie, kto trzyma krytyczną wiedzę; bez tego próby wdrożenia CI/CD zamieniają się w zgadywanie i kolejne prezentacje zamiast realnej zmiany.