Menu
Szybki wybór
Hosting Domeny VPS SSL Kalkulator Porównania FAQ
Aktywne kody
Wszystkie kody rabatowe

Terraform Modules i Remote State — organizacja dużej infrastruktury

Opublikowano: 10 kwietnia 2026 · Kategoria: VPS / DevOps

Gdy infrastruktura rośnie powyżej kilku serwerów, płaski plik main.tf staje się niemożliwy do utrzymania. Terraform modules rozwiązują ten problem analogicznie jak funkcje w programowaniu — enkapsulują logikę, przyjmują inputs i zwracają outputs. Remote state eliminuje konflikty przy pracy zespołowej. Ten artykuł pokazuje jak budować modularną infrastrukturę Terraform od prostych modułów VPS po zaawansowane setupy z Terragrunt.

Tworzenie modułu — struktura i konwencje

Moduł to po prostu katalog z plikami .tf. Konwencja nakazuje trzy główne pliki: main.tf (zasoby), variables.tf (inputs) i outputs.tf (exports). Oto przykładowy moduł tworzący VPS na Hetzner Cloud:

# modules/hetzner-vps/variables.tf
variable "server_name" {
  description = "Nazwa serwera"
  type        = string
}

variable "server_type" {
  description = "Typ serwera (cx11, cx21, cx31...)"
  type        = string
  default     = "cx21"
}

variable "location" {
  description = "Lokalizacja datacenter"
  type        = string
  default     = "nbg1"
  validation {
    condition     = contains(["nbg1", "fsn1", "hel1"], var.location)
    error_message = "Nieprawidłowa lokalizacja. Dozwolone: nbg1, fsn1, hel1."
  }
}

variable "ssh_keys" {
  description = "Lista nazw kluczy SSH z Hetzner Cloud"
  type        = list(string)
}

variable "tags" {
  description = "Mapa tagów dla serwera"
  type        = map(string)
  default     = {}
}

# modules/hetzner-vps/main.tf
locals {
  # Locals — obliczone wartości wewnętrzne modułu
  all_tags = merge(
    {
      "managed-by" = "terraform"
      "module"     = "hetzner-vps"
    },
    var.tags
  )
}

resource "hcloud_server" "main" {
  name        = var.server_name
  server_type = var.server_type
  image       = "ubuntu-22.04"
  location    = var.location
  ssh_keys    = var.ssh_keys
  labels      = local.all_tags

  lifecycle {
    ignore_changes = [ssh_keys]
  }
}

resource "hcloud_firewall" "main" {
  name = "${var.server_name}-firewall"

  rule {
    direction = "in"
    port      = "22"
    protocol  = "tcp"
    source_ips = ["0.0.0.0/0", "::/0"]
  }

  rule {
    direction = "in"
    port      = "80"
    protocol  = "tcp"
    source_ips = ["0.0.0.0/0", "::/0"]
  }

  rule {
    direction = "in"
    port      = "443"
    protocol  = "tcp"
    source_ips = ["0.0.0.0/0", "::/0"]
  }
}

resource "hcloud_firewall_attachment" "main" {
  firewall_id = hcloud_firewall.main.id
  server_ids  = [hcloud_server.main.id]
}

# modules/hetzner-vps/outputs.tf
output "server_id" {
  description = "ID serwera"
  value       = hcloud_server.main.id
}

output "server_ip" {
  description = "Publiczny adres IPv4"
  value       = hcloud_server.main.ipv4_address
}

output "server_ipv6" {
  description = "Publiczny adres IPv6"
  value       = hcloud_server.main.ipv6_address
}

Wywoływanie modułu z konfiguracji głównej

# main.tf — wywołanie modułu
module "web_server" {
  source = "./modules/hetzner-vps"

  server_name = "web-prod-01"
  server_type = "cx21"
  location    = "nbg1"
  ssh_keys    = ["moj-klucz", "klucz-ci"]
  tags = {
    "environment" = "production"
    "role"        = "web"
  }
}

module "db_server" {
  source = "./modules/hetzner-vps"

  server_name = "db-prod-01"
  server_type = "cx31"   # Większy dla bazy danych
  location    = "fsn1"
  ssh_keys    = ["moj-klucz"]
  tags = {
    "environment" = "production"
    "role"        = "database"
  }
}

# Używanie outputs modułu w innych zasobach
output "web_server_ip" {
  value = module.web_server.server_ip
}

# Ansible inventory generowany przez Terraform
resource "local_file" "ansible_inventory" {
  content = templatefile("${path.module}/templates/inventory.tmpl", {
    web_ip = module.web_server.server_ip
    db_ip  = module.db_server.server_ip
  })
  filename = "${path.module}/../ansible/inventory/production"
}

Remote State — S3 + DynamoDB locking

Remote state w S3 z lockowaniem przez DynamoDB to najpopularniejsza konfiguracja dla AWS. Dla innych cloudów używasz analogicznych rozwiązań (GCS, Azure Blob, MinIO dla on-premise). Inicjalizacja backendu musi być wykonana raz:

# backend.tf — konfiguracja remote state
terraform {
  backend "s3" {
    bucket         = "moja-firma-terraform-state"
    key            = "prod/web/terraform.tfstate"
    region         = "eu-central-1"
    dynamodb_table = "terraform-state-lock"
    encrypt        = true

    # Opcjonalne: niestandardowe endpointy (MinIO, DigitalOcean Spaces)
    # endpoint       = "https://fra1.digitaloceanspaces.com"
    # skip_region_validation = true
    # force_path_style = true
  }

  required_providers {
    hcloud = {
      source  = "hetznercloud/hcloud"
      version = "~> 1.45"
    }
  }
}

# Tworzenie zasobów S3 i DynamoDB (jednorazowo, własną konfiguracją)
# aws s3 mb s3://moja-firma-terraform-state --region eu-central-1
# aws s3api put-bucket-versioning \
#   --bucket moja-firma-terraform-state \
#   --versioning-configuration Status=Enabled
# aws dynamodb create-table \
#   --table-name terraform-state-lock \
#   --attribute-definitions AttributeName=LockID,AttributeType=S \
#   --key-schema AttributeName=LockID,KeyType=HASH \
#   --billing-mode PAY_PER_REQUEST \
#   --region eu-central-1

# Inicjalizacja backendu
# terraform init

Workspaces — izolacja środowisk

# Zarządzanie workspaces
terraform workspace list          # Wylistuj workspaces
terraform workspace new staging   # Utwórz workspace
terraform workspace select prod   # Przełącz na workspace
terraform workspace show          # Pokaż aktualny workspace
terraform workspace delete staging  # Usuń (wymaga pustego state)

# Użycie workspace w konfiguracji
locals {
  env_config = {
    prod = {
      server_type = "cx31"
      count       = 3
    }
    staging = {
      server_type = "cx11"
      count       = 1
    }
    dev = {
      server_type = "cx11"
      count       = 1
    }
  }
  current = local.env_config[terraform.workspace]
}

module "web_servers" {
  count  = local.current.count
  source = "./modules/hetzner-vps"

  server_name = "${terraform.workspace}-web-${count.index + 1}"
  server_type = local.current.server_type
  ssh_keys    = var.ssh_keys
}

Terragrunt — DRY konfiguracja dla wielu modułów

Terragrunt eliminuje powtarzanie bloku backend w każdym module. Struktura katalogów odzwierciedla środowiska i serwisy:

# Struktura projektu z Terragrunt
# infra/
# ├── terragrunt.hcl          # Root — wspólna konfiguracja
# ├── prod/
# │   ├── env.hcl             # Zmienne środowiska
# │   ├── web-servers/
# │   │   └── terragrunt.hcl  # Konfiguracja serwisów
# │   └── database/
# │       └── terragrunt.hcl
# └── staging/
#     └── web-servers/
#         └── terragrunt.hcl

# Root terragrunt.hcl
locals {
  env_vars = read_terragrunt_config(find_in_parent_folders("env.hcl"))
  env      = local.env_vars.locals.environment
}

generate "backend" {
  path      = "backend.tf"
  if_exists = "overwrite_terragrunt"
  contents = <<-EOF
    terraform {
      backend "s3" {
        bucket         = "moja-firma-tf-state"
        key            = "${path_relative_to_include()}/terraform.tfstate"
        region         = "eu-central-1"
        dynamodb_table = "terraform-state-lock"
        encrypt        = true
      }
    }
  EOF
}

# prod/web-servers/terragrunt.hcl
terraform {
  source = "../../../modules/hetzner-vps"
}

include "root" {
  path = find_in_parent_folders()
}

inputs = {
  server_name = "web-prod-01"
  server_type = "cx31"
  ssh_keys    = ["prod-key"]
}

# Uruchomienie wszystkich modułów naraz
# cd infra/prod
# terragrunt run-all plan
# terragrunt run-all apply

Porównanie strategii organizacji infrastruktury

Strategia Skala Zalety Wady
Pojedynczy main.tf 1-5 zasobów Prosta, szybka do startu Nie skaluje się
Moduły lokalne 5-50 zasobów Reużywalność, DRY Bez wersjonowania modułów
Rejestr publiczny Dowolna Gotowe rozwiązania, przetestowane Zależność zewnętrzna
Workspaces 2-3 środowiska Prosta izolacja środowisk Ta sama konfiguracja w każdym ws
Terragrunt 50+ zasobów, wiele środowisk DRY backend, run-all, dependencies Dodatkowe narzędzie do nauki

Najczęstsze pytania

Co to jest moduł Terraform i dlaczego warto go używać? +
Moduł Terraform to zestaw plików .tf zgrupowanych w katalogu, które realizują konkretną funkcjonalność (np. tworzenie VPS z firewallem i DNS). Zamiast kopiować te same zasoby między projektami, piszesz moduł raz i wywołujesz go wielokrotnie z różnymi parametrami. Moduły mają inputs (variables), outputs i locals — to jak funkcje w programowaniu. Zmniejszają duplikację kodu, ułatwiają standaryzację i umożliwiają reużywalność między środowiskami (dev/staging/prod).
Czym jest remote state w Terraform i dlaczego jest konieczny? +
Remote state to przechowywanie pliku terraform.tfstate w zdalnym backendzie (S3, GCS, Terraform Cloud, Azure Blob) zamiast lokalnie. Jest konieczny przy pracy zespołowej, bo lokalne tfstate blokuje współpracę (kto ma plik ten może deployować). Remote state + DynamoDB locking zapobiega równoczesnemu uruchomieniu terraform apply przez dwóch deweloperów. Dodatkowo zdalne state zawiera backup i audit log zmian infrastruktury.
Jaką różnicę robią workspaces w Terraform? +
Workspace to izolowana instancja state dla tej samej konfiguracji Terraform. Możesz mieć workspace default (prod), staging, dev — każdy z własnym tfstate. Przełączasz przez `terraform workspace select staging`. Wadą jest to że workspaces w tym samym katalogu oznaczają tę samą konfigurację — przy różnicach między środowiskami lepiej użyć terragrunt lub osobnych katalogów per środowisko. Workspaces sprawdzają się przy identycznej infrastrukturze różniącej się tylko nazwami/rozmiarami.
Co to jest Terragrunt i kiedy warto go używać? +
Terragrunt to wrapper nad Terraform eliminujący powtórzenia konfiguracji backendu i providerów między modułami. Zamiast kopiować blok backend w każdym module, definiujesz go raz w root terragrunt.hcl, a child konfiguracje dziedziczą. Terragrunt dodaje też funkcje: generate (generuje pliki .tf), dependency (referencje do outputs innych modułów), run-all (uruchamia terraform w wielu katalogach naraz). Warto używać przy infrastrukturze 10+ modułów.
Jak działają outputs modułu i jak z nich korzystać? +
Outputs to wartości eksportowane z modułu dla rodzica lub innych modułów. Definiujesz je w outputs.tf wewnątrz modułu (output "server_ip" { value = hcloud_server.main.ipv4_address }). W module nadrzędnym odczytujesz przez module.nazwa_modulu.server_ip. Outputs mogą być też sensitive = true dla haseł. Dzięki outputs moduły mogą się komponować: moduł bazy danych eksportuje connection_string, który moduł aplikacji otrzymuje jako input.

Sprawdź oferty pasujące do tego scenariusza

Poniżej masz szybkie przejścia do ofert i stron z kodami rabatowymi tam, gdzie są dostępne.