Skip to content

Kapsułki do kawy i GitLab CI — jak uprościłem pipeline dla całej organizacji

Jeśli kiedykolwiek używałeś ekspresu na kapsułki, wiesz jak to działa: wkładasz kapsułkę, naciskasz guzik i masz kawę. Nie musisz wiedzieć, jak dokładnie działa zaworek, jaka jest temperatura wody ani skąd bierze się ciśnienie. Ekspres robi swoje, kapsułka dostarcza składniki — ty dostajesz wynik. Dokładnie tak samo zacząłem myśleć o GitLab CI — i tak powstał wzorzec, który uprościł pipeline dla całej organizacji.

  • Copy-paste pipelines: każdy projekt z własnym .gitlab-ci.yml, każdy inny, każdy z błędami
  • Brak spójności: zmiana jednej reguły = ręczna aktualizacja w 15 projektach
  • Duplikacja logiki: kod pipeline rozrzucony po wszystkich repozytoriach

Jeden centralny „ekspres” — repozytorium flows/gitlab — z którego każdy projekt pobiera gotowe kapsułki:

  • Jeden punkt zmiany: poprawka w kapsułce wchodzi do wszystkich projektów automatycznie
  • Zero duplikacji: żaden projekt nie kopiuje logiki pipeline
  • Hermetyczność: każdy job to osobna, testowalna jednostka z własną dokumentacją
  • Rozszerzalność: nowa technologia = nowy katalog w pipelines/, bez ruszania wspólnej bazy

⚙️ flows/gitlab

Centralne repozytorium szablonów CI/CD

Zawiera:

  • Wspólne joby (common/) — yamllint, shellcheck, wersjonowanie
  • Szablony per technologia (pipelines/) — ansible, opentofu, image-builder
  • Wzorzec Job Capsule z pełną dokumentacją i walidacją

🔗 Odwiedź


  1. Problem: copy-paste pipelines wszędzie
  2. Wzorzec Job Capsule
  3. Jak to wygląda od środka
  4. Mała magia: !reference
  5. Co zyska projekt konsumenta?
  6. Obsługa niestandardowych przypadków

Jeśli masz kilkanaście projektów w organizacji, to prędzej czy później natrafisz na ten problem: każdy projekt ma swój .gitlab-ci.yml, każdy trochę inny, każdy z innymi błędami. Ktoś zapomniał dodać yamllint, ktoś inny ściągnął regułę shellcheka bo “przeszkadzała”, a jeszcze ktoś inny ma pipeline napisany dwa lata temu przez osobę, która już tu nie pracuje.

Utrzymanie tego to koszmar. Zmiana jednej reguły = ręczna aktualizacja w 15 projektach.


Tu dochodzimy do sedna. Każdy job w systemie to hermetyczna kapsułka — zbiór plików, który wie wszystko o sobie:

common/jobs/yamllint/
├── main.yml            # Definicja joba — kiedy się odpala, jakie zmienne
├── script.sh.yml       # Co robi
├── validate.sh.yml     # Czy ma wszystko, czego potrzebuje
├── before_script.sh.yml
├── after_script.sh.yml
└── README.md

Ta kapsułka nie wie nic o projekcie, który ją “pije”. Nie wie, czy to Ansible, czy Docker, czy Terraform. Po prostu odpala yamllint i sprawdza pliki YAML.

Jeśli zmienię coś w kapsułce yamllint, zmiana wchodzi do wszystkich projektów automatycznie — przy następnym odpaleniu pipeline.


Główny .gitlab-ci.yml to orkiestrator — on decyduje, którą kapsułkę technologii załadować na podstawie jednej zmiennej:

include:
  - local: common/.gitlab-ci.yml        # wspólne joby: yamllint, shellcheck, wersjonowanie...
  - local: pipelines/${PROJECT_TYPE}/.gitlab-ci.yml  # technologia z zmiennej

common/ to wspólna podstawa — joby, które każdy projekt dostaje gratis: sprawdzanie Conventional Commits, linting YAML-i, shellcheck dla skryptów, wersjonowanie przez semantic-release.

pipelines/ to szablony per technologia — tu żyje logika specyficzna dla Ansible, OpenTofu, image buildera i reszty.

Stagi są zawsze takie same:

prepare → validate → dependency → build → deployment → tests → publish

Nie każdy projekt przechodzi przez wszystkie — joby mają reguły, kiedy się odpalają. yamllint chodzi tylko gdy zmieniły się pliki *.yml. Deployment tylko na tagach v*.*.*.


GitLab CI ma mechanizm o nazwie !reference, który pozwala “wstrzyknąć” listę poleceń z innego miejsca w YAML-u. Dzięki temu kapsułka nie duplikuje kodu — zamiast tego mówi:

before_script:
  - !reference [.common.job-prepare.script.sh]    # inicjalizacja
  - !reference [.common.yamllint.validate.sh]     # walidacja parametrów
script:
  - !reference [.common.logo.script.sh]           # ASCII art w logach
  - !reference [.common.yamllint.script.sh]       # właściwy yamllint

Każda sekcja jest osobnym plikiem — osobno testowalnym, osobno dokumentowalnym.


Nowy projekt Ansible? Cała konfiguracja po stronie projektu wygląda tak:

.gitlab-ci.yml:

include:
  - project: pl.rachuna-net/flows/gitlab
    ref: main
    file: .gitlab-ci.yml

GitLab CI/CD Variables:

PROJECT_TYPE = ansible-playbook

To dosłownie wszystko. Pipeline sam ogarnie: wersjonowanie, linting, sprawdzanie commitów, deployment na środowiska, publikację wersji. Projekt nie musi wiedzieć jak to działa — tak jak nie musisz wiedzieć, jak działa ekspres.

Zmienna PROJECT_TYPEPrzeznaczenie
ansible-playbookProjekty Ansible
ansible-roleRole Ansible
opentofu-iacInfrastructure as Code (OpenTofu)
opentofu-moduleModuły OpenTofu
image-builderBudowanie obrazów Docker

Co jeśli projekt potrzebuje czegoś niestandardowego?

Section titled “Co jeśli projekt potrzebuje czegoś niestandardowego?”

Tu kapsułkowy model dalej działa. Technologia może “nadpisać” dowolny job przez własną kopię kapsułki z własnym skryptem — bez modyfikowania common/. Ekspres pozostaje ten sam, kapsułka dostaje inny środek.

# pipelines/ansible-playbook/jobs/build/main.yml
.pipelines.ansible-playbook.build:
  extends: [.common.build]    # bazuje na kapsułce common
  script:
    - !reference [.common.logo.script.sh]
    - !reference [.pipelines.ansible-playbook.build.script.sh]  # własna logika

ZasóbCzego się nauczysz
GitLab CI DocsSkładnia i możliwości GitLab CI
!reference DocsJak działa mechanizm wstrzykiwania
Conventional CommitsStandard commitów stosowany w projekcie
Our ReposKod i dokumentacja organizacji

Maciej Rachuna | rachuna-net.pl

Artykuł bazuje na realnym wdrożeniu w środowisku homelab. Wzorzec Job Capsule to wynik praktyki, a nie teorii — kod dostępny w repozytorium pl.rachuna-net/flows/gitlab. Pytania i PR-y mile widziane.