Dlaczego wdrożyliśmy Finansowanie Faktur w OpenShift?
Budując razem z ING system Finansowania Faktur oparty o mikroserwisy, stanęliśmy przed wyborem technologii środowiska uruchomieniowego. Aby obniżyć koszty oraz zapewnić wysoki poziom bezpieczeństwa i dostępności zdecydowaliśmy się na technologię kontenerową.
Całość została wdrożona na platformie RedHat OpenShift, bazującej na orkiestracji Kubernetes.
Wymagania i kontekst projektu
Aby lepiej wyjaśnić podjęte decyzje, należy powiedzieć kilka słów o projekcie:
- projekt realizowano metodyką zwinną (agile) z użyciem scrum,
- aby zmniejszyć nakład pracy i czas realizacji projektu architekturę systemu oparto o mikroserwisy,
- wymagania dotyczące bezpieczeństwa były bardzo wysokie, co jest charakterystyczne dla systemów realizowanych dla sektora finansowego,
- infrastruktura miała być utrzymywana w chmurze AWS.
Wybór technologii kontenerowej
Zaczęliśmy od analizy dostępnych modeli utrzymania usług pod kątem potrzeb ING oraz zespołów realizacyjnych i hostingowych e-point. Rozważaliśmy m.in. klasyczny model uruchomienia usług na maszynach fizycznych bądź wirtualnych z oddzielnymi kontami dla poszczególnych mikroserwisów. Takie rozwiązanie generowałoby jednak zbyt wysokie koszty utrzymania i developmentu. Dlatego postanowiliśmy użyć technologii kontenerowej wykorzystującej silnik Docker.
Jak działa konteneryzacja?
Konteneryzacja pozwala na zamknięcie aplikacji w obrazie wraz z własnym systemem operacyjnym i bibliotekami zależnymi. Podczas uruchamiania, na podstawie tych obrazów, powstają kontenery z działającą aplikacją. Te same obrazy (system operacyjny z bibliotekami i aplikacją) są uruchamiane na różnych środowiskach testowych i produkcyjnych, co zapewnia to samo środowisko uruchomieniowe dla aplikacji i jednocześnie zmniejsza ryzyko pojawienia się błędów. Ponadto izolacja całych systemów dla każdego procesu, instancji aplikacji i mikroserwisu znacznie podnoszą bezpieczeństwo.
Samodzielne silniki Docker na wielu maszynach - rozwiązanie, które odrzuciliśmy
Zdecydowawszy się na technologię kontenerową, musieliśmy w dalszej kolejności wybrać, jaki model architektury infrastruktury kontenerowej zastosujemy. Rozważaliśmy m.in. uruchomienie samodzielnych (standalone) silników Docker na oddzielnych maszynach fizycznych, jednak rozwiązanie to ma wiele niedoskonałości. Mogłoby to wywołać problemy z utrzymaniem spójności w przypadku awarii jednego węzła fizycznego i konieczności, np. aktualizacji aplikacji. Jeśli w międzyczasie aplikacja zostałaby zaktualizowana, to późniejsze uruchomienie uprzednio wyłączonego/zepsutego węzła fizycznego spowodowałoby uruchomienie poprzedniej wersji aplikacji. Nie mogliśmy dopuścić do takiej sytuacji.
Ponadto wszelkie prace związane ze zmianą liczby aplikacji, skalowaniem, wdrażaniem dodatkowych mikroserwisów wymagałyby ręcznej zmiany konfiguracji zarówno przez programistów czy inżynierów DevOps, jak i hosting na frontowych serwerach http czy firewallach. Należałoby również prowadzić ewidencję m.in. otwartych portów, aby zapobiec konfliktom.
Kolejne ryzyko wiązało się z zapewnieniem i granulacją dostępu do tak stworzonej architektury. Dostęp byłby de facto możliwy do całego silnika (np. przez Docker API), a co za tym idzie, również do systemu operacyjnego maszyny fizycznej lub wirtualnej. Ze względu na liczbę zaangażowanych osób i zespołów chcieliśmy tego uniknąć.
Jeśli chodzi o bezpieczeństwo, hasła np. do bazy danych czy integracji z systemami zewnętrznymi byłyby przechowywane jako czysty tekst na dyskach serwera, podobnie jak w wypadku uruchomienia maszyn z oddzielnymi kontami dla poszczególnych mikroserwisów. W obu tych modelach problematyczne byłoby utrzymanie konfiguracji aplikacji.
Biorąc pod uwagę powyższe ograniczenia i trudności w implementacji środowiska wysokiej niezawodności (high-availability), postanowiliśmy użyć platformę do uruchamiania kontenerów. Rozważaliśmy użycie Docker Swarm, ale stabilność tego rozwiązania pozostawia dużo do życzenia, zwłaszcza w obszarze sieci. Ponadto granulacja nadawania dostępów, polityki uprawnień, ustawień firewall i możliwości zarządzania kontenerami były niewystarczające. Kolejne ograniczenia wynikały z braku dynamicznego zarządzania pamięcią masową (storage).
Architektura infrastruktury - OpenShift
Odrzuciwszy powyższe rozwiązania, zdecydowaliśmy się na platformę typu PaaS (Platform as a Service). Musieliśmy jeszcze uwzględnić kilka czynników.
Po pierwsze, wybrana platforma musiała współpracować z usługami chmurowymi, zapewniając m.in. dynamiczną aprowizację zasobów (dynamic provisioning). Po drugie, wszystkie węzły miały być uruchamiane w systemach Linux. Z uwagi na kwestie bezpieczeństwa mogliśmy wziąć pod uwagę tylko dystrybucje obsługujące SELinux. Jest on natywnie uruchomiony w systemach RedHat, który jest jego głównym twórcą i zapewnia stabilne działanie. Ponadto bardzo ważnym kryterium była stabilność samego silnika dockerowego i dobra współpraca z jądrem systemu operacyjnego (kernel) oraz systemem bazowym.
Spośród dostępnych technologii orkiestracji zawęziliśmy wybór do rozwiązań opartych na Kubernetes. Po dalszej analizie wybór padł na OpenShift. Podczas wyboru uwzględnione zostały następujące kwestie dot. platformy:
- bezpieczeństwo
- stabilność
- wydajność
- funkcjonalność
Zwróciliśmy uwagę na:
- stabilność Kubernetes i zapewnienie wysokiej dostępności utrzymywanych systemów
- rozszerzenia podnoszące funkcjonalność platformy: Routers, DeploymentConfigs, wbudowany rejestr obrazów, hawkular metrics, stos EFK (ElasticSearch, Fluentd, Kibana)
- dobrą współpracę elementów całego ekosystemu: systemu operacyjnego, platformy PaaS i oprogramowania zarządzającego
- łatwą skalowalność: zwiększenie ilości Podów (jednostek wdrożeniowych, składających się z jednego lub więcej kontenerów) nie wymaga zmian w konfiguracji aplikacji ani w pozostałej architekturze. Całość może wykonać inżynier DevOps, więc zaangażowanie zespołu administratorów infrastruktury nie jest potrzebne
- łatwiejsze dodanie mocy obliczeniowej w postaci dodatkowej maszyny do klastra: nie wymaga to rekonfiguracji aplikacji ani udziału programistów oraz dokonuje się całkowicie bezprzerwowo
- szybkie i łatwe dodawanie nowych środowisk (projektów) do klastra
- limitowanie zasobów (quota, limits), aby jedna usługa nie zagłodziła pozostałych
- scentralizowane zarządzanie tożsamością użytkowników
- szeroką kontrolę dostępów opartą na rolach (RBAC, Role Based Access Control)
- bezprzerwową aktualizację i szybką możliwość powrócenia do poprzedniej wersji aplikacji - “rolling update” i “rollback”
Ważnym argumentem nietechnicznym był fakt, że jest to oprogramowanie znanego producenta rozwiązań klasy Enterprise, co podnosi wiarygodność i zmniejsza ryzyka projektowe.
Rys. 1. Openshift projects view
W przypadku systemu operacyjnego węzłów, wybór został zawężony do systemu Linux z rodziny RedHat m.in. ze względu na:
- wysoką stabilność silnika dockerowego dostępnego w systemach RedHat, w porównaniu z innymi testowanymi w owym czasie,
- natywną obsługę SELinux,
- współpracę z platformą OpenShift.
Ze względu na wcześniejsze doświadczenie zespołu e-point we wdrażaniu i utrzymaniu platformy OpenShift uruchomiliśmy platformę w wersji OpenShift Origin na systemach CentOS. Ta opcja nie uwzględnia płatnego wsparcia RedHat, co pozwala zmniejszyć koszty utrzymania dla klienta.
Chmura AWS
Aby zapewnić systemowi wysoką dostępność, architekturę przygotowano w oparciu o trzy strefy AWS. W każdej strefie znajduje się jeden węzeł zarządzający (master). Także węzły obliczeniowe (compute node) rozłożono w trzech strefach. Szyfrowanie bezpiecznym protokołem SSL zakotwiczyliśmy na Load Balancerach AWS oraz wydzieliliśmy osobne routery wewnątrz klastra OpenShift dla ruchu produkcyjnego, usług klastrowych i usług testowych.
Rys. 2. OpenShift cloud infrastructure view
Warto zwrócić uwagę na wdrożenie dynamicznej aprowizacji dysków chmurowych EBS i udziałów EFS. Dzięki temu przy dodawaniu usług wymagających trwałych zasobów dyskowych (persistent storage) nie trzeba angażować administratorów infrastruktury, a cała operacja może być wykonywana bezprzerwowo.
System posiada relacyjną bazę danych PostgreSQL. Ustaliliśmy, że wykorzystamy usługę RDS z replikacją. Replika znajduje się w innej strefie niż baza główna (master), w innej infrastrukturze fizycznej i sieciowej, dzięki czemu konfiguracja spełnia wymagania systemu wysokiej dostępności. Dostęp do bazy danych z mikroserwisów uruchomionych w klastrze jest realizowany przez tzw. serwisy zewnętrzne (external services) OpenShift. Dzięki temu zmniejszono ryzyko przypadkowego podłączenia aplikacji klienta do złej bazy danych, np. z innego projektu. Ponadto dzięki temu utrzymano przejrzystość projektu (visibility).
Wewnętrzny rejestr obrazów
Wdrożyliśmy również wewnętrzny rejestr obrazów Docker z back-endem w obiektowej pamięci dyskowej (object storage). W nim znajduje się przestrzeń dla każdego projektu OpenShift, gdzie przesyłane i przechowywane są obrazy, a użytkownicy i usługi mają nadane stosowne uprawnienia ograniczające dostęp do właściwych obrazów.
Pamiętajmy, że jeśli ten sam obraz lub jego warstwa (layer) znajduje się w przestrzeniach dwóch projektów, to nie zajmuje dwa razy więcej miejsca. Jeśli obraz o tym samym identyfikatorze (tj. skrócie sha256) należy do dwóch lub więcej projektów, to fizycznie zajmuje tyle samo miejsca co jedna sztuka. Dzięki temu zapewniamy wysoki poziom bezpieczeństwa przy jednoczesnym obniżeniu kosztów obiektowej pamięci dyskowej.
OpenShift Secrets i zagadnienia bezpieczeństwa
Szczególną uwagę poświęciliśmy kwestii bezpieczeństwa haseł. Hasła nie powinny być przechowywane na maszynach uruchamiających aplikacje, a wbudowanie haseł w obraz naruszyłoby bezpieczeństwo całego systemu. Dlatego wybraliśmy tzw. OpenShift Secrets, czyli obiekty, dzięki którym hasła i inne dane wymagające szczególnej ochrony mogą być montowane jako katalog lub plik w kontenerze uruchamiającym aplikację lub przekazywane do niego jako zmienna środowiskowa. W żadnym wypadku nie zostaną zapisane na dysku węzła, na którym uruchamiany jest kontener.
Aby zapewnić bezpieczeństwo na poziomie systemu operacyjnego OpenShift domyślnie uruchamia kontenery z wysokim numerem użytkownika, np. 1000100000 w restrykcyjnym trybie bezpieczeństwa (restricted security context). Każdy Pod ma oddzielną nazwę hosta (hostname) i adres IP. Ponadto pliki typu /etc /passwd, /etc /shadow, nie są współdzielone. Z tych powodów należy uprzednio zaplanować i odpowiednio przygotować obrazy oraz sposób uruchamiania i pracy aplikacji.
Monitoring i logowanie
Bez monitoringu i logów aplikacji zespół utrzymaniowy jest ślepy, a działania proaktywne (pielęgnacyjne) są niemożliwe. Nie da się wówczas wykrywać sytuacji nadzwyczajnych i błędów ani przeprowadzić analizy pozdarzeniowej czy powłamaniowej (forensics).
W konsoli OpenShift można śledzić aplikacje, gdy kontenery wypisują logi na standardowe wyjście, co znacznie ułatwia analizę. W opisywanym projekcie właśnie tak skonfigurowano aplikacje i kontenery. Ponadto logi są zapisywane na trwałych zasobach dyskowych. Dzięki podom typu daemonset są one przesyłane do tzw. agregatora logów, a ponadto synchronizowane do obiektowej pamięci dyskowej (object storage) za pomocą protokołu S3 w celu archiwizacji. Ustalona polityka logów zapewnia ich retencję i nadzoruje, aby nie doszło do przepełnienia zasobów dyskowych.
W celu dokładniejszego monitorowania parametrów aplikacji i stanu maszyn wirtualnych Java (Java Virtual Machine) wdrożono wewnątrz klastra OpenShift stos narzędzi monitorujących: Grafana, InfluxDB, Telegraf, Jolokia, SpringBoot Actuator.
OpenShift zapewnia samouzdrawianie (autohealing) odpowiednio skonfigurowanej i wdrożonej aplikacji. W każdym uruchamianym kontenerze wdrożono czujniki (healthcheck), które badają, czy działa ona prawidłowo. Na ich podstawie kontroler decyduje, czy możliwe jest puszczenie ruchu produkcyjnego do danego kontenera (instancji aplikacji), czy też wymagany jest jego restart.
Podsumowanie
Wybór OpenShift pozwolił nam zapewnić wysoki poziom bezpieczeństwa i niezawodności, a także w wielu miejscach obniżyć koszty projektu. Wdrażanie i utrzymanie aplikacji w klastrze OpenShift:
- unifikuje procesy tworzenia aplikacji i architektury, wdrażania i utrzymania systemu,
- zwiększa spójność i przejrzystość projektu,
- zaciera różnice między środowiskami programistycznymi, CI, testowymi i produkcyjnym, co jest nieosiągalne w praktyce dla klasycznego modelu,
- znacząco ułatwia kontrolę dużej liczby mikroserwisów,
- usprawnia proces wprowadzania nowego pracownika do programowania i wdrażania aplikacji.
Tym samym wykorzystanie OpenShift podnosi jakość projektu.