W True Engineering skonfigurowaliśmy proces ciągłego dostarczania aktualizacji na serwery klientów i chcemy podzielić się tym doświadczeniem.
Na początek opracowaliśmy system online dla klienta i wdrożyliśmy go w własnym klastrze Kubernetes. Teraz nasze wydajne rozwiązanie przeniosło się na platformę klienta, dla czego skonfigurowaliśmy w pełni automatyczny proces Continuous Deployment. Dzięki temu przyspieszyliśmy time-to-market – dostarczanie zmian do środowiska produkcyjnego.
W tym artykule opowiemy o wszystkich etapach procesu Continuous Deployment (CD) lub dostarczania aktualizacji na platformę klienta:
- jak zaczyna się ten proces,
- synchronizacja z repozytorium Git klienta,
- kompilacja backendu i frontend
- automatyczne wdrożenie aplikacji w środowisku testowym,
- automatyczne wdrożenie na Prod.
W trakcie podzielimy się szczegółami konfiguracji.

1. Start CD
Ciągłe dostarczanie zaczyna się od momentu, gdy programista umieszcza zmiany w gałęzi wydaniowej naszego repozytorium Git.
Nasza aplikacja działa na bazie architektury mikrousługowej i wszystkie jej komponenty są przechowywane w jednym repozytorium. Dzięki temu wszystkie mikrousługi są zbierane i instalowane, nawet jeśli zmienił się jeden z nich.
Zorganizowaliśmy pracę przez jedno repozytorium z kilku powodów:
- Wygoda rozwoju — aplikacja rozwija się aktywnie, więc można pracować od razu z całym kodem.
- Jednolity pipeline CI/CD, który gwarantuje, że aplikacja jako jednolita całość przechodzi przez wszystkie testy i dostarczana jest do środowiska prod klienta.
- Wykluczamy zamieszanie w wersjach — nie musimy przechowywać mapy wersji mikrousług i opisywać dla każdej mikrousługi jej konfiguracji w skryptach Helm.
2. Synchronizacja z repozytorium Git kodu źródłowego klienta
Wprowadzone zmiany są automatycznie synchronizowane z repozytorium Git klienta. Tam skonfigurowano kompilację aplikacji, która uruchamia się po aktualizacji gałęzi, oraz wdrożenie na produkcję. Oba procesy odbywają się w ich środowisku z repozytorium Git.
Nie możemy pracować z repozytorium klienta bezpośrednio, ponieważ potrzebujemy własnych środowisk do rozwoju i testowania. Używamy do tych celów naszego repozytorium Git – jest on zsynchronizowany z ich repozytorium Git. Gdy tylko programista publikuje zmiany w odpowiedniej gałęzi naszego repozytorium, GitLab natychmiast przesyła te zmiany do klienta.

Następnie trzeba wykonać kompilację. Składa się ona z kilku etapów: kompilacji backendu i frontendu, testowania i dostarczenia do produkcji.
3. Kompilacja backendu i frontendu
Kompilacja backendu i frontendu to dwa równoległe zadania, które są realizowane w systemie GitLab Runner. Jej konfiguracja początkowej kompilacji znajduje się w tym samym repozytorium.
.
GitLab Runner pobiera kod z odpowiedniego repozytorium, za pomocą polecenia kompilacji aplikacji Java przeprowadza kompilację i wysyła go do rejestru Docker. Tutaj kompilujemy backend i frontend, uzyskujemy obrazy Docker, które przechowujemy w repozytorium po stronie klienta. Do zarządzania obrazami Docker używamy .
Synchronizujemy wersje naszych obrazów z wersją wydania, które zostanie opublikowane w Docker. Aby wszystko działało płynnie, wprowadziliśmy kilka ustawień:
1. Pomiędzy środowiskiem testowym a produkcyjnym kontenery nie są ponownie kompilowane. Zastosowaliśmy parametryzację, aby ten sam kontener mógł działać bez ponownej kompilacji we wszystkich ustawieniach, zmiennych środowiskowych i usługach zarówno w środowisku testowym, jak i na produkcji.
2. Aby zaktualizować aplikację za pomocą Helm, należy podać jej wersję. Mamy kompilację backendu, frontend i aktualizację aplikacji – to trzy różne zadania, dlatego ważne jest, aby wszędzie używać tej samej wersji aplikacji. Do tego zadania wykorzystujemy dane z historii Git, ponieważ nasza konfiguracja klastra K8S i aplikacji znajduje się w jednym repozytorium Git.
Wersję aplikacji uzyskujemy z wyników wykonania polecenia
git describe --tags --abbrev=7.
4. Automatyczne wdrażanie wszystkich zmian w środowisku testowym (UAT)
Kolejnym etapem w tym skrypcie kompilacji jest automatyczna aktualizacja klastra K8S. Dzieje się to pod warunkiem, że cała aplikacja została skompilowana, a wszystkie artefakty zostały opublikowane w Docker Registry. Po tym rozpoczyna się aktualizacja środowiska testowego.
Aktualizacja klastra uruchamiana jest za pomocą . Jeśli coś poszło nie tak, Helm automatycznie i samodzielnie cofną wszystkie swoje zmiany. Nie trzeba go kontrolować.
Dostarczamy razem z buildującą konfigurację klastra K8S. Dlatego następnym krokiem jest aktualizacja jej: configMaps, deploymenty, usługi, sekrety i wszelkie inne konfiguracje K8S, które zmieniliśmy.
Po tym Helm uruchamia aktualizację RollOut samej aplikacji w środowisku testowym. Zanim aplikacja zostanie wdrożona na produkcję. Zrobiono to, aby użytkownicy ręcznie sprawdzili funkcje biznesowe, które udostępniliśmy w środowisku testowym.
5. Automatyczne wdrażanie wszystkich zmian na produkcję
Aby wdrożyć aktualizację w środowisku produkcyjnym, wystarczy nacisnąć jeden przycisk w GitLab — a kontenery zostaną natychmiast dostarczone do środowiska produkcyjnego.
Ta sama aplikacja może działać w różnych środowiskach — testowym i produkcyjnym — bez rebuildingu. Używamy tych samych artefaktów, nie zmieniając nic w aplikacji, a parametry definiujemy z zewnątrz.
Elastyczne parametryzowanie ustawień aplikacji zależy od tego, w jakim środowisku ta aplikacja będzie działać. Wszystkie ustawienia środowisk przenieśliśmy na zewnątrz: wszystko parametryzowane jest przez konfigurację K8S i parametry Helma. Gdy Helm wdraża wersję w środowisku testowym, stosowane są testowe parametry, a w środowisku produkcyjnym — parametry produkcyjne.
Najtrudniejsze było parametryzowanie wszystkich używanych usług i zmiennych, które zależą od środowiska, i przekształcenie ich w zmienne środowiskowe oraz opis-konfigurację parametrów środowiskowych dla Helma.
W parametrach aplikacji używane są zmienne środowiskowe. Ich wartości są ustawiane w kontenerach za pomocą K8S configmap, która jest szablonowana przy użyciu szablonów Go. Na przykład, ustawienie zmiennej środowiskowej dla nazwy domeny można zrobić tak:
APP_EXTERNAL_DOMAIN: {{ (pluck .Values.global.env .Values.app.properties.app_external_domain | first) }}
.Values.global.env – w tej zmiennej przechowywana jest nazwa środowiska (prod, stage, UAT).
.Values.app.properties.app_external_domain – w tej zmiennej określamy w pliku .Values.yaml potrzebną domenę
Podczas aktualizacji aplikacji Helm tworzy plik configmap.yaml na podstawie szablonów i wypełnia wartość APP_EXTERNAL_DOMAIN odpowiednią wartością w zależności od środowiska, w którym uruchamiana jest aktualizacja aplikacji. Ta zmienna ustawiana jest już w kontenerze. Dostęp do niej ma aplikacja, co oznacza, że w każdym środowisku aplikacji ta zmienna będzie miała inną wartość.
W stosunkowo niedawnym czasie w Spring Cloud pojawiło się wsparcie dla K8S, w tym praca z configMaps: . Obecnie projekt jest intensywnie rozwijany i znacznie się zmienia, więc nie możemy go używać w produkcji. Jednak aktywnie monitorujemy jego stan i używamy go w konfiguracjach DEV. Gdy tylko stanie się stabilny — przejdziemy z użycia zmiennych środowiskowych na niego.
Podsumowując
Zatem, Continuous Deployment jest skonfigurowany i działa. Wszystkie aktualizacje odbywają się za jednym naciśnięciem przycisku. Dostarczanie zmian do środowiska produkcyjnego jest automatyczne. I co ważne, aktualizacje nie zatrzymują pracy systemu.

Plany na przyszłość: automatyczna migracja bazy
Zastanawialiśmy się nad aktualizacją bazy i możliwością jej cofnięcia. Przecież jednocześnie działają dwie różne wersje aplikacji: stara działa, a nowa się podnosi. A starą wyłączymy dopiero wtedy, gdy upewnimy się, że nowa wersja działa. Migracja bazy musi pozwalać na pracę z obiema wersjami aplikacji.
Dlatego nie możemy po prostu zmienić nazwy kolumny ani innych danych. Możemy jednak stworzyć nową kolumnę, skopiować do niej dane ze starej kolumny i napisać wyzwalacze, które przy aktualizacji danych jednocześnie będą je kopiować i aktualizować w innej kolumnie. A po udanym wdrożeniu nowej wersji aplikacji, po okresie wsparcia po uruchomieniu, będziemy mogli usunąć starą kolumnę i zbędny wyzwalacz.
Jeśli nowa wersja aplikacji działa nieprawidłowo, możemy cofnąć się do wcześniejszej wersji, w tym wcześniejszej wersji bazy. Innymi słowy, nasze zmiany pozwolą na jednoczesną pracę z wieloma wersjami aplikacji.
Planujemy zautomatyzować migrację bazy za pomocą zadania K8S, włączając ją do procesu CD. I na pewno podzielimy się tym doświadczeniem na Habrze.
Źródło: habr.com
