Modul 10 — Infrastructure as Code

48. Terraform základy: providery, resources, state

První praktické kroky s Terraformem — providery, definice resources, pracovní cyklus init/plan/apply a k čemu slouží state soubor.

Odhadovaná délka studia: 60 minut · Stav: nehotovo

Technologie: Terraform

Úvod a kontext

Terraform je nejrozšířenější open-source nástroj pro deklarativní
Infrastructure as Code, vyvíjený firmou HashiCorp. Na rozdíl od
cloud-specifických nástrojů (např. AWS CloudFormation) je Terraform
multi-cloud — stejným jazykem (HCL) lze spravovat zdroje v AWS,
Azure, GCP, ale i v desítkách dalších systémů (DNS providery, GitHub,
Kubernetes, a další), díky pluginové architektuře providerů. Tato lekce
projde základní stavební bloky a pracovní cyklus.

Teorie

HCL — HashiCorp Configuration Language

Terraform konfigurace se píše v jazyce HCL, deklarativním formátu
navrženém tak, aby byl čitelnější než čisté JSON, ale strojově stejně
dobře parsovatelný:

resource "typ_zdroje" "lokální_jméno" {
  argument1 = "hodnota"
  argument2 = 42
}

Providery

Provider je plugin, který Terraformu říká, jak komunikovat
s konkrétní platformou (API volání, autentizace, mapování HCL zdrojů na
skutečné API operace). Provider se deklaruje v bloku terraform a
nakonfiguruje v bloku provider:

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "eu-central-1"
}

Verze provideru je vhodné omezit (~> 5.0 znamená „libovolná verze
5.x, ale ne 6.0“), aby automatický upgrade provideru nerozbil existující
konfiguraci neočekávanou změnou chování.

Resources

Resource (zdroj) je základní stavební blok — jedna spravovaná
entita: virtuální server, síť, DNS záznam, databáze. Blok resource
tvar resource "typ" "lokální_jméno" { ... }, kde typ je definovaný
providerem (např. aws_instance, aws_s3_bucket) a lokální jméno slouží
jen pro odkazování uvnitř Terraform konfigurace, nemá vliv na skutečné
jméno zdroje v cloudu:

resource "aws_instance" "web" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t3.micro"

  tags = {
    Name        = "web-server"
    Environment = "production"
  }
}

Na jiný zdroj se lze odkázat přes typ.lokální_jméno.atribut, čímž
Terraform automaticky odvodí závislosti mezi zdroji a vytvoří je ve
správném pořadí:

resource "aws_security_group" "web_sg" {
  name = "web-sg"
  # ... pravidla ...
}

resource "aws_instance" "web" {
  ami                    = "ami-0c55b159cbfafe1f0"
  instance_type          = "t3.micro"
  vpc_security_group_ids = [aws_security_group.web_sg.id]
}

Zde aws_instance.web závisí na aws_security_group.web_sg — Terraform
proto nejdřív vytvoří bezpečnostní skupinu a teprve pak instanci.

Pracovní cyklus: init, plan, apply, destroy

terraform init      # stáhne potřebné providery a inicializuje backend
terraform plan       # spočítá a vypíše, co by se změnilo (bez zásahu)
terraform apply       # po potvrzení skutečně provede změny
terraform destroy      # zruší veškerou infrastrukturu spravovanou touto konfigurací

terraform plan je klíčový bezpečnostní krok — ukáže přesně, co bude
vytvořeno (+), změněno (~) nebo smazáno (-), ještě než se cokoliv
skutečně stane. V týmovém provozu se plan typicky pouští automaticky
v CI při pull requestu, aby recenzenti viděli dopad změny, a apply
až po schválení a mergi.

State

Terraform potřebuje vědět, jaké zdroje už vytvořil a s jakými
identifikátory, aby při dalším plan/apply mohl spočítat rozdíl mezi
požadovaným a aktuálním stavem. K tomu slouží state soubor
(terraform.tfstate) — JSON soubor s mapováním mezi zdroji v HCL
konfiguraci a skutečnými identifikátory zdrojů v cloudu.

Ve výchozím nastavení se state ukládá lokálně na disk, což je v praxi
nebezpečné pro cokoliv víc než osobní experimenty:

  • pokud state ztratíte, Terraform „zapomene“, že zdroje existují, a
    při dalším apply by se je pokusil vytvořit znovu (duplicitně),
  • při práci v týmu si dva lidé mohou state navzájem přepsat.

Proto se v produkci téměř vždy používá vzdálený backend — state se
ukládá centrálně (např. v AWS S3 s DynamoDB tabulkou pro zamykání, aby
dva apply neběžely současně):

terraform {
  backend "s3" {
    bucket         = "moje-firma-terraform-state"
    key            = "projekty/web-app/terraform.tfstate"
    region         = "eu-central-1"
    dynamodb_table = "terraform-locks"
    encrypt        = true
  }
}

State soubor obsahuje i citlivé hodnoty (např. hesla generovaná
Terraformem) v čitelné podobě — proto musí být zabezpečený (šifrování
na backendu, přístupová práva) a nikdy se necommituje do Gitu.

Proměnné a výstupy

Konfiguraci lze parametrizovat proměnnými (variable) a exportovat
hodnoty pro použití jinde (output):

variable "environment" {
  description = "Nasazované prostředí"
  type        = string
  default     = "staging"
}

resource "aws_instance" "web" {
  instance_type = var.environment == "production" ? "t3.medium" : "t3.micro"
  # ...
}

output "instance_ip" {
  value = aws_instance.web.public_ip
}

Praktický příklad

Kompletní minimální konfigurace vytvářející jeden S3 bucket a vypisující
jeho jméno jako výstup:

# main.tf
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "eu-central-1"
}

resource "aws_s3_bucket" "uploads" {
  bucket = "moje-appka-uploads-2024"

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

output "bucket_name" {
  value = aws_s3_bucket.uploads.bucket
}

Spuštění a typický výstup plan:

$ terraform init
Terraform has been successfully initialized!

$ terraform plan
Terraform will perform the following actions:

  # aws_s3_bucket.uploads will be created
  + resource "aws_s3_bucket" "uploads" {
      + bucket = "moje-appka-uploads-2024"
      + id     = (known after apply)
      + tags   = {
          + "Environment" = "production"
          + "ManagedBy"   = "terraform"
        }
    }

Plan: 1 to add, 0 to change, 0 to destroy.

$ terraform apply
# ... po potvrzení "yes" ...
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Outputs:
bucket_name = "moje-appka-uploads-2024"

Shrnutí

Terraform popisuje infrastrukturu deklarativně v HCL, kde provider
říká, s jakou platformou se komunikuje, a resource popisuje jednotlivé
spravované zdroje s automaticky odvozenými závislostmi. Pracovní cyklus
je initplan (náhled beze změny) → apply (skutečná změna po
potvrzení). Terraform si vede state — mapování mezi konfigurací a
skutečnými zdroji — který se v týmovém provozu musí ukládat na vzdálený,
zamykatelný a šifrovaný backend, nikdy lokálně ani v Gitu.

Kontrolní otázky

  1. K čemu slouží terraform plan a proč je důležité ho spouštět před
    apply?
  2. Co se stane, pokud se ztratí terraform.tfstate bez vzdáleného
    backendu, a proč je to problém?
  3. Proč se state soubor nikdy necommituje do Gitu?
  4. Mini cvičení: napište HCL definici pro aws_security_group
    s jedním pravidlem, které povoluje příchozí provoz na portu 443,
    a odkažte se na její id z bloku aws_instance podobně jako
    v příkladu v lekci.

Lekce na sebe nejsou zamčené — libovolnou lekci můžete otevřít i označit jako hotovou v jakémkoliv pořadí.