Jest to drugi artykuł z dwóch o tematyce jak zautomatyzować ścieżkę zmian w projekcie od wypchnięcia kodu do repozytorium do publikacji usługi w k8s.
Zagadnienia, jakie są poruszane w tym i poprzednim artykule to:
- Co to jest DevOps i GitOps
- Co to jest ArgoCD
- Konfiguracja procesu GitLab CI
- Instalacja i konfiguracja ArgoCD
- Dobre praktyki GitOps
Pierwszy artykuł opisywał automatyczne przygotowanie obrazu dockerowego po wypchnięciu zmian w kodzie do repozytorium (CI). Ten artykuł opisuje automatyczne wystawienie zmian po poprzednim procesie w klastrze k8s.
ArgoCD – instalacja
Zaczniemy od instalacji narzędzia ArgoCD, które będzie odpowiadać za proces GitOps. Zakładamy, że już jest przygotowany klaster Kubernetes z zainstalowanym narzędziem Helm. Z racji, że ArgoCD to tak naprawdę zbiór zasobów Kubernetes najłatwiej będzie je zainstalować przez Helma.
- Najpierw tworzymy namespace argocd poleceniem:
kubectl create namespace argocd
- Dodajemy rejestr chartów poleceniem:
helm repo add argo https://argoproj.github.io/argo-helm
- Później instalujemy narzędzie poleceniem:
helm upgrade -–install argocd argo/argo-cd –namespace argocd
Mamy już w tym momencie ArgoCD zainstalowane. Tak jak jest to opisane w instrukcji dalszego użytkowania w konsoli, ustawiamy przekierowanie portu, aby dostać się do narzędzia przez przeglądarkę poleceniem:
kubectl port-forward service/argocd-server -n argocd 8080:443
Po przejściu do przeglądarki i wpisaniu adresu https://localhost:8080 i akceptowaniu certyfikatu widzimy ekran do logowania:
Domyślny użytkownik ma nazwę admin. Hasło jest generowane podczas pierwszego uruchomienia narzędzia. Można się do niego dostać wykonując polecenie:
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath=”{.data.password}” | base64 -d
Po wprowadzeniu hasła i zalogowaniu powinniśmy ujrzeć widok pustej listy aplikacji:
ArgoCD – konfiguracja
Żeby skonfigurować aplikację na Argo CD, najpierw trzeba przygotować konfigurację Kubernetes deklaratywną, a następnie umieścić w repozytorium kodu. W ramach testu będzie to to samo repozytorium co kod projektu. Tworzymy folder deploy i w ramach tego folderu tworzymy takie pliki:
deploy.yaml
Zawiera on konfigurację deploymentu k8s. Jest w nim informacja, jaki obraz będzie uruchomiony.
apiVersion: apps/v1 kind: Deployment metadata: name: deploy-main-api spec: selector: matchLabels: name: deploy-app-api replicas: 1 strategy: type: RollingUpdate template: metadata: labels: name: deploy-app-api spec: containers: - name: api image: gitlab-registry.test.pl/argocd-gitlab-example:main.1449 ports: - containerPort: 3000
service.yaml
Plik z konfiguracją jak API będzie widoczne w sieci.
kind: Service apiVersion: v1 metadata: name: http-service-api spec: selector: name: deploy-app-api type: ClusterIP ports: - port: 80 protocol: TCP targetPort: 3000
Istotne jest w nim to, że wewnętrzny port kontenera 3000 będzie widoczny w ramach klastra jako 80.
ingress.yaml
W pliku tym opisujemy, jak API będzie widoczne poza klastrem, po domenie:
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: http-ingress annotations: kubernetes.io/ingress.class: nginx nginx.ingress.kubernetes.io/backend-protocol: "HTTP" nginx.ingress.kubernetes.io/rewrite-target: "/$2" nginx.ingress.kubernetes.io/x-forwarded-prefix: "/" spec: rules: - host: api.test.pl http: paths: - backend: service: name: http-service-api port: number: 80 pathType: Prefix path: /()(.*)
W artykule nie zagłębiono się bardziej w opis tych plików, bo nie jest to temat tego artykułu, a jak dobrze wiemy temat konfiguracji aplikacji w Kubernetes to temat “rzeka” ?.
Jak już mamy konfigurację przygotowaną to teraz po stronie ArgoCD w ustawieniach musimy dodać połączenie do repozytorium gitowego.
Po podaniu połączenia wracamy do ekranu głównego i klikamy przycisk NEW APP, żeby dodać nową aplikację. W oknie ustawimy nazwę aplikacji w polu Application Name, Project name ustawiamy default
, SYNC POLICY ustawiamy na Automatic
, żeby po aktualizacji zmian w gicie automatycznie został wykonany deployment.
Ustawiamy też opcję AUTO-CREATE NAMESPACE na true
, żeby to ArgoCD było odpowiedzialne za utworzenie namespace. W sekcji SOURCE uzupełniamy pole Repository URL adresem repozytorium gdzie mamy kod projektu. W polu Path wstawiamy wartość deploy
, ponieważ tak nazwaliśmy folder, w którym mamy konfigurację deploymentu. W sekcji DESTINATION ustawiamy Cluster URL na wartość https://kubernetes.default.svc.
Skutkiem tego aplikacja będzie uruchomiona na tym samym klastrze co i ArgoCD. W polu Namespace wstawiamy wartość test-api
, bo z taką nazwą chcemy, żeby automatycznie został stworzony namespace k8s. Po ustawieniu wszystkich opcji klikamy przycisk “CREATE”.
Po utworzeniu powinna pojawić się aplikacja na liście, a po kliknięciu w nią po chwili czekania wszystkie statusy powinny zaświecić się na zielono. Będzie to oznaczać, że zarówno stan w klastrze jest taki sam, jak był zdefiniowany w repozytorium, jak i że aplikacja została poprawnie uruchomiona w klastrze:
Gitlab – tworzenie procesu CD
Mamy już uruchomione API, które będzie podmieniane jak zmieni się konfiguracja deploymentu w repozytorium. Teraz rozwiniemy proces CI w Gitlab tak, aby w kolejnym kroku powstał automatyczny deployment, czyli żeby proces był już CI/CD.
Wracamy do folderu z konfiguracją deploymentu. Stworzymy sobie dodatkowy plik o nazwie deploy.yaml__template
z treścią:
apiVersion: apps/v1 kind: Deployment metadata: name: deploy-main-api spec: selector: matchLabels: name: deploy-app-api replicas: 1 strategy: type: RollingUpdate template: metadata: labels: name: deploy-app-api spec: containers: - name: api image: gitlab-registry.test.pl/argocd-gitlab-example:${TAG} ports: - containerPort: 3000
Jak widzimy nie różni się on od pliku deploy.yaml
praktycznie niczym, oprócz tego, że zamiast tagu obrazu mamy wartość ${TAG}
. Ten schemat pozwoli na podmianę tagu i zastąpienie pliku deploy.yaml
automatycznie przez Gitlab po wypchnięciu zmian w kodzie.
Wracamy teraz do pliku .gitlab-cli.yaml
. Tworzymy sekcję variables
i ustawiamy w niej zmienną IMAGE_TAG
:
variables: IMAGE_TAG: $CI_COMMIT_BRANCH.$CI_PIPELINE_ID
Zmienna przechowa taki sam tag, jaki był też użyty podczas budowania i pushowania do rejestru.
Tworzymy drugie zadanie, tym razem w stanie deploy
, które będzie używało obrazu node:
deploy:qa: stage: deploy image: node:16
W sekcji needs
dodajemy jeszcze informację, że do deploymentu potrzebny będzie krok z publikacją obrazu:
needs: - push:docker
Teraz w sekcji ze skryptami instalujemy zależność envsub
, która pozwoli na podmianę pliku z deploymentem z odpowiednią nazwą tagu:
script: - npm install -g envsub
Ustawiamy konfigurację dla repozytorium GIT, żebyśmy mogli później odesłać zmiany.
- git config --global user.name "${GITLAB_USER_NAME}" - git config --global user.email "${GITLAB_USER_EMAIL}"
Podmieniamy deployment
z nowym tagiem:
- envsub --env TAG=$(echo $IMAGE_TAG) deploy/deploy.yaml__template deploy/deploy.yaml
I na końcu oznaczamy zmiany w gicie do wysłania:
- git add deploy/deploy.yaml - git commit -m "change image tag $IMAGE_TAG" - git remote rm origin && git remote add origin https://user-ci:${GIT_CI_TOKEN}@gitlab.com/argocd-gitlab-example - git push origin HEAD:$CI_COMMIT_REF_NAME -o ci.skip
W poleceniu push dodaliśmy flagę -o ci.skip
, żeby nie zapętlić wywołań Gitlab CI. Użytkownik do odesłania zmian nazywa się user-ci
, a token jest przechowywany w zmiennej GIT_CI_TOKEN
. Oczywiście po stronie Gitlaba musi być skonfigurowany taki użytkownik, musi mieć wygenerowany token autoryzacyjny z uprawnieniem write-repository
i dodany w projekcie w ustawieniach CI/CD w sekcji ze zmiennymi:
Cała struktura procesu CI/CD w pliku konfiguracyjnym Gitlab wygląda teraz następująco:
stages: - push - deploy variables: IMAGE_TAG: $CI_COMMIT_BRANCH.$CI_PIPELINE_ID push:docker: stage: push image: name: gcr.io/kaniko-project/executor:debug entrypoint: [""] script: - mkdir -p /kaniko/.docker - echo "{\"auths\":{\"$CI_REGISTRY\":{\"auth\":\"$(echo -n ${CI_REGISTRY_USER}:${CI_REGISTRY_PASSWORD} | base64 | tr -d '\n')\"}}}" > /kaniko/.docker/config.json - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination $CI_REGISTRY/emil.juchnikowski/argocd-gitlab-example:$CI_COMMIT_BRANCH.$CI_PIPELINE_ID --cache=true --build-arg "BUILD_VERSION=$CI_PIPELINE_ID" deploy:qa: stage: deploy image: node:16 needs: - push:docker only: refs: - main script: - npm install -g envsub - git config --global user.name "${GITLAB_USER_NAME}" - git config --global user.email "${GITLAB_USER_EMAIL}" - envsub --env TAG=$(echo $IMAGE_TAG) deploy/deploy.yaml__template deploy/deploy.yaml - git add deploy/deploy.yaml - git commit -m "change image tag $IMAGE_TAG" - git remote rm origin && git remote add origin https://user-ci:${GIT_CI_TOKEN}@gitlab.com/argocd-gitlab-example - git push origin HEAD:$CI_COMMIT_REF_NAME -o ci.skip
Możemy teraz odesłać zmiany i poczekać jak obraz się zbuduje i opublikuje przez ArgoCD w klastrze. Zmiany po chwili są już widoczne w ArgoCD:
Oznacza to, że proces od odesłania zmian w kodzie, aż po deployment API w klastrze przeszedł prawidłowo.
GitOps – dobre praktyki
Dobrą praktyką jest utrzymywanie dwóch oddzielnych repozytoriów. W pierwszym powinien być kod projektu, a w drugim konfiguracja deploymentu. Daje to odizolowanie części deweloperskiej od części wdrożeniowej danego rozwiązania. Wtedy programiści i osoby od wdrożeń widzą w swoim repozytorium tylko to, za co odpowiadają i można im nadać tylko do tych zasobów dostęp.
Dobrą praktyką jest też utrzymywanie jednej aplikacji, która nasłuchuje na zmiany reszty aplikacji, usług, rozwiązań w danym klastrze, lub zbiorze klastrów zarządzanych przez rozwiązanie GitOps. Automatyzuje to proces wystawiania wszystkich rozwiązań. ArgoCD posiada zasób Application, który pozwala na publikację w klastrze danego rozwiązania deklaratywnie, czyli za pomocą plików konfiguracyjnych.
Nie tylko API, które przygotowaliśmy w poprzednim artykule, może być wystawiane deklaratywnie w klastrze, ale też to, co w tym artykule wyklikaliśmy, może być tak przygotowane i wypchnięte do repozytorium. ArgoCD jedną główną aplikacją będzie nasłuchiwać na zmiany w celu zaktualizowania wszystkich zasobów.
Podsumowanie
Artykuł ten jako dopełnienie poprzedniego w tej serii przedstawił proces Continuous Delivery z użyciem GitOps i narzędzia ArgoCD. Miał on na celu pokazanie przejścia całego procesu od CI do CD w sposób uproszczony. Oczywiście w projekcie, w którym będzie wdrażany ten przykład, trzeba będzie rozbudować część CI np. o wykonywanie testów, audyt obrazów. Natomiast proces CD należałoby rozbudować np. o wystawianie wersji na wiele środowisk oraz wydzielenie kodu i konfiguracji klastra do oddzielnych repozytoriów.
Autor
Emil Juchnikowski — pracuje jako Architekt Oprogramowania. Swoją karierę rozpoczął jako .NET Developer ponad 10 lat temu. Teraz już z technologią .NET ma bardzo mało wspólnego. Rozwija się w technologiach JS, w różnych kierunkach: frontend Angular i Ionic, backend NodeJS i NestJS, bazy danych MongoDB. Poza tym zajmuje się też zagadnieniami DevOps, GitOps opartymi o konteneryzację w Kubernetes. Poza pracą lubi biegać na długie dystanse oraz trenuje boks. Jego motto to: Jedynym ograniczeniem jest nasza wyobraźnia… a niektórzy twierdzą, że jeszcze czas i pieniądze.
w jaki sposob mam dwa pytanie
1. w jaki sposob plik deply.yaml__template pobiera obraz
skoro tam jest nazwa image ze zmienna ${TAG}
a w variables w gitlab-ci.yaml jest zmienna IMAGE_TAG ?
2. Czy mozna jakos konfiguracje argocd trzymac w tym samym projekcie/repo gdzie gitlab-ci.
Tutaj musimy utworzyc w argocd projekt ktory patrzy na dany katalog w galezi repo. Nie mozna tego kroku tez wepchnac w CI/CD – konfiguracja argo w oddzielnym katalogu i stworzenie projektu w argo zeby nie trzeba bylo ustawiac tego recznie ?
1. envsub –env TAG=$(echo $IMAGE_TAG) deploy/deploy.yaml__template deploy/deploy.yaml
W tej linijce jest przekazanie do zmiennej z szablonu TAG wartości z variable IMAGE_TAG
2. Możesz po stronie argocd stworzyć aplikację, która będzie nasłuchiwała na katalog w repozytorium w którym będą konfiguracje wszystkich innych aplikacji argocd. Takie podejście nazywa się App of Apps. Możesz też oczywiście dodawać taką aplikację procesem Gitlab, tylko wtedy po stronie procesu musi być połączenie do klastra k8s i wywołanie np. polecenia kubectl apply na pliku z konfiguracją aplikacji.