Backend, Bezpieczeństwo

Jak bezpiecznie wdrażać aplikację na produkcję

“Jedyny słuszny język programowania”, “jedyny słuszny framework”, “jedyne słuszne narzędzie do wdrażania aplikacji”. W mojej opinii, nie ma czegoś takiego. Bez zamknięcia tego w kontekście wyzwania, z którym mamy się zmierzyć, taka generalizacja nie ma sensu. Dlatego przedstawię swoje doświadczenia związane z wdrażaniem kodu, które było na dany moment oparte o (moim zdaniem) najlepsze dostępne rozwiązania. Były to rozwiązania, które spełniały swoje założenia, czyli dzięki nim dostarczaliśmy kod na produkcję. Czy były bez wad? Nie. Czy widzieliśmy możliwość usprawnień? Zdecydowanie tak.

W pracy związanej z programowaniem piękne jest to, że nie ma jednej słusznej drogi prowadzącej do celu. I nie mówię wyłącznie o pisaniu kodu – mam na myśli cały proces wytwarzania oprogramowania: od fazy koncepcyjnej, poprzez prototypowanie, implementację, testowanie, wdrażanie, klepanie się po plecach po odniesieniu sukcesu lub szukanie kozła ofiarnego po porażce.

Wiele z tych dróg może prowadzić na manowce, wiele jest też skrótów, które kuszą wizją szybszego osiągnięcia celu (czy na pewno?) oraz wiele takich, które faktycznie doprowadzą nas do realizacji naszego celu. Specjalnie kładę nacisk na “wiele” ponieważ dużo zależy od nas samych, naszej kreatywności, Twojej i zespołu, w którym pracujesz.

Początki bywają trudne (proste?)

Z komercyjnym programowaniem mam doświadczenie od 2005 roku. Cały ten okres nauczył mnie tego, że poczucie braku wiedzy jest u mnie permanentne. Przez ten czas jednak obserwowałem, jak zmienia się podejście do wdrażania kodu na świecie, jak i w firmach, w których pracowałem. Sam miałem ten przywilej wgrywania kodu na produkcję za pomocą ftp i pamiętam swoje modlitwy, aby w tym czasie nikt inny nie nadpisał moich plików. Jednak to było lata temu, i “tak to się robiło” w tamtym czasie.

Ftp, sftp, svn/git, phing, ansible, postępująca automatyka, testy, Bamboo, Jenkins… to tylko niektóre narzędzia, które przysłużyły mi się w wydawaniu kodu. Jednak to jest tylko narzędziówka, która zmienia się z czasem.

Dziś mogłoby się wydawać, że użycie ftp do wdrażania kodu to archaizm. Jednak z rozmów w “społeczności” wynika, że tak do końca nie jest. Nawet patrząc z perspektywy mojej pracy w Displate.com to nie taka odległa przeszłość, ponieważ jeszcze 3-4 lata temu używane były skrypty, które wygrywały kod na serwery produkcyjne. Nie wstydzę się o tym pisać, ponieważ taka sytuacja jest wpisana w naturę startupów.

Niektóre mają to szczęście, że projektują je ludzie, którzy znają się na robocie i robią to dobrze, zachowując konsensus pomiędzy elastycznością biznesową, a jakością technologiczną. Inne z kolei są projektowane przez ludzi bez dużego zaplecza programistycznego jednak mających wizję połączoną z pasją realizacji.

Z czasem niektóre projekty upadają, inne jednak nabierają wiatru w żagle i są rozwijane na podstawie tego, co już zostało zrobione. Z upływem czasu świadomość pewnych barier zaczyna się coraz bardziej uwidaczniać. Displate był właśnie w takim miejscu. Świadomość tego, że wraz z rozwojem serwisu od strony biznesowej musi też iść rozwój od strony technicznej, coraz bardziej była zauważalna. Dzięki temu mogliśmy usprawnić wiele procesów w dziale IT. Teraz sytuacja wygląda zupełnie inaczej – czarne wieki FTP odeszły w niepamięć nastała nowa era CI/CD.

Czytając ponownie to, co napisałem powyżej można odnieść wrażenie, że to było takie “tadaaaa” i nagle mamy CI/CD. To nieprawda. Musisz wiedzieć, że to nie jest prosty proces w szczególności jeśli pracujemy na istniejących już aplikacjach, które wymagają również refaktoringu. Mamy jeszcze wiele pracy przed sobą, wiele rzeczy do ogarnięcia i wdrożenia. Jednak cały czas mamy świadomość, co było wcześniej, gdzie jesteśmy teraz i to w którym kierunku chcemy podążać.

Strategie wdrażania kodu

Nie wiem jak bardzo doświadczonym programistą jesteś, nie wiem czym się zajmujesz, jednak ważna jest świadomość sytuacji w jakiej znajduje się Twój projekt, ewentualnie projekt w firmie, do której chcesz dołączyć. Nie należy demonizować tego, że w danej chwili firma nie używa tego, co jest “top of the top”. Ważne jest podejście firmy do zmian. Jeśli jest zgoda i chęć stopniowego ulepszanie procesu, to już jesteśmy na dobrej drodze.

To, o czym wcześniej pisałem to było głównie podkreślenie podejścia firmy do zmian w tym do zmian przy wdrażaniu kodu oraz pobieżnie wymieniona narzędziówka, która jedynie dotykała tematu. Jednak sama narzędziówka nie zapewni nam bezpiecznego wdrożenia. To jest trochę jak z pieniędzmi (podobno) szczęścia nie dają, ale jednak szczęściu pomagają.

Same narzędzia jednak to nie wszystko. Ważna jest również strategia, za pomocą której wdrażamy kod. Poniższe strategie nie są wszystkimi możliwymi podejściami, jednak na pewno stanowią pewną bazę, od której możesz zacząć wdrażać się głębiej w temat.

Oto one:

  • Recreate
  • Ramped (rolling update)
  • Blue/Green
  • Canary
  • A/B testing
  • Shadow

Każda z tych strategii zasługuje na osobny artykuł. Wspomnę jedynie w kilku zdaniach o każdej z nich i odrobinę rozwinę temat jednej z nich a mianowicie “Shadow”. Dlaczego na niej? To nie jest technika, która jest przez nas najczęściej wykorzystywana, ale jest na tyle interesująca i niosąca ze sobą bardzo ciekawe możliwości, że warto ją poznać odrobinę lepiej.

Trochę teorii

Recreate – można powiedzieć, że to strategia odchodząca w niebyt, ze względu na czasową niedostępność serwisu, na który wdrażamy kod.

Proces:

  • zatrzymanie wszystkich instancji produkcyjnych,
  • wdrożenie nowej wersji kodu,
  • uruchomienie serwisu na instancjach działających z nową wersją kodu.

Niewątpliwym plusem jest prostota takiego podejścia i w niektórych przypadkach użycie tej strategii może być nawet uzasadnione, jednak czasowa niedostępność serwisu zdecydowanie przekreśla to podejście jako domyślną strategię dostarczania nowych funkcjonalności.

Ramped – stopniowe zastępowanie (rolling) istniejących instancji na nowy kod.

Proces:

  • zatrzymanie jednej (bądź n) instancji,
  • wdrożenie nowej wersji kodu na zatrzymane instancje,
  • uruchomienie nowych instancji,
  • przepięcie na nie ruchu produkcyjnego,
  • zatrzymanie pozostałych instancji ze starym kodem (nie obsługujących już ruchu),
  • wdrożenie nowej wersji kodu na zatrzymane instancje,
  • uruchomienie zatrzymanych instancji,
  • wpuszczenie na nie ruchu.

Proces może wydawać się skomplikowany, jednak wbrew pozorom taki nie jest. W tej chwili jest to najczęściej wybierany (to nie znaczy, że zawsze) sposób wdrażania kodu.

Blue/Green

Proces:

  • wdrażamy nowy kod na n instancji (przeważnie n jest równa liczbie instancji obsługujących ruch),
  • przeprowadzamy/uruchamiamy testy na nowych instancjach w konfiguracji produkcyjnej,
  • po pozytywnie przeprowadzonych testach przepinamy ruch na instancje z nową wersją kodu.

Dużym plusem tego podejścia jest bardzo szybki rollback wprowadzonych zmian gdyby wyniknął jakiś problem w trakcie testów. Należy też pamiętać, że etap testowania nie jest czymś, co zastępuje dotychczasowe testy (unit, funkcjonalne, kontraktowe) tylko stanowi ich uzupełnienie.

Canary

Proces:

  • wdrażamy nowy kod na n instancji,
  • ruch jest proporcjonalnie kierowany na instancje z nowym i starym kodem (np 90% stare, 10% nowe).

Dzięki takiemu podejściu jesteśmy w stanie produkcyjnie uruchomić jednocześnie starą i nową wersie kodu, co daje nam możliwość monitoringu i obserwacji jak nowa wersja zachowuje się w realnym środowisku pracy (stabilność, performance itp.). W końcu nikt nie jest lepszym testerem od użytkownika końcowego.

Pół żartem, pół serio dzięki tego typu wdrożenia angażujemy użytkownika niejako do testowania nowych wersji kodu. Pozostaje nam obserwować i reagować. Muszę podkreślić, że stosowanie blue/green deployment nie upoważnia nas do przykładania mniejszej wagi do procesu testowania w trakcie implementacji. Tutaj otrzymujemy możliwość zbadania takich czynników, które ciężko by było nam wytworzyć na środowiskach testowych (np. jak aplikacja zachowa się podczas obciążenia realnym ruchem).

A/B Testing

Proces:

  • wdrażamy nowy kod na n instancji,
  • ruch na podstawie “określonych kryteriów” (np. kraj z którego pochodzi użytkownik) jest dzielony pomiędzy stare i nowe instancje.

Jak pewnie zauważyłeś, to podejście jest bardzo zbliżone do poprzedniego (cannary), ale tutaj jest pewna różnica. Celem wdrożenia nie jest dostarczenie nowej wersji oprogramowania samo w sobie, ale prowadzenie eksperymentów. Eksperymentów, które dadzą odpowiedź na zadane pytania biznesowe, np. czy nowy sposób prezentacji produktu przyczyni się do wzrostu sprzedaży. Konfrontujemy ze sobą starą i nową wersję aplikacji. Zapinamy odpowiedni monitoring metryk i przeprowadzamy test.

Użytkownicy na podstawie pewnych kryteriów np. oznaczeń w ciastku czy lokalizacji, kierowani są na odpowiednią wersję oprogramowania, a nam nie pozostaje nic innego, jak uzbroić się w cierpliwość i czekać na sygnał, czy nasz “super pomysł na ficzer” jest tak super jak nam się wydawało.

Shadow

Proces:

  • wdrażamy nowy kod na n instancji (przeważnie na taką samą ile jest instancji produkcyjnych),
  • ruch jest klonowany obsługiwany zarówno przez starą i nową wersję kodu.

“Ruch jest klonowany” – o co chodzi?

Nowy wersja aplikacji obsługuje kopię ruchu produkcyjnego, jednak nie ma to wpływu na samego użytkownika, działa niejako w cieniu (shadow). Możemy ją potraktować jak kopię środowiska wraz z warstwą utrwalania stanu (zapisu). Różnica jest jedynie w nowych funkcjonalnościach dostarczonych z daną wersją kodu. Użytkownik nawet nie jest świadomy tego, że jego żądanie jest obsługiwane de facto przez dwie aplikacje.

Rezultat, który zobaczy w wyniku swoich działań będzie pochodził z aplikacji zbudowanej na podstawie starej wersji kodu. Co w takim razie daje nam ten sposób wdrażania aplikacji? Niewątpliwie jest to bezpieczeństwo. Jesteśmy w stanie w środowisku produkcyjnym poprzez monitorowania nowych instancji stwierdzić czy nowa wersja aplikacji działa poprawnie. Co ważne sprawdzamy jej działanie na rzeczywistym (pamiętaj: kopii) ruchu, dzięki temu nie tylko testujemy poprawność biznesową, ale też samą wydajność przy pełnym obciążeniu.

W przypadku jakichkolwiek problemów, czy to związanych z procesami biznesowymi, czy wydolnościowymi, jesteśmy w stanie bardzo prosto usunąć wdrożoną aplikację, usuwając instancje, na których działa. Użytkownik pozostanie w błogiej nieświadomości, że gdzieś w cyfrowym świecie wydarzyła się “tragedia” i dalej będzie mógł bez przeszkód używać swojego ulubionego serwisu.

Czy to podejście ma wady? Owszem. Jedną z nich jest koszt samego wdrożenia. Musimy powołać do życia kopię środowiska produkcyjnego i utrzymywać ją przez pewien czas, a to wiąże się oczywiście z $$$. Trzeba też pamiętać, że w pewnych specyficznych przypadkach, np. płatnościach, musimy odpowiednio obsłużyć ten proces. Nie możemy obciążyć użytkownika podwójnymi kosztami pochodzącymi ze starej i nowej wersji aplikacji, podobnie nie powinniśmy wysyłać dwóch maili dotyczących tego samego zakupu. Jak widzisz tych pułapek trochę jest, zatem do tematu trzeba podejść z głową.

Więcej na drugiej stronie artykułu.“Jedyny słuszny język programowania”, “jedyny słuszny framework”, “jedyne słuszne narzędzie do wdrażania aplikacji”. W mojej opinii, nie ma czegoś takiego. Bez zamknięcia tego w kontekście wyzwania, z którym mamy się zmierzyć, taka generalizacja nie ma sensu. Dlatego przedstawię swoje doświadczenia związane z wdrażaniem kodu, które było na dany moment oparte o (moim zdaniem) najlepsze dostępne rozwiązania. Były to rozwiązania, które spełniały swoje założenia, czyli dzięki nim dostarczaliśmy kod na produkcję. Czy były bez wad? Nie. Czy widzieliśmy możliwość usprawnień? Zdecydowanie tak.

Inne spojrzenie na shadow deployment

Jeśli odejdziemy trochę od książkowego podejścia, to można powiedzieć, że strategie: canary, a/b testing, shadow posiadają pewną również trochę inną interpretację. Jeśli chodzi o samą zasadę to pozostaje ona bez zmian. Przestajemy jednak mówić o instancjach, a skupiamy się na “ficzerach”, czyli nowych funkcjonalnościach, które chcemy udostępnić użytkownikom i przetestować w środowisku produkcyjnym. Sam kod jest dostarczany wtedy na produkcję dzięki strategiom ramped lub blue/green (o recreate nie wspominam z wiadomych względów).

Finalnie wszystkie instancje obsługujące naszą aplikację będą oparte na tej samej wersji kodu. Jednak to nie znaczy, że każdy ma dostęp do wszystkich funkcjonalności. Jeśli mówimy o cannary i a/b tsting to przywilej korzystania z nich ma tylko pewna część wybrańców, którzy spełniają określone “warunki” (np. pochodzą z danego kraju).

Gdy omawiałem strategie wdrożenia na podstawie instancji, propagacja ruchu była realizowana poza aplikacją na warstwie infrastruktury (load balancer). Jednak w tym przypadku, kiedy udostępniamy tylko poszczególne funkcjonalności, to już aplikacja musi zadbać o to, w jaki sposób będą one udostępniane. Bardzo użyteczne w tym przypadku jest zaimplementowanie mechanizmu “feature flags”, dzięki któremu jesteśmy w stanie sterować naszą aplikacją i po części użytkownikami, którzy w niej korzystają. Pisząc “sterować”, mam na myśli włączanie/przełączanie/wyłączanie konkretnych funkcjonalności dla użytkowników, którzy spełniają określone reguły np. lokalizacja, posiadają konkretne ciastko, itp.

Inaczej ma się sprawa, gdy mówimy o strategii “shadow”, w tym przypadku użytkownik musi zostać przeprowadzony starą ścieżką, jeśli chodzi o dany proces np. dodanie produktu do systemu, jak również w tle (w cieniu) musi zostać przeprocesowana nowa ścieżka. Najlepiej, żeby to nie miało wpływu na użytkownika, zarówno jeśli chodzi o stan aplikacji (czyli nie może nastąpić sytuacja, że dodał dwa takie same produkty), jak i o kwestie wydajnościowe. Jednak nie zwalnia nas to z obowiązku zarejestrowania, jak działa funkcja uruchomiona w tle.

Finalnie chcemy przeanalizować jej działanie i porównać dane w stosunku do jej starszej wersji. W tym podejściu jest więcej pułapek, na które musimy uważać. Bardzo dużo zależy od tego, co chcemy wdrażać i nakład pracy poświęcony na odpowiednie przeprocesowanie użytkownika jest znaczący.

Czy ta strategia jest najlepsza, ze wszystkich możliwych? Nie jest. Nie jest też podejściem często przeze mnie wybieranym ponieważ wdrożeń gdzie jest sens ją stosować wbrew pozorom nie jest dużo. Nasuwa się więc pytanie czy jest sens poświęcać tej strategii więcej uwagi? Moim zdaniem jest i to nie podlega dyskusji.

Historia pewnej porażki

Wyobraź sobie sytuację, że musisz wraz z zespołem refaktoryzować jakąś kluczową część systemu. Przykładem może być system do naliczania prowizji dla użytkownika. Co teraz robisz? Zapewne wydzieracie tą część systemu z monolitu, który pamięta jeszcze czasy, gdy zarywaliśmy nocki grając w Quake 3 Arena (jeśli nie wiesz co to, to już w samo w sobie znaczy, że to coś starego). Tworzycie bazylion mikroserwisów (lub nie), rozpraszacie wszystko, co tylko możliwie i gdzie to tylko możliwe, aż finalnie zbliża się godzina zero.

Czas na wdrożenie, uruchomienie i zebranie pochwał. Wybieracie okienko czasowe, gdzie ruch jest najmniejszy, czyli pewnie gdzieś w nocy, zamawiacie pizze, wdrażacie kod i… wszystko działa, prowizje się naliczają, wypłaty są realizowane.

Rano poklepujecie się po plecach, zbierasz gratulację od szefostwa. Z niecierpliwością zaczynasz wyczekiwać końca miesiąca z myślą o bonusie za udane wdrożenie strategicznego projektu. Aby zabić czas zabierasz się za naprawianie kolejnej części aplikacji. Następuje chwila spokoju, a Ty oddajesz się nowemu projektowi. Po pewnym czasie zaczynają jednak napływać niepokojące wieści z działu obsługi klienta.

Coś komuś się nie naliczyło. Ktoś miał problem z wypłatą. Co gorsza to nie są generyczne przypadki. Problem pojawia się wybiórczo i nie jesteś w stanie określić skali osób nim dotkniętych. Zaczynają się niewygodnie pytania w stylu: co poszło nie tak? Liczba zgłoszeń rośnie wprost proporcjonalnie do niezadowolenia użytkowników z serwisu.

Grunt coraz bardziej zaczyna palić się wam pod stopami, nie jesteście w stanie odtworzyć prawidłowego stanu systemu, zaczyna się szukanie winnego, premie zostaną cofnięte… i tak naprawdę jesteście w cz….. d…. .

Jesteś pewien, że chcesz coś takiego przeżywać? Ja przeżyłem i uwierz mi, nie jest to nic miłego.

Oczywiście trochę przejaskrawiłem, jednak to dobrze obrazuje pewien przypadek, gdzie jak najbardziej sensowne jest użycie podejścia “shadow”, czyli tak zwanego równoległego uruchomienia starej i nowej wersji systemu.

W sytuacjach gdy:

  • potrzebujemy monitorować działanie usługi w dłuższym okresie,
  • potrzebujemy sprawdzić zachowanie usługi przy obciążeniu produkcyjnym,
  • wdrażamy kluczowe części systemu,
  • refaktorujemy serwis i chcemy mieć pewność, że z biznesowego punktu widzenia nic się nie zmieniło,
  • wszystkie lub dowolna kombinacja powyższych,

… jak najbardziej uzasadnione jest poświęcenie czasu na obsługę shadow deployment.

Wdrożenie to nie tylko wgranie kodu na produkcję

Wdrożenie aplikacji nie kończy się w chwili wrzuceniu kodu na produkcję. Cały proces rozkłada się w czasie na konkretne etapy, a podział na etapy dotyczy zarówno kontekstu instancji, jak i ficzerów:

1. Równoległe uruchomienie dwóch wersji aplikacji/ficzerów (zapewne pamiętasz, że jedna ma działać w tle).

2. Monitoring obu wersji. To czas gdy zbieramy dane. Porównujemy rezultaty operacji jakie zostały przeprowadzone przez starą wersję i nową wersję. Przykładowo jeśli dokonujemy “tylko” rafaktoryzacji to oczekujemy, że naliczone zostaną identyczne prowizje zarówno przez stary system jak również przez jego nową implementację. Dzięki temu, że trzymamy te dane osobno możemy je ze sobą zestawić. Przeszukać pod kątem anomalii, porównać wygenerowane raporty itp.

Dodatkowo jesteśmy również w stanie stwierdzić czy nowa wersja działa stabilnie przy produkcyjnym obciążeniu. Ile powinny trwać tego rodzaju testy? Nie ma reguły, to zależy w dużej mierze od tego, co jest wdrażane i od czasu potrzebnego do nabrania pewność że zebrane dane i przeprowadzone analizy dają jednoznaczną odpowiedź na pytanie czy aplikacja działa poprawnie. Na tym etapie, gdy wyjdą jakiekolwiek błędy to jesteśmy w stanie albo bezboleśnie je załatać i wgrać poprawkę albo jeśli to jest coś poważniejszego wycofać całe wdrożenie (jednak to już jakiś ekstremalny przypadek). Czy użytkownik będzie miał świadomość, że coś skaszanilśmy, absolutnie nie. On nawet nie będzie wiedział, że gdzieś działała jakaś inna wersja kodu.

3. Finalnie jeśli wszystkie znaki na niebie, ziemi i nawet w /dev/null wskazują, że wdrożone zmiany działają poprawnie możemy przełączyć wersję kodu obsługujące użytkownika. Podkreślam – przełączyć. Nowa wersja zaczyna obsługiwać żądania od użytkownika, a stara wersja przechodzi do podziemia i zaczyna działać w tle. Zauważ, że nie musimy dokonywać żadnych migracji danych itp, ponieważ wcześniej aplikacja działająca w tle cały czas odkładała i zapisywała dane, a Ty te dane zweryfikowałeś i stwierdziłeś że są poprawne. Ostatni raz już wspomnę, że pozostawienie poprzedniej wersji kodu działającej w tle jest bardzo istotne na tym etapie. Dlaczego? Dowiesz się w następnym punkcie.

4. Następuje kolejny proces weryfikacji. Pewnie ciśnie Ci się na usta pytanie: “kurcze, ile to razy można weryfikować aplikację, robiliśmy to podczas developmentu, procesu akceptacyjnego, przeszły automaty, podczas działania w tle i teraz kolejny raz, daj pan żyć, ile jeszcze?”

Zauważ, jedną istotną różnicę. Teraz nowa wersja aplikacji jest dostępna publicznie i używana przez użytkownika, a poniekąd wiadomo, że nie ma lepszego testera aplikacji niż użytkownik końcowy i jest spore prawdopodobieństwo, że zgłosi nam napotkany przez siebie błąd, a wręcz pewność, jeśli na tym błędzie jest w jakiś sposób stratny. Jeśli w firmie, w której pracujesz jest dział obsługi klienta, to jest to najlepszy moment, aby go poinformować o wdrożeniu nowej wersji usługi i w razie jakichkolwiek zgłoszeń powinien bezpośrednio Ciebie (zespół) informować.

Dzięki takiemu podejściu jesteśmy w stanie szybko dowiedzieć się o potencjalnych problemach, których nie udało Ci się wychwycić a przytrafiły się użytkownikom. W przypadku gdyby pojawiło się coś poważnego, czego w odpowiednio krótkim czasie nie będziesz w stanie naprawić, to ponownie przełączamy wersje aplikacji. Ruch użytkowników ponownie jest procedowany przez starszą wersję i to bez żadnych migracji danych, ponieważ cały czas działała w tle i przeprowadzałą równolegle procesy biznesowe utrwalając ich stan. Ty w tym czasie na spokojnie i bez stresu (powiedzmy) tworzysz łatki do nowego kodu.

5. Z czasem nabierzesz pewności co do działającego kodu i nadchodzi chwila wyłączenia oraz usunięcia z kodu zbędnej już starej wersji aplikacji. Dopiero na tym etapie można powiedzieć, że wdrożenie kodu się zakończyło.

Dla osób, które przytłoczyła ilość tekstu napiszę bardziej zwięzłą wersję.

  1. Równoległe uruchomienie dwóch wersji aplikacji/ficzerów (zapewne pamiętasz, że jedna ma działać w tle).
  2. Monitoring obu wersji, porównanie rezultatów ich działania.
  3. W przypadku gdy rezultaty są zgodne z oczekiwanymi przełączamy użytkownika na nową wersję a stara zaczyna działać w tle.
  4. Ponowny monitoring, analizujemy również sygnały napływające od użytkowników.
  5. Finalnie usuwamy kod powiązany ze starą wersją aplikacji.

Mam nadzieję, że teraz dostrzegasz potencjał tego rodzaju deploymentu i masz świadomość kiedy jest sens go stosować. Ostatnim razem, gdy użyłem tej strategii, było wdrożenie nowej wersji generowania miniatur prac (grafik) serwowanych użytkownikowi.

Przykład z życia

Displate to serwis, który opiera się na grafikach. Użytkownik szuka plakatu, my go drukujemy na metalu i dzięki temu nasz produkt staje się w pewien sposób unikalny. Na pewnym etapie przygniotła nas skala grafik do obsłużenia sięgająca już nie tysięcy, ale milionów prac.

Stare rozwiązanie nie udźwignęło tematu, a nieskończone skalowanie serwerów w górę pożerało budżet. Finalnie zaimplementowaliśmy rozwiązanie oparte na Cloudflare + AWS Lambda + S3 + PHP, jednak z perspektywy artykułu istotny jest sposób wdrożenia.

Ze względu na to, że rozproszyliśmy nasz system, praktycznie całą implementację mogliśmy uruchomić “na boku” produkcyjnie i testować niezależnie, z racji tego, że to były nowe usługi. Nie byliśmy jednak w stanie sprawdzić jakości wszystkich grafik, tym bardziej, że konkretne miniatury były generowane na żądanie, a nie podczas wgrywania pliku do systemu.

Dodatkowo musieliśmy zmienić adresację do nowego miejsca, gdzie zapisywaliśmy wygenerowane pliki miniatur. Nie mogliśmy sobie pozwolić na sytuację, w której uruchamiamy nowy system na produkcji i nagle zasypują nas informacje o błędach przy generowaniu plików, lub użytkownicy będą wysyłać screeny błędnymi grafikami lub ich brakiem. Doszliśmy więc do wniosku, że najsensowniejszym rozwiązaniem na wdrożenie będzie shadow deployment.

W pierwszym etapie wdrożenia przy każdym żądaniu o grafikę w tle nowy system generował nową. Już na tym etapie okazało się, że podjęliśmy słuszną decyzję. Wgrywane grafiki okazały się na tyle różnorodne, że część z nich wygenerowała problemy, do których po rozwiązanie trzeba było zapuścić się przynajmniej na drugą stronę z wynikami wyszukiwania w googlu.

Dokonfigurowanie lambdy zajęło nam około dwóch tygodni. Podczas gdy my ciężko pracowaliśmy w tym okresie, użytkownicy – na tyle na ile pozwalał stary system – bez problemu używali bieżącej wersji serwisu. Po przepięciu systemu na nowy udało się już uniknąć zgłoszeń od użytkowników, a to, co się pojawiło, to były już problemy z samymi wgrywanymi plikami (niepoprawna kompresja, itp), a nie z systemem. Tak więc z perspektywy czasu mogę powiedzieć, że wybór okazał się słuszny i dał nam przestrzeń do działania, gdy pojawiły się problemy. Dodatkowo system zmierzył się z obciążeniem produkcyjnym, co utwierdziło nas w przekonaniu, że nie będzie z nim problemów pod tym kątem po udostępnieniu go użytkownikom.

Parę słów na koniec

Jeśli jesteś w tym miejscu to jestem pod wrażeniem, że wytrwałeś i dziękuję Ci za to.

Możesz się poczuć trochę przytłoczony tym wszystkim, w szczególności gdy wcześniej nie miałeś z tym tematem do czynienia. Jednak nie przejmuj się, z czasem wszystko się zacznie układać w jedną całość. Które z tych rozwiązań jest najlepsze? Moja odpowiedź: “to zależy”.

Dla mnie nie ma w tym przypadku jedynej słusznej drogi. Zdecydowanie nie polecam “Recreate” przy wdrożeniach produkcyjnych. Jeśli chodzi o środowiska developerskie/akceptacyjne to ten rodzaj wdrażania jest jeszcze do przyjęcia, jednak jeśli chodzi o wdrożenie produkcyjne, to nigdy przenigdy go nie stosuj. Jeśli w tej chwili tak wygląda Twój sposób wdrażania nowych wersji, to proponuję poświęcić trochę czasu temu aspektowi i zmienić ten sposób na dowolny inny.

Na zakończenie daję Ci taką oto złotą myśl: “nie myśl tylko happy path’em, przygotuj sobie też plan na wypadek, gdyby coś poszło nie tak”. Czasami może to być szybkie wycofanie zmian, czasami po prostu wyświetlanie użytkownikowi stosownej informacji o niedostępności usługi. Ważne, aby ten plan był, dzięki temu będziesz miał więcej czasu na reakcję w przypadku ewentualnych problemów.

Obyś miał jak najmniej nieudanych wdrożeń!

teleturniej programista100k

Zdjęcie główne artykuły pochodzi z unsplash.com.

Tomasz Tomczyk. Wcześniej fizyk i tancerz, obecnie Janusz programowania (Backend Team Lead w Displate) oraz ogarniacz mozołów życia codziennego. W pracy ceni otwarty umysł i szacunek do zastanego kodu.

Podobne artykuły