Platformy PaaS jako narzędzie do szybkiego prototypowania cz.2.

W poprzednim artykule cyklu zaprezentowaliśmy prostą aplikację, którą w ekspresowy sposób można wdrożyć na produkcję z wykorzystaniem Heroku. Co w przypadku, gdy oczekujemy czegoś więcej niż tylko połączenia z zewnętrznym API jak np. dodatkowej warstwy persystencji? Oczywiście dostawcy PaaS, aby dostarczyć dojrzałe rozwiązanie musieli udostępnić odpowiednie oprzyrządowanie. W przypadku Heroku w sukurs przychodzą nam Add-ony (dodatki), które można w dowolny sposób podłączać do już stworzonej aplikacji. Oczywiście jak we wszystkim w modelu PaaS nie mamy takiej dowolności w porównaniu do własnoręcznego zarządzania, ale w zamian otrzymujemy prekonfigurowane komponenty, które działają właściwie od razu.
Oczywiście każdy z dodatków jakie możemy użyć na platformie ma swój koszt i pewne ograniczenia regionalne, ale odpadają wszelkie dodatkowe koszty związane z administracją i utrzymaniem.

Przykładowo dodając bazę postgres na platformie Heroku wystarczy wywołać:

Jak widzimy utworzenie nowej bazy danych jest bajecznie proste, a podobne działanie naturalnie podjąć także z poziomu UI. Chciałbym tu zwrócić uwagę na pewną rzecz – mianowicie jak dostać się do tak skonfigurowanej bazy z poziomu naszej aplikacji?

Rąbka tajemnicy uchyla powyższa linijka – „klucz” do bazy znany również jako „connection string” ląduje w zmiennej konfiguracyjnej DATABASE_URL

Już na pierwszy rzut oka widać, że baza została posadowiona na silniku EC2 od Amazona. Dodatkowo wszystko co z nią związane zostało wygenerowane sposób pseudolosowy. Spróbujmy zatem wykorzystać tą świeżo przygotowaną bazę danych w naszej testowej aplikacji.

 

Zacznijmy od przygotowania modelu – chcielibyśmy przetrzymywać w niej użytkownika:

Aby przygotować tabelę, w tym przykładzie chciałem posłużyć się technologią wersjonowania bazy danych Flyway, która stanowi głównego konkurenta do już dość dojrzałego Liquibase. Osobiście nigdy nie miałem preferencji w kierunku którejkolwiek z tych technologii, jednak zawsze odnosiłem wrażenie, że Flyway pozwala szybciej wystartować, a o to po części chodzi w tym przykładzie. Zatem stworzymy skrypt V1__Create_new_table_user.sql:

Wydaje się, że jeśli chodzi o warstwę dostępu do danych to mamy wszystko, żeby pójść dalej z naszym przykładem. Obsłużmy zatem w kodzie zapis nowego użytkownika:

Powyższy handler należy spiąć z istniejącym routingiem w naszej aplikacji, po szybkim refactoringu otrzymujemy:

Zwróćcie proszę uwagę, że w tym przypadku podobnie jak i w poprzednim przykładzie, gdzie odpytywaliśmy Marvel’owskie API staram posługiwać się paradygmatem reaktywnym, który nieśmiało pojawia się w coraz większej ilości projektów, nie zawsze w miejscach w których rzeczywiście jest wymagany i potrzebny (jak np. tutaj :-)).

Celem przetestowania repozytorium przygotujemy prosty test oparty o TestContainers, technologia ta wymaga od was utrzymywania od was demona Docker’owego zarówno na stacji roboczej jak i na pipeline’ach, jednak w odróżnieniu od zagnieżdżonych baz danych pozwala na przeprowadzenie testów integracyjnych w otoczeniu praktycznie tożsamym z produkcją.

Metoda oznaczona @DynamicPropertySource umożliwia przeciążenie konfiguracji danymi w runtime pozyskanymi ze świeżo postawionego kontenera bazodanowego. Alternatywnie można do tego wykorzystać inicjalizator kontekstu:

Gdy nasze testy już działają należałoby spróbować połączyć wszystko z bazą danych. Uważny czytelnik zapewne zauważył, że DATABASE_URL dostarczany przez Heroku ma jednak nieco inny format niż formaty obsługiwane przez Spring’a (i JDBC), jednak i ten problem został poniekąd zaadresowany, ale po kolei…

W przypadku gdy korzystamy z aplikacji Javowej na platformie (po autodetekcji przy pierwszym deploy’u), Heroku automatycznie wzbogaca ją o „buildpack”, czyli niezbędny zbiór skryptów i narzędzi jak np. maven. Szczegóły można znaleźć w dokumentacji: https://devcenter.heroku.com/articles/java-support.

Dodatkowo buildpack automatycznie będzie próbował utworzyć zmienne środowiskowe SPRING_DATASOURCE_USERNAME, SPRING_DATASOURCE_PASSWORD, SPRING_DATASOURCE_URL.
Oczywiście nawet w przypadku gdy korzystamy w nieco inny sposób z JDBC jesteśmy w stanie sobie poradzić np. przetwarzając początkowy DATABASE_URL. https://devcenter.heroku.com/articles/connecting-to-relational-databases-on-heroku-with-java#using-the-database_url-in-plain-jdbc

Oczywiście możemy wykorzystać polecenie heroku config:get i ręcznie przeciążyć ustawienia aby osiągnąć konfigurację „pod nas”, jednak w pewnym sensie byłaby to forma tightcoupling, której raczej chcemy unikać.

Wzbogaceni o tą wiedzę spróbujmy skonfigurować Flyway, tym co daje nam platforma Heroku:

Próba przygotowania tabeli powinna zakończyć się powodzeniem, co jednak z konfiguracją naszego reaktywnego repozytorium? URL’e różnią się od tych oczekiwanych – nawet w przypadku testu integracyjnego kłuje w oczy linijka w którym podmieniłem jdbc na r2dbc. Dla uproszczenia przykładu i nie tworzenia wszystkiego manualnie po prostu nadpisałem connectionFactory i obsługę properties, co nie jest być może rozwiązaniem idealnym, ale szybkim.

Mając przygotowaną aplikację – możemy zaobserwować, że pipeline kończy się już na etapie testów:[ERROR] UserRepositoryIntegrationTest ? ContainerLaunch Container startup fai

Testcontainers które wykorzystaliśmy w projekcie wymagają Docker’a a zatem musimy wzbogacić nasz pipeline (.gitlab-ci.yml) o obraz Docker in Docker.

I voill’a 🙂 Celem potwierdzenia, że nasza baza danych już działa:

w odpowiedzi powinniśmy otrzymać naszego świeżo utworzonego użytkownika z nowo nadanym id’kiem:

Podsumowanie:
W tych dwóch krótkich artykułach opowiadających o platformie Heroku poruszyliśmy całą gamę tematów związaną z modelem PaaS i utworzyliśmy szkielet aplikacji która:

  • posiada podstawowy pipeline CI/CD
  • działa w sposób reaktywny
  • swoje testy integracyjne opiera o kontenery testowe

I to wszystko w zaledwie w dwóch krótkich artykułach – co jest całkiem niezłym wynikiem. Niestety wszystko ma swoją cenę i koszt(poza rachunkiem) – obnażyliśmy też jedną z największych słabości PaaS, czyli konieczność dostosowania się do waszego dostawcy. Planując budowę i obsługę aplikacji w tym modelu należy dokładnie przeanalizować co dostawcy oferują i czy jesteśmy w stanie poradzić sobie z ograniczeniami.

Platformy PaaS jako narzędzie do szybkiego prototypowania cz.1.

Koncepcja PaaS stanowi jeden z modeli chmury obliczeniowej opierającej się o usługi związane z uruchamianiem i zarządzaniem aplikacjami w ramach określonego ekosystemu. Wśród dostawców występuje ogromne zróżnicowanie oferowanych usług i rodzajów wsparcia. W ramach krótkiej serii artykułów chciałbym pokazać jak „ugryźć” temat i jak możemy za w miarę niewielkie pieniądze lub całkowicie za darmo z naszej lokalnej aplikacji wylądować z projektem działającym gdzieś w sieci.

Heroku

Jako pierwszego dostawcę w tym cyklu chciałbym przedstawić Heroku. Nie posiadają oni własnej infrastruktury, natomiast korzystają z Amazonowej platformy EC2. Od początku Heroku posiadało doskonałe wsparcie dla języka Ruby, z czasem jednak paletę wspieranych technologii poszerzono w tym także o Javę i NodeJS. Podobnie jak większość dostawców także i w tym przypadku mamy podstawowy zestaw narzędzi zupełnie darmowy.

Wdrażana aplikacja uruchamiana jest w oparciu o skonteneryzowane środowisko Linuxowe. Kontener taki w przypadku tego dostawcy nazywany jest „dyno” i to jego użytkowanie i typ stanowi podstawę rozliczenia. Dla przykładu najtańsze „dyno” posiada ograniczenie do 512 MB RAM, wyłącza się po 30 minut nieaktywności, ale… pozwala nam ekspresowo przetestować małego POC’a lub stanowić podstawę prezentacji (jako alternatywa dla ngrok’a).

Konto można założyć za pomocą dość prostego formularza. Warto zainstalować także lokalnego klienta heroku pozwalającego na wygodne zarządzanie całą platformą

W paru krokach spróbuję przedstawić w jaki sposób można zbudować aplikację opartą o framework SpringBoot odpytującą zewnętrzne API. Dość interesujące publiczne API znajdziemy [tutaj]. W ramach małego PoC’a spróbujemy odpytać jeden z endpointów.
Na początek proponuję wykorzystać [Spring initializr] i wybrać Javę 14. Z zależności użyję Reactive Web i Lombok’a – tak przygotowany projekt można umieścić w repozytorium Gitlab’owym, co daje dostęp do dość wygodnych i bardzo zaawansowanych pipeline’ów. W podstawowej konfiguracji otrzymujemy również 2000 minut procesora – w sam raz dla mniejszych i mniej aktywnych projektów realizowanych hobbystycznie.

Przygotowujemy źródło niezbędnej konfiguracji (application.yml):

Zarówno klucz prywatny jak i publiczny możemy uzyskać rejestrując się w serwisie Marvel’a. Pamiętajcie, żeby traktować owe dane jako poufne i nie commitować ich do publicznego repozytorium. Pracując w środowisku chmurowym warto wyrabiać w sobie dobre nawyki związane z security także dla własnego bezpieczeństwa. Przykładowo uzyskanie niepowołanego dostępu do naszych kluczy AWS’owych może skutkować narażeniem na poważne koszty.

Przykładowy kod kliencki – odpytanie jednego z endpointów API:

Aby sprawdzić czy wszystko działa i czy z naszej aplikacji da się połączyć z Marvelowym API musimy przygotować najprostszy endpoint:

Przygotowaną aplikację możemy potestować lokalnie i jeśli wszystko działa jak powinno, możemy spróbować podjąć próbę deploymentu.

Wdrożenie

Gdy mamy przygotowany kod – powinniśmy się zająć miejscem na którym zdeployujemy naszą aplikację. Po zalogowaniu się do panelu Heroku możemy stworzyć nową aplikację:


Kolejna formatka pozwala na podjęcie decyzji, gdzie chcemy by fizycznie znajdował się nasz kontener z aplikacją. Z wszystkich regionów AWS do wyboru są dwa (Stany Zjednoczone i Europa). Możemy też choć nie musimy nadać naszej aplikacji nazwę, po przejściu tego kroku w odpowiedzi mamy gotową aplikację typu Hello World czekającą na zastąpienie ciekawszym (bo naszym) projektem.

Wróćmy na chwilę do naszej aplikacji. Sugerowałem użycie gitlaba, ze względu na doskonałe CI/CD, które dostarcza. Dodatkowo Gitlab Pipelines posiadają bardzo niski punkt wejścia.

Przykładowo poniższy kawałek kodu pozwala nam na zdefiniowanie etapu (stage) build z jednym zadaniem (job) o nazwie build. Gitlabowy runner w pierwszym kroku pobiera obraz dockerowy posiadający maven i Javę 14, a następnie uruchamiany jest na nim build (clean package).

Jak zatem powiązać nasz build z przygotowaną aplikacją w chmurze Heroku? Wystarczy dobrać odpowiednie narzędzie do deploymentu. Jednym z tego typu narzędzi jest dpl używany także przez Travis’a – posiadający wsparcie dla kilkunastu największych dostawców chmurowych (min. Azure i AWS).

Rozbudowa naszego pipeline’u o deployment na Heroku staje się banalnie prosta:

Cała magia dzieje się w linijce:

HEROKU_APP_NAME i HEROKU_API_KEY są zmiennymi środowiskowymi, które możemy zdefiniować w ramach naszego projektu na Gitlabie. Nazwę aplikacji na Heroku znajdziemy bez trudu po zalogowaniu do głównego dashboard. Klucz (token) można stworzyć wywołując odpowiednie polecenie z CLI.

Tutaj bardzo ważna uwaga. Powyższy klucz daje pełny dostęp do konta Heroku – nie należy go udostępniać, ani commbitować do publicznych repozytoriów. W przypadku Gitlab – możemy umieścić go w chronionej zmiennej środowiskowej:

Chroniona zmienna środowiskowa w Gitlab, sprawi że będzie ona przekazywana ona tylko i wyłącznie do pipeline’ów uruchamianych na chronionych gałęziach (np. master – stąd zadanie deploymentu zostało w zawężone właśnie do tej gałęzi). Dodatkowo dodanie maskowania sprawi, że wartość nie zostanie wypisana w logach runnerów. Oczywiście nie jest to idealne zabezpieczenie, a zatem nie traktujcie powyższego rozwiązania jako wzorca do powielenia w produkcyjnej aplikacji.

Po uzupełnieniu zmiennych środowiskowych. Nasz pipeline powinien już działać i wdrożenie aplikacji do chmury Heroku powinno się wykonać.

W logach runner’a na GitLab można podpatrzeć działanie maskowania zmiennych środowiskowych:

Ostatecznie aplikacja powinna być dostępna pod URLem https://<nazwa naszej aplikacji>.herokuapp.com/marvelheroes. Na tym etapie możemy nie być jeszcze w stanie połączyć się z API Marvel’a które definiowaliśmy na początku. Czego brakuje? Oczywiście kluczy. W ustawieniu ich dla naszej aplikacji pomoże nieoceniony Heroku CLI.

Poniższe polecenia pozwalają na wyświetlenie istniejących zmiennych środowiskowych i ustawienie interesujących nas kluczy dla aplikacji:

Teraz pozostaje nam tylko odpytać naszą aplikację za pomocą curl’a i powinniśmy uzyskać interesującą nas odpowiedź.

Podsumowanie

W tym pozornie krótkim artykule udało się nam przejść przez wszystkie kroki wymagane do postawienia prostego hobbystycznego projektu.

Od wygenerowania aplikacji, poprzez przygotowanie i skonfigurowanie miejsca w chmurze Heroku aż po zestawienie prostego pipeline’u. Warto zwrócić uwagę, że najwięcej kodu i wysiłku nawet w tak banalnym przykładzie zajęło stworzenie naszej aplikacji. To też ukazuje potęgę rozwiązań typu PaaS -> developer może skupiać się na tym co dla niego najważniejsze, czyli tworzeniu własnego projektu.

ZeroTurnaround cheatsheets – nie tylko dla Javowców

Zapewne większość z was kojarzy firmę ZeroTurnaround – to twórcy świetnego narzędzia JRebel, pozwalającego na przeładowywanie kodu w locie, co znacząco pomaga przy pracy z cięższymi projektami. W zasadzie największą wadą tego rozwiązania były koszty licencji, jednak kto pracował, ten miał okazję docenić, gdy nie musiał czekać X minut na cały redeploy aplikacji.. Ale nie o tym i nie jest wpis sponsorowany 🙂 Chłopaki i dziewczyny z ZT na swoim blogu wrzucają fajne skrótowe cheatsheety, które mogą przydać się każdemu z was:

To tyle 🙂 Wielkie dzięki ZeroTurnaround!