Profesjonalny workflow CI/CD dla aplikacji Kotlin + Ktor z użyciem Cloud Build i GitHub

· 756 słów · do przeczytania, w minut 4

Na początku słowo wyjaśnienia: w poprzednim artykule zapowiedziałem kontynuację dotyczącą monitorowania aplikacji. Ten temat pojawi się w osobnym wpisie.

Dziś natomiast skupiam się na czymś, co powinno zostać omówione wcześniej, na poprawnym workflow CI/CD dla aplikacji Kotlin + Ktor w chmurze Google.

Założenia

  1. Kod źródłowy jest hostowany w serwisie GitHub

  2. Każda Pull Request (PR) automatycznie uruchamia budowanie i testy

  3. Merge do main wyzwala budowanie obrazu kontenerowego i opcjonalny deployment

  4. Budowanie obrazu odbywa się za pomocą Jib (bez Dockera)

Co to jest Pull Request (PR) i merge?

GitHub (i inne współczesne systemy kontroli wersji) pozwalają na rozwijanie oprogramowania w gałęziach (branches). Programiści pracują nad nowymi funkcjami lub poprawkami błędów w osobnych gałęziach, a następnie, gdy zmiany są gotowe, tworzą tzw. Pull Request (PR).

PR to prośba o włączenie (merge) zmian z jednej gałęzi (optymalnie zawierającej jedną zmianę funkcjonalną lub poprawkową) do głównej gałęzi projektu, najczęściej main. W ramach PR inni członkowie zespołu mogą przeglądać kod (code review), sugerować poprawki, komentować lub zatwierdzać zmiany. Dopiero po zatwierdzeniu i mergu (czyli scalenia zmian) do main, kod staje się częścią głównej wersji aplikacji.

Taki proces umożliwia:

  • wyższą jakość kodu (dzięki code review)

  • unikanie błędów w głównej gałęzi (a przynajmniej ich minimalizację)

  • automatyczne uruchamianie testów przed scaleniem (CI)

Połączenie GitHub z Cloud Build

Po skonfigurowaniu połączenia z repozytorium w zakładce Repositories (Cloud Build) możemy utworzyć triggery, które będą reagować na zdarzenia zachodzące w projekcie GitHub.

Trigger 1: Pull Request (CI)

Ten trigger uruchamia się automatycznie dla każdego PR skierowanego do main. Pozwala na zbudowanie aplikacji i uruchomienie testów jeszcze przed wykonaniem merge.

  • Typ: "Pull request"

  • Gałąź docelowa: ^main$

  • Konfiguracja builda: cloudbuild.test.yaml

steps:
  - name: 'gradle:8.14.2-jdk21'
    dir: '.'
    env:
      - 'PROJECT_ID=$PROJECT_ID'
    entrypoint: bash
    args:
      - -c
      - |
        ./gradlew build --no-daemon

Trigger 2: Merge do main (CI/CD)

Ten trigger uruchamia się po zatwierdzeniu PR i scaleniu go z main. Na tym etapie budowany jest obraz kontenera i opcjonalnie następuje deploy.

  • Typ: "Push to branch"

  • Gałąź: ^main$

  • Konfiguracja builda: cloudbuild.yaml

steps:
  - name: 'gradle:8.14.2-jdk21'
    dir: '.'
    env:
      - 'PROJECT_ID=$PROJECT_ID'
    entrypoint: bash
    args:
      - -c
      - |
        ./gradlew jib --no-daemon

Dobre praktyki

Pull Requesty powinny być wykorzystywane jako formalny punkt walidacji kodu przed jego scaleniem do głównej gałęzi projektu. To właśnie w PR-ach odbywa się przegląd zmian, uruchamiane są testy i zapadają decyzje o dopuszczeniu kodu do produkcyjnej wersji aplikacji.

Zdecydowanie nie należy wdrażać aplikacji automatycznie na podstawie kodu znajdującego się w Pull Requeście. Proces deploymentu powinien być uruchamiany dopiero po scaleniu zmian do gałęzi main, co gwarantuje, że do środowiska trafia jedynie przetestowany i zaakceptowany kod.

Warto również przechowywać w repozytorium dwa osobne pliki konfiguracyjne cloudbuild.yaml: jeden dla buildów i deploymentu, drugi dla budowania i testowania PR. Pozwala to na precyzyjne kontrolowanie, co i kiedy jest wykonywane w zależności od kontekstu.

Dla większej elastyczności dobrze jest korzystać z mechanizmu zmiennych, takich jak PROJECT_ID, co umożliwia łatwe przenoszenie konfiguracji między projektami i środowiskami.

Co dalej?

W kolejnym kroku warto rozważyć dodanie automatycznego wdrażania aplikacji do usługi Cloud Run po pomyślnym zakończeniu procesu builda. To pozwoli jeszcze bardziej zautomatyzować cały proces CI/CD, skracając czas między zakończeniem pracy programisty a udostępnieniem nowej wersji użytkownikom.

Kolejnym rozszerzeniem może być włączenie skanowania bezpieczeństwa budowanego obrazu kontenerowego, jako osobnego kroku w pipeline. Dzięki temu możliwe będzie wykrywanie znanych podatności w zależnościach i bibliotekach już na etapie integracji.

Warto również rozbudować workflow o testy end-to-end (E2E) lub testy kontraktowe, które pozwolą jeszcze lepiej zabezpieczyć jakość aplikacji, szczególnie w środowiskach z mikrousługami lub systemami komunikującymi się po API.

Podsumowanie

Konfiguracja przedstawiona w tym artykule stanowi solidny fundament pod profesjonalny proces CI/CD dla aplikacji pisanych w Kotlinie i uruchamianych na Cloud Run. Dzięki wyraźnemu podziałowi na buildy dla PR-ów i buildy produkcyjne uzyskujemy bezpieczeństwo, powtarzalność i kontrolę nad całym procesem wdrożeniowym.

Zastosowanie narzędzia Jib pozwala na budowanie obrazów kontenerowych bez konieczności używania Dockera, co dodatkowo upraszcza infrastrukturę i skraca czas buildów.

Automatyczne uruchamianie testów w PR-ach zwiększa jakość kodu, a deployment po zatwierdzeniu zmian pozwala mieć pewność, że do środowiska trafiają jedynie sprawdzone i zaakceptowane aktualizacje.

Całość tworzy przejrzysty i nowoczesny workflow, który może być z powodzeniem wykorzystywany nawet w bardziej złożonych projektach.

W kolejnym wpisie przygotujemy testy i prosty monitoring.