Platformy PaaS jako narzędzie do szybkiego prototypowania cz. 5. – PostgreSQL for Azure

W poprzedniej części naszego cyklu udało nam się z sukcesem przenieść naszą aplikację pod postacią obrazu Docker’owego na Azure. Jednak na Heroku wciąż pozostała baza danych, co sugeruje, że nasza migracja jest wciąż niepełna. W tym artykule chciałbym skupić się na postawieniu dedykowanej bazy danych w chmurze Microsoft’u.

W portalu po wpisaniu frazy Azure Database for PostgreSQL będziemy mogli wybrać jeden z czterech rodzajów wdrożeń. Każdy z nich różni się skalą, możliwościami i kosztami.

Dla przykładowej aplikacji zrealizowanej przez nas w tym cyklu artykułów interesuje nas pojedynczy serwer, który powinien wytrzymać obciążenie aplikacji prototypowej.

Kolejne ekrany pozwolą na dokonfigurowanie wybranej opcji.Pamiętajmy, że w naszym przykładzie zagadnienia security spłycam do minimum, zatem starajcie się chronić hasła które ustawiacie.

Aprowizacja bazy potrwa chwilę, jednak jak się można domyślić utworzona przez nas baza będzie posiadała tylko użytkownika administracyjnego. Odwiedzenie bazy danych pozwoli na lekkie poluzowanie polityki bezpieczeństwa. Zacznijmy od lekkiej liberalizacji ustawień Firewall’a poprzez zezwolenie na dostęp z innych usług Azure. Jeśli chcemy pracować z naszym klientem bazy danych(psql) powinniśmy także odblokować nasz adres IP.

Do dalszej konfiguracji konieczny już będzie klient psql, ale jeśli nie chcemy go instalować wystarczy, że skorzystamy z AzureCLI otrzymamy wygodny terminal zaszyty w przeglądarce internetowej.

Nie ma znaczenia czy dalszą konfigurację wykonamy z terminala, czy z przeglądarki, do połączenia potrzebujemy jednak klienta psql:

Następnie należy utworzyć kolejno bazę danych, rolę i odpowiednie przywileje:

Po utworzeniu nowej roli, możemy zaktualizować nasze połączenia. Przykładowy connection string będzie wyglądał następująco:

I tyle, po restarcie nasza aplikacja powinna już uruchamiać się i korzystać z bazy świeżo utworzonej w Azure.

Jeśli dotrwaliście aż dotąd, można uznać że to ostatni punkt migracji. Jedyną pozostałością jaka została po Heroku, to dług w postaci dziwnie podzielonych properties zawierających poświadczenia bazodanowe.

Platformy PaaS jako narzędzie do szybkiego prototypowania cz. 4. – Azure App Service

Zanim ruszymy dalej z artykułami chmurowymi, krótkie ostrzeżenie -korzystając z AWS, czy Azure powinniśmy pamiętać, że subskrybujemy usługi płatne – do założenia konta potrzebna jest karta debetowa lub kredytowa (i niestety wszelkiego rodzaju prepaidy odpadają). Dodatkowo wiele oferowanych usług klasy Enterprise po prostu kosztuje krocie, należy więc za każdym razem sprzątać testowane przez nas usługi i kontrolować koszty. Powinniśmy też zadbać o ochronę naszego konta i dostępu do zasobów tj. włączyć MFA, chronić tokeny, stosować zasadę minimalnych uprawnień itd.
Jakiekolwiek zaniedbanie w tej dziedzinie może narazić nas na spore koszty.
Sami dostawcy oferują na swoich stronach zestawy najlepszych praktyk dot. bezpieczeństwa https://aws.amazon.com/architecture/security-identity-compliance/ i https://azure.microsoft.com/mediahandler/files/resourcefiles/security-best-practices-for-azure-solutions/Azure%20Security%20Best%20Practices.pdf z którymi zdecydowanie warto się zapoznać.

Zarówno w Azure jak i w AWS jako nowi użytkownicy możemy liczyć na pakiet powitalny w postaci sporego zestawu darmowych usług (w pewnych granicach oczywiście). Bazowa oferta pozwoli jednak na spokojny deployment prototypowanej aplikacji w modelu PaaS za free (https://azure.microsoft.com/pl-pl/pricing/details/app-service/windows/). Darmowa oferta nie jest tak dobra w porównaniu do Heroku, ale jest to poniekąd cena za ogromną uniwersalność całej platformy. Ufff… wydaje mi się, że na tym etapie możemy zakończyć informacje wstępne i spróbować założyć konto. Nie będę opisywał tego etapu, ale posiadanie już konta w usługach Microsoft skraca drastycznie cały proces – wystarczy uregulować kwestie dot. płatności i możemy działać.

Konfiguracja

Po pierwszym zalogowaniu do portalu azure (http://portal.azure.com/) zobaczymy następujące wręcz „komiksowe” menu do zarządzania zasobami.

Azure portal – dashboard

W wyszukiwarce znajdujemy App Services i klikamy + Add. Pojawia się menu w którym możemy właściwie wyklikać całą naszą aplikację:

Gdy dopiero zaczynamy naszą przygodę z Azure zapewne będziemy musieli utworzyć pierwszą grupę zasobów. Z początku ten obiekt może wydawać się czymś nadmiarowym, bo dlaczego nie utworzyć zasobu bezpośrednio na naszej subskrybcji? Spójrzmy na konto Azure z perspektywy na przykład dużej firmy – możemy mieć w niej zdefiniowane, wiele aplikacji i środowiska, często użytkowanych przez rozmaite zespoły. W takim heterogonicznym świecie łatwo stracić możliwość zarządzania całością i jakiejkolwiek kontroli kosztów. Wydzielenie grup zasobów np. z podziałem na aplikacje pozwala uzyskać nam pełną kontrolę nad naszą chmurą i płatnościami.

Z ustawień dotyczących deploymedntu pozostaje odpowiedni dobór nazwy jak i typu samego deploymentu:

Czym różni się sposób publikacji Code vs Docker Container? Code pozwala na zdefiniowanie wdrożenia w analogiczny sposób jak w przypadku Heroku – opieralibyśmy się o zbudowanie aplikacji ze źródeł po stronie Azure’a. Przygotowaliśmy sobie obraz Dockerowy po to by maksymalnie uniezależnić się od konkretnego dostawcy, zatem skorzystamy z tego i wybierzemy Docker Container.

Kolejny ekran konfiguracji oczekuje wartości, które powinniśmy już znać z poprzedniej odsłony naszego cyklu:

Przygotowaliśmy już obraz, posiadamy rejestr w Gitlab, wypadałoby wprowadzić właściwy adres rejestru, nazwę użytkownika, hasło, a także obraz – zdobyć je można w Gitlabie w Settings -> Repository -> Deploy Tokens.

W scope tworzonego token’a interesuje nas tylko możliwość odczytu obrazów w repozytorium – z punktu widzenia deploymentu jest to zupełnie wystarczające. Hasło pojawi się jednorazowo, jeśli nie skopiujemy go pozostaje nam utworzenie kolejnego token’u.

Pełną nazwę obrazu jak i adres repozytorium, uzyskamy na zakładce Container Registry.

Po wypełnieniu formatki i kliknięciu na URL możemy zauważyć, że nasza aplikacja nie startuje.

Oglądając Container Settings możemy zobaczyć logi ze startu – brakuje nam zmiennych środowiskowych. Na szczęście odłożyliśmy potrzebne dane budując kontener.

Uzupełnienie konfiguracji powinniśmy wykonać w sekcji Configuration. Oprócz zmiennych środowiskowych, które wykorzystywaliśmy uruchamiając obraz Dockerowy powinniśmy dodać jeszcze jedną: WEBSITES_PORT. W skrócie wskazuje ona na którym porcie nasłuchuje nasza aplikacja – w tym przypadku należy ustawić ją na 8080.

W zasadzie to tyle – ponowne odwiedziny URL z naszą aplikacją sprawią, że będziemy w stanie już korzystać z naszego API.

Platformy PaaS jako narzędzie do szybkiego prototypowania cz. 3. – Azure App Service

W poprzednim artykułach opisywałem Heroku, który idealnie pokrywa przypakdi hobbystyczne, co jednak kiedy chcemy przeprowadzić pewien mniej lub bardziej zaawansowany proof-of-concept w firmie, a management nie specjalnie ma ochotę na zawieranie nowych umów i sugeruje skorzystanie z już posiadanych przez nas subskrybcji.

W środowiskach korporacyjnych dominuje głównie dwóch dostawców pokrywających większość rynku i wyznaczających standardy dla konkurencji. Pierwszy z nich – AWS zapoczątkował wielką rewolucję chmurową, która odmieniła cały świat IT, zmuszając nawet najbardziej konserwatywne firmy jak banki do opracowania swoich rozwiązań (private cloud) lub wykorzystania chmury publicznej w części prowadzonych projektów (hybrid cloud).
Microsoft Azure powstał jako odpowiedź na rosnącą popularność chmur obliczeniowych i szybko stał się faworytem korporacji ze względu na ogromną ilość oferowanych usług w modelu SaaS (jak Office 365 i Dynamics). Jak już wspominałem wcześniej nawet jeśli twoja korporacja nie posiada jeszcze projektów realizowanych w chmurze Azure – jest duża szansa, że posiada subskrybcję Office365, czy Microsoft Dynamics.

W tym artykule chciałbym kontynuować naszą zabawę z modelem PaaS. Spróbujmy przenieść aplikację, którą zbudowaliśmy pod chmurę Heroku na Azure App Service. Jeśli nie popełniliśmy zasadniczych błędów projektowych, to nie powinniśmy stać się uzależnieni od dostawcy (ang. vendor lock-in) i migracja całości nie powinna kosztować nas zbyt dużo wysiłku.

Większość serwisów PaaS można uniezależnić całkowicie od języka programowania i platformy, poprzez odpowiednią konteneryzację. Jeśli pamiętacie heroku addons jak i buildpack’i wspomniane w poprzedniej części – to uniezależnienie się od nich stanowi klucz do uwolnienia naszego serwisu od określonego dostawcy i wspieranych przez niego technologii.

Przykładowa architektura:

źródło Azure Portal

Na czym zatem polega konteneryzacja?

Jest to rodzaj wirtualizacji na poziomie systemu operacyjnego (OS-level virtualization) i wbrew powszechnej opinii nie stanowi nowego wynalazku – jej wczesnym pierwowzorem były jail’e i chroot znany z systemów uniksowych (1982). W świecie backend developerów dopiero pojawienie się Docker’a upowszechniło konteneryzację. Od tej pory tworzone usługi i oprogramowanie mogły w całości uniezależnić się stack’u systemowego czy technologicznego.

Obraz Dockerowy można złożyć z kilku warstw za pomocą kilku wbudowanych poleceń takich jak FROM, CMD, COPY, ARG. Dla przykładu:

Po utworzeniu pliku Docker file jw. zbudowanie obrazu i uruchomienie kontenera sprowadza się do dwóch poleceń:

Rozbudowa aplikacji

W przypadku naszej aplikacji moglibyśmy wyodrębnić całą logikę poprzez przekopiowanie utworzonych artefaktów w następujący sposób:

W zasadzie poza skopiowaniem artefaktów i uruchomieniem naszej aplikacji zmieniamy jeszcze kontekst użytkownika na pozbawionego przywilejów ROOT’a (dobra praktyka).
Po próbie budowy, możemy niestety zaobserwować dość nieprzyjemny wyjątek:

W dość wymowny sposób pokazuje o czym zapomnieliśmy podczas uruchamiania/tworzenia obrazu.
Do uruchomienia potrzebujemy kilku dodatkowych zmiennych środowiskowych – w końcu w Heroku użyliśmy aż pięciu (!). Powinny one zostać przekazane do kontenera. Przygotujmy zatem plik w następującym formacie:

Wartości można oczywiście zabrać z naszej wcześniej utworzonej aplikacji na Heroku. Na powyższym przykładzie wychodzi też cena pokazanej wcześniej automagii.
Czy projektując aplikację nie pod konkretnego dostawcę podjęlibyśmy identyczną decyzję, jeśli chodzi o konfigurację podłączenia do bazy danych?
Zapewne zależałoby nam na rozdzieleniu pewnych elementów, dodatkowo moglibyśmy uniknąć zaprezentowanego wcześniej hack’a.
Kolejna próba z uwzględnieniem zmiennych środowiskowych i przekierowaniem portów pozwala już na lokalne uruchomienie skonteneryzowanej aplikacji:

Teraz wypadałoby poszerzyć nasz projekt o budowanie obrazu w pipelinie. Na szczęście przy oparciu naszego amatorskiego projektu o Gitlaba uzyskaliśmy możliwość skorzystania z ichniejszego rejestru obrazów (rozmiar całego repozytorium jak i rejestru posiada ograniczenie 10GB).
Użycie pipeline’ów w Gitlab sprawia, że uzyskanie do niego dostępu również jest banalnie proste poprzez predefiniowane zmienne:

Przygotowany etap wykonuje budowanie obrazu przy założeniu, że wcześniej odłożyliśmy je:

przykładowy rejestr

Po uruchomieniu kilku pipeline’ów zobaczymy, że rejestr zapełnia się kolejnymi tagami z kolejnymi wersjami aplikacji. Jak zatem powinniśmy decydować, która z nich powinna zostać zdepluyowan’a na nasze docelowe produkcyjne środowisko?

Można dodać do pipeline’u kolejny etap, którego celem będzie wykonanie właśnie tej operacji tj. odpowiednie tagowanie ostatniego build’a.

Et voilla. Nowy stage sprawi, że pod tagiem latest zawsze będzie się ukrywał najnowszy obraz pipeline’u masterowego.

Ktoś mógłby zadać pytanie, ale gdzie w tym wszystkim Azure? Dlaczego przygotowywać aplikację w ten sposób?
W następnej części cyklu zobaczymy uzysk z tych wszystkich operacji, ponadto zastanowimy się czy dałoby się podejść do tematu nieco inaczej.

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ć 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ązania 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 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.
Jednak każdy z dodatków jakie możemy użyć na platformie ma swoją cenę i pewne ograniczenia regionalne, ale w zamian 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 można 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 jakiejkolwiek 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 zacznijmy od utworzenia skryptu 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 też 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 takich 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’u, 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.

WeeklyJavaSnippets#2 Singletons in Java

Singleton – wzorzec czy antywzorzec?

Wśród programistów, często spotykam się z opinią, że Singleton stanowi antywzorzec. Niestety, rzadko też słyszę dobre argumenty podpierające tą tezę. Fakt, jest on jednym z najpowszechniejszej stosowanych wzorców projektowych i jednym z pierwszych jaki poznaje każdy programista. Konsekwencjami tego jest olbrzymia ilość błędnych implementacji, które krążą po repozytoriach i potrafią przyprawić o prawdziwy zawrót głowy podczas czytania kodu i poszukiwania przyczyny problemów.

Czytaj dalej WeeklyJavaSnippets#2 Singletons in Java

Widmo krąży po Javie… widmo boilerplate’u

Problem (?)

W zasadzie ktokolwiek zajmujący się Javą, a mający w swoim życiorysie romans z platformą .NET przyzna, że Java nie jest zwięzłym językiem. Już sam kod akcesorów, które trzeba tworzyć/generować za każdym razem potrafi doskonale zaciemnić nam obraz klasy. Dla przykładu znany z konkurencyjnej platformy mechanizm Properties doskonale adresuje ten problem, skracając boilerplate do minimum, jak i pozostawia programistę w pełnej kontroli:

Czytaj dalej Widmo krąży po Javie… widmo boilerplate’u