# Terraform — iklim.co Hetzner Cloud Altyapısı

Bu dizin, iklim.co test ve prod ortamlarının Hetzner Cloud altyapısını Terraform ile yönetir.

## Dizin Yapısı

```text
terraform/hetzner/
  test/    — test ortamı: 1 app + 1 db node, single-node Swarm
  prod/    — prod ortamı: 3 app + 3 db node, 3-manager HA Swarm
```

Her ortam kendi bağımsız Terraform state dosyasına sahiptir; birbirini etkilemez.

## Ortam Karşılaştırması

| Özellik | Test | Prod |
| --- | --- | --- |
| App node sayısı | 1 (`iklim-app-01`) | 3 (`iklim-app-01/02/03`) |
| DB node sayısı | 1 (`iklim-db-01`) | 3 (`iklim-db-01/02/03`) |
| App sunucu tipi | `cpx42` | `cpx42` |
| DB sunucu tipi | `cpx42` | `cpx32` |
| Swarm mimarisi | Single-node | 3-manager HA |
| App subnet | `10.10.10.0/24` | `10.20.10.0/24` |
| DB subnet | `10.10.20.0/24` | `10.20.20.0/24` |
| Floating IP | Var | Var |
| Placement group | Yok | Spread (farklı fiziksel host) |

## Ön Koşullar

- Terraform >= 1.5 kurulu olmalı.
- Her ortam için **ayrı** Hetzner Cloud projesi ve API token'ı hazır olmalı (aşağıya bak).
- `terraform.tfvars` dosyası oluşturulmuş olmalı (bkz. `terraform.tfvars.example`).

## ⚠️ Her Ortam İçin Ayrı Hetzner Projesi Gereklidir

Test ve prod **farklı Hetzner Cloud projeleri** altında çalışır; her projenin kendi API token'ı olmalıdır.

**Neden ayrı proje?**

- Hetzner Cloud, aynı SSH public key'i bir projede yalnızca bir kez kabul eder. Test ortamının token'ı prod için kullanılırsa `terraform apply` SSH key çakışmasıyla (`uniqueness_error`) hata verir.
- Kaynak izolasyonu: test destroy işlemi prod kaynaklarını etkilemez.
- Maliyet ve kota takibi ortam bazında yapılabilir.

**Token nasıl alınır:**

1. [Hetzner Cloud Console](https://console.hetzner.cloud) → ilgili projeyi seç (ör. `iklim_prod`).
2. **Security → API Tokens → Generate API Token** — Read & Write izni ver.
3. Token yalnızca bir kez gösterilir; hemen kopyala.
4. `terraform.tfvars` içindeki `hcloud_token` değerine yapıştır.

> Test `terraform.tfvars` içindeki token prod dizinine **kopyalanmamalıdır.** Her ortamın kendi dizininde kendi token'ı olmalıdır.

## terraform.tfvars Kurulumu

```bash
# Test için:
cd terraform/hetzner/test
cp terraform.tfvars.example terraform.tfvars
# hcloud_token = iklim_test projesi token'ı

# Prod için:
cd terraform/hetzner/prod
cp terraform.tfvars.example terraform.tfvars
# hcloud_token = iklim_prod projesi token'ı
```

`terraform.tfvars` içindeki değişkenler:

| Değişken | Açıklama |
| --- | --- |
| `hcloud_token` | **Ortama özel** Hetzner Cloud API token'ı — test ve prod token'ları birbirinden farklıdır |
| `location` | Sunucu lokasyonu (örn. `fsn1`) |
| `image` | Sunucu işletim sistemi (örn. `rocky-10`) |
| `server_type_app` | App sunucu tipi |
| `server_type_db` | DB sunucu tipi |
| `admin_ssh_public_key_path` | Admin SSH public key dosya yolu |
| `admin_allowed_cidrs` | SSH erişimine izin verilen CIDR listesi |

> `terraform.tfvars` hassas bilgi içerir; repository'e commit edilmez.

## Terraform Komutları

Tüm komutlar ilgili ortam dizininden çalıştırılmalıdır:

```bash
# Test:
cd terraform/hetzner/test

# Prod:
cd terraform/hetzner/prod
```

### Başlatma

```bash
terraform init
```

### Değişiklik önizleme

```bash
terraform plan
```

### Uygulama

```bash
terraform apply
```

### Kaynakları kaldırma

```bash
terraform destroy
```

## Ansible Inventory Üretimi

`terraform apply` tamamlandıktan sonra Ansible inventory'si şu komutlarla üretilir:

```bash
# Test inventory:
cd terraform/hetzner/test
terraform output -raw ansible_inventory_yaml \
  > ../../../ansible/test/inventory/generated/test.yml

# Prod inventory:
cd terraform/hetzner/prod
terraform output -raw ansible_inventory_yaml \
  > ../../../ansible/prod/inventory/generated/prod.yml
```

Sunucu ekleme/silme veya IP değişimi sonrası inventory yeniden üretilmelidir.

## Outputs

### Test

| Output | Açıklama |
| --- | --- |
| `ansible_inventory_yaml` | Ansible inventory YAML |
| `test_private_ips` | Node private IP haritası |
| `test_public_ips` | Node public IPv4 haritası |
| `test_floating_ip` | Swarm giriş noktası floating IP |

### Prod

| Output | Açıklama |
| --- | --- |
| `ansible_inventory_yaml` | Ansible inventory YAML |
| `prod_private_ips` | Node private IP haritası (`app` ve `db` alt anahtarlarıyla) |
| `prod_public_ips` | Node public IPv4 haritası |
| `prod_floating_ip` | Swarm giriş noktası floating IP — DNS A kaydı bu IP'ye yönlendirilir |

```bash
# Floating IP'yi görmek için:
terraform output prod_floating_ip   # veya test_floating_ip
```

## Güvenlik Duvarı Özeti

### App Firewall (her iki ortam)

| Port | Protokol | Kaynak | Açıklama |
| --- | --- | --- | --- |
| `22` | TCP | `admin_allowed_cidrs` | SSH |
| `80` | TCP | 0.0.0.0/0 | HTTP (SWAG) |
| `443` | TCP | 0.0.0.0/0 | HTTPS (SWAG) |
| `2377` | TCP | DB subnet | Docker Swarm control plane |
| `7946` | TCP/UDP | DB subnet | Docker Swarm node discovery |
| `4789` | UDP | DB subnet | Docker Swarm VXLAN overlay |

### DB Firewall (her iki ortam)

App subnet kaynaklı:

| Port | Açıklama |
| --- | --- |
| `22/tcp` | SSH |
| `5432/tcp` | PostgreSQL |
| `27017/tcp` | MongoDB |
| `2377/tcp` | Docker Swarm control plane |
| `7946/tcp,udp` | Docker Swarm node discovery |
| `4789/udp` | Docker Swarm VXLAN overlay |

Prod'a özgü (app subnet kaynaklı):

| Port | Açıklama |
| --- | --- |
| `2379/tcp` | etcd client (Patroni + APISIX) |

Prod'a özgü (DB subnet içi, karşılıklı):

| Port | Açıklama |
| --- | --- |
| `5432/tcp` | Patroni replikasyon |
| `27017/tcp` | MongoDB replica set internal |
| `2379/tcp` | etcd client |
| `2380/tcp` | etcd peer |
| `8008/tcp` | Patroni REST API |

> IP kısıtlaması (admin paneli, dashboard vb.) Hetzner Firewall'da değil, SWAG nginx konfigürasyonunda yapılır.

## Sonraki Adım

Terraform apply ve inventory üretiminin ardından Ansible bootstrap çalıştırılır. Detaylar için `ansible/README.md` dosyasına bakın.
