Kurs NestJS · Moduł 12: Konteneryzacja i CI/CD

Infrastructure as Code z Terraform

5 min czytania
W tej lekcji5

Serwer produkcyjny postawiono rok temu, klikając w panelu dostawcy. Działa. Ale nikt nie pamięta, jaki dokładnie wybrano rozmiar maszyny, które porty otwarto i czy dysk ma 50 czy 100 gigabajtów. Postawienie identycznego środowiska testowego to teraz kilka godzin zgadywania - i tak wyjdzie inaczej.

Rzymianie nie budowali akweduktów z pamięci. Każdy zaczynał się od planu architektonicznego: rysunku, z którego dowolna ekipa mogła odtworzyć tę samą konstrukcję w innej prowincji. Infrastructure as Code to ten sam pomysł: infrastruktura definiowana w kodzie, a nie klikaniem.

Nie myl tego z narzędziami, które brzmią podobnie, a robią co innego: to nie framework do testowania infrastruktury, nie system monitorowania serwerów i nie narzędzie do kompresji obrazów Dockera. To sposób opisania tego, co ma istnieć.

Plan w pliku

Terraform czyta pliki z rozszerzeniem .tf. Opis składa się z kilku rodzajów bloków:

1provider "aws" {
2  region = "eu-central-1"
3}
4
5variable "app_name" {
6  type    = string
7  default = "legion-api"
8}
9
10variable "app_port" {
11  type    = number
12  default = 3000
13}
14
15resource "aws_instance" "legion_server" {
16  ami           = "ami-0abcdef1234567890"
17  instance_type = "t3.micro"
18
19  tags = {
20    Name = var.app_name
21  }
22}
23
24output "server_ip" {
25  value = aws_instance.legion_server.public_ip
26}

provider mówi, u kogo budujemy - tutaj AWS w regionie eu-central-1. variable to parametr planu; dzięki niemu ten sam opis postawi środowisko testowe i produkcyjne, różniące się tylko wartościami. resource opisuje rzecz, która ma istnieć - maszynę, sieć, bazę. output wskazuje, co Terraform ma pokazać po zakończeniu; adres IP zwykle poznajesz dopiero po utworzeniu serwera.

Zwróć uwagę na sposób odwoływania się: var.app_name sięga po zmienną, a aws_instance.legion_server.public_ip - po pole utworzonego zasobu. Z tych odwołań Terraform sam wyprowadza kolejność tworzenia: skoro output potrzebuje adresu maszyny, maszyna musi powstać wcześniej.

Cztery komendy

Praca z Terraformem to zawsze ta sama sekwencja:

  1. terraform init - inicjalizacja projektu; pobiera providery wymienione w plikach.
  2. terraform plan - podgląd zmian bez ich stosowania.
  3. terraform apply - zastosowanie zmian.
  4. terraform output - sprawdzenie wyników.

Drugi krok jest sercem całego narzędzia, więc nazwijmy go wprost: terraform plan to DRY RUN. Porównuje Twój opis ze stanem faktycznym i wypisuje, co zamierza utworzyć, zmienić i usunąć - nie ruszając niczego. Nie tworzy zasobów, nie usuwa infrastruktury i nie pobiera providerów; od pobierania jest init.

Ten podgląd jest jedyną chwilą, w której zobaczysz zamiar narzędzia, zanim stanie się faktem. Czytaj go - zwłaszcza linie zaczynające się od minusa, bo Terraform potrafi uznać, że pewnej zmiany nie da się wykonać w miejscu, i usunąć zasób, żeby utworzyć go na nowo. Przy maszynie testowej to niedogodność; przy bazie z danymi - katastrofa.

Plan zapisany do pliku

W zespole i w CI stosuje się wariant dwuetapowy, żeby mieć pewność, że wdrażasz dokładnie to, co przejrzałeś:

1terraform init
2terraform plan -out=tfplan
3terraform apply tfplan
4terraform output

-out=tfplan zapisuje wyliczony plan do pliku. apply tfplan stosuje dokładnie ten plan, zamiast wyliczać go na nowo.

Różnica ma znaczenie, gdy między przeglądem a zatwierdzeniem minie kilka minut, a ktoś w tym czasie zmieni infrastrukturę. Zwykłe apply policzyłoby wtedy plan od nowa i zrobiło coś innego, niż przeczytałeś na ekranie.

Stan, którego nie wolno commitować

Terraform musi pamiętać, co już utworzył - inaczej przy każdym uruchomieniu stawiałby wszystko od zera. Zapisuje to w pliku terraform.tfstate.

Ten plik nie należy do repozytorium Git, i to z dwóch powodów naraz. Po pierwsze zawiera wrażliwe dane - hasła do baz, klucze, adresy - zapisane jawnym tekstem, bo Terraform musi je pamiętać między uruchomieniami. Po drugie Git nie zapewnia blokady przy pracy zespołowej: gdy dwie osoby uruchomią apply jednocześnie, powstaną dwie wersje stanu i konflikt, którego nie da się rozstrzygnąć scaleniem, bo to nie jest plik do czytania przez człowieka.

Powody, które czasem się słyszy - że plik jest za duży dla Gita albo że Terraform sam go usuwa po apply - są nieprawdziwe.

Rozwiązaniem jest backend zdalny: wspólne miejsce, które trzyma stan i blokuje go na czas operacji.

1terraform {
2  backend "s3" {
3    bucket         = "imperium-terraform-state"
4    key            = "legion-api/terraform.tfstate"
5    region         = "eu-central-1"
6    dynamodb_table = "terraform-locks"
7  }
8}

Tabela wskazana w dynamodb_table służy właśnie do blokady - druga osoba uruchamiająca apply zobaczy komunikat, że stan jest zajęty, zamiast zepsuć wspólną infrastrukturę.

Podsumowanie

Plan architektoniczny gotowy, prowincje odtwarzalne:

  • Infrastructure as Code to podejście, w którym infrastruktura jest definiowana w kodzie - nie framework testowy, nie monitoring, nie kompresja obrazów,
  • pliki .tf zawierają bloki: provider (u kogo), variable (parametry), resource (co ma istnieć), output (co pokazać po zakończeniu),
  • kolejność tworzenia Terraform wyprowadza sam, z odwołań między zasobami,
  • cztery kroki: terraform init → terraform plan → terraform apply → terraform output,
  • terraform plan to DRY RUN - pokazuje podgląd zmian bez ich stosowania; nie tworzy, nie usuwa i nie pobiera providerów,
  • czytaj plan, zwłaszcza usunięcia - Terraform potrafi odtworzyć zasób zamiast zmienić go w miejscu,
  • w zespole: plan -out=tfplan, potem apply tfplan - wdrażasz dokładnie to, co przejrzałeś,
  • terraform.tfstate nie trafia do Gita: zawiera wrażliwe dane i nie ma blokady dla pracy zespołowej,
  • rozwiązaniem jest backend zdalny z blokadą stanu na czas operacji.

W następnej lekcji zbierzesz cały ten moduł w jeden projekt - konteneryzację, pipeline i wdrożenie. A na razie zapamiętaj: klikanie w panelu tworzy serwer, którego nikt nie umie odtworzyć; plan w pliku tworzy taki, który odtworzy każdy.

Kod do tej lekcji: src/terraform-iac.ts
1// Infrastructure as Code - Plany Architektoniczne Imperium
2console.log("=== TERRAFORM - INFRASTRUCTURE AS CODE ===\n");
3
4interface TerraformCommand {
5  command: string;
6  description: string;
7  analogy: string;
8}
9
10const commands: TerraformCommand[] = [
11  {
12    command: 'terraform init',
13    description: 'Inicjalizacja - pobierz providery i moduly',
14    analogy: 'Zebranie materialow budowlanych',
15  },
16  {
17    command: 'terraform plan',
18    description: 'Podglad zmian (DRY RUN)',
19    analogy: 'Przejrzenie planow architektonicznych',
20  },
21  {
22    command: 'terraform apply',
23    description: 'Zastosowanie zmian na infrastrukturze',
24    analogy: 'Rozpoczecie budowy',
25  },
26  {
27    command: 'terraform destroy',
28    description: 'Usuniecie calej infrastruktury',
29    analogy: 'Rozbiorka budowli',
30  },
31];
32
33console.log("Komendy Terraform:\n");
34commands.forEach((c, i) => {
35  console.log(`${i + 1}. ${c.command}`);
36  console.log(`   Opis: ${c.description}`);
37  console.log(`   Analogia: ${c.analogy}\n`);
38});
39
40// Porownanie reczne vs IaC
41console.log("=== RECZNE vs IaC ===\n");
42const comparison = {
43  'Powtarzalnosc': ['Kazde srodowisko inne', 'Identyczne srodowiska'],
44  'Historia zmian': ['Brak', 'Pelna historia w Git'],
45  'Code review': ['Brak', 'Zespol weryfikuje infrastrukture'],
46  'Disaster recovery': ['Dni', 'Minuty'],
47};
48
49console.log("Cecha           | Reczne              | IaC (Terraform)");
50console.log("-".repeat(65));
51Object.entries(comparison).forEach(([key, [manual, iac]]) => {
52  console.log(`${key.padEnd(17)}| ${manual.padEnd(20)}| ${iac}`);
53});
54
55// Kluczowe zasoby
56console.log("\n=== ZASOBY TERRAFORM DLA NestJS ===\n");
57const resources = [
58  'aws_instance - serwer EC2 dla API',
59  'aws_docdb_cluster - MongoDB (DocumentDB)',
60  'aws_security_group - firewall',
61  'aws_s3_bucket - state backend',
62];
63resources.forEach(r => console.log(`  - ${r}`));
64

Widzisz błąd w tej lekcji?

Sprawdź się

Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.

  1. 1. Czym jest Infrastructure as Code (IaC)?

  2. 2. Co robi komenda 'terraform plan'?

To 2 z 8 pytań do tej lekcji. Pozostałe rozwiążesz w grze.

Zadania praktyczne w grze

  • Edytor kodu

    Zdefiniuj providera AWS (region eu-central-1), zmienne (environment, app_name, app_port), resource aws_instance z user_data uruchamiającym Docker

  • Układanie w pionie

    Uporządkuj kroki pracy z Terraform od pierwszego do ostatniego:

  • Klikanie w kolejności

    Ułóż elementy workflow terraform plan + apply w prawidłowej kolejności:

  • Edytor kodu

    Oznacz done=true dla wszystkich wymaganych elementów checklisty: Docker, CI/CD, Kubernetes, Observability, IaC, Secrets

  • Układanie w pionie

    Uporządkuj elementy definicji zasobu EC2 w Terraform (od zewnętrznego do wewnętrznego):

  • Układanie w pionie

    Uporządkuj kroki wdrożenia aplikacji od początku do końca:

  • Klikanie w kolejności

    Ułóż elementy konfiguracji healthcheck w Docker:

  • Układanie w poziomie

    Ułóż elementy instrukcji kopiowania z etapu builder:

  • Układanie w pionie

    Uporządkuj providerów secrets według priorytetu (od najwyższego):

  • Klikanie w kolejności

    Ułóż elementy komendy wdrożenia zasobów Kubernetes:

  • Układanie w poziomie

    Ułóż elementy komendy skalowania Deployment do 5 replik:

Przydatne artykuły