Każdy zasób w chmurze, jaki kiedykolwiek stworzyłeś, klikając w konsoli webowej (VM, load balancer, rekord DNS), da się też opisać w pliku tekstowym i utworzyć jedną komendą. Plik trafia do git, przechodzi przez pull request i odtwarza ten sam setup na drugim koncie, bez konieczności pamiętania, które dziewięć ustawień zmieniłeś ręcznie. Dokładnie to robi Terraform.

Co właściwie robi Terraform

Terraform to open-source’owe narzędzie od HashiCorp, które zamienia infrastrukturę na pliki konfiguracyjne napisane w HCL (HashiCorp Configuration Language) i wprowadza je w życie przez API dostawcy chmury. Piszesz blok resource, opisujący bucket S3, instancję EC2 albo rekord DNS w Cloudflare, uruchamiasz terraform apply, a Terraform sam wylicza wywołania API potrzebne, żeby ten zasób powstał — a potem go śledzi, więc kolejny apply zmienia tylko to, co faktycznie się zmieniło.

Model jest deklaratywny, nie proceduralny: opisujesz stan docelowy, nie kroki do niego prowadzące. Nie piszesz “utwórz bucket, potem ustaw jego politykę, potem włącz versioning”. Piszesz, jak bucket ma wyglądać, a Terraform sam ustala kolejność operacji, w tym co musi powstać najpierw, gdy jeden zasób zależy od drugiego.

Sam Terraform nic nie wie o AWS, Azure ani Cloudflare. Ta wiedza mieszka w providerach: pluginach tłumaczących bloki HCL na wywołania konkretnego API. Terraform Registry wymienia ich tysiące, oficjalnych i utrzymywanych przez społeczność, obejmujących wszystko od trzech dużych chmur po ustawienia repozytoriów GitHub i monitory Datadog. Ten sam mechanizm tworzy zarządzany klaster Kubernetes (google_container_cluster, aws_eks_cluster) albo pojedynczy rekord DNS przed CDN. Jeśli jakaś usługa ma API, to spora szansa, że ktoś już napisał do niej provider.

Jak działają terraform plan i apply

Minimalny katalog roboczy ma co najmniej dwa pliki.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# terraform.tf — jakich providerów potrzebuje ta konfiguracja
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
  required_version = ">= 1.5"
}

provider "aws" {
  region = "eu-west-1"
}
1
2
3
4
5
6
7
8
9
# main.tf — sam zasób
resource "aws_s3_bucket" "reports" {
  bucket = "acme-monthly-reports"

  tags = {
    Environment = "production"
    ManagedBy   = "terraform"
  }
}

W codziennej pracy używasz czterech komend:

1
2
3
4
terraform init     # pobiera plugin providera aws, konfiguruje backend
terraform plan     # pokazuje, co by się zmieniło, nic nie dotykając
terraform apply    # prosi o potwierdzenie, potem wykonuje wywołania API
terraform destroy  # usuwa wszystko, czym zarządza ta konfiguracja

terraform init czyta required_providers i pobiera odpowiednie binaria pluginów do .terraform/. Uruchamiasz go raz na katalog, i ponownie za każdym razem, gdy dodajesz providera albo moduł.

terraform plan to komenda, której będziesz używać najczęściej. Porównuje twoje pliki .tf, zapisany state i rzeczywistą infrastrukturę tak, jak raportuje ją API providera, a potem wypisuje diff: zasoby do utworzenia (+), zmiany (~) albo usunięcia (-). Nic jeszcze się nie dzieje. Właśnie to sprawia, że bezpiecznie jest uruchamiać Terraform na produkcji — czytasz plan, zanim cokolwiek zostanie dotknięte.

terraform apply uruchamia plan ponownie, pokazuje go jeszcze raz i czeka, aż wpiszesz yes, zanim wywoła API providera w odpowiedniej kolejności zależności. W CI przekazujesz -auto-approve albo, lepiej, stosujesz już przejrzany plik planu: terraform apply tfplan. Ten sam pipeline, który przy każdym merge uruchamia Budować i Publikować Obraz Docker z GitHub Actions, może uruchomić terraform plan na pull requeście i terraform apply po jego zmergowaniu do main.

Do czego służy plik state

Po pierwszym apply Terraform zapisuje terraform.tfstate — plik JSON mapujący każdy zasób z twojej konfiguracji na realny obiekt, który utworzył, razem z ID. To nie jest opcjonalna księgowość. Dzięki temu plan wie, co już istnieje, i nie musi za każdym razem skanować całego konta AWS. Dzięki temu Terraform łączy też aws_s3_bucket.reports w twoim kodzie z bucketem acme-monthly-reports-a8f3 w rzeczywistości.

Z tego wynikają wprost dwie konsekwencje.

State może zawierać sekrety. Hasło do bazy danych ustawione jako argument zasobu ląduje w state jawnym tekstem, bo Terraform potrzebuje pełnych atrybutów zasobu, żeby wykryć dryf. Nigdy nie commituj terraform.tfstate do git.

State trzeba współdzielić i blokować. Domyślnie jest to plik lokalny, który działa w pojedynkę i psuje się w momencie, gdy druga osoba uruchomi apply z własnego laptopa: teraz są dwa źródła prawdy. Rozwiązaniem jest zdalny backend, ze state przechowywanym w S3, Azure Blob, GCS albo Terraform Cloud, z blokadą, która nie pozwala dwóm apply ścigać się jednocześnie.

1
2
3
4
5
6
7
8
9
terraform {
  backend "s3" {
    bucket       = "acme-terraform-state"
    key          = "reports/terraform.tfstate"
    region       = "eu-west-1"
    use_lockfile = true
    encrypt      = true
  }
}

use_lockfile to natywne blokowanie backendu S3, dostępne stabilnie od Terraform 1.11 — zapisuje plik blokady bezpośrednio w buckecie, więc w aktualnych wersjach osobna tabela DynamoDB do blokowania nie jest już potrzebna.

Gdy cały zespół korzysta z tego samego backendu, plan każdej osoby odzwierciedla ostatni apply wszystkich pozostałych.

Jak ponownie wykorzystać konfigurację za pomocą zmiennych i modułów

Wpisanie na sztywno eu-west-1 i nazwy bucketu sprawdza się w pięciolinijkowym przykładzie, nie w prawdziwym środowisku. Terraform oddziela kształt infrastruktury od wartości, które zmieniają się w zależności od środowiska.

1
2
3
4
5
6
7
8
9
# variables.tf
variable "environment" {
  type    = string
  default = "staging"
}

variable "bucket_name" {
  type = string
}
1
2
3
4
5
6
7
8
# main.tf, odwołujący się do zmiennej
resource "aws_s3_bucket" "reports" {
  bucket = var.bucket_name

  tags = {
    Environment = var.environment
  }
}
1
terraform apply -var="bucket_name=acme-prod-reports" -var="environment=production"

Albo wrzuć wartości do pliku terraform.tfvars, żeby nie wpisywać flag na nowo przy każdym uruchomieniu. outputs.tf robi coś odwrotnego. Udostępnia wartość obliczoną przez Terraform, na przykład publiczny adres IP instancji albo wygenerowany ARN, żeby inne narzędzie albo inna konfiguracja Terraform mogły z niej skorzystać:

1
2
3
output "bucket_arn" {
  value = aws_s3_bucket.reports.arn
}

Gdy zestaw zasobów powtarza się między projektami (standardowy VPC, standardowy stack web-app), zamykasz go w module: katalog plików .tf przywoływany argumentem source, który przyjmuje wejścia i zwraca wyjścia jak funkcja. W większości zespołów kończy się na garstce wewnętrznych modułów i wielu cienkich konfiguracjach, które wywołują je z różnymi zmiennymi.

Kiedy Terraform jest złym narzędziem

Terraform tworzy zasoby; nie konfiguruje tego, co na nich działa. Utworzy instancję EC2, ale instalowanie pakietów, zarządzanie użytkownikami i utrzymywanie konfiguracji zsynchronizowanej na tej instancji to inna praca — tradycyjnie Ansible, skryptu cloud-init albo obrazu kontenera, który instancja pobiera i uruchamia. Przepuszczanie konfiguracji aplikacji przez Terraform to zły kierunek. Tworzenie VPC przez Ansible to drugi zły kierunek. Większość prawdziwych setupów używa obu naraz, a Terraform przekazuje adres IP instancji bezpośrednio do inventory Ansible poprzez output.

Dla bucketu, który skasujesz za godzinę, albo jednorazowej testowej VM, plik state i cykl napisz-plan-apply kosztują więcej, niż oszczędzają. CLI AWS albo konsola są szybsze do czegoś, czego nie zamierzasz utrzymywać ani odtwarzać.

Czym różnią się Terraform, OpenTofu i Pulumi

Terraform nie jest już jedynym narzędziem czytającym HCL. HashiCorp przeniósł go z open-source’owej licencji MPL na Business Source License w 2023 roku, a Linux Foundation utrzymuje teraz OpenTofu, forka, który zachował licencję MPL i pozostał kompatybilny ze składnią HCL i formatem state Terraform. Przejście na niego później to zmiana konfiguracji, nie przepisanie od zera.

Pulumi idzie w drugą stronę: zamiast HCL infrastrukturę piszesz w TypeScript, Pythonie albo Go. To ma znaczenie, jeśli zespołowi zależy na prawdziwej kontroli przepływu i testach jednostkowych dla logiki infrastruktury, a HCL na to nie pozwala.

Jak wypróbować Terraform bez konta w chmurze

Siatką bezpieczeństwa Terraform jest plan, więc możesz przećwiczyć cały cykl za darmo, zanim spróbujesz go na czymś płatnym. Providery random i local nie wymagają żadnych danych uwierzytelniających do chmury.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
terraform {
  required_providers {
    random = {
      source  = "hashicorp/random"
      version = "~> 3.0"
    }
  }
}

resource "random_pet" "name" {
  length = 2
}

output "generated_name" {
  value = random_pet.name.id
}

Uruchamiasz terraform init i terraform apply w pustym katalogu i dostajesz prawdziwy plik state oraz prawdziwy zarządzany zasób, a nie zapłacisz za to ani grosza. Zrób to raz, przeczytaj wynik planu linia po linii, aż symbole +, ~ i - zaczną coś dla ciebie znaczyć, a potem uruchom te same cztery komendy na providerze, który rozlicza się za godzinę.

Powiązane artykuły