Rys. 1. Granica odpowiedzialności: pipeline modyfikuje repozytorium, klaster zmienia wyłącznie kontroler.

Większość zespołów, które „mają GitOps”, ma w rzeczywistości pipeline wykonujący kubectl apply po każdym commicie. To dobry pierwszy krok, ale nie jest to GitOps, a różnica nie jest akademicka. Ujawnia się w najgorszym możliwym momencie: podczas awarii, gdy ktoś poprawił coś ręcznie na klastrze i o tym zapomniał.

Skrypt: sekwencja kroków

Skrypt deployu, niezależnie od tego, czy jest to Bash, zadanie w pipeline czy playbook Ansible, działa imperatywnie. Wykonuje określone kroki w określonej kolejności i kończy pracę. Jeśli po jego zakończeniu stan środowiska zmieni się z innego powodu, skrypt tego nie zauważy. Wie tylko to, co zrobił w chwili uruchomienia.

To ma konsekwencje:

  • Zmiana wykonana ręcznie na klastrze zostaje tam do następnego uruchomienia pipeline’u, czyli często do następnej zmiany w kodzie aplikacji, która może nastąpić za tygodnie.
  • Repozytorium mówi jedno, klaster drugie, a nikt nie ma narzędzia, które to zestawi.
  • Wycofanie zmiany wymaga ponownego uruchomienia skryptu ze starszej wersji, co nie zawsze jest tym samym co przywrócenie starego stanu.

GitOps: pętla uzgadniania

W GitOps repozytorium jest jedynym źródłem prawdy o pożądanym stanie, a w klastrze działa kontroler, który w pętli porównuje ten stan z rzeczywistością i usuwa różnice. Nie ma „uruchomienia deployu”. Jest ciągłe uzgadnianie.

Praktycznie oznacza to trzy rzeczy, których skrypt nie da:

  1. Wykrywanie dryfu. Ręczna zmiana na klastrze jest widoczna w ciągu minut jako rozjazd między repozytorium a stanem faktycznym. Można ją automatycznie cofnąć albo przynajmniej zobaczyć, że istnieje.
  2. Samonaprawa. Jeśli ktoś usunie zasób, kontroler go odtworzy. Jeśli ktoś zmieni liczbę replik ręcznie, wróci ona do wartości z repozytorium.
  3. Wycofanie jako operacja na Gicie. Powrót do poprzedniej wersji to git revert. Kontroler zajmie się resztą, a historia zmian jest kompletna, bo nic nie działo się poza repozytorium.

Gdzie kończy się rola pipeline’u

GitOps nie eliminuje CI. Pipeline nadal buduje obraz, uruchamia testy i publikuje artefakt. Zmienia się jednak granica odpowiedzialności: pipeline nie dotyka klastra. Zamiast tego modyfikuje manifesty w repozytorium, na przykład podnosi tag obrazu, i na tym kończy pracę. Wdrożeniem zajmuje się kontroler w klastrze.

Ta granica upraszcza uprawnienia. Pipeline nie potrzebuje dostępu do API klastra, a kontroler nie potrzebuje dostępu do systemu CI. Kompromitacja jednego z nich nie daje automatycznie kontroli nad drugim.

# Fragment manifestu, który pipeline modyfikuje po zbudowaniu obrazu.
# Kontroler GitOps zauważy zmianę i wdroży nową wersję.
spec:
  template:
    spec:
      containers:
        - name: app
          image: registry.example.internal/team/app:1.4.2

Kiedy skrypt wystarczy

Nie każde środowisko potrzebuje pętli uzgadniania. Jeśli deploy odbywa się na pojedynczy serwer, kilka razy w roku, i nikt nie ma do niego dostępu poza pipeline’em, skrypt jest prostszy i wystarczający. GitOps zaczyna się opłacać, gdy spełniony jest co najmniej jeden z warunków:

  • więcej niż jedna osoba może zmieniać środowisko,
  • wdrożenia są częste albo dotyczą wielu komponentów,
  • wymagany jest audyt tego, kto i kiedy zmienił konfigurację,
  • środowisko musi dać się odtworzyć od zera w przewidywalnym czasie.

Podsumowanie

Skrypt odpowiada na pytanie „co zrobić”. GitOps odpowiada na pytanie „jak ma wyglądać stan docelowy” i pilnuje odpowiedzi w sposób ciągły. Pierwsze podejście jest prostsze w uruchomieniu, drugie w utrzymaniu. Wybór zależy od tego, na którym etapie środowisko spędzi więcej czasu, a odpowiedź niemal zawsze brzmi: w utrzymaniu.