Systemy rozproszone niezależne od platformy – wprowadzenie do Dapr.io

Inetum | Rozwój oprogramowania | 02.06.2021

Tworzenie coraz bardziej popularnych systemów rozproszonych oraz migracja do architektury mikrousług może być dziś o wiele łatwiejsza. Z pomocą przychodzą narzędzia takie jak Dapr, które pozwalają budować systemy rozproszone niezależne od platformy z „klocków”. Przeczytaj artykuł, w którym znajdziesz wprowadzenie do Dapr.io – narzędzia, które wychodzi naprzeciw wyzwaniom architektury mikrousług.

Architektura mikrousług i jej wyzwania

Budowa rozproszonych systemów w architekturze mikrousług (ang. microservices) rozwiązuje problemy z dostępnością, skalowalnością, zwiększa naszą elastyczność oraz skraca czas dostarczenia nowej funkcjonalności na rynek, co jest istotnym aspektem projektów rozwoju oprogramowania. Natomiast złożoność takich systemów wymaga wiedzy, jak radzić sobie z takimi wyzwaniami jak rozproszone transakcje, równoważenie obciążenia, przechowywanie stanu, monitoring, komunikacja pomiędzy usługami oraz jej bezpieczeństwo. W konsekwencji programiści część swojego czasu muszą przeznaczyć na podejmowanie decyzji o wyborze narzędzi, wzorców i rozwiązywanie problemów, które nie mają bezpośredniego związku z implementacją logiki biznesowej.

Dapr – budowa z klocków systemów rozproszonych niezależnych od platformy

W odpowiedzi na te wyzwania w październiku 2019 roku Microsoft powołał do życia na platformie GitHub (https://github.com/dapr) projekt open source Dapr (Distributed Application Runtime), którego pierwsza stabilna wersja przeznaczona do użytku produkcyjnego została wydana w lutym 2021 roku. Zgodnie z przyjętym mottem “Any language, any framework, run anywhere” kluczową cechą Dapr jest niezależność od języka programowania oraz środowiska, w którym mikrousługi są uruchamiane. Stanowi on zbiór dobrych praktyk (wzorców projektowych) zaimplementowanych w formie niezależnych bloków konstrukcyjnych (ang. building blocks) udostępnianych z wykorzystaniem wzorca przyczepy (ang. sidecar). Wielu programistów nurtuje  pytanie „Czy Dapr to Service Mesh?”. Podczas gdy narzędzia Service Mesh takie jak Istio, Linkerd czy OSM ułatwiają komunikację pomiędzy mikrousługami za pomocą sidecar proxy, zadaniem DAPR jest raczej wspieranie developerów budujących aplikacje rozproszone.

Przeczytaj także: Service Mesh – ale komu to potrzebne.

DAPR w systemach rozproszonych

Wzorzec przyczepy

Wzorzec przyczepy jest jednowęzłowym wzorcem składającym się z dwóch kontenerów: aplikacji (zawierającego logikę biznesową) i przyczepy. Zadaniem kontenera przyczepy jest rozszerzanie (udostępnianie nowej funkcjonalności) i usprawnienie kontenera aplikacji. Zaletą korzystania z tego wzorca jest modułowość oraz możliwość ponownego wykorzystania komponentów użytych jako przyczepy. Ułatwia on również integrację ze starszymi rozwiązaniami bez konieczności ingerencji w ich kod.

JPro 02.06 graphic 2 - Systemy rozproszone niezależne od platformy – wprowadzenie do Dapr.io

Kontener lub proces (Dapr nie jest zależny od Kubernetesa i może być uruchamiany niezależnie nawet na urządzeniach brzegowych) przyczepy Dapr udostępnia kontenerowi/procesowi aplikacji uniwersalne API do poszczególnych bloków konstrukcyjnych. Komunikacja odbywa za pomocą protokołów HTTP/gRPC. Wykorzystanie bloków konstrukcyjnych nie wymaga zatem umieszczania w kodzie aplikacji żadnych zależności do Dapr, a integracja odbywa się w trakcie uruchamiania kodu, a nie kompilacji. Dodatkowo, aby uczynić proces bardziej intuicyjnym, Dapr udostępnia SDK dla takich języków, jak:

  • Go
  • Java
  • JavaScript
  • Python
  • .NET
  • PHP
  • C++

Takie podejście umożliwia również aplikacjom skorzystanie z rozwiązań/wzorców niedostępnych w języku, w którym zostały napisane. Przykładowo, aplikacja napisana w PHP może skorzystać z dobrodziejstw wzorca aktora (to wzorzec wykorzystywany dla aplikacji wielowątkowych).

Bloki konstrukcyjne

Bloki konstrukcyjne stanowią szkielet dla powstających na nich systemów rozproszonych, hermetyzując funkcjonalności infrastruktury. Każdy blok składa się z publicznego API oraz komponentów, które implementują jego funkcjonalność. Komponenty te mogą być wymieniane bez konieczności zmieniania udostępnianego API, co stanowi dodatkową warstwę abstrakcji. Takie podejście odseparowuje oraz uniezależnia aplikację od   rozwiązań różnych dostawców i umożliwia migrację bez konieczności ingerencji w kod.

Budowa bloku konstrukcyjnego

Przykładowe użycie: migracja z Mongo DB do Redis

Nasza aplikacja do zarządzania stanem aplikacji wykorzystuje no-SQLową bazę danych – rozwiązanie MongoDB, odwołując się do niego bezpośrednio w kodzie poprzez SDK. Ze względów na wydajność podjęliśmy decyzję o migracji do Redis. W konsekwencji musimy zmienić SDK oraz kod aplikacji charakterystyczny dla MongoDB, aby dostosować go do Redis. W przypadku gdy od początku wykorzystywalibyśmy blok konstrukcyjny zarządzania stanem z Dapr, taka zmiana ograniczyłaby się do korekty typu komponentu w pliku konfiguracyjnym YAML.

bloki kontrukcyjne i ich odpowiedzialności

Dapr składa się z siedmiu bloków konstrukcyjnych, natomiast z uwagi na jego otwartą naturę dodawanie kolejnych nie stanowi problemu.  Przyjrzyjmy się poszczególnym blokom w kontekście ich odpowiedzialności.

Service-to-service invocation

Umożliwia bezpośrednią komunikację między usługami, zapewniając:

  • niezawodność – automatyczne ponawianie wysyłania wiadomości w przypadku błędów np. sieciowych
  • wzajemne uwierzytelnianie (mTLS) oraz szyfrowane
  • kontrolę dostępu (ACL)
  • równoważenie obciążenia – algorytm Round Robin
  • automatyczne wykrywanie usług ( service discovery) – wykorzystuje Kubernetes DNS lub mDNS w zależności od środowiska.

State management

Umożliwia zarządzanie stanem usług / aktorów, zapewniając:

  • magazyny danych w postaci wtyczek – można podpiąć jedno z wielu dostępnych na rynku rozwiązań, Redis, MongoDB, MySQL itp.
  • optymistyczną kontrolę współbieżności optymistyczną kontrolę współbieżności z wykorzystaniem nagłówków Etag
  • spójność danych – wspiera silną ( strong consistency) oraz ostateczną spójność (ang. eventual consistency)
  • operacje masowe (ang. bulk)

Publish and subscribe

Implementuje wzorzec Publish/Subscribe, zapewniając:

  • integrację z takimi rozwiązaniami jak Redis Streams, Azure Service Bus, AWS SNS/SQS itp.
  • format przesyłanych komunikatów zgodny ze specyfikacją CloudEvents
  • gwarancję dostarczenia komunikatu (co najmniej raz)
  • kontrolę czasu życia wiadomości ( Time-To-Live)
  • tematyczne grupowanie komunikatów, umożliwiając kontrolę wysyłających oraz odbierających
  • grupowanie odbiorców komunikatów – implementuje wzorzec konkurujących odbiorców

Resource bindings

Umożliwia dwukierunkową integrację z zewnętrznymi źródłami (systemami/komponentami) w oderwaniu od konkretnej implementacji. Wiązanie zdarzeń z konkretnymi akcjami odbywa się na poziomie plików konfiguracyjnych YAML. Przykładowo w odpowiedzi na zdarzenia rejestracji użytkownika wywołujemy zewnętrzną usługę SendGrid wysyłającą e-mail z potwierdzeniem. Zmiana usługodawcy do wysyłki e-maili nie pociągnie zmian w kodzie aplikacji – wystarczy zmienić konfigurację i może to zrobić operator aplikacji, nie angażując programistów.

Actors

Implementuje wzorzec aktora (ang. actor model), który upraszcza budowę wysoce dostępnych i niezawodnych systemów bez konieczności stosowania złożonych wzorców współbieżności i skalowania. W modelu tym aktor stanowi podstawową jednostkę przetwarzania współbieżnego. Aktorzy są całkowicie hermetyczni i komunikują się ze światem zewnętrznym tylko za pomocą wiadomości.

Charakterystyka aktorów:

  • zarządzają swoim własnym stanem (aktorzy nie współdzielą stanu)
  • odbierają wiadomości od innych aktorów i przetwarzają je sekwencyjnie (jeden aktor = jeden wątek)
  • komunikują się asynchronicznie z innymi aktorami

Podobnie jak we frameworku Microsoft Orleans, w Dapr zastosowano koncepcję wirtualnego aktora, gdzie zarządzanie cyklem życia aktora odbywa się automatycznie.

Observability

Zapewnia ustrukturyzowane podejście do wykorzystania:

  • metryk – do ich monitorowania możemy wykorzystać np. Grafanę
  • logów – przeglądanie możemy zrealizować za pomocą np. FluentD lub Elasticsearch i Kibany
  • distributed tracing – zgodne ze standardami W3C Trace Context, co zapewnia integrację z takimi rozwiązaniami jak ZIPKIN, Jaeger, Application Insights

Secrets

Zapewnia bezpieczne przechowywanie wrażliwych danych konfiguracyjnych, tj. hasła, klucze, integrując się z takimi rozwiązaniami jak Azure Key Vault, HashiCorp Vault, Kubernetes Secrets itp.

Aplikacje rozproszone a Dapr – podsumowanie

Pandemia COVID-19 i związane z nią zmiany i wyzwania biznesowe gwałtownie przyśpieszyły tempo przenoszenia infrastruktury do chmury obliczeniowej i wpłynęły na wzrost popularności środowisk wielochmurowych (ang. multicloud) oraz hybrydowych. Wymaganie wysokiej skalowalności aplikacji wymusiło „rozbijanie” monolitów
i transformację do architektury mikrousług. Powyższe stawia przed firmami poważne wyzwanie technologiczne oraz kadrowe (z uwagi na ograniczoną dostępność specjalistów z danej dziedziny). Rozwiązania takie jak Dapr (https://dapr.io) oraz Open Application Model (https://oam.dev) i stojące za nimi koncepcje, dostarczając zbiór najlepszych praktyk oraz usprawniając proces migracji, mogą tą drogę znacznie skrócić i sprawić, aby stała się mniej wyboista.