# 04 - Prod Terraform IaC Bu asamanin amaci prod Hetzner Cloud Project icinde HA odakli IaaS kaynaklarini Terraform ile olusturmaktir. Bu dokuman prod Terraform ajanina tek basina verilebilir. ## Kapsam Terraform prod ortaminda sunlari olusturur: - Private network: `iklim-prod-net` - Subnetler: - App/Swarm subnet: `10.20.10.0/24` - DB subnet: `10.20.20.0/24` - Firewall: - Public ingress: sadece `22/tcp`, `80/tcp`, `443/tcp` - Private ingress: `07-private-network-port-matrisi.md` dosyasindaki prod kurallari - SSH key - Placement groups: - `iklim-prod-app-spread` - `iklim-prod-db-spread` - Floating IP: app entry point icin sabit IPv4 (`iklim-app-01`'e atanir) - Servers: - `iklim-app-01` - `iklim-app-02` - `iklim-app-03` - `iklim-db-01` - `iklim-db-02` - `iklim-db-03` - Ansible inventory output DB cluster yazilimi Terraform ile kurulmayacak. DB node'lari sadece makine, network ve firewall seviyesinde hazirlanacak. ## Onerilen Dosya Yapisi ```text terraform/ hetzner/ prod/ versions.tf providers.tf variables.tf locals.tf network.tf firewall.tf placement.tf servers.tf floating_ip.tf outputs.tf terraform.tfvars.example ``` `terraform.tfvars`, state dosyalari ve token repo'ya commit edilmeyecek. ## Degiskenler `environment` sabiti `locals.tf` icindedir; `tfvars` ile override edilmez. Minimum degiskenler: ```hcl hcloud_token = "secret" location = "fsn1" image = "rocky-10" server_type_swarm = "cx42" server_type_db = "cx52" admin_ssh_public_key_path = "~/.ssh/id_ed25519.pub" admin_allowed_cidrs = ["X.X.X.X/32"] ``` Server type degerleri kapasiteye gore degisebilir. Bu dokuman topoloji ve guvenlik kararini tanimlar; sizing daha sonra revize edilebilir. ## Server Rolleri ve Private IP Plani | Server | Private IP | Rol | | --- | --- | --- | | `iklim-app-01` | `10.20.10.11` | Swarm manager + app worker + runner (primary, FIP alir) | | `iklim-app-02` | `10.20.10.12` | Swarm manager + app worker + runner | | `iklim-app-03` | `10.20.10.13` | Swarm manager + app worker + runner | | `iklim-db-01` | `10.20.20.11` | Manuel DB cluster node | | `iklim-db-02` | `10.20.20.12` | Manuel DB cluster node | | `iklim-db-03` | `10.20.20.13` | Manuel DB cluster node | Private IP'ler `locals.tf` icinde `swarm_private_ips` ve `db_private_ips` map'leri olarak sabit tanimlanir. Sunucu listesi `for_each` ile bu map'lerden turetilir. ## Placement Group Karari Prod icin iki ayri spread placement group: ```text iklim-prod-app-spread: iklim-app-01/02/03 iklim-prod-db-spread: iklim-db-01/02/03 ``` Bu sayede Swarm quorum node'lari kendi aralarinda farkli fiziksel host'lara, DB node'lari da kendi aralarinda farkli fiziksel host'lara yerlestirilmeye calisilir. Notlar: - Hetzner kabinet secimi dogrudan sunmaz. - Spread placement group farkli fiziksel host hedefler. - Farkli lokasyon/region felaket kurtarma bu asamada konu disidir. - Ileride scale buyudugunde multi-location DR ayri tasarlanmalidir. ## Floating IP `iklim-prod-app-fip` adli IPv4 floating IP olusturulur ve `iklim-app-01`'e atanir. DNS A kaydi bu IP'ye yonlendirilir. Failover gerekirse floating IP baska bir app node'una tasinabilir. ## Public Firewall Public ingress: | Port | Kaynak | Hedef | | --- | --- | --- | | `22/tcp` | `admin_allowed_cidrs` | Tum prod node'lari | | `80/tcp` | `0.0.0.0/0`, `::/0` | `iklim-app-*` (Floating IP uzerinden) | | `443/tcp` | `0.0.0.0/0`, `::/0` | `iklim-app-*` (Floating IP uzerinden) | Prod'da su portlar public acilmayacak: - `8200/tcp` Vault - `5432/tcp` PostgreSQL - `27017/tcp` MongoDB - `6379/tcp` Redis - `5672/tcp`, `15672/tcp`, `61613/tcp`, `15674/tcp` RabbitMQ - `2377/tcp`, `7946/tcp`, `7946/udp`, `4789/udp` Docker Swarm - `9180/tcp` APISIX Admin API - `9090/tcp` Prometheus - `3000/tcp` Grafana ## Private Firewall Private ingress (app subnet `10.20.10.0/24` kaynakli): | Port | Servis | Erisim yontemi | | --- | --- | --- | | `15672/tcp` | RabbitMQ Management | SWAG arkasinda `443` — IP kisitli | | `9090/tcp` | Prometheus | SWAG arkasinda `443` — IP kisitli | | `3000/tcp` | Grafana | SWAG arkasinda `443` — IP kisitli | | `9000/tcp` | APISIX Dashboard | SWAG arkasinda `443` — IP kisitli | | `9180/tcp` | APISIX Admin API | Docker overlay icinden sadece Dashboard erisir | | `8200/tcp` | Vault | Docker overlay / private network | | `2377/tcp` | Docker Swarm control plane | App subnet icinden | | `7946/tcp`, `7946/udp` | Docker Swarm node discovery | App subnet icinden | | `4789/udp` | Docker Swarm VXLAN overlay | App subnet icinden | | `6379/tcp` | Redis | App subnet icinden | | `5672/tcp` | RabbitMQ AMQP | App subnet icinden | | `61613/tcp` | RabbitMQ STOMP | App subnet icinden | | `15674/tcp` | RabbitMQ Web STOMP | App subnet icinden | DB firewall ek kurallar (db subnet `10.20.20.0/24` kaynakli): | Port | Servis | Kural | | --- | --- | --- | | `5432/tcp` | PostgreSQL replication | DB subnet icinden | | `27017/tcp` | MongoDB replica set | DB subnet icinden | IP kisitlamasi Hetzner firewall'da degil, SWAG nginx konfigurasyonunda yapilir. ## Lifecycle ve Resize Politikasi ### server_type Degisikligi (Yeniden Boyutlandirma) `server_type` degistirmek Terraform destroy+create **tetiklemez**. `hcloud` provider bunu natively destekler: sunucuyu durdurur, Hetzner Resize API'sini cagirir, yeniden baslatir. `terraform.tfvars` icinde degeri guncelle, `terraform apply` calistir. Downtime olur (sunucu durur ve baslar) ancak disk, kurulu yazilim ve Docker volumes korunur. `ignore_changes` veya manuel adim gerekmez. ### Hangi Degisiklikler Sunucuyu Zorla Yeniden Olusturur? | Degisen alan | Davranis | Not | | --- | --- | --- | | `server_type` | In-place resize (provider native) | `terraform apply` yeterli | | `hcloud_server_network` | Sadece attachment guncellenir | Ayri resource kullanildigi icin | | `hcloud_firewall_attachment` | Sadece attachment guncellenir | Ayri resource kullanildigi icin | | `placement_group_id` | Hetzner API degisime izin vermiyor → destroy+create | Degistirme | | `image` | Disk imaji degisir → destroy+create | Degistirme | | `location` | Baska datacenter'a tasinamaz → destroy+create | Degistirme | ### Network ve Firewall Attachment Ayrimi `network` blogu ve `firewall_ids` `hcloud_server` icine gomulmez. Bunun yerine ayri resource tanimlanir: - `hcloud_server_network` — private IP atamasi (`for_each` ile her node icin) - `hcloud_firewall_attachment` — firewall iliskisi (`for_each` ile turetilen server listesi) ### prevent_destroy Korumasi Her sunucuya `lifecycle { prevent_destroy = true }` eklenir. Kasitli silmek icin once lifecycle blogunu gecici olarak kaldir. ## Kabul Kriterleri - `terraform plan` sadece prod Hetzner Project token'i ile calisir. - 6 server olusur (`iklim-app-01/02/03`, `iklim-db-01/02/03`). - Swarm node'lari `iklim-prod-app-spread` placement group icindedir. - DB node'lari `iklim-prod-db-spread` placement group icindedir. - Public firewall sadece `22`, `80`, `443` ingress'e izin verir. - Private firewall `07-private-network-port-matrisi.md` ile uyumludur. - DB replication portlari yalnizca DB subnet'ten erisilebilir. - Floating IP olusur ve `iklim-app-01`'e atanir. - Terraform state ve secret tfvars commit edilmez.