Wstęp
Niniejszy artykuł jest kontynuacją pierwszej części publikacji, w której skupiłem się na stworzeniu aplikacji w Go, IaC z wykorzystaniem Terraform, a także obiektów Kubernetesowych z wykorzystaniem Kustomiza.
W ramach przypomnienia – stan środowiska na chwilę obecną prezentuje się następująco:
- skonteneryzowana aplikacja;
- dwa klastry Kubernetesa w chmurze publicznej Google;
- deployment tworzony za pomocą Kustomiza.
O czym dokładnie będzie artykuł?
Postaram się dziś przedstawić prosty proces CI/CD, który będzie łatwy do modyfikacji i utrzymania, a jego stworzenie nie będzie czasochłonne.
Użyte technologie
- GitHub Action
- Bash
- Istniejące komponenty
Diagram infrastruktury
Implementacja rozwiązania
Manifesty GitHub Action
Czym w ogóle jest GitHub Action? Jest to system CI/CD hostowany po stronie GitHuba, darmowy do pewnej puli wywołań, oparty na Azure DevOps. Rozwiązanie jest dopracowane i bardzo przyjemne w użyciu. Świetnie się sprawdza w przypadku typowych aplikacji webowych oraz klasycznych przepływów. Minusem jest konieczność używania GitHuba jako repozytorium.
Struktura
Całość implementacji opiera się na stworzeniu trzech plików w folderze ’.github/workflows’ i umieszczeniu ich w poszczególnych repozytoriach.
Repozytorium aplikacyjne
Tutaj dodany został jeden plik, wyglądający następująco:
---
name: Build app and manifests
on: [push]
env:
APP_NAME: "go-hello-world"
jobs:
build-image:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: google-github-actions/setup-gcloud@master
with:
project_id: ${{ secrets.PROJECT_ID }}
service_account_key: ${{ secrets.GCP_SA_KEY }}
export_default_credentials: true
- run: gcloud info
- name: auth to CR
run: gcloud auth configure-docker
- name: build app
run: docker build -f $(pwd)/Dockerfile $(pwd) -t $APP_NAME
- name: tag app
run: docker tag $APP_NAME gcr.io/${{ secrets.PROJECT_ID }}/$APP_NAME:${{ github.sha }}
- name: push image
run: docker push gcr.io/${{ secrets.PROJECT_ID }}/$APP_NAME:${{ github.sha }}
deploy-k8s-manifests:
needs: build-image
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: imranismail/setup-kustomize@v1
with:
kustomize-version: "4.0.4"
- run: git clone https://${REPO_TOKEN}@github.com/linuxpolska/k8s-infra-and-objects.git
env:
REPO_TOKEN: ${{secrets.REPO_TOKEN}}
- run: cd k8s-infra-and-objects && git checkout dev
- run: cd k8s-infra-and-objects/kustomize/dev && kustomize edit set image hello=gcr.io/${{ secrets.PROJECT_ID }}/$APP_NAME:${GITHUB_SHA}
- run: cd k8s-infra-and-objects/kustomize/prod && kustomize edit set image hello=gcr.io/${{ secrets.PROJECT_ID }}/$APP_NAME:${GITHUB_SHA}
- run: git config --global user.email "3sky@protonmail.com"
- run: git config --global user.name "3sky"
- run: cd k8s-infra-and-objects && git add . && git commit -m "Set hello image tag to ${GITHUB_SHA}" && git push origin dev
Przedstawiony proces jest dość prosty. Akcja uruchamia się przy pushu do repozytorium, niezależnie od gałęzi. W kroku `build-image` pobieramy kod aplikacji, logujemy się do naszego rejestru w GCP, budujemy i tagujemy kontener, po czym umieszczamy go we wspomnianym zasobie. Drugi krok jest bardziej zawiły. Pobieramy kod, instalujemy kustomize w wybranej wersji, pobieramy repozytorium manifestów za pomocą tokena. Pobieramy branch dev, następnie modyfikujemy manifesty o nową wersję aplikacji, po czym umieszczamy zmodyfikowane pliki w repozytorium, także w gałęzi dev.
Repozytorium manifestów
Tutaj akcje zostały podzielone na dwa pliki, aby zwiększyć czytelność podziału przepływów.
- dev.yaml
---
name: Deploy app to K8S pipeline - DEV
on:
push:
branches:
- dev
env:
APP_NAME: "go-hello-world"
CLUSTER_NAME: "gke-dev"
CLUSTER_ZONE: "europe-west3-a"
jobs:
deploy-to-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: dev
- uses: imranismail/setup-kustomize@v1
with:
kustomize-version: "4.0.4"
- uses: google-github-actions/setup-gcloud@master
with:
project_id: ${{ secrets.PROJECT_ID }}
service_account_key: ${{ secrets.GCP_SA_KEY }}
export_default_credentials: true
- name: install kubectl
uses: azure/setup-kubectl@v1
- name: config cluster
run: gcloud container clusters get-credentials $CLUSTER_NAME --zone $CLUSTER_ZONE
- name: Update the cluster
run: cd kustomize && kustomize build dev | kubectl apply -f -
Powyższa akcja uruchamia się tylko w przypadku wypchnięcia kodu do gałęzi dev. Pobiera ona kod ze wskazanej referencji, instaluje kustomize oraz kubectl, loguje się GKE, po czym aplikuje zmiany na działający klaster. Jest to proste wdrożenie zmiany na wybrane środowisko.
- main.yaml
---
name: Deploy app to K8S pipeline - PROD
on:
push:
branches:
- main
env:
APP_NAME: "go-hello-world"
CLUSTER_NAME: "gke-prod"
CLUSTER_ZONE: "europe-west3-a"
jobs:
deploy-to-prod:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: imranismail/setup-kustomize@v1
with:
kustomize-version: "4.0.4"
- uses: google-github-actions/setup-gcloud@master
with:
project_id: ${{ secrets.PROJECT_ID }}
service_account_key: ${{ secrets.GCP_SA_KEY }}
export_default_credentials: true
- name: install kubectl
uses: azure/setup-kubectl@v1
- name: config cluster
run: gcloud container clusters get-credentials $CLUSTER_NAME --zone $CLUSTER_ZONE
- name: Update the cluster
run: cd kustomize && kustomize build prod | kubectl apply -f-
Jak widać proces tutaj jest dokładnie taki sam, jak w przypadku niższych środowisk. Z jedną małą różnicą. Akcja uruchamia się tylko w przypadku pusha do gałęzi main. I to właśnie tutaj w nieskomplikowany sposób zaimplementowana została metodyka GitOps. Po wdrożeniu na dev wystawiamy pull request. Osoba z odpowiednimi uprawnieniami weryfikuje wdrożenia i w przypadku spełnienia kryteriów jakości akceptuje zmianę. W tym momencie uruchamia się powyższa akcja, wdrażając aplikacje na środowisko produkcyjne.
Podsumowanie
Jak widać omawiany przepły jest dość prosty. Nic jednak nie stoi na przeszkodzie, aby dodać do niego kolejne elementy w postaci testów jednostkowych, kontroli bezpieczeństwa, pełnej automatyzacji RP’ów, łącznie z przypisaniem konkretnej osoby. Co do wykorzystanego narzędzia, GitHub Action bardzo dobrze integruje się z kodem źródłowym i zdarzeniami, które zachodzą nad repozytorium. Dużym plusem jest szeroki zakres akcji, które można wykorzystać. Dzięki nim, nie musimy sami implementować poszczególnych działań, a możemy wykorzystać zamknięty twór zachowujący się jak funkcja. W związku z tym zachęcam do eksperymentów, szczególnie w przypadku projektów, które można potraktować jako naukę własną.