Kurs NestJS · Moduł 12: Konteneryzacja i CI/CD
Infrastructure as Code z Terraform
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:
terraform init- inicjalizacja projektu; pobiera providery wymienione w plikach.terraform plan- podgląd zmian bez ich stosowania.terraform apply- zastosowanie zmian.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
.tfzawierają 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 planto 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, potemapply tfplan- wdrażasz dokładnie to, co przejrzałeś, terraform.tfstatenie 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}`));
64Widzisz błąd w tej lekcji?
Sprawdź się
Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.
1. Czym jest Infrastructure as Code (IaC)?
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: