Aplikacje „chmurowo-zorientowane” (cloud native) lub po prostu „chmurowe” są tworzone specjalnie do pracy w infrastrukturach chmurowych. Zwykle budowane są jako zestaw luźno powiązanych mikrousług, zapakowanych w kontenery, które są zarządzane przez platformę chmurową. Takie aplikacje są domyślnie gotowe na awarie, co oznacza, że niezawodnie działają i skalują się nawet przy poważnych awariach na poziomie infrastruktury. Drugą stroną medalu są zestawy ograniczeń (kontrakty), które platforma chmurowa nakłada na aplikacje kontenerowe, aby móc nimi zarządzać w trybie automatycznym.

Pomimo doskonałej świadomości potrzeby i znaczenia przejścia na aplikacje chmurowe, wiele organizacji wciąż nie wie, od czego zacząć. W tym wpisie zaprezentujemy szereg zasad, których przestrzeganie podczas tworzenia aplikacji kontenerowych pozwoli zrealizować potencjał platform chmurowych i osiągnąć niezawodne działanie oraz skalowanie aplikacji nawet przy poważnych awariach na poziomie infrastruktury IT. Ostatecznym celem przedstawionych tutaj zasad jest nauczenie się tworzenia aplikacji, które mogą być automatycznie zarządzane przez platformy chmurowe, takie jak Kubernetes.
Zasady projektowania oprogramowania
W świecie programowania zasady oznaczają dość ogólne reguły, które należy przestrzegać podczas tworzenia oprogramowania. Można je zastosować w pracy z dowolnym językiem programowania. Każda zasada ma swoje cele, a narzędziem do ich osiągnięcia są zazwyczaj wzorce i praktyki. Istnieje również szereg fundamentalnych zasad tworzenia jakościowego oprogramowania, z których wynikają wszystkie pozostałe. Przykłady fundamentalnych zasad to:
- (Keep it simple, stupid) – nie komplikować;
- (Don’t repeat yourself) – nie powtarzać się;
- (You aren’t gonna need it) – nie tworzyć tego, czego nie ma bezpośredniej potrzeby;
- (Separation of concerns) – oddzielać odpowiedzialności.
Jak widać, zasady te nie określają żadnych konkretnych reguł, lecz dotyczą tzw. przesłanek zdrowego rozsądku opartych na praktycznym doświadczeniu, które dzieli wielu programistów i do których regularnie się odwołują.
Ponadto istnieje – zestaw pięciu podstawowych zasad programowania i projektowania obiektowego, sformułowanych przez Roberta Martina. SOLID obejmuje ogólne i otwarte na interpretację zasady, które – jeśli stosowane łącznie – pomagają w tworzeniu bardziej jakościowych systemów oprogramowania i ich lepszym utrzymywaniu w długim okresie.
Zasady SOLID odnoszą się do sfery OOP i formułowane są w języku pojęć i koncepcji takich jak klasy, interfejsy i dziedziczenie. Analogicznie, również dla aplikacji chmurowych można sformułować zasady rozwoju, przy czym podstawowym elementem nie będzie klasa, a kontener. Stosując te zasady, można tworzyć aplikacje kontenerowe, które lepiej odpowiadają celom i zadaniom platform chmurowych, takich jak Kubernetes.
Konteneryzacja w chmurze: podejście Red Hat
Dziś do kontenerów można stosunkowo łatwo pakować praktycznie każdą aplikację. Jednak aby aplikacje mogły być skutecznie automatyzowane i orkiestracje w ramach platformy chmurowej typu Kubernetes, wymagane są dodatkowe wysiłki.
Podstawą dla poniższych pomysłów była metodologia i wiele innych prac dotyczących różnych aspektów tworzenia aplikacji internetowych, od zarządzania kodem źródłowym po modele skalowalności. Opisane zasady odnoszą się tylko do rozwoju aplikacji kontenerowych, które są zbudowane na bazie mikroserwisów i przeznaczone dla platform chmurowych, takich jak Kubernetes. Podstawowym elementem w naszych rozważaniach jest obraz kontenera, a przez docelowe środowisko wykonawcze kontenerów rozumiemy platformę orkiestracji kontenerów. Celem proponowanych zasad jest stworzenie kontenerów, dla których na większości platform orkiestracji można automatyzować zadania związane z dystrybucją (scheduling – wybór hosta do uruchomienia instancji kontenera), skalowaniem i monitorowaniem. Zasady są przedstawiane w dowolnej kolejności.
Zasada jednolitości zadań (Single Concern Principle, SCP)
Ta zasada jest w dużej mierze podobna do zasady pojedynczej odpowiedzialności (Single Responsibility Principle, ), który jest częścią zestawu SOLID i stwierdza, że każdy obiekt powinien mieć jedną odpowiedzialność, a ta odpowiedzialność powinna być całkowicie enkapsulowana w klasie. Istota SRP polega na tym, że każda odpowiedzialność to powód do zmian, a klasa powinna mieć tylko jeden powód do zmiany.
W SCP zamiast słowa „odpowiedzialność” (responsibility) używamy słowa „zadanie” (concern), aby wskazać na wyższy poziom abstrakcji i szersze przeznaczenie kontenera w porównaniu do klasy OOP. Jeśli celem SRP jest posiadanie tylko jednego powodu do zmian, to za SCP stoi chęć zwiększenia możliwości ponownego użycia i wymiany kontenerów. Przestrzegając SRP i tworząc kontener, który rozwiązuje jedno zadanie i robi to w sposób funkcjonalnie kompletny, zwiększasz szanse na ponowne użycie tego kontenera w różnych kontekstach aplikacji.
Zasada SCP stwierdza, że każdy kontener powinien rozwiązywać jedno jedyne zadanie i robić to dobrze. Co więcej, SCP w świecie kontenerów osiąga się łatwiej niż SRP w świecie OOP, ponieważ kontenery zwykle wykonują jeden proces, a przez większość czasu ten proces rozwiązuje jedno jedyne zadanie.
Jeśli jakiś mikrousługa kontenerowa musi rozwiązywać kilka zadań jednocześnie, można ją rozbić na kontenery jednozadaniowe i połączyć je w ramach jednego podu (jednostki wdrożeniowej platformy kontenerowej) przy użyciu wzorców sidecar i kontenerów inicjalizacyjnych. Ponadto SCP ułatwia wymianę starego kontenera (np. serwera WWW lub brokera wiadomości) na nowy, który rozwiązuje to samo zadanie, ale ma rozszerzoną funkcjonalność lub lepiej się skaluję.

Zasada wysokiej obserwowalności (High Observability Principle, HOP)
Podczas korzystania z kontenerów jako zunifikowanego sposobu pakowania i uruchamiania aplikacji same aplikacje są traktowane jako „czarna skrzynka”. Jednak jeśli są to kontenery chmurowe, muszą one dostarczać środowisku wykonawczemu specjalne interfejsy API, aby monitorować integralność kontenerów i w razie potrzeby podejmować odpowiednie działania. Bez tego nie uda się zunifikować automatyzacji aktualizacji kontenerów i zarządzania ich cyklem życia, co z kolei pogorszy niezawodność i użyteczność systemu oprogramowania.
W praktyce aplikacja kontenerowa powinna przynajmniej posiadać API do różnych typów kontroli integralności: testów aktywności (liveness) i testów gotowości (readiness). Jeśli aplikacja aspiruje do więcej, powinna dostarczać również inne środki kontroli swojego stanu. Na przykład, rejestrowanie ważnych zdarzeń przez STDERR i STDOUT w celu agregacji logów za pomocą Fluentd, Logstash i innych podobnych narzędzi. A także integrację z bibliotekami śledzenia i zbierania metryk, takimi jak OpenTracing, Prometheus itd.
Ogólnie, aplikację można nadal traktować jako „czarną skrzynkę”, ale musi być ona wyposażona we wszystkie API, które są potrzebne platformie, aby optymalnie ją monitorować i zarządzać nią.
Zasada dostosowania do cyklu życia (Life-cycle Conformance Principle, LCP)
LCP jest antytezę HOP. Jeśli HOP stwierdza, że kontener musi dostarczać platformie interfejsy API do odczytu, LCP wymaga od aplikacji zdolności do odbierania informacji od platformy. Co więcej, kontener musi nie tylko odbierać zdarzenia, ale także na nie reagować. Stąd nazwa zasady, którą można traktować jako wymaganie dostarczenia platformie interfejsów API do zapisu.

Platformy mają różne typy zdarzeń, które pomagają zarządzać cyklem życia kontenera. Jednak to sama aplikacja powinna decydować, które z nich przyjmować i jak na nie reagować.
Jest oczywiste, że niektóre zdarzenia są ważniejsze od innych. Na przykład, jeśli aplikacja źle znosi awaryjne zakończenie pracy, musi odbierać wiadomości signal: terminate (SIGTERM) i jak najszybciej inicjować procedurę zakończenia, aby zdążyć przed otrzymaniem signal: kill (SIGKILL), który następuje po SIGTERM.
Ponadto dla cyklu życia aplikacji istotne mogą być takie zdarzenia, jak PostStart i PreStop. Na przykład, po uruchomieniu aplikacji może być wymagany pewien czas na "rozgrzewkę", zanim będzie mogła odpowiadać na zapytania. Lub aplikacja może wymagać szczególnego zwolnienia zasobów podczas zakończenia pracy.
Zasada niezmienności obrazu kontenera (Image Immutability Principle, IIP)
Powszechnie uważa się, że aplikacje kontenerowe powinny pozostawać niezmienne po zbudowaniu, nawet jeśli są uruchamiane w różnych środowiskach. Stąd wynika potrzeba eksternalizacji przechowywania danych w czasie wykonywania (innymi słowy, używania do tego zewnętrznych narzędzi), a także polegania na zewnętrznych, dostosowanych do konkretnego środowiska wykonawczego, konfiguracjach, zamiast modyfikować lub tworzyć unikalne kontenery dla każdego środowiska. Po jakichkolwiek zmianach w aplikacji obraz kontenera powinien być odbudowywany i wdrażany we wszystkich używanych środowiskach. Przy okazji, w zarządzaniu systemami IT stosuje się podobną zasadę, znaną jako zasada niezmienności serwerów i infrastruktury.
Celem IIP jest zapobieganie tworzeniu oddzielnych obrazów kontenerów dla różnych środowisk wykonawczych i używanie wszędzie tego samego obrazu wraz z odpowiednią konfiguracją dla konkretnego środowiska. Przestrzeganie tej zasady umożliwia realizację takich istotnych z perspektywy automatyzacji praktyk systemów chmurowych, jak cofnienie (roll-back) i wdrożenie (roll-forward) aktualizacji aplikacji.

Zasada jednorazowości procesów (Process Disposability Principle, PDP)
Jedną z najważniejszych cech kontenera jest jego efemeryczność: instancja kontenera jest łatwa do stworzenia i łatwa do zniszczenia, dlatego w każdej chwili można ją łatwo zastąpić inną instancją. Powodów takiej wymiany może być wiele: niezdanie testu na sprawność, skalowanie aplikacji, przenoszenie na inny host, wyczerpanie zasobów platformy lub inne sytuacje.
W związku z tym, aplikacje kontenerowe powinny przechowywać swoje dane za pomocą zewnętrznych narzędzi lub wykorzystywać wewnętrzne schematy rozproszone z redundancją. Dodatkowo, aplikacja powinna uruchamiać się szybko i kończyć działanie w krótkim czasie, a także być gotowa na niespodziewaną awarię sprzętu.
Jedną z praktyk, która wspiera wdrażanie tej zasady, jest tworzenie małych kontenerów. Środowiska chmurowe mogą automatycznie dobierać hosta do uruchomienia instancji kontenera, dlatego im mniejszy rozmiar kontenera, tym szybciej się uruchomi – po prostu szybciej skopiuje się na docelowego hosta przez sieć.
Zasada samowystarczalności (Self-containment Principle, S-CP)
Zgodnie z tą zasadą, na etapie budowy kontenera wszystkie niezbędne komponenty są w niego włączane. Kontener powinien być tworzony z założeniem, że w systemie jest tylko czyste jądro Linux, dlatego wszystkie dodatkowe biblioteki muszą być umieszczone w samym kontenerze. Tam również powinny znajdować się takie elementy, jak środowisko wykonawcze dla odpowiedniego języka programowania, platforma aplikacji (jeśli to konieczne) oraz inne zależności, które będą potrzebne podczas działania aplikacji kontenerowej.

Wyjątki są robione jedynie dla konfiguracji, które różnią się w zależności od środowiska, i powinny być dostarczane na etapie wykonania, na przykład za pośrednictwem Kubernetes ConfigMap.
Aplikacja może zawierać kilka skonteneryzowanych komponentów, na przykład oddzielny kontener DBMS w składzie kontenerowej aplikacji webowej. Zgodnie z zasadą S-CP, te kontenery powinny być zachowane oddzielnie, tak aby kontener DBMS zawierał wszystko, co potrzebne do działania bazy danych, a kontener aplikacji webowej – wszystko niezbędne do działania aplikacji webowej, w tym serwer webowy. W wyniku tego, w trakcie działania kontener aplikacji webowej będzie zależny od kontenera DBMS i będzie się z nim komunikował w razie potrzeby.
Zasada ograniczenia na etapie wykonania (Runtime Confinement Principle, RCP)
Zasada S-CP określa, jak powinien być zbudowany kontener i co powinien zawierać binarny plik obrazu. Jednak kontener to nie tylko „czarna skrzynka”, która ma tylko jedną cechę – rozmiar pliku. W trakcie działania kontener zyskuje także inne wymiary: objętość używanej pamięci, czas procesora oraz inne zasoby systemowe.

I tutaj przyda się zasada RCP, zgodnie z którą kontener powinien oddzielać swoje wymagania dotyczące zasobów systemowych i przekazywać je platformie. Mając profile zasobów każdego kontenera (jakie zasoby CPU, pamięci, sieci i systemu dyskowego są mu potrzebne), platforma może optymalnie realizować dyspozycję i automatyczne skalowanie, zarządzać wydajnością IT oraz utrzymywać poziomy SLA dla kontenerów.
Oprócz zaspokajania wymagań dotyczących zasobów kontenera, aplikacja również musi przestrzegać wyznaczonych przez siebie ram. W przeciwnym razie, w przypadku niedoboru zasobów, platforma z większym prawdopodobieństwem umieści ją na liście aplikacji, które należy przerwać lub migrować.
Mówiąc o orientacji na chmurę, przede wszystkim mamy na myśli sposób działania.
Powyżej sformułowaliśmy szereg ogólnych zasad, które stanowią metodologiczny fundament do budowania wysokiej jakości aplikacji kontenerowych dla środowisk chmurowych.
Warto zauważyć, że oprócz tych ogólnych zasad będziesz również potrzebować dodatkowych zaawansowanych metod i technik pracy z kontenerami. Ponadto mamy kilka krótkich zaleceń, które mają bardziej konkretny charakter i powinny być stosowane (lub nie) w zależności od sytuacji:
- Staraj się zmniejszać rozmiar obrazów: usuwaj pliki tymczasowe i nie instaluj niepotrzebnych pakietów – im mniejszy rozmiar kontenera, tym szybciej jest on budowany i kopiowany na docelowy host przez sieć.
- Skup się na losowych identyfikatorach użytkowników (User-ID): nie używaj polecenia sudo ani żadnych specjalnych identyfikatorów użytkowników do uruchamiania swoich kontenerów.
- Oznaczaj ważne porty: numery portów można ustalać także w czasie działania, ale lepiej wskazać je za pomocą polecenia EXPOSE – innym ludziom i programom będzie łatwiej korzystać z Twoich obrazów.
- Przechowuj dane trwałe na wolumenach: dane, które muszą pozostać po zniszczeniu kontenera, należy zapisywać na wolumenach.
- Zadawaj metadane obrazu: tagi, etykiety i adnotacje ułatwiają korzystanie z obrazów – inni deweloperzy będą ci wdzięczni.
- Synchronizuj hosta i obrazy: niektóre aplikacje kontenerowe wymagają synchronizacji kontenera z hostem zgodnie z określonymi atrybutami, takimi jak czas czy identyfikator maszyny.
- Na koniec dzielimy się szablonami i najlepszymi praktykami, które pomogą w efektywnym wprowadzaniu wymienionych powyżej zasad:
11 czerwca o 11:00
Czego się nauczysz:
- Niezmienny Red Hat Enterprise Linux CoreOS
- Siatka usług OpenShift
- Framework operatora
- Framework Knative
Źródło: habr.com
