Infrastruktura jako kod z open source: Terraform, Ansible, Packer i przyjaciele w prawdziwych projektach

0
2
Rate this post

Nawigacja:

Dlaczego infrastruktura jako kod i dlaczego w oparciu o open source

Od serwerów „klikanek” do deklaratywnej infrastruktury

Model pracy z infrastrukturą przeszedł drogę od fizycznych serwerów, przez wirtualizację, aż do dzisiejszej chmury i kontenerów. Przez lata dominowało podejście „klikania” środowisk w panelach administracyjnych: ktoś wchodził do konsoli AWS, Azure czy vSphere i ręcznie tworzył maszyny, sieci, reguły firewalli. Działało to dopóki skala była mała, a zespół znał każdą maszynę po imieniu. Przy kilkudziesięciu środowiskach i dziesiątkach usług chmurowych ręczne podejście przestaje działać – zbyt łatwo o błąd, pomyłkę, rozjazd między środowiskami.

Infrastruktura jako kod (Infrastructure as Code, IaC) zmienia tę dynamikę. Zamiast klikać, opisujesz infrastrukturę w plikach tekstowych, najczęściej deklaratywnie. To znaczy: opisujesz stan docelowy, a nie sekwencję kroków „zrób to, potem tamto”. Narzędzie typu Terraform porównuje deklarację z rzeczywistym stanem w chmurze i generuje plan zmian. Dzięki temu w każdym momencie można odtworzyć środowisko z repozytorium Git, a konfiguracja nie jest ukryta w czyjejś pamięci lub historii działań w UI.

Efektem tej zmiany jest przejście z kultury „serwerów-pupili” (każdy unikalny, konfigurowany indywidualnie) do „serwerów-bydła” (musi dać się go szybko odtworzyć, nie przywiązujemy się do konkretnej instancji). To fundament dla kontinuum: CI/CD, GitOps dla infrastruktury, automatyczne skalowanie, szybkie odtwarzanie po awarii.

Kluczowe korzyści: powtarzalność, audytowalność i czas tworzenia środowisk

Najczęściej pierwszą odczuwalną korzyścią z przejścia na infrastrukturę jako kod w praktyce jest powtarzalność. Środowisko developerskie, testowe i produkcyjne bazują na tych samych modułach i wzorcach. Różnią się parametrami (rozmiar instancji, liczba replik, włączone integracje), ale nie sposobem tworzenia. Dzięki temu błędy konfiguracji wykrywane są wcześniej, bo dev i stage są zbliżone do produkcji.

Druga mocna korzyść to audyto­walność i historia zmian. Cała infrastruktura żyje w Git: merge requesty, code review, komentarze, diff-y. Zamiast pytać „kto miesiąc temu zmienił tę regułę bezpieczeństwa?” można zobaczyć commit z opisem, kto, kiedy i po co to zrobił. Tak samo łatwo odnaleźć, który commit wprowadził regresję w środowisku lub niepotrzebne koszty.

Trzeci efekt to radykalne skrócenie time to environment. Nowe środowisko testowe nie wymaga wielodniowego klikania, tylko odpalenia dobrze przygotowanego pipeline’u: Terraform tworzy infrastrukturę, Packer dostarcza bazowe obrazy, Ansible konfiguruje systemy, a pipeline aplikacyjny deployuje release. Przy dojrzałym podejściu do IaC nowe środowiska „tymczasowe” (na testy wydajności, PoC, warsztaty) można tworzyć i usuwać automatycznie.

Ostatnia, często niedoceniana korzyść to łatwiejsze disaster recovery. Jeśli cała infrastruktura (VPC, sieci, bazy, uprawnienia, load balancery, kolejki) jest opisana w kodzie, odtworzenie środowiska w innym regionie lub nawet w innej chmurze nie jest ręcznym odtwarzaniem z dokumentacji, tylko uruchomieniem sprawdzonego procesu. Różnica między awarią „na tygodnie” a „na godziny” potrafi sprowadzać się do tego, czy istnieje solidne IaC.

Open source kontra narzędzia komercyjne w świecie IaC

Narzędzia do infrastruktury jako kodu istnieją zarówno w formie komercyjnej (SaaS, płatne platformy orkiestracji), jak i open source. Decyzja „open source czy rozwiązanie zamknięte” ma realne konsekwencje. Oprogramowanie otwarte (Terraform OSS, Ansible, Packer, Helm, Kustomize) daje przede wszystkim elastyczność i brak vendor lock-in. Kod możesz uruchomić lokalnie, w dowolnym CI/CD, w wielu chmurach. W razie potrzeby da się przejść do innego dostawcy bez przepisywania całej infrastruktury na nowy format.

Open source oznacza też społeczność i szybkość rozwoju. Nowe funkcje wprowadzane są często przez użytkowników, którzy borykali się z realnymi problemami. Ekosystem wokół Terraform (moduły społeczności, registry), Ansible Galaxy, czy oficjalne role i kolekcje powoduje, że start jest dużo łatwiejszy – nie wszystko trzeba pisać od zera. Można zacząć od gotowych modułów/motywów i dostosować je do swoich wymagań.

Narzędzia komercyjne i SaaS-owe (np. zarządzany Terraform Cloud, komercyjne platformy IaC) pomagają w obszarach security, governance i integracji z istniejącym ekosystemem firmy. Problem zaczyna się, gdy większość logiki biznesowej i procesów „zamyka się” w danej platformie. Migracja staje się wtedy bolesna. Stawiając na otwarte narzędzia, organizacja zachowuje kontrolę nad kodem, a opcje komercyjne można traktować jako nadbudowę (np. do zdalnego stanu Terraform, policy as code, SSO).

Typowe obawy zespołów przy wejściu w IaC i open source

Próg wejścia w infrastrukturę jako kod jest realny. Najczęstsze obawy to: krzywa uczenia, poczucie „magii narzędzi” i tematy bezpieczeństwa. Terraform czy Ansible w pierwszym kontakcie potrafią przytłoczyć: providerzy, moduły, role, inventory, templaty. Pomaga podejście iteracyjne – zacząć od prostego use case’u (np. jeden serwer aplikacyjny z LB), a dopiero później budować kompleksowe środowiska wieloregionowe.

Druga obawa to „magia”: narzędzia robią wiele za kulisami. Kończy się to strachem: „co jeśli Terraform skasuje mi całą bazę?”. Rozwiązaniem jest rygorystyczne korzystanie z planów, review i testów. `terraform plan` w CI, obowiązkowe review zmian infrastrukturalnych, stage/test przed produkcją. Podobnie w Ansible: `–check`, `–diff`, testy Molecule. Im bardziej przejrzysty pipeline, tym mniej „magii”.

Bezpieczeństwo open source to trzeci częsty temat. Źródła narzędzi są publiczne, ale samo w sobie nie jest to problemem – często wręcz ułatwia audyt. Kwestie bezpieczeństwa koncentrują się raczej na przechowywaniu sekretów i dostępie do chmury. Tu znów kluczem jest proces: menedżery tajemnic (Vault, AWS Secrets Manager, SSM Parameter Store, GCP Secret Manager), bezpieczne role IAM zamiast kluczy stałych, szyfrowanie stanu Terraform, brak twardo zakodowanych haseł w repozytoriach.

Fundamenty: model deklaratywny, imperatywny i budulce IaC

Deklaratywność kontra imperatywność na przykładzie Terraform i skryptów

W świecie infrastruktury jako kodu warto jasno rozdzielić podejście deklaratywne i imperatywne. W modelu deklaratywnym opisujesz co ma istnieć. Terraform dostaje opis zasobów (VPC, subnety, instancje, RDS, reguły security groups), a następnie sam wylicza, jakie akcje użyć API chmury, żeby doprowadzić rzeczywistość do tego stanu. Dla użytkownika ważny jest stan końcowy.

Model imperatywny to tradycyjne skrypty bash/PowerShell lub nawet API SDK w Pythonie/Go: opisujesz jak coś zrobić, krok po kroku. Instancja jest tworzona, potem przypisujesz IP, potem konfigurujesz firewall. Taki kod jest bardziej elastyczny, bo możesz zrobić wszystko, co oferuje API, ale utrzymanie staje się trudne, gdy środowisko się rozrasta, bo trzeba ręcznie zarządzać kolejnością, warunkami i błędami.

Typowy wzorzec w dojrzałych projektach to: Terraform jako warstwa deklaratywna infrastruktury, a tam, gdzie konieczne są drobiazgowe, proceduralne kroki (np. drobne operacje na API, migracje danych), można dorzucić lekką warstwę imperatywną (np. skrypt wcięty jako provisioner lub osobny krok w pipeline). Kluczem jest, aby większość stanu opierała się o deklaracje, a imperatywność była wyjątkiem, nie fundamentem.

Idempotencja jako warunek sensownego IaC

Idempotencja to pojęcie, bez którego infrastruktura jako kod zamienia się w chaos. Operacja idempotentna to taka, którą można wykonać wiele razy i efekt końcowy jest ten sam. Jeśli playbook Ansible zainstaluje Nginx, uruchomi usługę i otworzy porty, to kolejne uruchomienie nie powinno „psuć” niczego ani zmieniać niepotrzebnie stanu, tylko potwierdzić, że docelowa konfiguracja jest już osiągnięta.

Terraform jest z zasady projektowany jako system idempotentny: na podstawie stanu i konfiguracji generuje plan. Jeśli stan jest zgodny z konfiguracją, plan jest pusty. Idempotencja pęka w momencie, kiedy dokonuje się zmian ręcznie (np. w konsoli AWS) albo używa się niestandardowych provisionerów typu `local-exec` czy `remote-exec` bez kontroli. Podobnie w Ansible: użycie gołych poleceń `shell`/`command` bez warunków i bez testów zmiany sprawia, że kolejne uruchomienia powodują inne efekty.

Projektując IaC, każdą operację warto rozważać w kategoriach: „czy mogę to odpalić dzisiaj, jutro i za tydzień bez niespodzianek?”. Jeśli odpowiedź brzmi „nie”, to taki fragment kodu prędzej czy później stanie się źródłem awarii lub manualnych „łatek”.

Stan, drift, plan, apply i rollback – słownik pojęć

Najważniejsze pojęcia, które trzeba oswoić:

  • Stan (state) – zapisany obraz tego, co IaC „wie” o infrastrukturze. W Terraform to plik state (lokalny lub zdalny), zawierający ID zasobów, atrybuty, zależności. Bez zaufanego stanu narzędzie nie jest w stanie wygenerować poprawnego planu.
  • Drift – rozjazd między stanem zapisanym w narzędziu, a rzeczywistością. Jeśli ktoś ręcznie zmieni reguły security group w konsoli, stan Terraform jest nieaktualny. Przy kolejnym `plan` różnice zostaną wykryte – Terraform spróbuje doprowadzić rzeczywistość do zgodności z deklaracją (czasem korygując ręczne zmiany, czasem sygnalizując konflikt).
  • Plan – w Terraform to wynik `terraform plan`: propozycja zmian (create, update, delete), którą można przejrzeć, skomentować, zatwierdzić. W pipeline’ach IaC przejrzenie planu jest często warunkiem akceptacji mergowania lub wejścia na produkcję.
  • Apply – wykonanie planu: zmiany w rzeczywistym środowisku. W dobrze zorganizowanym procesie apply odbywa się w zautomatyzowanym pipeline’ie CI/CD, a nie ręcznie z laptopa inżyniera.
  • Rollback – w infrastrukturze nie ma prostego „ctrl+z”. Rollback oznacza najczęściej zastosowanie innej wersji konfiguracji (np. poprzedniego commitu w repozytorium) i ponowne `apply`. To działa dobrze, jeśli stan i kod są spójne, a zmiany nie obejmują destrukcyjnych operacji bez kopii zapasowych (np. drop bazy).

Granice między infrastrukturą, konfiguracją i aplikacją

Wielu zespołom trudno jest narysować linię między tym, co powinien robić Terraform, a tym, co jest domeną Ansible czy pipeline’u aplikacyjnego. Zdrowy podział odpowiedzialności wygląda zazwyczaj tak:

  • Terraform – tworzy i utrzymuje infrastrukturę: VPC, subnety, security groups, load balancery, instancje, bazy danych, kolejki, tematy pub/sub, sieci VPN, konta service account, uprawnienia IAM. Odpowiada za „szkielet” i zasoby chmurowe.
  • Packer – buduje obrazy maszyn (golden images): system operacyjny + minimalny zestaw pakietów + podstawowa konfiguracja agenta (monitoring, logging). Rezultat to szablony AMI (AWS), zobrazowania dysków GCP, obrazy dla OpenStack czy template-y dla VMware.
  • Ansible (lub inne narzędzia konfiguracji) – zarządza konfiguracją systemów i usług: instalacja pakietów, konfiguracja daemons (Nginx, PostgreSQL, Redis), pliki konfiguracyjne, prawa dostępu, firewall na poziomie systemu, lokalne skrypty pomocnicze.
  • Pipeline aplikacyjny (CI/CD) – dostarcza i aktualizuje kod aplikacji: obrazy Docker, deploymenty Kubernetes, migracje baz danych, release management.

Ten podział nie jest religią, ale bardzo pomaga uniknąć „spaghetti IaC”: skryptów, w których Terraform coś tworzy, potem w provisionerze odpala kod, który instaluje całe środowisko, a potem ręcznie zmienia konfigurację. Im czytelniej rozdzielone warstwy, tym łatwiej testować, wymieniać komponenty i zmieniać dostawców.

Panel sterowania automatyki przemysłowej z zaawansowanymi modułami
Źródło: Pexels | Autor: Ludovic Delot

Przegląd ekosystemu: Terraform, Ansible, Packer i ich „przyjaciele”

Terraform jako silnik provisioningowy

Terraform jest obecnie de facto standardem dla deklaratywnego zarządzania infrastrukturą w wielu chmurach (AWS, Azure, GCP, OpenStack, VMware, Kubernetes i dziesiątki innych). Główna rola Terraform to provisioning, czyli tworzenie, modyfikacja i usuwanie zasobów. Z poziomu jednego repozytorium można tworzyć:

  • sieci (VPC/VNet, subnety, peeringi, bramy NAT, VPN, routing),
  • zasoby obliczeniowe (EC2, VM, autoscaling, instancje zarządzane),
  • bazy danych (RDS, Cloud SQL, Cosmos DB), kolejki, load balancery, DNS, certyfikaty,
  • konta i uprawnienia (IAM users, roles, policies, service accounts),
  • zasoby na poziomie SaaS: Cloudflare, Datadog, New Relic, GitHub, GitLab, a nawet narzędzia CI.

Ansible jako klej konfiguracyjny

Ansible domyka obszar, którego Terraform z założenia nie dotyka: stan systemu operacyjnego i usług na hostach. Jego siła to prostota (YAML, brak agenta) i szeroki ekosystem modułów. W praktyce najczęściej używa się go do:

  • standardyzacji systemów (hardening, użytkownicy, logrotate, NTP, pakiety bazowe),
  • konfiguracji usług (Nginx, PostgreSQL, Redis, RabbitMQ, ElasticSearch, systemd),
  • bootstrappingu klastrów (np. wstępne przygotowanie węzłów pod Kubernetes),
  • zadań operacyjnych: rollout konfiguracji, rotacja certyfikatów, zmiany haseł.

Ważna jest rola Ansible jako „kleju” między warstwą infrastruktury a aplikacją. Terraform stawia instancje, load balancer i security groups. Ansible dostarcza na nie ustandaryzowaną konfigurację. Pipeline aplikacyjny dopiero na tym fundamencie wdraża kontenery lub binarki. Rozbijanie odpowiedzialności na te trzy warstwy ułatwia debugowanie i audyt – wiadomo, gdzie szukać przyczyn problemów.

Packer i budowanie obrazów

Packer (obecnie HashiCorp Packer, z silnym wsparciem open source’owych builderów) rozwiązuje problem „snowflake server”, czyli serwerów, które powstały ręcznie i nikt nie pamięta, co dokładnie na nich jest. Model jest prosty: definicja obrazu w JSON/HCL, lista builderów (AWS, GCP, VMware, VirtualBox, Docker) i provisionery (najczęściej Ansible lub shell).

Typowy przepływ w dojrzałym środowisku:

  1. Packer buduje obraz bazowy (np. Ubuntu + agenty monitoringu/logowania + twarde ustawienia bezpieczeństwa).
  2. Obraz jest oznaczany tagiem/wersją i publikowany w docelowej platformie (AMI w AWS, image family w GCP itp.).
  3. Terraform korzysta z konkretnego artefaktu (np. identyfikator obrazu przekazany jako zmienna) przy tworzeniu instancji.
  4. Ansible dokłada tylko cienką warstwę różnicową (konfiguracja per usługa, per środowisko).

Taki podział minimalizuje czas provisioning’u instancji oraz zmniejsza ryzyko, że w krytycznym momencie „apt-get update” nagle zmieni wersję kluczowego pakietu. Większość decyzji o wersjach zapada na etapie budowy obrazu, a nie podczas startu maszyny produkcyjnej.

„Przyjaciele” ekosystemu IaC: Vault, Consul, Atlantis i spółka

Główne narzędzia rzadko działają w izolacji. Wokół Terraform, Ansible i Packer wyrósł ekosystem mniejszych komponentów, które w praktyce podnoszą ergonomię i bezpieczeństwo:

  • Vault – menedżer sekretów (hasła, klucze API, certyfikaty). Integruje się z Ansible (lookup pluginy), Terraformem (provider Vault) i pipeline’ami CI. Kluczowa funkcja to dynamiczne sekrety (np. krótkotrwałe poświadczenia do DB lub AWS) oraz rotacja.
  • Consul / etcd / Zookeeper – rejestry usług i magazyny konfiguracji dynamicznej. Terraform może tworzyć serwisy/klastry, Ansible konfiguruje agenty, a aplikacje odczytują konfigurację w runtime.
  • Atlantis / Spacelift / env0 / Terraform Cloud – systemy „Terraform as a Service”. Ogarniają remote state, locki, uruchamianie plan/apply z komentarzy w PR. Umożliwiają całkowite wyrzucenie `terraform apply` z laptopów inżynierów.
  • Molecule dla Ansible – framework testowy, który odpala role na kontenerach/VM-kach, uruchamia asercje i umożliwia testy regresji konfiguracji.
  • Terragrunt – nakładka na Terraform, która redukuje powtarzalność (DRY) w konfiguracjach multi-account/multi-region.

Komponenty te są opcjonalne, ale przy dużej skali znacząco zmniejszają „tarcie operacyjne”. Restart usługi z nowym certyfikatem, rollout nowej konfiguracji syslog czy przydzielanie dostępów do chmury stają się procesem utrwalonym w kodzie, a nie wiedzą plemienną.

Projektowanie infrastruktury jako kodu: struktura repozytoriów i modularność

Monorepo, polirepo, repo per środowisko – co ma sens

Jedna z pierwszych decyzji organizacyjnych dotyczy struktury repozytoriów. Najczęściej spotykane są trzy modele:

  • Monorepo IaC – jedno repo zawierające całą infrastrukturę (czasem z podziałem na katalogi: prod/, stage/, shared/). Plusem jest spójność i jedna historia zmian. Minusem – większe ryzyko kolizji i dłuższe pipeline’y.
  • Repo per „domenę” – np. osobne repo dla „platformy” (sieci, konta, IAM), inne dla każdego dużego systemu biznesowego. Pozwala delegować odpowiedzialność i uprawnienia. Ryzyko: trudniej przeprowadzić zmianę horyzontalną (np. nowy standard tagowania) w jednym strzale.
  • Repo per środowisko – np. infra-prod, infra-stage. Łatwe zarządzanie uprawnieniami, ale konfiguracje zaczynają żyć własnym życiem i dryf pomiędzy środowiskami rośnie.

W praktyce najlepiej sprawdza się model mieszany: jedno repo „platformowe” (organizacja, konta, sieci, globalne zasoby), kilka repo per system/wielką domenę, w środku jasny podział na środowiska (prod/stage/dev) za pomocą katalogów lub workspace’ów. Konfiguracja między środowiskami powinna się różnić głównie parametrami (rozmiar maszyn, liczba replik, adresy), a nie logiką.

Warstwy kodu: core, moduły, instancje

Dobrze zaprojektowane IaC nie przypomina pliku „main.tf z 3000 liniami”. Zazwyczaj widać trzy poziomy:

  • Warstwa core/platform – definicje globalne: organizacja chmurowa, konta/projekty, standardowe role IAM, centralne logowanie, VPC „szkieletowe”, VPN, peeringi międzyprojektowe. Rzadko się zmienia, ale jest krytyczna.
  • Moduły współdzielone – elementy, z których korzystają różne systemy: „standardowa aplikacja webowa za ALB”, „baza RDS z backupami”, „klaster EKS/GKE”, „bucket S3/GCS z loggingiem i retencją”. Każdy moduł ma sensowny interfejs (wejścia/wyjścia) i dokumentację.
  • Instancje modułów (stacki) – konkretne zastosowania modułów w danych środowiskach: „system-płatności-prod”, „backoffice-stage”. Tutaj spięte są parametry (rozmiary, nazwy, tagi) oraz zależności między modułami.

Taki podział zabezpiecza przed sytuacją, w której każdy zespół rozwiązuje ten sam problem na nowo, ale też chroni przed „mega-modułami”, które robią wszystko i są nieedytowalne ze strachu przed regresją. Moduły powinny być małe, wyspecjalizowane i warte reuse’u.

Modularność w Terraform: praktyczne wzorce

Typowy moduł Terraform (np. modules/aws-app-service) zawiera:

  • main.tf – definicje zasobów,
  • variables.tf – interfejs wejściowy, typy, domyślne wartości,
  • outputs.tf – to, co moduł eksponuje na zewnątrz (np. adresy, ARN-y, ID),
  • README.md – opis wejść/wyjść i przykład użycia.

Moduły warto wersjonować (tagi git, semver) i używać ich po referencji do konkretnego taga, np.:

module "app" {
  source  = "git::ssh://git@github.com/org/infra-modules.git//aws-app-service?ref=v1.4.2"
  name    = "payments-api"
  env     = "prod"
  replicas = 4
}

Wersjonowanie pozwala wdrażać zmiany stopniowo: jedna aplikacja aktualizuje się do v1.5.0, inne wciąż działają na v1.4.2. W razie regresji łatwo wrócić do poprzedniej wersji modułu bez ruszania całej infrastruktury.

Struktura katalogów pod Terraform – przykład z życia

Model, który dobrze działa przy kilku systemach i wielu środowiskach, wygląda mniej więcej tak:

infra/
  global/
    organization/
    networking/
  modules/
    aws-app-service/
    aws-rds/
    aws-eks/
  projects/
    billing/
      stage/
        main.tf
        backend.tf
        variables.tf
      prod/
        main.tf
        backend.tf
        variables.tf
    crm/
      dev/
      prod/

global/ zawiera rzadko zmieniające się „foundation”. modules/ to katalog współdzielonych modułów. projects/ skupiają konkretne systemy biznesowe, a w nich katalogi środowiskowe odwołujące się do modułów. Do tego każdy katalog środowiskowy ma własną konfigurację remote state (np. inny bucket, inny klucz).

Modularność w Ansible: role i kolekcje

W Ansible odpowiednikiem modułów są role. Dobrze zaprojektowana rola ma jasno zdefiniowany cel (np. „postgresql-server”, „nginx-reverse-proxy”, „prometheus-node-exporter”) i strukturę katalogów:

roles/
  nginx/
    tasks/
    templates/
    files/
    defaults/
    vars/
    handlers/
    meta/

Playbook środowiskowy jest cienki – głównie dołącza role i przekazuje zmienne:

- hosts: web
  roles:
    - role: nginx
      vars:
        nginx_sites:
          - name: app
            server_name: app.example.com
            upstream: http://127.0.0.1:8080

Przy większej skali wchodzą kolekcje (collections) – zbiory ról, pluginów i modułów wydzielone per domena (np. company.common, company.monitoring). Dystrybucja odbywa się wtedy przez Galaxy lub prywatny registry, a projekty korzystają z wersjonowanych kolekcji zamiast kopiować rolę z repo do repo.

Parametryzacja i DRY vs. overengineering

Pokusa parametryzowania wszystkiego jest duża. Praktyka pokazuje jednak, że zbyt elastyczne moduły stają się nieczytelne. Zamiast jednego modułu „super-baza” z 40 zmiennymi lepsze bywają dwie wersje: np. aws-rds-postgres i aws-rds-mysql, każda z rozsądnym zestawem parametrów.

Podobnie w Ansible: jeśli rola ma dziesiątki warunków typu when: ansible_os_family == "Debian" i obsługuje pięć distro, to debugowanie takiej logiki jest uciążliwe. Czasem czyściej jest rozdzielić ją na dwie role (np. nginx-debian, nginx-redhat) i wspólną warstwę „meta-roli”, która wybierze właściwą implementację.

Dobry test: jeśli nowy członek zespołu nie jest w stanie zrozumieć modułu/roli po 10 minutach czytania README + kodu, struktura jest zbyt skomplikowana.

Niebieskie kable ethernet w serwerowni symbolizujące infrastrukturę jako kod
Źródło: Pexels | Autor: cnrdmroglu

Terraform w prawdziwym projekcie: stan, moduły, wersjonowanie

Remote state, locki i separacja odpowiedzialności

Plik stanu Terraform to newralgiczny element. Zapis lokalny (terraform.tfstate na dysku) jest akceptowalny wyłącznie dla piaskownicy lub pojedynczego inżyniera. W prawdziwym projekcie używa się remote state oraz blokady (lockingu):

  • AWS – S3 jako backend + DynamoDB do locków,
  • GCP – GCS jako backend + wbudowany mechanizm locków w Terraform Cloud / zewnętrzny serwis,
  • Azure – Azure Storage + blob leases jako lock.

Każdy „stack” (np. billing/prod) ma swój oddzielny plik state. Pozwala to delegować odpowiedzialność: zespół billing ma dostęp do swojego stanu, ale nie może modyfikować core networking. Dodatkowo ogranicza blast radius – błąd w konfiguracji dotyczy jednego systemu, a nie całej organizacji.

Struktura workspace’ów i środowisk

Workspaces w Terraform bywają nadużywane. Dobrze się sprawdzają tam, gdzie kod jest identyczny, a różnią się tylko parametry (np. dev, qa, prod w małej aplikacji). W większych projektach czytelniejszy jest model „katalog per środowisko” z oddzielnymi backendami, ponieważ:

  • łatwiej nadać uprawnienia (osobne bucket/key per środowisko),
  • łatwiej ustawić inne pipeline’y i harmonogramy,
  • review obejmuje konkretny kontekst (zmiana w prod/ jest widoczna jako taka).

Tip: jeśli workspace’y są używane, nigdy nie wolno polegać na domyślnym workspace default. Zawsze jawny wybór (np. przez flagę w CI) i kontrola, który plan/aplikacja dotyczy którego workspace’a.

Cykl życia zmian: plan z CI, review, apply z automatu

Bezpieczny proces zmian Terraform składa się najczęściej z kilku kroków:

  1. Developer modyfikuje kod w gałęzi i otwiera pull request.
  2. Pipeline CI odpala terraform fmt, terraform validate, opcjonalnie statyczną analizę (tflint, checkov) oraz terraform plan z użyciem zdalnego stanu i odpowiednich poświadczeń.
  3. Wynik planu jest publikowany jako artefakt lub w komentarzu do PR (Atlantis / custom bot).
  4. Review obejmuje zarówno kod, jak i plan (co konkretnie zostanie utworzone/usunięte/zmienione).
  5. Apply: kto naciska guzik i na jakich zasadach

    Najczęstszy dylemat organizacyjny: czy terraform apply wykonuje developer, SRE, czy automat? Im większy system, tym bardziej sprawdza się model „automat po merge’u do głównej gałęzi”, z kilkoma zabezpieczeniami:

    • Oddzielne pipeline’y per środowisko – merge do main może od razu uruchamiać apply dla dev, ale z stage/prod lepiej powiązać pipeline z ręczną aprobatą (manual job, environment protection w GitLab/GitHub).
    • Brak local apply – lokalne apply dopuszczalne wyłącznie na piaskownicach z własnym stanem. Dla wspólnych stacków tylko CI z kontrolowanym zestawem poświadczeń.
    • Logi i audyt – CI loguje kto zmergował zmianę, kto kliknął approve, jaki plan został zastosowany. Przy incydencie da się odtworzyć sekwencję zdarzeń.

    W praktyce złoty środek to: developer przygotowuje zmianę, zespół robi review planu, a guzik „deploy to prod” ma wąska grupa (np. on-call SRE lub tech lead). W mniejszych zespołach rolę SRE spełnia po prostu senior developer, ale proces jest taki sam.

    Drift detection i reconcile infrastruktury

    Terraform nie „pilnuje” zasobów non stop. Jeśli ktoś zmieni parametr w konsoli chmurowej, powstaje drift (rozjazd między stanem rzeczywistym a deklaracją). Minimalny zestaw praktyk, żeby nad tym panować:

    • Zakaz ręcznych zmian na zasobach zarządzanych przez IaC, egzekwowany politykami (IAM, SCP, organizacyjne policy) oraz procesem review.
    • Okresowe plany tylko-do-odczytu – np. raz dziennie/tygodniowo CI odpala terraform plan z parametrem -detailed-exitcode. Kod 2 oznacza różnice, ale bez apply; raport trafia do Slacka lub jako issue.
    • Selective ignore – dla niektórych atrybutów (np. aws_autoscaling_group.desired_capacity) można celowo użyć lifecycle.ignore_changes, bo oczekuje się, że auto-scaler będzie je modyfikował.

    Gdy drift jest wykryty, zespół podejmuje decyzję: albo wracamy do stanu z kodu (apply nadpisuje zmiany ręczne), albo aktualizujemy kod, aby odzwierciedlał uzasadnioną zmianę w infrastrukturze.

    Zmiany niszczące (destructive) i podejście „no surprises”

    Niektóre modyfikacje w Terraform prowadzą do recreate zasobu (np. zmiana CIDR VPC, engine version bazy w starszych providerach). Kilka prostych reguł ogranicza niespodzianki:

    • Review planu pod kątem -/+ – czerwone -/+ lub destroy to sygnał alarmowy. Przy krytycznych zasobach warto wymagać dodatkowego review (np. drugiego SRE).
    • Stopnie pośrednie – przed zmianą parametru wrażliwego (np. typ maszyny bazy) lepiej zbudować mechanizm migracji: nowy moduł, replikację, przełączenie ruchu, dopiero potem usunięcie starego.
    • Wymuszona akceptacja – niektóre narzędzia (Atlantis, Spacelift, Terraform Cloud) pozwalają oznaczać plany jako „requires explicit approval” przy wykryciu destrukcyjnych operacji.

    Wersjonowanie providerów i samego Terraform

    Provider cloudowy, którego wersja samoistnie skacze, to proszenie się o chaos. Stabilność zapewnia kilka prostych mechanizmów:

    • W każdym projekcie zdefiniowany constraint na wersję Terraform, np.:
      terraform {
        required_version = "~> 1.6.0"
      }
    • Constrainty na wersje providerów (AWS/GCP/Azure/Kubernetes), np.:
      terraform {
        required_providers {
          aws = {
            source  = "hashicorp/aws"
            version = "~> 5.50"
          }
        }
      }
    • Repozytorium lockfile (.terraform.lock.hcl) commitowane do Gita, co ujednolica wersje providerów w całym zespole.

    Upgrade wersji Terraform lub Providera to normalna zmiana infrastrukturalna: osobny PR, terraform init -upgrade, plan, test na niższym środowisku, dopiero potem prod.

    Debugowanie problemów ze stanem i najczęstsze awarie

    Przy pracy zespołowej kłopoty ze stanem prędzej czy później się pojawią. Typowe przypadki:

    • „Resource already exists” – ktoś utworzył zasób ręcznie lub w innym stacku. Rozwiązanie: przenieść zasób pod właściwy stack (import) lub rozbić monolit na mniejsze stacki z wyraźnymi granicami.
    • „State lock” wiszący po przerwanym apply – najczęściej wystarczy usunąć lock w backendzie (np. rekord w DynamoDB) po upewnieniu się, że nie ma aktywnego procesu.
    • Rozjechany state po merge’ach – brak serializacji apply. CI musi zapewniać, że dla danego stacka nie lecą równolegle dwa pipeline’y z apply.

    Zapasowym narzędziem są komendy terraform state (mv, rm, list, show). Używa się ich rozważnie, najlepiej w parze z drugim inżynierem, bo błąd może zostawić „sieroty” w chmurze lub w stanie.

    Import istniejącej infrastruktury do Terraform

    Rzadko ma się luksus zielonego pola. Częściej część infrastruktury istnieje już od lat. Integracja z Terraformem przebiega zazwyczaj tak:

  1. Dla wybranego zasobu dopisuje się blok resource w kodzie z odpowiadającą konfiguracją (jak najbardziej zbliżoną do tego, co jest w chmurze).
  2. Wykonuje się terraform import, wskazując Type/Name i ID zasobu w chmurze.
  3. Odpala się terraform plan i koryguje kod, aż plan nie pokazuje niechcianych zmian (poza kosmetycznymi tagami itp.).

Importu nie robi się hurtowo na tysiącach zasobów bez planu. Lepiej iterować: system po systemie, warstwa po warstwie, z jasną decyzją, kto od tego momentu „jest właścicielem” danego fragmentu (Terraform czy ręczne zarządzanie).

Ansible w praktyce: role, inventory, idempotencja i testy

Modele inventory: statyczne, dynamiczne i hybrydy

Inventory to serce Ansible. W małych środowiskach wystarcza statyczne inventory w YAML/INI:

[web]
web-1.internal
web-2.internal

[db]
db-1.internal

W chmurze lepiej sprawdzają się inventory dynamiczne, np. w AWS:

  • oficjalny plugin aws_ec2 (filtry po tagach, regionach),
  • własne skrypty generujące inventory na podstawie API chmury lub danych z CMDB.

Hybrydowy model łączy obie techniki: dynamiczne inventory wybiera hosty po tagach, a statyczne pliki w repo definiują grupy logiczne ([payments_web], [crm_batch]) z parametrami specyficznymi dla środowiska.

Organizacja zmiennych: group_vars, host_vars, vault

Bez porządku w zmiennych rośnie chaos. Typowy, czytelny układ to:

  • group_vars/all/ – ustawienia globalne (np. ścieżki, standardowe użytkowniki, parametry logów),
  • group_vars/<grupa>/ – konfiguracja wspólna dla roli systemu (np. group_vars/web/ z domyślnym nginx_worker_processes),
  • host_vars/<host>/ – wyjątki, pojedyncze nadpisania dla konkretnego hosta.

Dane wrażliwe (hasła, klucze API) lądują w Ansible Vault:

ansible-vault create group_vars/db/vault.yml

Klucz do Vaulta trzyma się poza repo (secrets w CI, menedżer haseł). Uwaga: pliki z Vaultem powinny mieć jasną konwencję nazewniczą (vault.yml, secrets.yml), żeby nie mieszać jawnych i zaszyfrowanych danych w jednym pliku.

Idempotencja: jak pisać taski, które nie robią side‑effectów

Idempotencja (wielokrotne uruchomienie daje ten sam efekt) nie dzieje się automatycznie. Kilka praktycznych wzorców:

  • Korzystanie z modułów deklaratywnych zamiast command/shell. Przykład: user zamiast ręcznego useradd, apt zamiast apt-get install.
  • Jeśli trzeba użyć shell, ustawianie creates, removes lub changed_when/failed_when, np.:
    - name: Migrate database schema
      shell: /opt/app/migrate.sh
      args:
        creates: /var/lib/app/.migrated_2024_03_01
  • Moduł template połączony z notify i handlerem – restart serwisu tylko gdy plik rzeczywiście się zmienił.

Dobry znak: przy drugim uruchomieniu playbook wyświetla głównie status ok, a nie changed. Jeśli wszystko jest wiecznie „changed”, idempotencja nie działa.

Roles as interfaces: kontrakty między zespołami

Rola Ansible pełni funkcję kontraktu: zespół A utrzymuje rolę postgresql-server, a zespoły B, C używają jej po prostu konfigurując zmienne. Żeby taki kontrakt działał:

  • Rola ma jasno opisane wejścia (zmienne w defaults/main.yml z komentarzami) i outputy logiczne (np. dobrze zdefiniowany stan usług, ścieżki plików, użytkownicy).
  • Zmiany breaking (np. inne nazwy zmiennych) są robione z bumpem wersji roli/kolekcji i changelogiem.
  • Role utrzymywane centralnie są wydawane jako kolekcje, np. company.db, company.web, a projekty konsumują je przez collections/requirements.yml:
---
collections:
  - name: company.web
    version: ">=1.4.0,<2.0.0"

Playbooki środowiskowe i „thin playbooks”

Playbooki powinny być szczupłe: mapują grupy hostów na role i przekazują tylko parametry specyficzne dla środowiska. Przykład dla prod:

- name: Configure production web nodes
  hosts: web_prod
  gather_facts: true
  roles:
    - role: company.web.nginx_app
      vars:
        nginx_app_env: "prod"
        nginx_app_error_log_level: "warn"

Logika warunkowa powinna siedzieć w rolach, nie w playbookach. Playbook staje się wtedy czymś w rodzaju definicji „stacka konfiguracyjnego” – łatwo porównać stage i prod, bo różnice ograniczają się do kilku parametrów.

Testowanie ról Ansible: Molecule i testy dymne

Konfiguracja bez testów kończy się „deploy and pray”. Najbardziej praktyczne podejście to:

  • Molecule do testów jednostkowych ról – uruchamianie roli na ephemeral maszynach (Docker, pod w K8s, VM) oraz asercje w testach (np. za pomocą testinfra lub prostych komend sprawdzających).
  • Testy dymne odpalane po deployu – np. prosty playbook z modułami uri, command, service, który weryfikuje, że kluczowe serwisy odpowiadają i logi nie zawierają krytycznych błędów.
  • W pipeline CI – minimum: ansible-lint + yamllint + Molecule dla nowych/zmienionych ról.

Przykładowy scenariusz Molecule (skrót):

scenario:
  name: default

driver:
  name: docker

platforms:
  - name: ubuntu-nginx
    image: "ubuntu:22.04"

provisioner:
  name: ansible
  playbooks:
    converge: converge.yml

Integracja Ansible z Terraform: od infrastruktury do konfiguracji

Typowa ścieżka: Terraform tworzy infrastrukturę (VM, load balancery, security groups), a Ansible konfiguruje systemy. Spięcie tych światów można zrobić na kilka sposobów:

  • Pliki inventory generowane z Terraform – po terraform apply uruchamiany jest krok, który na podstawie terraform output -json generuje plik inventory.ini lub inventory.yml.
  • Bezpośrednie użycie dynamic inventory – hosty są wybierane po tagach nadanych z Terraform (np. Service=payments, Environment=prod), a konkretny deployment jest odpalany na podstawie tych tagów.
  • Outputs jako konfiguracja – np. adresy baz danych czy endpointy S3 eksportowane przez Terraform trafiają do Ansible jako zmienne środowiskowe w CI albo jako pliki vars.

Prosty przykład generowania inventory w CI (bash + Terraform):

Najczęściej zadawane pytania (FAQ)

Co to jest infrastruktura jako kod (IaC) i na czym polega podejście deklaratywne?

Infrastruktura jako kod (Infrastructure as Code, IaC) to sposób zarządzania infrastrukturą (serwery, sieci, bazy danych, uprawnienia) poprzez pliki tekstowe trzymane w repozytorium, zamiast ręcznego „klikania” w panelach chmury. Narzędzia typu Terraform czy Ansible czytają te pliki i na ich podstawie tworzą lub modyfikują środowiska.

Podejście deklaratywne oznacza, że opisujesz stan docelowy („ma istnieć VPC, trzy subnety, klaster Kubernetes i RDS”), a narzędzie samo wylicza kroki potrzebne, żeby doprowadzić infrastrukturę do tego stanu. Nie piszesz sekwencji poleceń, tylko opisujesz rezultat, który chcesz osiągnąć. To zmniejsza liczbę błędów i ułatwia utrzymanie dużych środowisk.

Dlaczego warto przejść z ręcznego „klikania” na Terraform, Ansible i inne narzędzia IaC?

Ręczne tworzenie środowisk działa tylko przy małej skali. Gdy pojawia się kilkanaście–kilkadziesiąt środowisk (dev, test, stage, prod, PoC), ręczne podejście prowadzi do rozjazdów konfiguracji, trudnych do odtworzenia błędów i braku wiedzy, co faktycznie jest wdrożone.

IaC daje trzy kluczowe korzyści: powtarzalność (te same moduły i wzorce dla dev/stage/prod), pełną historię zmian w Git (łatwy audyt: kto, kiedy, co zmienił) oraz dużo szybsze tworzenie środowisk – często sprowadzone do uruchomienia pipeline’u. Dodatkowo uproszcza disaster recovery, bo całe środowisko można zdefiniować jako kod i odtworzyć w innym regionie lub chmurze bez ręcznego „składania z pamięci”.

Terraform vs Ansible vs Packer – do czego służy każde z tych narzędzi?

Terraform służy do zarządzania infrastrukturą w chmurze i on-prem (VPC, sieci, instancje, bazy, load balancery, DNS). Działa deklaratywnie i utrzymuje stan środowiska, dzięki czemu widzi różnicę między tym, co jest w kodzie, a tym, co faktycznie istnieje u dostawcy.

Ansible odpowiada za konfigurację systemów i aplikacji (pakiety, usługi, pliki konfiguracyjne, użytkownicy). Działa bardziej „wewnątrz” maszyn – po tym, jak Terraform postawi infrastrukturę, Ansible może ją skonfigurować. Packer z kolei buduje obrazy maszyn (AMI, obrazy GCE, szablony dla vSphere) z preinstalowanym oprogramowaniem. Typowy przepływ: Packer tworzy obrazy bazowe, Terraform stawia infrastrukturę z tych obrazów, Ansible wykonuje konfigurację warstwy systemowej i aplikacyjnej.

Czy narzędzia open source do IaC są bezpieczne w firmowych projektach?

Sam fakt, że narzędzie jest open source, nie obniża bezpieczeństwa – kod jest publiczny i może być audytowany, a społeczność zwykle szybko reaguje na błędy. Kluczowe ryzyka w projektach IaC dotyczą raczej tego, jak przechowujesz sekrety (hasła, klucze API) i jak konfigurujesz dostęp do chmury.

Bezpieczne podejście to m.in.: użycie menedżerów tajemnic (Vault, AWS Secrets Manager, GCP Secret Manager), krótkotrwałe role i tokeny zamiast stałych kluczy, szyfrowanie stanu Terraform (np. w S3 + KMS) i zero haseł w repozytorium Git. Dobrą praktyką są także obowiązkowe code review dla zmian w IaC oraz testowe środowisko, na którym weryfikujesz plany przed wejściem na produkcję.

Jak uniknąć „magii” i przypadkowego skasowania zasobów przez Terraform lub Ansible?

Podstawowy mechanizm kontroli to praca na planach i trybach „dry-run”. W Terraform zawsze przeglądaj terraform plan i wymuszaj jego generowanie w CI – zmiany powinny być widoczne w merge requeście, zanim ktoś uruchomi apply. W Ansible używaj --check (symulacja) i --diff (pokazuje różnice w plikach) oraz testów Molecule dla ról.

W praktyce dobrze działa wzorzec: zmiana IaC → PR w repo → review kogoś z zespołu → uruchomienie planu na środowisku testowym → dopiero potem produkcja. Uwaga: wrażliwe zasoby (np. bazy danych z produkcyjnymi danymi) można dodatkowo chronić tagami i regułami polityk (policy as code), które blokują destrukcyjne operacje bez specjalnego wyjątku.

Open source czy platforma komercyjna do IaC – co wybrać na start?

Dla większości zespołów dobrym punktem wyjścia jest „goły” ekosystem open source: Terraform OSS, Ansible, Packer, Helm/Kustomize. Dają dużą elastyczność, brak vendor lock-in i łatwo przenoszą się między chmurami oraz różnymi systemami CI/CD. Możesz je uruchamiać lokalnie, w GitLab CI, GitHub Actions czy Jenkinsie bez przepisywania logiki.

Platformy komercyjne (Terraform Cloud/Enterprise, dedykowane SaaS-y IaC) dokładają wygodę w obszarach security, governance, SSO, policy as code i zarządzania stanem. Problem zaczyna się, gdy większość logiki biznesowej i procesów „zamyka się” w danym produkcie – wyjście z takiej platformy jest wtedy kosztowne. Rozsądne podejście: logikę infrastruktury trzymać w narzędziach open source, a rozwiązania komercyjne traktować jako nadbudowę, którą można w razie potrzeby zmienić.

Od czego zacząć naukę Terraform/Ansible, żeby nie utonąć w złożoności?

Najlepszym podejściem jest start od bardzo prostego, realnego use case’u i iteracyjne dokładanie kolejnych elementów. Przykład: w Terraform zacznij od jednego VPC, subnetu i instancji z load balancerem; dopiero gdy to działa stabilnie, dołóż bazę, kolejkę, monitoring. W Ansible na początek jedna rola instalująca i konfigurująca konkretną aplikację, bez nadmiarowej abstrakcji.

Tip: używaj gotowych modułów i ról z oficjalnych rejestrów (Terraform Registry, Ansible Galaxy), ale traktuj je świadomie – przeczytaj kod, zrozum zmienne i ograniczenia. Z czasem przenieś powtarzalne wzorce do własnych modułów/collection, spójnych z resztą organizacji. Taki spokojny, iteracyjny rozwój jest dużo bezpieczniejszy niż próba „zrobienia całej chmury w Terraformze” w jednym sprintcie.

Najważniejsze punkty

  • Przejście z ręcznego „klikania” infrastruktury na model Infrastructure as Code (IaC) pozwala opisać cały stan środowiska w deklaratywnych plikach, co eliminuje rozjazdy między środowiskami i uniezależnia konfigurację od pamięci pojedynczych osób.
  • IaC umożliwia zmianę podejścia z „serwerów-pupili” na „serwery-bydło” – każdą instancję da się szybko odtworzyć z kodu, co jest bazą pod CI/CD, GitOps dla infrastruktury, automatyczne skalowanie i sprawne odtwarzanie po awarii.
  • Najważniejsze korzyści praktyczne to powtarzalność (te same moduły dla dev/stage/prod), pełna historia zmian w Git (audyt, diff-y, code review) oraz drastyczne skrócenie czasu tworzenia nowych środowisk dzięki zautomatyzowanym pipeline’om (np. Terraform + Packer + Ansible).
  • Opisanie całej infrastruktury (VPC, sieci, bazy, uprawnienia, load balancery, kolejki) w kodzie znacząco upraszcza disaster recovery – odtwarzanie środowiska w innym regionie lub chmurze staje się kwestią odpalenia znanego procesu, a nie ręcznego rekonstruowania konfiguracji.
  • Otwarte narzędzia IaC (Terraform OSS, Ansible, Packer, Helm, Kustomize) dają elastyczność, brak twardego vendor lock-in i bogaty ekosystem gotowych modułów oraz ról, co ułatwia start i późniejsze migracje między dostawcami chmury.
Poprzedni artykułPlanowanie sieci w bloku i domu jednorodzinnym: typowe problemy z zasięgiem Wi‑Fi i proste sposoby na ich obejście
Izabela Kucharski
Izabela Kucharski to specjalistka od cyberbezpieczeństwa i ochrony prywatności w sieci. Pracowała przy audytach bezpieczeństwa, wdrażaniu polityk ochrony danych oraz szkoleniach dla użytkowników nietechnicznych. Na Pirat-Pirat.pl tworzy poradniki dotyczące VPN, higieny cyfrowej, konfiguracji systemów i aplikacji pod kątem bezpieczeństwa. Jej teksty łączą perspektywę techniczną z naciskiem na prostotę wdrożenia w realnym życiu. Zanim zarekomenduje narzędzie, analizuje jego politykę prywatności, model biznesowy i wyniki niezależnych testów. Stawia na rzetelne źródła, aktualność informacji i odpowiedzialne podejście do danych użytkowników.