HashiCorp Consul – networking i zarządzanie konfiguracją

HashiCorp Consul – networking i zarządzanie konfiguracją

Cześć, zapraszam na przedostatnią część z serii wpisów poświęconych narzędziom HashiCorp. Tym razem skupimy się na jednym z bardziej niedocenianych narzędzi służących do service discovery, service mesh i zarządzania configuracją. Zapraszam do lektury wpisu, w którym omówimy architekturę Consula, jego podstawowe funkcje oraz najlepsze praktyki związane z jego wykorzystaniem. Tradycyjnie, w drugiej części wpisu, rozbudujemy naszą platformę o to narzędzie. W sposób automatyczny wdrożymy klaster Consula oraz zarejestrujemy aplikacje. Dodatkowo, wykorzystamy Fabio jako load balancer, serdecznie zapraszam.

Inne wpisy z tej serii dotyczące narzędzi HashiCorp:

  1. HashiCorp Packer – automatyzacja budowy obrazów maszyn
  2. HashiCorp Terraform – infrastruktura jako kod
  3. HashiCorp Vault – centrum zarządzania sekretami
  4. HashiCorp Consul – networking i zarządzanie configuracją ( Jesteś tutaj! )
  5. HashiCorp Nomad – prosty orkiestrator aplikacji

HashiCorp Consul

HashiCorp Consul to odpowiedź na rosnącą złożoność współczesnych systemów informatycznych oraz ewolucję architektury aplikacji, przechodzących od monolitycznych do mikroserwisowych. W miarę jak aplikacje stają się coraz bardziej rozproszone i autonomiczne, pojawiają się wyzwania związane z zarządzaniem komunikacją między nimi oraz zapewnianiem bezpieczeństwa tego procesu. Konfiguracja dużej liczby mikroserwisów staje się również zadaniem problematycznym.

Consul oferuje rozwiązania dla tych wyzwań poprzez funkcje takie jak Service Discovery, które umożliwiają aplikacjom odnajdywanie się nawzajem w elastyczny i dynamiczny sposób. Service Mesh, który dodaje możliwość kontrolowania komunikacji między serwisami, w bardziej abstrakcyjny sposób często określany jako segmentacja. Oraz Key/Value Store wspomagający zarządzanie konfiguracją w dynamicznym systemie.

W bardzo przystępny sposób opowiada o tym Armon Dadgar CTO i wspołzalozyciej HashiCorp na tym filmiku.

Słowniczek pojęć

Consul jest narzędziem ściśle związanym z infrastrukturą sieciową, dlatego zanim przejdziemy do omawiania architektury konieczne będzie zapoznanie się z kilkoma kluczowymi terminam.

Agent – to każdy węzeł w klastrze Consul, czyli każdy node, na którym działa demon Consula. Agent może funkcjonować w jednym z dwóch trybów: serwerowym lub klienckim. Głównym zadaniem agenta jest utrzymywanie synchronizacji z pozostałymi członkami klastra oraz wykonywanie health checks.

Klient – klient to agent Consula który nie jest serwerem, wykonuje on health check i przekazuje wszystkie RPC (rządania od klijeta) do serverów Consula. Zużywa on bardzo mało zasobów w porównaniu do trybu serwerowego i główną jego aktywnością jest uczestnictwo w komunikacji czyli Gossip LAN (protokół Serf).

Serwer – jest to agent Consula, który uczestniczy w głosowaniu na lidera klastra oraz sam może zostać liderem (wyjątkiem jest serwer tylko do odczytu). Komunikuje się on za pomocą komunikacji Raft z liderem i Serf między klientami. W przeciwieństwie do klienta, serwer jest stanowy (stateful). Jeśli posiadamy kilka Data Center uczestniczy w komunikacji WAN Gossip. Serwer przekazuje żądania do lidera klastra.

Datacenter – jest to grupa członków klastra zlokalizowanych geograficznie blisko siebie. Często odpowiada to pojęciu regionu w kontekście infrastruktury w chmurze.

Leader – jeden z serwerów, do którego kierowane są zapytania i żądania od wszystkich członków klastra. Lider pełni kluczową rolę w utrzymaniu spójności. W przypadku utraty lidera rozpoczyna się tzw. Consensus czyli proces wybierania nowego lidera.

Gossip LAN – to mechanizm komunikacji między członkami klastra znajdującymi się w tym samym datacenter. Dzięki niemu członkowie klastra w danym obszarze mogą wymieniać informacje i utrzymywać synchronizację w czasie rzeczywistym.

Gossip WAN – podobnie jak w przypadku LAN ale dotyczy tylko komunikacji między serwerami Consula zlokalizowanych w różnych datacenter.

RPC (Remote Procedure Call) – jeden z mechanizmów używanych do komunikacji między klientem, a serwerem. W tym kontekście, RPC umożliwia klientowi wywołanie procedury lub funkcji na zdalnym serwerze, który przetwarza żądanie i zwraca odpowiedź.

ACL – Podstawowy sposób przyznawania lub ograniczania dostepów do różnych funkcjonalności Consula, który możemy przypisać do tokena.

Architektura

Teraz, kiedy posiadamy już podstawową wiedzę na temat kluczowych pojęć, które będziemy wykorzystywać, przejdźmy do omówienia architektury. Consul pełni niezwykle istotną rolę, odpowiadając za przepływ informacji oraz zabezpieczanie danych, odnajdywanie serwisów i konfigurację parametrów. Z tego względu najczęściej wdrażany jest w nieparzystych liczbach jako klaster. Takie podejście ma na celu minimalizację ryzyka utraty dostępności w przypadku awarii jednego z serwerów, co pozwala zachować integralność działania całego systemu.

Poniżej przedstawiam przykład prostego klastra, który składa się z trzech serwerów Consula oraz trzech klientów. Dodatkowo, zaznaczyłem liniami, jak odbywa się komunikacja oraz jakie domyślne porty są używane.

Jak można zauważyć na pierwszy rzut oka, komunikacja w sieci lokalnej (LAN Gossip) zachodzi między każdym członkiem klastra. Warto przypomnieć, że do tego celu wykorzystywany jest Serf, a domyślny port to 8301. LAN Gossip wykorzystuje protokół UDP, ale w przypadku jego niedostępności automatycznie przechodzi na protokół TCP.

Dużo ważniejsza dla synchronizacji i stanu klastra jest komunikacja Raft odbywająca się tylko na poziomie serwer-lider, domyśle na porcie 8300. Zadaniem lidera jest replikowanie wszystkich danych pomiędzy swoich followersów. Follower w tym wypadku to serwer Consula który nie jest liderem. Wszystkie serwery Consula biorą udział w głosowaniu na lidera, wyjątkiem są tylko serwery typu read replica.

Warto pamiętać, że przy wyborze liczby serwerów w klastrze Consula nie powinno się wybierać liczby parzystej. Taki wybór może spowodować problem z równym rozdzieleniem głosów na dwa różne serwery, co doprowadzi do podziału klastra. Zaleca się, aby liczba serwerów była liczbą nieparzystą, taką jak 3, 5 lub 7. Takie wartości są powszechnie rekomendowane przez HashiCorp.

Przyjęcie liczby serwerów w zakresie 3-7 jest często wystarczające do obsługi tysięcy agentów w klastrze. Większa liczba serwerów może wpłynąć niekorzystnie na czas komunikacji między agentami. Dla bardziej szczegółowych informacji dotyczących odporności na awarie warto skorzystać z dokumentacji, gdzie znajdują się szczegółowe wytyczne i rekomendacje dotyczące konfiguracji klastra.

Porty

Consul w domyśnej konfiguracji wymaga otwarcia następujących portów:

PrzeznaczeniePort
server: Server RPC address (TCP Only)8300
LAN Serf: The Serf LAN port (TCP and UDP)8301
Wan Serf: The Serf WAN port (TCP and UDP)8302
HTTP: The HTTP API (TCP Only)8500
HTTPS: The HTTPs APIdisabled (8501)
gRPC: The gRPC APIdisabled (8502)
gRPC TLS: The gRPC API with TLS connectionsdisabled (8503)
DNS: The DNS server (TCP and UDP)8600
Sidecar Proxy Min: Inclusive min port number to use for automatically assigned sidecar service registrations.21000
Sidecar Proxy Max: Inclusive max port number to use for automatically assigned sidecar service registrations.
21255
źródło:https://developer.hashicorp.com/consul/docs/install/ports

Jeśli chodzi o porty odnoszące się do Sidecar proxy jest to ściśle związane z service mesh, poruszę ten temat tylko teoretycznie.

Pierwsze uruchomienie

By zacząć pracę z tym narzędziem musimy je najpierw zainstalować. Instrukcję instalacji znajdziemy tutaj. Consul, podobnie jak HashiCorp Vault, można uruchomić w dwóch trybach: trybie „dev” i trybie „prod”. Tryb „dev” jest przeznaczony wyłącznie do celów testowych.

Wykonując polecenie consul agent -dev uruchamiany Consula w trybie testowym. Tryb ten jest ograniczony, a wszystkie operacie trzymane są w pamięci, więc w momencie zamknięcia aplikacji tracimy wszystkie dane. Tryb normalny/produkcyjny wymaga podania pliku konfiguracyjnego np. consul agent -config-file=consul_config.hcl

Na porcie 8500 maszyny lokalnej znajdziemy UI Consula.


Jeśli planujesz utworzyć własny klaster Consul, gorąco polecam skorzystanie z tutoriali przygotowanych przez HashiCorp. Zaoszczędzimy dzięki temu masę czasu.

Plik konfiguracyjny Consula

Plik konfiguracyjny Consula jest niezbędny zarówno dla agenta działającego w trybie serwera, jak i trybie klienta. Poniżej przedstawiam przykład konfiguracji dla agenta Consula działającego w trybie serwera:

datacenter = "datacenter-1"
node_name = "consul-1"
server = true
bootstrap_expect = 3
data_dir = "/home/consul/data"
log_level = "INFO"
enable_syslog = true

# Addresses
client_addr = "0.0.0.0"
bind_addr = "<HOST_IP_ADDRES>"

# ACL configuration
acl = {
  enabled = true
  default_policy = "allow"
  enable_token_persistence = true
}

# UI configuration
ui_config {
  enabled = true
}

# Join other Consul agents
retry_join = [ "provider=aws tag_key=function tag_value=consul-server region=eu-central-1" ]

# Gossip encryption
encrypt = "ABCDE-FGHIJ-LMNOP-RSTUW"
  • datacenter – określa, do którego centrum danych ma należeć ten członek Consula. Jest to ważne, aby agent wiedział, do jakiego środowiska ma się podłączyć
  • node_name – to unikalna nazwa danego członka klastra Consula
  • server – określa, czy agent ma działać w roli serwera
  • bootstrap_expect – to liczba oczekiwanych serwerów, które muszą być obecne w klastrze, zanim zostanie uruchomiony proces głosowania na lidera. Jest to ważne, aby zapewnić poprawne inicjowanie klastra
  • bind_addr – to adres IP, na którym Consul nasłuchuje na połączenia od innych agentów w klastrze
  • client_addr – to adres, pod którym użytkownik będzie łączyć się z danym agentem
  • retry_join – to lista adresów IP innych agentów Consula, z którymi ten agent ma próbować dołączyć do klastr
  • encrypt – to klucz używany do szyfrowania i deszyfrowania komunikacji w protokole Gossip LAN (Serf)

W powyższym przypadku dla retry_join zastosowałem providera aws dzięki któremu połaczy się on z wszystkimi innymi instancjami spełniającymi kryteria. Jest to dużo bardziej elastyczne i dynamiczne rozwiązanie niż podawanie statycznej listy adresów IP.

Drugi plik konfiguracyjny przeznaczony dla agenta w trybie klienta będzie omawiany od sekcji „service”:

server = false
datacenter = "datacenter-1"
node_name = "consul-client-1"

# Logging
log_level = "INFO"
enable_syslog = false

# Data persistence
data_dir = "home/backend/data"

# Networkig
client_addr = "127.0.0.1"
bind_addr = "<HOST_IP_ADDRES>"

# Join other Consul agents
retry_join = [ "provider=aws tag_key=function tag_value=consul-server region=eu-central-1" ]

## ACL configuration
acl = {
  enabled = true
  default_policy = "deny"
  enable_token_persistence = true
  tokens {
    agent  = "<token>"
    default  = "<token>"
  }
}

service {
  name = "backend"
  id = "backend-1"
  port = 8080
  address = "<HOST_IP_ADDRES>"
  token = "<token>"

  check
  {
   ...
  }
}

# Gossip encryption
encrypt = "ABCDE-FGHIJ-LMNOP-RSTUW"
  • service.name – zawiera nazwe serwisu jaki będzie zarejestrowany w Consulu
  • service.id – unikalna nazwa danego serwisu
  • service.token – jest to token jaki będzie wykorzystywany przez agenta wykonujacego czynności zwiazane z tym konkretnym serwisem
  • service.check – sekcja zawiera parametry zwiazane z health check dla danego serwisu

Warto zaznaczyć, że na jednym węźle może być zarejestrowanych wiele różnych serwisów.

Konfiguracja HealtCheck

Newralgicznym elementem konfiguracji klijentów consula jest Healt check. Do wyboru mamy kilka rodzajów healt check min. TCP, HTTP, Script, UDP, Doker. Poniżej udostępniam przykłądy healt check dla HTTP i TCP.

service {
  name = "backend"
  id = "backend-1"
  tags = [
    "urlprefix-/workout/all",
    "urlprefix-/workout/add",
    "urlprefix-/workout/update",
    "urlprefix-/workout/delete"
    ]
  port = 8080
  address = "10.0.1.203"
  token = "<token>"

  check
  {
    id       = "backend",
    name     = "backend status check",
    service_id = "backend-10.0.1.203",
    http     = "http://localhost:8080/actuator/health",
    interval = "15s",
    timeout = "5s"
  }
}

Widok healt check w konsoli Consula dla serwisu backend.

service {
  name = "frontend"
  id = "frontend-1"
  tags = [
    "urlprefix-/"
    ]
  port = 80
  address = "10.0.2.159"
  token = "<token>"

  check
  {
    id       = "frontend",
    name     = "frontend status check",
    service_id = "frontend-10.0.2.159",
    tcp     = "localhost:80",
    interval = "15s",
    timeout = "5s"
  }
}

Widok healt check w konsoli Consula dla serwisu frontend.

Pełną listę dostępnych healt check wraz z opisem znajdziemy w tym miejscu.

Zrozumienie parametrów pliku konfiguracyjnego jest dość ważne. Dokumentacja w tym obszarze jest bardzo obszerna i z pewnością pomoże w zrozumieniu, jak przygotować plik konfiguracyjny, aby spełnić konkretne wymagania i potrzeby danego agenta.

Consul CLI

W codziennych obowiązkach raczej w niewielkim zakresie korzysta się z CLI Consula. Zazwyczaj w momencie tworzenia nowego klastra lub naprawiania błędów. W dalszej części wpisu na bieżąco będę omawiał komendy z których będę korzystał. Liczba dostępnych komend wydaje się dość duża, na szczęście wiele z nich jest mało rozbudowanych i są dość konkretne.

Z podstawowych CLI na pewno można wyróżnić consul members, który pozwala na sprawdzenie wszystkich członków, oraz consul operator dzięki której możemy znaleźć lidera klastra.

Niezbędne przed pracą z CLI Consula jest podanie dwóch zmiennych środowiskowaych:
export CONSUL_HTTP_ADDR="http://consul.example.com:8500"
export CONSUL_HTTP_TOKEN="token"

Domyślnie, jeśli nie ustawimy zmiennej CONSUL_HTTP_ADDR w interfejsie wiersza poleceń (CLI), Consul będzie próbował nawiązać połączenie na porcie 8500 na lokalnej maszynie. A dzięki tokenowi, Consul sprawdza czy posiadamy uprawnienia do wykonania akcji. O tokenach więcej za chwilę.

Zarządzanie dostępami – ACL

Access Control List (ACL) to mechanizm, który służy do autoryzacji użytkowników w Consulu. Podobnie jak w przypadku narzędzia HashiCorp Vault, działanie ACL opiera się na przydzielaniu lub odbieraniu uprawnień za pomocą polityk. Każda akcja wykonywana w Consulu, czy to poprzez interfejs wiersza poleceń (CLI), interfejs użytkownika (UI) lub interfejs programistyczny (API), musi być autoryzowana poprzez token. Token ten określa, czy użytkownik ma uprawnienia do wykonania danej operacji. W przypadku braku tokenu, Consul automatycznie przypisuje operacji tzw. anonymous token.

Bootrstrap ACL

Domyślnie system ACL jest wyłączony, musimy wykonać bootstrap klastra by ustanowić master token tzw. Bootstrap Token (Global Management), który podobnie jak w przypadku HasiCorp Vault służy do skonfigurowania pozostałych tokenów dostępowych.

Bootstrap w klastrze Consul jest wykonywane, gdy klaster wybrał już lidera. Możesz to zrobić za pomocą polecenia consul acl bootstrap. W odpowiedzi otrzymasz plik JSON, który może wyglądać podobnie do poniższego:

AccessorID: edcfdcda-b6d0-1434-5539-b5acegda3c9a
SecretID: 4434s091-a4s9-48e6-0432-1fcb0fd5a1c8
Description: Bootstrap Token (Global Management)
Local: false
Create Time: 2023-09-21 21:02:21.543292134 +0000 UTC
Policies:
00000000-0000-0000-0000-000000000001 - global-management

SecretID jest wartością tokena, którą wykorzystujemy w żądaniach w Consulu w celu autoryzacji i uzyskania dostępu do określonych operacji. Warto zaznaczyć, że token Global Management zawsze jest przypisany do polityki o ID „00000000-0000-0000-0000-000000000001,” co nadaje mu pełne uprawnienia do zarządzania klastrem. Natomiast token anonimowy zawsze jest przypisany do polityki o ID „00000000-0000-0000-0000-000000000002,”

Po wygenerowaniu master tokena konieczne należy zmienić w pliku konfiguracyjnym serwera wartość default_policy z allow na deny.

# ACL configuration
acl = {
  enabled = true
  default_policy = "deny"   <--------
  enable_token_persistence = true
}

Oraz wykonać przeładowanie konfiguracji za pomocą consul reload.

Zasady, Polityki i Tokeny

Poniżej umieszczam rysunek z strony HashiCorp, który w prosty sposób pokazuje jak zasady, polityki i tokeny z sobą współgrają.

źródło: https://developer.hashicorp.com/consul/docs/security/acl/acl-policies

Zasady to najmniejszy fragment określający uprawnienia lub zakazy. Polityka to grupa zasad, natomiast do tokenu możemy przypisać wiele polityk.

Zacznijmy od najmniejszego elementu, poniżej widzimy składnie zasady (Rule), resource może przyjmować m.in. takie wartości jak: service, key, node, agent, session. Dodatkowo jeśli umieścimy je z _prefix oznacza to, że zasada dotyczy wszystkich zasobów o nazwie zaczynających się od niej.

Na przykład, token widoczny powyżej, dzięki polityce „A”, może odczytywać informacje dotyczące serwisów, które posiadają nazwę „web”, ale nie będzie miał dostępu do serwisów o nazwie „web-1”. Jeśli zamiast parametru service użylibyśmy service_prefix z wartością „web”, to token mógłby odczytywać dane dotyczące wszystkich serwisów, których nazwa zaczyna się od „web”. Jeśli pozostawimy pusty ciąg znaków w części „label”, oznacza to, że zasada dotyczy wszystkich zasobów danego rodzaju.

Podstawowa składnia zasady posiadająca label wygląda jak niżej

<resource> "<label>" {
  policy = "<policy disposition>"
}

policy disposition możemy przyjmować trzy stany, read, write oraz deny. Jak nie trudno się domyśleć read pozwala tylko na odczytywanie danych, write umożliwia zarówno na odczyt jak i zapis danych. A deny zakazuje nam danego działania. Deny zawsze nadpisuje pozostałe zasady jeśli stoją z sobą w sprzeczności.

Niektóre zasoby nie posiadają label, pełną listę dostępnych zasobów znajdziecie tutaj. Warto wiedzieć, że Consul posiada funkcjonalność umożliwiającą generowanie gotowych tokenów z politykami dopasowanymi do konkretnych serwisów i nodów. To znacząco przyspiesza pracę, ponieważ nie trzeba tworzyć całej polityki od zera. Szczegóły w tym linku.

Po stworzeniu pliku z politykami możemy je załadować do Consula za pomocą polecenia consul acl policy create -name "backend" -description "Policy for backend service" -rules @backend.hcl

service "backend" {
  policy = "write"
}

service_prefix "" {
  policy = "read"
}

Taką politykę możemy podpiąć do istniejącego tokenu za pomocą consul acl token update lub stworzyć nowy token z jej użyciem consul acl token create -description "Token for backend" -policy-name "backend" .

Jak widać, elastyczność edycji zasad w ACL w Consulu pozwala na tworzenie różnorodnych kompozycji z pomocą tylko niewielkiej liczby polityk.

Główne funkcjonalności

W tej sekcji omówię główne funkcjonalności HashiCorp Consul. Choć lista jest znacznie dłuższa, opisanie każdej z nich przez jedną osobę z praktycznego i wykonalnego punktu widzenia nie jest możliwe ani konieczne.

Service Discovery

Najważniejszym elementem HashiCorp Consul jest niezaprzeczalnie usługa Service Discovery. Jak już wspomniałem na początku, w erze mikroserwisów i szybkiego pojawiania się oraz znikania nowych usług niemożliwe jest ręczne śledzenie tych zmian. To właśnie mechanizmy działające w Consulu, takie jak automatyczna rejestracja i wyrejestrowywanie serwisów w klastrze oraz regularne sprawdzanie stanu serwisów (health checks), doskonale sprawdzają się w tej roli. Dzięki tym mechanizmom Consul zawsze posiada aktualne informacje o stanie klastra, co pozwala na tworzenie bardziej niezawodnych systemów.

Jeśli poprawnie skonfigurowaliśmy naszych agentów i serwisy możemy pobierać informacje o nich z katalogu Consula. Na poniższym zdjęciu widzimy klaster z zarejestrowanymi kilkoma serwisami.

Do interakcji z katalogiem Consula możemy wykorzystać CLI consul catalog, poniżej kilka przykładów.

Możemy też pobrać informację za pomocą API, fragment odpowiedzi poniżej. GET http://localhost:8500/v1/catalog/service/backend | jq

[
  {
    "ID": "2fda8a8c-bd9c-7805-3bea-9d33fd0548d7",
    "Node": "backend-10.0.1.203",
    "Address": "10.0.1.203",
    "Datacenter": "dev-eu-central-1",
    "TaggedAddresses": {
      "lan": "10.0.1.203",
     ...
    },
    ...
    },
    "ServiceKind": "",
    "ServiceID": "backend-10.0.1.203",
    "ServiceName": "backend",
   ...
  {
    "ID": "8c1915cf-2521-1739-8abd-09886bf21e98",
    "Node": "backend-10.0.2.181",
    "Address": "10.0.2.181",
    "Datacenter": "dev-eu-central-1",
    "TaggedAddresses": {
      "lan": "10.0.2.181",
      ...
    },
    ...
    },
    "ServiceKind": "",
    "ServiceID": "backend-10.0.2.181",
    "ServiceName": "backend",
 ...
  }
]

Oczywiście, ręczne wykonywanie takich operacji byłoby nieefektywne, dlatego istnieją gotowe frameworki i narzędzia, które współpracują z katalogiem Consul i automatyzują proces load balansingu. Przykłady to Fabio, HAProxy, NGINX, F5. W części praktycznej wykorzystamy Fabio. Jak skonfigurować pozostałe możliwości znajdziecie jak zwykle w dokumentacji.

Key/Value storage

Magazyn Key/Value jest drugą z najbardziej podstawowych funkcjonalności w Consulu. To prosty magazyn typu K/V (klucz-wartość), który umożliwia przechowywanie danych. Należy pamiętać, że nie jest to tożsame z funkcjonalnością K/V dostępną w HashiCorp Vault, i nie powinniśmy przechowywać w nim poufnych danych ani sekretów. Dane zapisane w magazynie K/V są przechowywane przez serwery Consula i replikowane w klastrze. Maksymalny rozmiar obiektu, który możemy umieścić w magazynie wynosi 512 KB. Do interakcji z tą funkcjonalnością możemy używać CLI Consula za pomocą komendy consul kv

Podstawowe pod komendy umieszczam poniżej. Wydaje mi się, że są dość oczywiste.

Przykład dla klucza /config/workoutrecorder/fabio-private_ip z panelu użytkownika.

Z ciekawostek spotkałem się z wykorzystywaniem magazynu Key/Value w Consulu jako miejsca przechowywania stanu dla HashiCorp Terraform. Nie jestem do końca przekonany nad takim rozwiązaniem, niemniej jednak daje ono pewną elastyczność. Z pewnością w takim przypadku dostęp do tej ścieżki powinien być ściśle ograniczony i zarezerwowany tylko dla maszyny.

Service Mesh

Przejdzmy do jednego z najciekawszych elementów Consula, czyli Service Mesh. W tym wpisie niestety nie znajdziecie praktycznego przykładu z jego użyciem. Ale postaram się przystępnie wyjaśnić na czym ta funkcjonalność polega.

Zacznijmy od prostego przykładu gdy dwa serwisy muszą z sobą współpracować.

Na tym prostym przykładzie widzimy, że nawiązanie połączenia z serwisu A do serwisu B wymaga precyzyjnej definicji reguły, która określa źródło, cel oraz porty, na których połączenie jest dozwolone. W sytuacji, gdy mamy duży i skomplikowany system, w którym adresy często się zmieniają lub istnieje wiele różnych połączeń, takie konfiguracje firewalla mogą zawierać setki takich reguł. W wielu przypadkach takie podejście jest ok, dopóki wszystko działa prawidłowo. Jednak gdy pojawią się problemy z połączeniami lub konieczne będzie debugowanie, to współczuje osobie która musi się tym zająć.

Jedną z zalet Service Mesh jest to, że wprowadza warstwę abstrakcji dla problemu komunikacji między usługami. My definiujemy jedynie reguły typu „Serwis A może rozmawiać z Serwisem B”, i nie interesuje nas na jakich adresach i portach te usługi się znajdują. W przypadku Consula, te reguły nazywane są Intention. Na poniższym rysunku przedstawiono sposób, w jaki ten problem jest rozwiązany.

Dzięki temu, że klaster Consula przechowuje wszystkie niezbędne informacje dotyczące adresów i połączeń między serwisami, może on zajmować się całą brudną robotą związaną z przekazywaniem adresów IP, ich porównywaniem oraz weryfikacją odpowiednich certyfikatów szyfrowania. Dodając Proxy do każdego z serwisów i łącząc je z klientem Consula, może on efektywnie nadzorować cały ruch i kontrolować, jakie serwisy mogą komunikować się z innymi.

Na przedstawionym powyżej rysunku cały ruch przychodzący i wychodzący z serwisu musi przejść przez proxy. Kiedy klient Consula otrzyma potwierdzenie, że dane połączenie jest dozwolone, proxy komunikuje się z odpowiednikiem w serwisie docelowym, a połączenie jest nawiązywane.

Temat Service Mesh jest dość skomplikowany, zdecydowanie polecam zapoznać się z dokumentacją. Za pomocą tego narzędzia jesteśmy w stanie wdrożyć w naszym systemie tzw. Zero trust security model.

Zabrakło

W wpisie starałem się przekazać najcześciej wykorzystywane funkcjonalności HasiCorp Consul. Celowo pominąłem wszelkie elementy, które wzbogacają wersję Enterprise Consula. Pomimo moich najszczerszych chęci narzędzie to jest tak bardzo rozbudowane, że nie sposób omówić go wyczerpująco w jednym wpisie.

Poza opisanymi powyżej funkcjonalnościami, gorąco zachęcam by zapoznać się jeszcze z następującymi tematami:

  • ESM Consul – zdalna rejestracja serwisów w przypadku braku możliwości instalacji agenta na hoście
  • Consul-templates i envconsul – auto uzupełnianie zmiennych w szablonach Consula
  • Watches – funkcjonalność wykrywająca i reagująca na zmiany w K/V, nodach, serwisach i nie tylko
  • Preperade Query – obsługa bardziej skomplikowanych zapytań o serwisy

Automatyczny deployment HashiCorp Consul

Tradycyjnie, po części teoretycznej, przechodzimy do części praktycznej. W poprzednich częściach naszych wpisów dotyczących narzędzi HashiCorp, mieliśmy już okazję korzystać z narzędzi takich jak Packer, Terraform, a ostatnio dodaliśmy serwer Vault. W tym przypadku do naszej układanki dołączymy pełnoprawny klaster Consula.

UWAGA NA ZMIANY W REPOZYTORIACH !!!

Proszę zwrócić uwagę na daty commitów, które znajdziecie we wszystkich repozytoriach dostępnych na https://github.com/red-devops. Często korzystam z wcześniejszego kodu w kolejnych wpisach, dostosowując go do bieżących zadań. W związku z tym może się zdarzyć, że kod prezentowany w tym wpisie będzie różnił się od tego, który znajdziecie w najnowszym commicie repozytorium. Niemniej jednak wszystkie główne commity zostawiam, dlatego proszę porównywać daty publikacji wpisu z datami commitów.

Założenia i komunikacja

Klaster Consula zostanie utworzony z trzech agentów, które będą działać jako serwery, oraz kilku innych, które będą pełniły rolę klientów. Klienci zostaną uruchomieni na trzech różnych hostach: Backend, Frontend i Fabio. Serwis backend to aplikacja Springowa, która komunikuje się z bazą danych, a frontend to serwer Nginx, który dostarcza użytkownikowi aplikację Angularową. Oba te serwisy działają w prywatnej podsieci, niedostępnej dla zewnętrznych użytkowników. Serwis Fabio zostanie umieszczony w publicznej podsieci i będzie pełnił funkcję proxy. Fabio integruje się z HashiCorp Consul i będzie dynamicznie mapował zdrowe serwisy oraz wykonywał load balancing. Poniżej przedstawiona jest uproszczona architektura z perspektywy Consula, z zaznaczoną logiczną ścieżką żądań od klienta aplikacji.


Po uruchomieniu wszystkich aplikacji i podłączeniu ich do klastra Consula, Fabio odczytuje dostępne serwisy w klastrze Consula i mapuje ścieżki zgodnie z dokumentacją.

Proces wysyłania żądań i zwracania odpowiedzi wygląda następująco:

  1. Użytkownik wysyła żądanie na publiczny adres IP oraz port 9999, na którym nasłuchuje Fabio.
  2. Fabio przekierowuje ruch na serwer frontendowy na porcie 80 i zwraca użytkownikowi stronę główną aplikacji „workoutrecorder”.
  3. Następnie aplikacja Angularowa w przeglądarce użytkownika ponownie wysyła zapytanie do backendu przez Fabio, w celu pobrania najnowszych danych.
  4. Aplikacja Springowa w serwisie backendowym sprawdza dane w bazie danych i przesyła je do działającej w przeglądarce użytkownika aplikacji Angularowej.

Etapy wdrażania

Nasz system znacząco się rozbudowała dlatego musimy zachować pewną kolejność działań. W celu deploymentu platformy i aplikacji będziemy używać następujących repozytoriów.

Główne repozytorium terraform: Workout-recorder-consul
Playbook ansibla instalujący HasiCorp Vault: Vault-server-ansible
Playbook ansibla instalującu HasiCorp Consul: Consul-server-ansible
Repo do release i deploymentu backendu: workoutrecorder-backend
Repo do release i deploymentu frontendu: workoutrecorder-frontend
Repo do deploymentu fabio: workoutrecorder-fabio

W znacznej większości cały proces przebiega automatycznie, to znaczy, że kod Terraforma tworzy w określonej kolejności całą infrastrukturę, sieć, grupy zabezpieczeń, serwer HashiCorp Vault, klaster Consula, bazę danych i instancje pod aplikacje. Następnie będziemy musieli użyć playbooków Ansibla do zainstalowania aplikacji frontend, backend, fabio wraz z agentami Consula na odpowiednich instancjach. Cały system zostanie wdrożony w chmurze AWS, a GitHub Self-Hosted Runner będzie odpowiedzialny za wykonanie tych czynności.

Zaczynamy

Rozpoczynamy od momentu, w którym nasz self-hosted runner jest już gotowy do rozpoczęcia pracy. Sieć została utworzona, a niezbędne grupy zabezpieczeń są skonfigurowane. Aby uruchomić proces za pomocą agenta, konieczne jest wysłanie commita, który go wywoła. Szczegóły i sposób wdrożenia infrastruktury zostały omówione w jednym z poprzednich wpisów dotyczących narzędzia HashiCorp Terraform, w sekcji „Wdrażamy infrastrukturę”.

W pierwszej kolejności zostanie utworzona instancja Vaulta, która jest niezbędna do przechowywania sekretów, które zostaną utworzone w kolejnych etapach. Oczywiście możemy korzystać z AWS Secret Manager, co czasami jest wygodne. Niemniej jednak, korzystając z HasiCorp Vault, zyskujemy większą kontrolę nad zarządzaniem sekretami.

Następnym krokiem będzie stworzenie klastra serwerów HasiCorp Consul. Terraform utworzy trzy instancje, a skrypty user_data zainstalują, skonfigurują i uruchomią serwery Consula z pomocą Ansibla. W kodzie Terraform dodałem zasób time_sleep, aby dać playbookowi Ansibla wystarczająco czasu na przeprowadzenie wszystkich konfiguracji. W przeciwnym przypadku Terraform wygenerowałby błąd, ponieważ provider Consula użyty w następnym kroku nie skonfigurowałby się poprawnie.

resource "time_sleep" "wait_5_minutes" {
  depends_on      = [aws_instance.consul_server]
  create_duration = "5m"
}

Zobaczmy, jakie operacje wykonuje playbook w celu konfiguracji serwerów Consula. Pomijając wszystkie operacje związane z przygotowaniem instancji, takie jak tworzenie grup, użytkowników, katalogów, itp., Ansible przeprowadza następujące kroki:

  1. Pobiera i rozpakowuje binarkę Consula.
  2. Pobiera sekret z HashiCorp Vault, który będzie używany do zaszyfrowania komunikacji w protokole gossip.
  3. Renderuje plik konfiguracyjny i plik usługi dla systemd.
  4. Wykonuje operację bootstrap ACL.
  5. Zmienia domyślną politykę na deny i przeładowuje konfigurację.

Fragment logów z wykonania user data poniżej.

W kroku terraform 170-data-base wykorzystujemy provaidera Consula w celu zapisania endpointu bazy danych do Key/Value store.

provider "consul" {
  address    = "http://${data.terraform_remote_state.consul_instance.outputs.consul_private_ip}:8500"
  token      = jsondecode(data.aws_secretsmanager_secret_version.consul_admin_token.secret_string)["acl-bootsrap-token"]
  datacenter = "${terraform.workspace}-${var.region}"

resource "consul_keys" "database_endpoint" {
  key {
    path  = "config/workoutrecorder/database-endpoint"
    value = module.db.db_instance_endpoint
  }
}

Podczas tworzenia bazy danych, korzystając z natywnego rozwiązania AWS, czyli RDS, nie mamy kontroli nad infrastrukturą, na której ta baza działa. Dlatego do kodu Terraform dodałem AWS Lambda, która wykonuje skrypty SQL w celu konfiguracji bazy danych dla aplikacji backendowej.

Ostatnim etapem w kodzie Terraform jest moduł 180-instances, w którym tworzone są trzy kolejne instancje dla aplikacji Fabio, Frontendu i Backendu. Ze względu na brak posiadanej domeny, ten etap może wydawać się nieco dziwny, ale zostanie to wyjaśnione w sekcji „Uwagi na koniec”. Po utworzeniu tych instancji pobieramy publiczny adres hosta Fabio i wydajemy nową wersję aplikacji frontendowej.

Gdy wszystkie aplikacje zarejestrują się w klastrze konsula powinniśmy zobaczyć widok jak na poniższym zdięciu.

Zwróćcie proszę uwagę na to, że serwisy używają dodatkowo tagów. Dzięki temu Fabio potrafi automatycznie mapować odpowiednie ścieżki na serwisy. Mapowania Fabia można sprawdzić domyślnie na porcie 9998.

Cała nasza aplikacja jest wreszcie wdrożona, więc możemy z niej skorzystać, odwiedzając port 9999 instancji, na której znajduje się Fabio.

Uwagi na koniec

Większość uwag, które mam odnośnie tego materiału, dotyczy rozwiązań wynikłych z prywatnego charakteru projektu. W warunkach produkcyjnych, niektóre elementy musiałyby być rozwiązane i dopracowane inaczej. Poniżej przedstawiam listę aspektów wymagających wyjaśnienia.

  1. Kod Terraform odpowiedzialny za deployment klastra Consula ma dość poważny błąd. W kodzie wykorzystuję zasób ’data.local_file.generated_key’ w celu pobrania klucza gossip. Niestety, niepotrzebnie dodałem proces geneorwania klucza do kodu Terraform. Ten błąd sprawia, że w momencie, gdy agent GitHuba, który czyści środowisko przy każdym uruchomieniu, próbuje zaktualizować lub usunąć ten zasób, nie znajduje go i wyrzuca wyjątek. W momencie tworzenia klastra problem ten nie występuje. Na pewno poprawię ten błąd w kolejnym wpisie z tej serii.
  2. Po stworzeniu instancji Fabio potrzebujemy jej publicznego adresu IP w celu wydania nowej wersji aplikacji frontendowej. Jest to konieczne, ponieważ aplikacja frontendowa działa w przeglądarce klienta i musi znać adres IP, pod którym działa Fabio, aby kierować zapytania do backendu. Fabio pełni tutaj rolę proxy. W normalnych warunkach proces wydawania nowej wersji aplikacji nie miałby nic wspólnego z adresem IP łączącym nasz system z siecią publiczną. Jednakże, ponieważ nie posiadam domeny, która mogłaby służyć jako drogowskaz w systemie DNS, musiałem obejść ten problem w ten sposób.
  3. W tej architekturze wykorzystujemy tylko jeden load balancer, który pełni podwójną rolę. Służy on zarówno jako proxy łączący się z publiczną siecią, jak i równoważy ruch bezpośrednio do hostów znajdujących się w prywatnej podsieci. Wybraliśmy to rozwiązanie ze względów oszczędnościowych. W typowej konfiguracji zaleca się stosowanie przynajmniej jednego dodatkowego load balancera tylko dla prywatnej podsieci.
  4. W wpisie wykorzystuję konstrukcję matrix, aby wygenerować pętlę z zadaniami Terraform. Dzięki temu polecenia Terraform zostaną uruchomione po kolei dla każdego folderu.

    Te rozwiązanie jest ok w przypadku prywatnego projektu ponieważ przyspiesza proces wdrażania. Jednak w typowych warunkach zaleca się mieć możliwość oddzielnego wykonywania kodu dla poszczególnych grup zasobów.

Podsumowanie

Consul jest nieoceniony w zadaniach związanych z Service Discovery, umożliwiając automatyczną rejestrację i odkrywanie usług w klastrze. Dzięki temu aplikacje mogą dynamicznie odnajdywać i komunikować się ze sobą, co jest kluczowe w środowiskach, gdzie usługi często pojawiają się i znikają. Service Mesh pozwala na prostsze kontrolowanie i zabezpieczanie ruchu między serwisami. Pozwala to zapewnić bezpieczną komunikację w naszym klastrze, bez wchodzenia w szczegóły zabezpieczeń. Wiele feacherów Consula opartych na Key/Value Store pozwoli na dynamiczną konfigurację hostów, co jest niezwykle cenne w środowiskach, które wymagają szybkiego reagowania na zmiany.

Podsumowując, HashiCorp Consul to narzędzie, które może stanowić serce naszej platformy, zapewniając automatyczne odkrywanie usług, kontrolę nad ruchem sieciowym i dynamiczną konfigurację. Te trzy funkcjonalności są tym co większość obecnych systemów informatycznych najbardziej potrzebuje.

Comments are closed.