Spis treści 12 sekcji
- Dlaczego Infrastructure as Code?
- Terraform vs CloudFormation vs CDK
- Instalacja i konfiguracja
- Kluczowe koncepcje Terraform
- Projekt praktyczny: VPC + EC2 + Security Group
- Workflow: init, plan, apply, destroy
- State management - lokalne vs zdalne
- Moduły - reużywalna infrastruktura
- Best practices
- Koszt Terraform
- Najczęściej zadawane pytania (FAQ)
- Następny krok
- Infrastructure as Code (IaC) eliminuje ręczne klikanie w konsoli - infrastruktura jest wersjonowana, powtarzalna i audytowalna jak kod aplikacji.
- Terraform to najpopularniejsze narzędzie IaC - darmowe w użyciu, działa z AWS, Azure, GCP i setkami innych providerów.
- W artykule budujemy kompletne środowisko: VPC + subnety + EC2 + Security Group - cały kod gotowy do skopiowania.
- State przechowuj zdalnie w S3 z lockiem w DynamoDB - nigdy lokalnie w zespole.
- Moduły Terraform pozwalają wielokrotnie używać tych samych komponentów dla dev/staging/prod.
Klikasz w konsolę AWS, tworzysz VPC, potem subnety, security groupy, instancje EC2. Wszystko działa. Tydzień później kolega prosi, żebyś postawił to samo na nowym koncie. Siadasz i klikasz od nowa. Trzeci raz? Czwarty? W pewnym momencie zapominasz jeden krok i spędzasz godzinę na debugowaniu.
Brzmi znajomo? Tak wyglądała moja codzienność, zanim odkryłem Terraform. Dziś jako MSP Engineer w ClearScale nie wyobrażam sobie pracy bez niego. Każdy zasób, który stawiam na AWS, istnieje najpierw w kodzie, przechodzi code review i jest wersjonowany w Git. W tym artykule pokażę Ci, jak zacząć pracę z Terraform od zera i zbudować kompletne środowisko na AWS. Jeśli dopiero poznajesz chmurę, zacznij od przewodnika po AWS.
Dlaczego Infrastructure as Code?
Ręczne zarządzanie infrastrukturą przez konsolę webową ma kilka poważnych problemów:
- Brak powtarzalności - nie da się kliknąć identycznie dwa razy. Zawsze coś się zmieni.
- Brak historii zmian - kto zmienił security group w piątek o 23:00? Nikt nie wie.
- Brak code review - nikt nie sprawdza, czy ten Internet Gateway naprawdę powinien być publiczny.
- Nie skaluje się - 5 zasobów da się wyklikać. 500 już nie.
- Brak testów - nie przetestujesz kliknięć w konsoli przed wdrożeniem na produkcję.
IaC rozwiązuje wszystkie te problemy. Infrastruktura zapisana jako kod jest wersjonowana w Git, przechodzi review, może być testowana i wdrażana automatycznie. To ten sam skok jakościowy, który przeszło programowanie, gdy porzuciliśmy edytowanie plików bezpośrednio na serwerach produkcyjnych.
Terraform vs CloudFormation vs CDK
Na AWS masz trzy główne opcje IaC:
| Cecha | Terraform | CloudFormation | AWS CDK |
|---|---|---|---|
| Język | HCL (własny, prosty) | YAML/JSON | TypeScript, Python, Java |
| Multi-cloud | Tak (AWS, Azure, GCP, 3000+ providerów) | Tylko AWS | Tylko AWS |
| State management | Własny (S3 + DynamoDB) | Zarządzany przez AWS | CloudFormation pod spodem |
| Koszt | Darmowy (licencja BUSL) | Darmowy | Darmowy |
| Szybkość wdrażania zmian | Szybki plan/apply | Wolniejszy (stack updates) | Szybki (hotswap) |
| Ekosystem | Terraform Registry (moduły) | Ograniczony | Construct Hub |
| Popularność | Najwyższa na rynku | Popularna w ekosystemie AWS | Rosnąca |
Dlaczego wybieram Terraform? Trzy powody: działa z każdym providerem (nie tylko AWS), ma ogromny ekosystem modułów i jest standardem rynkowym. Gdy szukasz pracy jako Cloud Engineer, Terraform jest wymieniony w 80% ofert. CloudFormation zna mniej osób, a CDK, choć świetny, generuje CloudFormation pod spodem.
Instalacja i konfiguracja
Instalacja Terraform
# macOS (Homebrew)
brew install terraform
# Windows (Chocolatey)
choco install terraform
# Linux (Ubuntu/Debian)
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install terraform
# Weryfikacja
terraform version
Konfiguracja AWS Provider
Terraform potrzebuje dostępu do Twojego konta AWS. Upewnij się, że masz skonfigurowany AWS CLI z odpowiednimi uprawnieniami IAM:
# Skonfiguruj AWS CLI (jeśli jeszcze tego nie zrobiłeś)
aws configure
# Podaj: Access Key ID, Secret Access Key, region (eu-central-1), output (json)
Stwórz plik providers.tf:
# providers.tf - konfiguracja providera AWS
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
default_tags {
tags = {
Project = "cloudmaniak-demo"
Environment = var.environment
ManagedBy = "terraform"
}
}
}
default_tags automatycznie dodaje tagi do każdego zasobu. Dzięki temu zawsze wiesz, który projekt stworzył dany zasób i czy jest zarządzany przez Terraform. To oszczędza godziny przy audytach kosztów.Kluczowe koncepcje Terraform
Zanim zaczniemy budować, poznaj sześć fundamentalnych koncepcji:
- Provider - plugin łączący Terraform z platformą (AWS, Azure, GCP). Definiujesz go w bloku
provider. - Resource - pojedynczy zasób infrastruktury (instancja EC2, VPC, S3 bucket). Każdy ma typ i nazwę.
- Variable - parametr wejściowy. Pozwala reużywać ten sam kod z różnymi wartościami (np. dev vs prod).
- Output - wartość wyjściowa, np. publiczne IP instancji po wdrożeniu.
- State - plik, w którym Terraform zapisuje aktualny stan infrastruktury. Porównuje go z kodem, żeby wiedzieć, co zmienić.
- Module - paczka zasobów, które możesz reużywać jak funkcję w programowaniu.
Projekt praktyczny: VPC + EC2 + Security Group
Zbudujemy kompletne środowisko: sieć VPC z subnetami, security group z regułami firewalla i instancję EC2 z Nginx. Jeśli chcesz najpierw zrozumieć, czym jest VPC, przeczytaj VPC od podstaw.
Struktura projektu
terraform-aws-demo/
├── providers.tf # Konfiguracja providera AWS
├── variables.tf # Zmienne wejściowe
├── vpc.tf # Sieć VPC, subnety, Internet Gateway
├── security.tf # Security Groups
├── ec2.tf # Instancja EC2
├── outputs.tf # Wartości wyjściowe
└── terraform.tfvars # Wartości zmiennych (nie commituj sekretów!)
Zmienne - variables.tf
# variables.tf - parametry projektu
variable "aws_region" {
description = "Region AWS"
type = string
default = "eu-central-1"
}
variable "environment" {
description = "Nazwa środowiska (dev/staging/prod)"
type = string
default = "dev"
}
variable "vpc_cidr" {
description = "Blok CIDR dla VPC"
type = string
default = "10.0.0.0/16"
}
variable "public_subnet_cidr" {
description = "Blok CIDR dla publicznego subnetu"
type = string
default = "10.0.1.0/24"
}
variable "instance_type" {
description = "Typ instancji EC2"
type = string
default = "t3.micro"
}
Sieć VPC - vpc.tf
Porównajmy tworzenie VPC w Terraform vs AWS CLI:
# vpc.tf - cała sieć w jednym pliku
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "${var.environment}-vpc"
}
}
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = {
Name = "${var.environment}-igw"
}
}
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id
cidr_block = var.public_subnet_cidr
availability_zone = "${var.aws_region}a"
map_public_ip_on_launch = true
tags = {
Name = "${var.environment}-public-subnet"
}
}
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
tags = {
Name = "${var.environment}-public-rt"
}
}
resource "aws_route_table_association" "public" {
subnet_id = aws_subnet.public.id
route_table_id = aws_route_table.public.id
}
# AWS CLI - te same zasoby, ale ręcznie i bez wersjonowania
aws ec2 create-vpc \
--cidr-block 10.0.0.0/16 \
--tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=dev-vpc}]'
# Zapisz VPC ID z outputu, np. vpc-0abc123
aws ec2 create-internet-gateway \
--tag-specifications 'ResourceType=internet-gateway,Tags=[{Key=Name,Value=dev-igw}]'
aws ec2 attach-internet-gateway \
--internet-gateway-id igw-xxx \
--vpc-id vpc-xxx
aws ec2 create-subnet \
--vpc-id vpc-xxx \
--cidr-block 10.0.1.0/24 \
--availability-zone eu-central-1a
aws ec2 create-route-table --vpc-id vpc-xxx
aws ec2 create-route \
--route-table-id rtb-xxx \
--destination-cidr-block 0.0.0.0/0 \
--gateway-id igw-xxx
aws ec2 associate-route-table \
--route-table-id rtb-xxx \
--subnet-id subnet-xxx
# 7 komend vs 5 bloków w Terraform
# Każde ID musisz ręcznie kopiować
aws_vpc.main.id i automatycznie dostaje referencję. W CLI musisz ręcznie kopiować ID z outputu każdej komendy. Przy 50 zasobach to koszmar.Security Group - security.tf
# security.tf - firewall dla instancji EC2
resource "aws_security_group" "web" {
name = "${var.environment}-web-sg"
description = "Security group dla serwera web"
vpc_id = aws_vpc.main.id
# SSH - ogranicz do swojego IP w produkcji!
ingress {
description = "SSH"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
# HTTP
ingress {
description = "HTTP"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
# HTTPS
ingress {
description = "HTTPS"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
# Ruch wychodzący - wszystko dozwolone
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "${var.environment}-web-sg"
}
}
# Tworzenie security group w CLI
aws ec2 create-security-group \
--group-name dev-web-sg \
--description "Security group dla serwera web" \
--vpc-id vpc-xxx
# Dodawanie reguł - każda osobna komenda
aws ec2 authorize-security-group-ingress \
--group-id sg-xxx \
--protocol tcp --port 22 \
--cidr 0.0.0.0/0
aws ec2 authorize-security-group-ingress \
--group-id sg-xxx \
--protocol tcp --port 80 \
--cidr 0.0.0.0/0
aws ec2 authorize-security-group-ingress \
--group-id sg-xxx \
--protocol tcp --port 443 \
--cidr 0.0.0.0/0
# 4 komendy vs 1 blok w Terraform
Instancja EC2 - ec2.tf
# ec2.tf - instancja z Nginx
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["al2023-ami-*-x86_64"]
}
}
resource "aws_instance" "web" {
ami = data.aws_ami.amazon_linux.id
instance_type = var.instance_type
subnet_id = aws_subnet.public.id
vpc_security_group_ids = [aws_security_group.web.id]
user_data = <<-EOF
#!/bin/bash
yum update -y
yum install -y nginx
systemctl start nginx
systemctl enable nginx
echo "<h1>Deployed by Terraform</h1>" > /usr/share/nginx/html/index.html
EOF
tags = {
Name = "${var.environment}-web-server"
}
}
# outputs.tf - informacje po wdrożeniu
output "instance_public_ip" {
description = "Publiczne IP serwera"
value = aws_instance.web.public_ip
}
output "instance_url" {
description = "URL serwera"
value = "http://${aws_instance.web.public_ip}"
}
# Znajdź najnowszy AMI Amazon Linux 2023
AMI_ID=$(aws ec2 describe-images \
--owners amazon \
--filters "Name=name,Values=al2023-ami-*-x86_64" \
--query 'sort_by(Images, &CreationDate)[-1].ImageId' \
--output text)
# Uruchom instancję
aws ec2 run-instances \
--image-id $AMI_ID \
--instance-type t3.micro \
--subnet-id subnet-xxx \
--security-group-ids sg-xxx \
--user-data file://user-data.sh \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=dev-web-server}]'
# Musisz osobno stworzyć plik user-data.sh
# Musisz ręcznie sprawdzić publiczne IP:
aws ec2 describe-instances \
--filters "Name=tag:Name,Values=dev-web-server" \
--query 'Reservations[0].Instances[0].PublicIpAddress'
Workflow: init, plan, apply, destroy
Terraform ma cztery kluczowe komendy, których będziesz używać codziennie:
# 1. Inicjalizacja - pobiera provider AWS
terraform init
# 2. Plan - pokazuje, co Terraform zamierza zrobić (bez zmian!)
terraform plan
# 3. Apply - tworzy/modyfikuje zasoby
terraform apply
# 4. Destroy - usuwa WSZYSTKIE zasoby zarządzane przez Terraform
terraform destroy
terraform plan przed apply. Plan pokaże Ci dokładnie, co zostanie stworzone, zmienione lub usunięte. W moim zespole obowiązkowe jest wklejanie output z planu do pull requesta. Dzięki temu reviewer widzi nie tylko zmianę w kodzie, ale też jej efekt na infrastrukturze.Po uruchomieniu terraform apply zobaczysz output z publicznym IP:
Apply complete! Resources: 7 added, 0 changed, 0 destroyed.
Outputs:
instance_public_ip = "3.121.45.67"
instance_url = "http://3.121.45.67"
Otwórz URL w przeglądarce - powinieneś zobaczyć stronę Nginx. Gdy skończysz testy, uruchom terraform destroy, żeby nie płacić za zasoby. Więcej o kosztach instancji przeczytasz w poradniku EC2.
State management - lokalne vs zdalne
Terraform zapisuje stan infrastruktury w pliku terraform.tfstate. Domyślnie jest to plik lokalny, ale w zespole to proszenie się o problemy - kto ma aktualny state? Co gdy dwie osoby uruchomią apply jednocześnie?
Rozwiązanie: remote state w S3 z lockiem w DynamoDB.
# backend.tf - konfiguracja zdalnego state
terraform {
backend "s3" {
bucket = "moja-firma-terraform-state"
key = "dev/terraform.tfstate"
region = "eu-central-1"
dynamodb_table = "terraform-locks"
encrypt = true
}
}
# Stwórz bucket S3 i tabelę DynamoDB (jednorazowo, przed konfiguracją backendu)
aws s3api create-bucket \
--bucket moja-firma-terraform-state \
--region eu-central-1 \
--create-bucket-configuration LocationConstraint=eu-central-1
aws s3api put-bucket-versioning \
--bucket moja-firma-terraform-state \
--versioning-configuration Status=Enabled
aws dynamodb create-table \
--table-name terraform-locks \
--attribute-definitions AttributeName=LockID,AttributeType=S \
--key-schema AttributeName=LockID,KeyType=HASH \
--billing-mode PAY_PER_REQUEST
terraform init z konfiguracją remote state. To jedyne zasoby, które tworzysz ręcznie (lub osobnym modułem Terraform).Moduły - reużywalna infrastruktura
Moduły w Terraform działają jak funkcje w programowaniu. Definiujesz komponent raz i używasz go wielokrotnie z różnymi parametrami.
# modules/vpc/main.tf - moduł VPC
variable "environment" { type = string }
variable "vpc_cidr" { type = string }
resource "aws_vpc" "this" {
cidr_block = var.vpc_cidr
enable_dns_support = true
enable_dns_hostnames = true
tags = { Name = "${var.environment}-vpc" }
}
output "vpc_id" { value = aws_vpc.this.id }
# ---
# Użycie modułu - dev
module "vpc_dev" {
source = "./modules/vpc"
environment = "dev"
vpc_cidr = "10.0.0.0/16"
}
# Użycie modułu - prod (ten sam kod, inne wartości)
module "vpc_prod" {
source = "./modules/vpc"
environment = "prod"
vpc_cidr = "10.1.0.0/16"
}
Możesz też używać gotowych modułów z Terraform Registry. Na przykład oficjalny moduł VPC od AWS:
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.5.0"
name = "dev-vpc"
cidr = "10.0.0.0/16"
azs = ["eu-central-1a", "eu-central-1b"]
public_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
private_subnets = ["10.0.10.0/24", "10.0.11.0/24"]
enable_nat_gateway = true
single_nat_gateway = true # Oszczędność na dev
}
Ten jeden blok tworzy VPC, subnety publiczne i prywatne, Internet Gateway, NAT Gateway, route tables i wszystkie asocjacje. W ręcznym CLI to byłoby kilkanaście komend. Uwaga na koszty: NAT Gateway nie wchodzi w Free Tier i kosztuje ok. 0,05 USD za godzinę (30-40 USD miesięcznie) plus opłaty za przetworzone dane - dlatego na dev ustawiamy single_nat_gateway = true, a po testach pamiętamy o terraform destroy.
Best practices
1. Używaj zmiennych i tfvars dla środowisk
# terraform.tfvars (dev)
environment = "dev"
instance_type = "t3.micro"
vpc_cidr = "10.0.0.0/16"
# prod.tfvars
environment = "prod"
instance_type = "t3.large"
vpc_cidr = "10.1.0.0/16"
# Wdrażanie na prod:
terraform apply -var-file="prod.tfvars"
2. Formatuj i waliduj kod
# Autoformatowanie - uruchamiaj przed każdym commitem
terraform fmt -recursive
# Walidacja składni
terraform validate
3. Nigdy nie przechowuj sekretów w plikach .tf
# ZLE - hasło w kodzie!
resource "aws_db_instance" "db" {
password = "SuperTajneHaslo123" # NIE RÓB TEGO
}
# DOBRZE - hasło z AWS Secrets Manager
data "aws_secretsmanager_secret_version" "db_password" {
secret_id = "prod/db/password"
}
resource "aws_db_instance" "db" {
password = data.aws_secretsmanager_secret_version.db_password.secret_string
}
4. Odpowiedni .gitignore
# .gitignore dla projektów Terraform
.terraform/
*.tfstate
*.tfstate.backup
*.tfvars # Jeśli zawierają sekrety
.terraform.lock.hcl warto commitować do repozytorium. Gwarantuje, że wszyscy w zespole używają tych samych wersji providerów. Sekretne .tfvars natomiast nigdy nie powinny trafić do Git.5. Taguj wszystkie zasoby
Blok default_tags w providerze (pokazany wcześniej) załatwia to automatycznie. Zawsze dodawaj co najmniej: Project, Environment i ManagedBy. Dzięki temu raport kosztów w AWS Cost Explorer pokaże Ci wydatki per projekt i per środowisko.
Koszt Terraform
Terraform CLI jest darmowy w użyciu (licencja BUSL - nie open source; w pełni otwartą alternatywą jest fork OpenTofu). Płacisz jedynie za zasoby AWS, które Terraform tworzy. HashiCorp oferuje też HCP Terraform (dawniej Terraform Cloud) - współpracę zespołową i remote runs, z darmowym planem do 500 zarządzanych zasobów - ale na start i dla małych zespołów CLI + S3 backend wystarczą w 100%.
Najczęściej zadawane pytania (FAQ)
Czy Terraform jest trudny do nauki?
HCL (język Terraform) jest prostszy niż większość języków programowania. Jeśli rozumiesz YAML i JSON, HCL opanujesz w jeden dzień. Nauka samego Terraform (workflow, state, moduły) zajmuje 1-2 tygodnie praktyki. Największe wyzwanie to nie sam Terraform, ale zrozumienie usług AWS, które za jego pomocą konfigurujesz.
Terraform czy CloudFormation - co wybrać na start?
Terraform. Jest standardem rynkowym, działa z wieloma providerami (nie tylko AWS) i ma większą społeczność. CloudFormation jest dobry, jeśli pracujesz wyłącznie z AWS i nie planujesz multi-cloud. Ale nawet wtedy Terraform jest prostszy składniowo (HCL vs YAML) i szybszy w cyklu plan/apply.
Co się stanie, jeśli usunę plik terraform.tfstate?
Terraform "zapomni" o istniejącej infrastrukturze. Zasoby na AWS nadal będą działać, ale Terraform nie będzie wiedział, że istnieją. Przy kolejnym apply spróbuje stworzyć je od nowa - zasoby o unikalnych nazwach (np. bucket S3) zwrócą błąd, a takie jak instancje EC2 czy VPC zostaną po cichu zduplikowane i podwoją koszty. Dlatego state przechowuj w S3 z wersjonowaniem - zawsze możesz przywrócić poprzednią wersję.
Jak zaimportować istniejące zasoby AWS do Terraform?
Komendą terraform import. Na przykład: terraform import aws_instance.web i-0abc123def zaimportuje istniejącą instancję EC2. Od Terraform 1.5 dostępny jest też blok import w kodzie HCL, który jest wygodniejszy. Pamiętaj, że po imporcie musisz ręcznie napisać odpowiadający kod .tf - import dodaje tylko state.
Czy mogę używać Terraform z AWS Free Tier?
Tak. Terraform sam w sobie jest darmowy. Zasoby, które tworzysz (np. instancja t2.micro/t3.micro, bucket S3) wchodzą w Free Tier normalnie. Jedyny koszt, który może się pojawić, to tabela DynamoDB dla state lockingu, ale w trybie PAY_PER_REQUEST przy niskim użyciu to grosze. Przeczytaj jak zacząć z AWS, żeby poznać limity Free Tier.
Następny krok
Masz już solidne podstawy Terraform z AWS. Oto co zrobić dalej:
- Sklonuj przykładowy projekt z tego artykułu i uruchom
terraform applyna swoim koncie AWS. - Rozbuduj infrastrukturę - dodaj bazę danych RDS i prywatny subnet. Sprawdź architekturę aplikacji webowej na AWS.
- Skonfiguruj remote state w S3 z DynamoDB lockiem, żeby być gotowym na pracę w zespole.
- Poznaj moduły z Terraform Registry i zacznij budować własne dla powtarzalnych komponentów.
- Zintegruj z CI/CD - dodaj
terraform plando pull requestów w GitHub Actions.
Jeśli chcesz zgłębić podstawy teoretyczne IaC, przeczytaj czym jest Infrastructure as Code. Masz pytania o Terraform lub potrzebujesz pomocy z konfiguracją? Napisz do mnie.
