Czy zawsze warto “iść w chmurę” ze swoim SaaSem? Anatol, jeden z absolwentów Akademii SaaS, o którego aplikacji do zarządzania budżetem domowym pisałem tutaj, miał ostatnio nieciekawą przygodę.
Kiedy Anatol opisał na prywatnej grupie Akademii SaaS tę dość stresującą dla każdego twórcy aplikacji historię poprosiłem go o podzielenie się wydarzeniami z szerszą publiką.
Zapraszam do czytania i komentowania. Swoją drogą ostatnio Michał Szafrański wspomniał aplikację Anatola w jednym z wpisów na swoim blogu.
Dlaczego wybrałem chmurę
Od ponad roku pracuję nad rozwojem aplikacji do zarządzania finansami osobistymi Family Finance Tracker. W momencie gdy udostępniłem aplikację szerszemu gronu Użytkowników, zdecydowałem się na hosting w Google Cloud. Pomysł był według mnie całkiem fajny i miał praktyczne uzasadnienie. Korzystam z uwierzytelniania przez Google Firebase, więc sensownym wydawało się posiadanie wszystkich usług u jednego providera.
Poza tym wizja prostego skalowania zarówno aplikacji jak i bazy danych – automatycznie, lub za pomocą kilku kliknięć była kusząca. Powszechna opinia o tego typu usługach jest taka, że to po prostu działa i zdejmuje z developera wiele dev-ops’owych obowiązków. Można się wtedy w pełni skupić nad rozwojem aplikacji.
Moja aplikacja napisana jest w Javie, uruchamiana za pomocą Spring Boot. Z tego powodu zdecydowałem się na wykorzystanie mechanizmu AppEngine, za pomocą którego można w Google Cloud uruchamiać obrazy Docker’owe.
To podejście ma swoje minusy. Jednym z nich jest chociażby dość wysoki koszt. W ten sposób nie da się wyskalować aplikacji do zera, tak aby nie płacić za serwery w momencie gdy Użytkownicy nie korzystają z aplikacji (np. w nocy).
Mimo dość wysokich kosztów finansowych, rozwiązanie jest bardzo wygodne. Wdrożenie nowej wersji można przeprowadzać w prosty sposób – z wiersza poleceń, lub bezpośrednio z IDE – za pomocą odpowiednich pluginów. Można to robić w dowolnym momencie, zapewniając jednocześnie uptime 100%, ponieważ najpierw uruchamiana jest nowa wersja, w momencie gdy jej wdrożenie zakończy się powodzeniem, Google Cloud automatycznie przekierowuje cały ruch ze starej wersji na nową. Następnie stara wersja jest wyłączana i w ten sposób pełny ruch obsługiwany jest wyłącznie przez nową.
Przez ponad rok wszystko działało bez zarzutu. Przez ten czas wdrażałem kilkanaście nowych wersji. Bez najmniejszych problemów.
Niespodziewana awaria
Problem pojawił się kilka dni temu, wieczorem – około 19:30, gdy próbowałem zrobić kolejne wdrożenie. Nie była to żadna duża wersja – standardowe wdrożenie, poprawiające kilka błędów, update’ujące kilka bibliotek. Bez żadnych zmian w schemacie bazy danych.
Po zakończonym sukcesem procesie deployment’u, aplikacja przestała działać. Każda próba wejścia na stronę kończyła się błędem HTTP 502.
Próby reanimacji usługi
Nie ukrywam, że dość mocno się zestresowałem i podjąłem próby rozwiązania problemu. Przejrzałem logi aplikacji. Wszystko wskazywało na to, że wystartowała poprawnie. Mimo to nie działała:
Nie było żadnych dodatkowych błędów czy wyjątków. Potem pojawiły się jedynie standardowe logi związane z procesami aplikacji. Pomimo tego, że na pierwszy rzut oka wszystko uruchamiało się normalnie, aplikacja nadal nie działała.
Po kilkunastu bezowocnych minutach szukania problemu, podjąłem decyzję, o uruchomieniu poprzednio działającej wersji, i przekierowanie na nią całego ruchu. Chciałem mieć czas na spokojną analizę problemu.
Okazało się jednak, że operacja, która powinna być formalnością i zapewniać bezpieczne przywrócenie funkcjonalności aplikacji, również nie działa.
Po uruchomieniu poprzedniej wersji, próbowałem przekierować na nią cały ruch.
Przekierowanie w AppEngine jest proste. Służy do tego dedykowany formularz, który wygląda tak:
Po wybraniu odpowiednich ustawień, i kilkudziesięciu sekundach oczekiwania, otrzymywałem komunikat: “Wystąpił nieoczekiwany błąd”, bez żadnego dodatkowego wyjaśnienia. Próbowałem kilkukrotnie. Dokładnie takie same przekierowania. Dopiero jedno z kolejnych zakończyło się powodzeniem.
Potem na spokojnie udało mi się odnaleźć w logach aktywności ślady po tych operacjach:
Przypominam, że wszystkie operacje były wyklikane z GUI, bez żadnej dodatkowej ingerencji z mojej strony.
Gdy po kilkunastu minutach udało mi się przekierować wreszcie ruch na poprzednią, działającą przez prawie miesiąc wersję, okazało się, że obecnie również ta wersja nie działa poprawnie. W tym momencie prawie wpadłem w panikę.
Nic nie działa i nie wiadomo dlaczego
Byłem praktycznie bezradny. Analizowałem logi, szukałem przyczyn, nie byłem w stanie ustalić, dlaczego aplikacja nie chce działać.
Sprawdzałem bazę danych. Na wykresach z liczbą połączeń działy się dość dziwne rzeczy. Widać było, że aplikacja nawiązuje połączenia, następnie po jakimś czasie liczba aktywnych połączeń spada do zera. Bez żadnych niepokojących wpisów w logach.
Moja nierówna walka z Google Cloud trwała około 3 godziny. Próbowałem różnych rzeczy. Wersję, która działała poprawnie dotychczas, zostawiłem i nic w niej nie zmieniałem. Nowo wrzucane wersje usuwałem, próbowałem robić redeployment. Próbowałem ponownie wrzucać starsze wersje z TAG’ów w GIT. Za każdym razem skutek był identyczny – brak działania aplikacji. W międzyczasie niektóre z deploymentów kończyły się błędami, podczas gdy chwilę później dokładnie ta sama wersja instalowała się poprawnie.
Próbowałem logować się po SSH, do uruchomionych instancji aplikacji, żeby tam, bezpośrednio w logach maszyny szukać informacji. I tu kolejna niespodzianka – brak możliwości zalogowania się po SSH. Otwierało się okienko połączenia i zatrzymywało się na takim stanie:
Próbowałem bezpośrednio z linuksowego terminala – skutek był analogiczny. Brak możliwości zalogowania po SSH – nie wiadomo dlaczego.
Trudna decyzja – rezygnuję z chmury
W tym momencie skończyły mi się wszystkie pomysły i sposoby na rozwiązanie problemu w skończonym czasie.
Pozostała tylko jedna opcja – rezygnacja z GoogleCloud i szybkie przeniesienie na serwer dedykowany. Miałem ten komfort, że architektura aplikacji mi na to pozwala. Zastanawiałem się jedynie jak długo to może potrwać.
Rzuciłem okiem na ofertę jednego z providerów, u których mam serwer VPS, gdzie testuję aplikację. Okazało się, że miesięczny abonament za dedykowany, dużo bardziej wydajny serwer, wyniesie tyle samo, co hosting w Google Cloud na minimalnych ustawieniach.
Złożyłem zamówienie, opłaciłem online. Około godzinę oczekiwałem na przygotowanie serwera. W tym czasie nadal bezskutecznie próbowałem reanimować usługę w Google Cloud.
Gdy po godzinie otrzymałem namiary na serwer, a chmura nadal nie działała, nie było już odwrotu. Przepiąłem domenę na nowy numer IP, tak aby w czasie gdy ja będę konfigurował serwer, DNSy już się propagowały.
Instalacja linuksa, bazy danych, konfiguracja uprawnień, dostępów, odtworzenie bazy danych, serwisy systemowe, java, certyfikaty SSL, nginx i jeszcze kilka rzeczy. Po dwóch dodatkowych godzinach miałem gotową, działającą konfigurację serwera. O drugiej nad ranem jeszcze część DNS’ów nie działała, ale mogłem już na wybranych potwierdzić poprawne działanie aplikacji.
Łącznie 3 godziny – tyle zajęła “ucieczka” z Google Cloud.
Wnioski – czyli czego ta sytuacja mnie nauczyła
Myślę, że nie będę Was zanudzał bardziej technicznymi szczegółami. Napiszę jeszcze jedynie o wnioskach, które z całej sytuacji wyciągnąłem.
Z góry zaznaczam i podkreślam, że nie jestem specjalistą od chmury. Poznałem ją na tyle, aby skonfigurować usługę, działać na niej bez problemu przez ponad rok. Moje doświadczenia dotyczą jedynie usługi AppEngine. Z innymi nie miałem do czynienia.
Chmura jest niesamowicie wygodna. Zapewnia wiele usług “out of the box”, rysuje fajne wykresy, skaluje się automatycznie i daje wiele, wiele więcej. Wszystko świetnie, dopóki nie zaczną się problemy. W takim przypadku jesteś pozostawiony sam sobie. Nie masz żadnego supportu. Można wykupić dodatkowe wsparcie w Google, ale kosztuje niemało i czasy reakcji w podstawowych planach nie są też rewelacyjne:
Chwilami miałem wrażenie, że jedyne co mi pozostaje, to po prostu usiąść i płakać. Dodatkowo każda operacja – czy to deployment, czy przekierowanie ruchu, czy wyłączenie lub włączenie usługi trwa od kilkudziesięciu sekund do kilku minut. W przypadku awarii, gdy nie wiesz co się dzieje, działasz trochę po omacku. Próbujesz różnych rozwiązań, na każdy test tracisz kilka lub kilkanaście minut. Czas leci nieubłaganie, a aplikacja nie działa. Dla mnie była to jedna z bardziej stresujących nocy w życiu.
Gdy zaczynają się problemy, komunikaty błędów są często bardzo lakoniczne. Niewiele z nich da się wywnioskować. Logów jest całkiem sporo, ale przebicie się przez nie zajmuje mnóstwo czasu. W moim przypadku dodatkowo nie dało żadnych rezultatów. Do dziś nie wiem co było przyczyną. Próbowałem już po przejściu na serwer dedykowany na spokojnie, z ciekawości, uruchomić ponownie usługę w chmurze, ale wciąż bez rezultatów. Co prawda nie mam już dziwnych błędów w stylu “Internal error occured”, ale nadal występują błędy 502, aplikacja nie działa.
W całym tym zamieszaniu, jestem przekonany, że w którymś miejscu popełniłem jakiś błąd. Problem w tym, że nie wiem gdzie i nie jestem w stanie szybko tego namierzyć. Być może Google wprowadził jakieś zmiany, które ja przeoczyłem. Być może nie zaktualizowałem jakiejś konfiguracji, a powinienem. Być może gdyby jakiś “magik” od Google Cloud zajął się problemem, byłby w stanie problem i naprawić aplikację dość szybko. Ja nie byłem.
Przeniesienie na serwer dedykowany dało mi sporo korzyści i pewien psychiczny komfort. Mam kilkunastoletnie doświadczenie w pracy z Linuksem, poruszam się teraz w znajomym otoczeniu, zatem mam dużo większą kontrolę nad tym co się dzieje z aplikacją – na niższym poziomie. Konsola ssh zapewnia mi pełen dostęp do serwera, logów systemowych i logów aplikacji.
Oczywiście serwery dedykowane również mają minusy i jestem tego w pełni świadomy. Muszę sam zadbać o wiele rzeczy, które chmura zapewnia od ręki, takie jak chociażby backupy bazy danych, czy certyfikaty SSL. Wymaga to dodatkowej pracy, ale nie jest dla mnie większym problemem. Jak zwykle – “coś za coś”.
Przez cały czas trwania awarii starałem się na bieżąco rzetelnie informować o problemach i postępach w ich rozwiązywaniu. Nie dość, że nie spotkał mnie żaden “hejt”, to dodatkowo otrzymałem wiele głosów wsparcia, oraz życzeń szybkiego rozwiązania problemu. Chciałbym w tym miejscu za to bardzo serdecznie podziękować. To było dla mnie bardzo ważne i budujące. Pamiętajcie zatem, że Użytkownicy Waszych aplikacji są w stanie wiele zrozumieć i wybaczyć, pod warunkiem, że będą w takiej sytuacji uczciwie potraktowani.
Absolutnie tym wpisem nie mam zamiaru dyskredytować chmury. W wielu zastosowaniach sprawdza się świetnie. Chciałbym tylko, abyście pamiętali, że możecie na pewnym etapie natknąć się na tego typu problemy. Wtedy warto mieć plan awaryjny – gotową procedurę na tego typu sytuację – szczególnie, jeśli nie jesteście guru od rozwiązań chmurowych.
Anatol Ogórek – Architekt Systemów Informatycznych, programista Java, twórca aplikacji do zarządzania finansami osobistymi Family Finance Tracker.