Add Hetzner Cloud production infrastructure with multi-node support
- This commit introduces the Terraform configuration to provision a production environment on Hetzner Cloud, building on the existing test setup. - Key improvements and new features include: * **Multi-node clusters:** Scaling to 3-node Swarm application and database clusters for improved resilience. * **High availability:** Utilizing a Hetzner Floating IP for the application entry point and `spread` placement groups for fault tolerance across physical hosts. * **Enhanced network security:** Internal management services (RabbitMQ, APISIX, Prometheus, Grafana) are restricted to the application subnet, expected to be accessed via an internal reverse proxy (SWAG). * **Internal database replication:** New firewall rules enable PostgreSQL replication and MongoDB replica set traffic within the database subnet. * **Refined test environment:** Updates to align `test` configuration with the new `prod` structure, including a dedicated floating IP and adjusted firewall rules. * **Configuration standardization:** Environment-specific details moved to `locals.tf` for clarity, with upgraded server types and migration to Rocky Linux as the base image. - Updates were also made to the latest version of Terraform to ensure consistency in the documentation
This commit is contained in:
parent
2d515f7206
commit
720c79d460
@ -6,31 +6,35 @@ Terraform/Ansible setup aşamalarından hangisinde ele alındığını gösterir
|
|||||||
## TEST ortamı
|
## TEST ortamı
|
||||||
|
|
||||||
| Roadmap adımı | Hangi aşamada ele alınmalı |
|
| Roadmap adımı | Hangi aşamada ele alınmalı |
|
||||||
| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------- |
|
| ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
|
||||||
| Hetzner firewall (sadece 22/80/443) | **Terraform `01-test-terraform-iaac.md`** — `firewall.tf` |
|
| Hetzner firewall (sadece 22/80/443) | **Terraform `01-test-terraform-iaac.md`** — `firewall.tf` |
|
||||||
| Sunucu oluşturma (`test-swarm-01`, `test-db-01`) | **Terraform `01-test-terraform-iaac.md`** — `servers.tf` |
|
| Sunucu oluşturma (`iklim-app-01`, `iklim-db-01`) | **Terraform `01-test-terraform-iaac.md`** — `servers.tf` |
|
||||||
| Private network + placement group | **Terraform `01-test-terraform-iaac.md`** — `network.tf`, `placement.tf` |
|
| Private network + placement group (`iklim-test-spread`) | **Terraform `01-test-terraform-iaac.md`** — `network.tf`, `placement.tf` |
|
||||||
|
| Floating IP (`iklim-test-app-fip`) | **Terraform `01-test-terraform-iaac.md`** — `floating_ip.tf` |
|
||||||
| Docker Engine kurulumu | **Ansible `02-test-ansible-bootstrap.md`** — `docker` role |
|
| Docker Engine kurulumu | **Ansible `02-test-ansible-bootstrap.md`** — `docker` role |
|
||||||
| Security hardening (SSH, UFW, fail2ban) | **Ansible `02-test-ansible-bootstrap.md`** — `hardening` role |
|
| Security hardening (SSH, firewalld, fail2ban) | **Ansible `02-test-ansible-bootstrap.md`** — `hardening` role |
|
||||||
| Docker Swarm init (`init/swarm-init.sh`) | **Ansible `02-test-ansible-bootstrap.md`** — `swarm` role (pipeline script idempotent çalışmaya devam eder) |
|
| Docker Swarm init (`init/swarm-init.sh`) | **Ansible `02-test-ansible-bootstrap.md`** — `swarm` role (pipeline script idempotent çalışmaya devam eder) |
|
||||||
| `type=service` node label | **Ansible `02-test-ansible-bootstrap.md`** — `swarm` role |
|
| `type=service` node label | **Ansible `02-test-ansible-bootstrap.md`** — `swarm` role |
|
||||||
| `/opt/iklimco/...` dizinleri | **Ansible `02-test-ansible-bootstrap.md`** — `node_dirs` role |
|
| `/opt/iklimco/...` dizinleri | **Ansible `02-test-ansible-bootstrap.md`** — `node_dirs` role |
|
||||||
|
| StorageBox DAVFS mount (`u469968-sub1`) | **Ansible `02-test-ansible-bootstrap.md`** — `storagebox` role |
|
||||||
| `act_runner` systemd kurulumu | **Ansible `03-test-runner-ve-deploy-onkosullari.md`** — `gitea_runner` role |
|
| `act_runner` systemd kurulumu | **Ansible `03-test-runner-ve-deploy-onkosullari.md`** — `gitea_runner` role |
|
||||||
| GoDaddy credentials storagebox'a yükleme | **Manuel kalır** — secret yönetimi, Terraform/Ansible dışı |
|
| GoDaddy credentials storagebox'a yükleme | **Manuel kalır** — secret yönetimi, Terraform/Ansible dışı |
|
||||||
|
|
||||||
## PROD ortamı
|
## PROD ortamı
|
||||||
|
|
||||||
| Roadmap adımı | Hangi aşamada ele alınmalı |
|
| Roadmap adımı | Hangi aşamada ele alınmalı |
|
||||||
| ----------------------------------------------- | ------------------------------------------------------------------------ |
|
| -------------------------------------------------------------------- | ------------------------------------------------------------------------ |
|
||||||
| 6 sunucu oluşturma (3 swarm + 3 db) | **Terraform `04-prod-terraform-iaac.md`** — `servers.tf` |
|
| 6 sunucu oluşturma (`iklim-app-01/02/03`, `iklim-db-01/02/03`) | **Terraform `04-prod-terraform-iaac.md`** — `servers.tf` |
|
||||||
| Private network + 2 placement group | **Terraform `04-prod-terraform-iaac.md`** — `network.tf`, `placement.tf` |
|
| Private network + 2 placement group | **Terraform `04-prod-terraform-iaac.md`** — `network.tf`, `placement.tf` |
|
||||||
| Firewall (sadece 22/80/443 public) | **Terraform `04-prod-terraform-iaac.md`** — `firewall.tf` |
|
| Firewall (sadece 22/80/443 public) | **Terraform `04-prod-terraform-iaac.md`** — `firewall.tf` |
|
||||||
| Docker Engine kurulumu (`prod-swarm-*`) | **Ansible `05-prod-ansible-bootstrap.md`** — `docker` role |
|
| Floating IP (`iklim-prod-app-fip`, `iklim-app-01`'e atanır) | **Terraform `04-prod-terraform-iaac.md`** — `floating_ip.tf` |
|
||||||
|
| Docker Engine kurulumu (`iklim-app-*`) | **Ansible `05-prod-ansible-bootstrap.md`** — `docker` role |
|
||||||
| Security hardening (tüm node'lar) | **Ansible `05-prod-ansible-bootstrap.md`** — `hardening` role |
|
| Security hardening (tüm node'lar) | **Ansible `05-prod-ansible-bootstrap.md`** — `hardening` role |
|
||||||
| Swarm init (`prod-swarm-01`) | **Ansible `05-prod-ansible-bootstrap.md`** — `swarm` role |
|
| Swarm init (`iklim-app-01`) | **Ansible `05-prod-ansible-bootstrap.md`** — `swarm` role |
|
||||||
| Manager join (`prod-swarm-02`, `prod-swarm-03`) | **Ansible `05-prod-ansible-bootstrap.md`** — `swarm` role |
|
| Manager join (`iklim-app-02`, `iklim-app-03`) | **Ansible `05-prod-ansible-bootstrap.md`** — `swarm` role |
|
||||||
| `type=service` node label (3 swarm node) | **Ansible `05-prod-ansible-bootstrap.md`** — `swarm` role |
|
| `type=service` node label (3 swarm node) | **Ansible `05-prod-ansible-bootstrap.md`** — `swarm` role |
|
||||||
| `/opt/iklimco/...` dizinleri | **Ansible `05-prod-ansible-bootstrap.md`** — `node_dirs` role |
|
| `/opt/iklimco/...` dizinleri | **Ansible `05-prod-ansible-bootstrap.md`** — `node_dirs` role |
|
||||||
|
| StorageBox DAVFS mount (`u469968-sub2`) | **Ansible `05-prod-ansible-bootstrap.md`** — `storagebox` role |
|
||||||
| 3× `act_runner` systemd (HA runner) | **Ansible `06-prod-runner-ha-ve-swarm.md`** — `gitea_runner` role |
|
| 3× `act_runner` systemd (HA runner) | **Ansible `06-prod-runner-ha-ve-swarm.md`** — `gitea_runner` role |
|
||||||
| GoDaddy credentials storagebox'a yükleme | **Manuel kalır** — secret yönetimi, Terraform/Ansible dışı |
|
| GoDaddy credentials storagebox'a yükleme | **Manuel kalır** — secret yönetimi, Terraform/Ansible dışı |
|
||||||
| DB node'ları Swarm'a join | **Kapsam dışı** — DB cluster ayrı yönetilir |
|
| DB node'ları Swarm'a join | **Kapsam dışı** — DB cluster ayrı yönetilir |
|
||||||
@ -51,5 +55,5 @@ Environment_Infrastructure/
|
|||||||
roadmap/
|
roadmap/
|
||||||
test-env/ ← Test ortamı Roadmap adımları
|
test-env/ ← Test ortamı Roadmap adımları
|
||||||
prod-env/ ← Prod Roadmap adımları
|
prod-env/ ← Prod Roadmap adımları
|
||||||
setup-vs-technical-debt-map.md ← Bu dosya
|
setup-vs-roadmap-map.md ← Bu dosya
|
||||||
```
|
```
|
||||||
|
|||||||
@ -42,8 +42,8 @@ Test ortami minimum topoloji:
|
|||||||
|
|
||||||
| Node | Rol | Not |
|
| Node | Rol | Not |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `test-swarm-01` | Swarm manager + app worker + Gitea runner | CI/CD test deploy bu node uzerinden calisir |
|
| `iklim-app-01` | Swarm manager + app worker + Gitea runner | CI/CD test deploy bu node uzerinden calisir |
|
||||||
| `test-db-01` | DB node | DB altyapisi manuel kurulacak; Gitea CI/CD ile kurulmayacak |
|
| `iklim-db-01` | DB node | DB altyapisi manuel kurulacak; Gitea CI/CD ile kurulmayacak |
|
||||||
|
|
||||||
Test DB kurulumu Terraform/Ansible ile sadece makine ve OS hazirligina kadar getirilir. PostgreSQL/MongoDB cluster kurulumu bu asamanin disindadir.
|
Test DB kurulumu Terraform/Ansible ile sadece makine ve OS hazirligina kadar getirilir. PostgreSQL/MongoDB cluster kurulumu bu asamanin disindadir.
|
||||||
|
|
||||||
@ -53,8 +53,8 @@ Prod ortami HA topoloji:
|
|||||||
|
|
||||||
| Node grubu | Adet | Rol |
|
| Node grubu | Adet | Rol |
|
||||||
| --- | ---: | --- |
|
| --- | ---: | --- |
|
||||||
| `prod-swarm-*` | 3 | Her biri Swarm manager + app worker |
|
| `iklim-app-*` | 3 | Her biri Swarm manager + app worker |
|
||||||
| `prod-db-*` | 3 | DB cluster node'lari |
|
| `iklim-db-*` | 3 | DB cluster node'lari |
|
||||||
|
|
||||||
Prod DB altyapisi manuel kurulacak; Gitea CI/CD ile kurulmayacak. Terraform DB makinelerini ve network/firewall kurallarini hazirlar, Ansible OS hardening ve temel bagimliliklari kurar.
|
Prod DB altyapisi manuel kurulacak; Gitea CI/CD ile kurulmayacak. Terraform DB makinelerini ve network/firewall kurallarini hazirlar, Ansible OS hardening ve temel bagimliliklari kurar.
|
||||||
|
|
||||||
@ -88,12 +88,12 @@ Tercih edilen kurulum:
|
|||||||
Prod HA icin `act_runner` tek makineye degil, 3 Swarm manager node'unun tamamına kurulacaktir. Boylece bir manager/runner kaybedildiginde pipeline calismaya devam edebilir. Runner label'lari hem ortak hem node-spesifik olmalidir:
|
Prod HA icin `act_runner` tek makineye degil, 3 Swarm manager node'unun tamamına kurulacaktir. Boylece bir manager/runner kaybedildiginde pipeline calismaya devam edebilir. Runner label'lari hem ortak hem node-spesifik olmalidir:
|
||||||
|
|
||||||
- Ortak: `prod-runner`
|
- Ortak: `prod-runner`
|
||||||
- Node spesifik: `prod-swarm-01`, `prod-swarm-02`, `prod-swarm-03`
|
- Node spesifik: `iklim-app-01`, `iklim-app-02`, `iklim-app-03`
|
||||||
|
|
||||||
Test icin tek runner yeterlidir:
|
Test icin tek runner yeterlidir:
|
||||||
|
|
||||||
- Ortak: `test-runner`
|
- Ortak: `test-runner`
|
||||||
- Node spesifik: `test-swarm-01`
|
- Node spesifik: `iklim-app-01`
|
||||||
|
|
||||||
## Deploy Lock Karari
|
## Deploy Lock Karari
|
||||||
|
|
||||||
@ -147,12 +147,12 @@ Kisitlar:
|
|||||||
|
|
||||||
Prod icin en az iki placement group onerilir:
|
Prod icin en az iki placement group onerilir:
|
||||||
|
|
||||||
- `prod-swarm-spread`: 3 Swarm manager/app node
|
- `iklim-prod-app-spread`: 3 Swarm manager/app node
|
||||||
- `prod-db-spread`: 3 DB node
|
- `iklim-prod-db-spread`: 3 DB node
|
||||||
|
|
||||||
Test icin opsiyonel:
|
Test icin opsiyonel:
|
||||||
|
|
||||||
- `test-spread`: `test-swarm-01` ve `test-db-01`
|
- `iklim-test-spread`: `iklim-app-01` ve `iklim-db-01`
|
||||||
|
|
||||||
Kaynaklar:
|
Kaynaklar:
|
||||||
|
|
||||||
|
|||||||
@ -14,10 +14,11 @@ Terraform test ortaminda sunlari olusturur:
|
|||||||
- Public ingress: sadece `22/tcp`, `80/tcp`, `443/tcp`
|
- Public ingress: sadece `22/tcp`, `80/tcp`, `443/tcp`
|
||||||
- Private ingress: `07-private-network-port-matrisi.md` dosyasindaki test kurallari
|
- Private ingress: `07-private-network-port-matrisi.md` dosyasindaki test kurallari
|
||||||
- SSH key
|
- SSH key
|
||||||
- Placement group: `test-spread`
|
- Placement group: `iklim-test-spread`
|
||||||
|
- Floating IP: swarm entry point icin sabit IPv4
|
||||||
- Server:
|
- Server:
|
||||||
- `test-swarm-01`
|
- `iklim-app-01`
|
||||||
- `test-db-01`
|
- `iklim-db-01`
|
||||||
- Ansible inventory output
|
- Ansible inventory output
|
||||||
|
|
||||||
Terraform DB yazilimini kurmaz. DB node sadece makine, network ve firewall seviyesinde hazirlanir.
|
Terraform DB yazilimini kurmaz. DB node sadece makine, network ve firewall seviyesinde hazirlanir.
|
||||||
@ -48,23 +49,24 @@ Minimum degiskenler:
|
|||||||
|
|
||||||
```hcl
|
```hcl
|
||||||
hcloud_token = "secret"
|
hcloud_token = "secret"
|
||||||
environment = "test"
|
|
||||||
location = "fsn1"
|
location = "fsn1"
|
||||||
image = "ubuntu-24.04"
|
image = "rocky-10"
|
||||||
server_type_swarm = "cx32"
|
server_type_swarm = "cx32"
|
||||||
server_type_db = "cx42"
|
server_type_db = "cx42"
|
||||||
admin_ssh_public_key_path = "~/.ssh/id_ed25519.pub"
|
admin_ssh_public_key_path = "~/.ssh/id_ed25519.pub"
|
||||||
admin_allowed_cidrs = ["X.X.X.X/32"]
|
admin_allowed_cidrs = ["X.X.X.X/32"]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`environment` sabiti `locals.tf` icindedir; `tfvars` ile override edilmez.
|
||||||
|
|
||||||
`location` icin tek lokasyonla baslanir. Farkli region/lokasyon felaket kurtarma bu asamada konu disidir; ileride dokumana eklenmelidir.
|
`location` icin tek lokasyonla baslanir. Farkli region/lokasyon felaket kurtarma bu asamada konu disidir; ileride dokumana eklenmelidir.
|
||||||
|
|
||||||
## Server Rolleri
|
## Server Rolleri
|
||||||
|
|
||||||
| Server | Private IP | Rol |
|
| Server | Private IP | Rol |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `test-swarm-01` | `10.10.10.11` | Swarm manager + app worker + Gitea runner |
|
| `iklim-app-01` | `10.10.10.11` | Swarm manager + app worker + Gitea runner |
|
||||||
| `test-db-01` | `10.10.20.11` | Manuel DB kurulumu icin hazir DB node |
|
| `iklim-db-01` | `10.10.20.11` | Manuel DB kurulumu icin hazir DB node |
|
||||||
|
|
||||||
Private IP'ler Terraform icinde sabit tanimlanmalidir. Ansible inventory ve firewall kurallari deterministik kalir.
|
Private IP'ler Terraform icinde sabit tanimlanmalidir. Ansible inventory ve firewall kurallari deterministik kalir.
|
||||||
|
|
||||||
@ -75,16 +77,30 @@ Public ingress:
|
|||||||
| Port | Kaynak | Hedef |
|
| Port | Kaynak | Hedef |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `22/tcp` | `admin_allowed_cidrs` | Tum test node'lari |
|
| `22/tcp` | `admin_allowed_cidrs` | Tum test node'lari |
|
||||||
| `80/tcp` | `0.0.0.0/0`, `::/0` | `test-swarm-01` |
|
| `80/tcp` | `0.0.0.0/0`, `::/0` | `iklim-app-01` |
|
||||||
| `443/tcp` | `0.0.0.0/0`, `::/0` | `test-swarm-01` |
|
| `443/tcp` | `0.0.0.0/0`, `::/0` | `iklim-app-01` |
|
||||||
|
|
||||||
Public ingress icin `8200/tcp`, `5432/tcp`, `27017/tcp`, `5672/tcp`, `15672/tcp`, `6379/tcp`, `2379/tcp`, `9180/tcp`, `9090/tcp`, `3000/tcp` acilmayacak.
|
Public ingress icin `8200/tcp`, `5432/tcp`, `27017/tcp`, `5672/tcp`, `15672/tcp`, `6379/tcp`, `2379/tcp`, `9000/tcp`, `9180/tcp`, `9090/tcp`, `3000/tcp` acilmayacak.
|
||||||
|
|
||||||
Private ingress icin `07-private-network-port-matrisi.md` kaynak alinacak.
|
Private ingress (app subnet `10.10.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; insan erisimi gerekmez |
|
||||||
|
| `8200/tcp` | Vault | Docker overlay / private network |
|
||||||
|
|
||||||
|
IP kisitlamasi Hetzner firewall'da degil, SWAG nginx konfigurasyonunda yapilir.
|
||||||
|
Bu portlarin hicbiri `admin_allowed_cidrs` kaynagiyla public'ten acilmaz.
|
||||||
|
|
||||||
|
Diger private ingress kurallari icin `07-private-network-port-matrisi.md` kaynak alinacak.
|
||||||
|
|
||||||
## Placement Group
|
## Placement Group
|
||||||
|
|
||||||
`test-spread` placement group `type = "spread"` olacak. Testte iki server oldugu icin bu grup `test-swarm-01` ve `test-db-01` makinelerinin farkli fiziksel host'lara dagitilmasini hedefler.
|
`iklim-test-spread` placement group `type = "spread"` olacak. Testte iki server oldugu icin bu grup `iklim-app-01` ve `iklim-db-01` makinelerinin farkli fiziksel host'lara dagitilmasini hedefler.
|
||||||
|
|
||||||
Not: Spread placement group farkli kabinet veya lokasyon garantisi degildir; tek fiziksel host arizasinin etkisini azaltir.
|
Not: Spread placement group farkli kabinet veya lokasyon garantisi degildir; tek fiziksel host arizasinin etkisini azaltir.
|
||||||
|
|
||||||
|
|||||||
@ -6,8 +6,8 @@ Bu asamanin amaci Terraform ile olusturulan test makinelerini Linux, hardening,
|
|||||||
|
|
||||||
| Host | Rol |
|
| Host | Rol |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `test-swarm-01` | Swarm manager + app worker |
|
| `iklim-app-01` | Swarm manager + app worker |
|
||||||
| `test-db-01` | Manuel DB kurulumu icin OS-hardening uygulanmis DB node |
|
| `iklim-db-01` | Manuel DB kurulumu icin OS-hardening uygulanmis DB node |
|
||||||
|
|
||||||
## Onerilen Dosya Yapisi
|
## Onerilen Dosya Yapisi
|
||||||
|
|
||||||
@ -28,13 +28,14 @@ ansible/
|
|||||||
docker/
|
docker/
|
||||||
swarm/
|
swarm/
|
||||||
node_dirs/
|
node_dirs/
|
||||||
|
storagebox/
|
||||||
```
|
```
|
||||||
|
|
||||||
## Base Role
|
## Base Role
|
||||||
|
|
||||||
Tum test node'larina uygulanir:
|
Tum test node'larina uygulanir:
|
||||||
|
|
||||||
- `apt update`
|
- `dnf update`
|
||||||
- temel paketler:
|
- temel paketler:
|
||||||
- `curl`
|
- `curl`
|
||||||
- `wget`
|
- `wget`
|
||||||
@ -42,9 +43,6 @@ Tum test node'larina uygulanir:
|
|||||||
- `jq`
|
- `jq`
|
||||||
- `unzip`
|
- `unzip`
|
||||||
- `ca-certificates`
|
- `ca-certificates`
|
||||||
- `gnupg`
|
|
||||||
- `lsb-release`
|
|
||||||
- `ufw`
|
|
||||||
- `fail2ban`
|
- `fail2ban`
|
||||||
- `chrony`
|
- `chrony`
|
||||||
- `python3`
|
- `python3`
|
||||||
@ -63,22 +61,21 @@ Tum test node'larina uygulanir:
|
|||||||
- `PermitEmptyPasswords no`
|
- `PermitEmptyPasswords no`
|
||||||
- `MaxAuthTries 3`
|
- `MaxAuthTries 3`
|
||||||
- `fail2ban` SSH jail aktif edilir.
|
- `fail2ban` SSH jail aktif edilir.
|
||||||
- `unattended-upgrades` aktif edilir.
|
- `dnf-automatic` ile otomatik guvenlik guncellestirmeleri aktif edilir.
|
||||||
- UFW default:
|
- `firewalld` default:
|
||||||
- incoming: deny
|
- incoming: deny (drop zone)
|
||||||
- outgoing: allow
|
- outgoing: allow
|
||||||
- Public SSH sadece admin CIDR'dan acilir.
|
- Public SSH sadece admin CIDR'dan acilir.
|
||||||
|
|
||||||
Not: Docker iptables kurallari UFW ile etkilesebilir. Hetzner Cloud firewall asil dis perimeter kabul edilir; UFW host icinde ikinci katman olarak kullanilir.
|
Not: Docker iptables kurallari firewalld ile etkilesebilir. Hetzner Cloud firewall asil dis perimeter kabul edilir; firewalld host icinde ikinci katman olarak kullanilir.
|
||||||
|
|
||||||
## Docker Role
|
## Docker Role
|
||||||
|
|
||||||
Sadece `test-swarm-01` uzerinde zorunludur. `test-db-01` uzerinde DB manual kurulum stratejisine gore opsiyonel tutulabilir.
|
Sadece `iklim-app-01` uzerinde zorunludur. `iklim-db-01` uzerinde DB manual kurulum stratejisine gore opsiyonel tutulabilir.
|
||||||
|
|
||||||
Docker kurulumu resmi Docker apt repository uzerinden yapilir:
|
Docker kurulumu resmi Docker dnf repository uzerinden yapilir:
|
||||||
|
|
||||||
- Docker GPG key
|
- Docker GPG key + dnf repository (`https://download.docker.com/linux/rhel/docker-ce.repo`)
|
||||||
- Docker apt source
|
|
||||||
- paketler:
|
- paketler:
|
||||||
- `docker-ce`
|
- `docker-ce`
|
||||||
- `docker-ce-cli`
|
- `docker-ce-cli`
|
||||||
@ -91,7 +88,7 @@ Docker convenience script kullanilmayacak. Production benzeri test ortami icin p
|
|||||||
|
|
||||||
## Swarm Role
|
## Swarm Role
|
||||||
|
|
||||||
`test-swarm-01` uzerinde:
|
`iklim-app-01` uzerinde:
|
||||||
|
|
||||||
- `docker swarm init`
|
- `docker swarm init`
|
||||||
- advertise addr: `10.10.10.11`
|
- advertise addr: `10.10.10.11`
|
||||||
@ -102,7 +99,7 @@ Docker convenience script kullanilmayacak. Production benzeri test ortami icin p
|
|||||||
- attachable: `true`
|
- attachable: `true`
|
||||||
- Node `type=service` label'i ile isaretlenir:
|
- Node `type=service` label'i ile isaretlenir:
|
||||||
```bash
|
```bash
|
||||||
docker node update --label-add type=service test-swarm-01
|
docker node update --label-add type=service iklim-app-01
|
||||||
```
|
```
|
||||||
- Node `AVAILABILITY=Active` kalir (drain edilmez); tek node hem manager hem worker'dir.
|
- Node `AVAILABILITY=Active` kalir (drain edilmez); tek node hem manager hem worker'dir.
|
||||||
|
|
||||||
@ -110,7 +107,7 @@ Test tek node Swarm oldugu icin join token kullanimi yoktur.
|
|||||||
|
|
||||||
## Node Directory Role
|
## Node Directory Role
|
||||||
|
|
||||||
`test-swarm-01` uzerinde deploy on kosullari:
|
`iklim-app-01` uzerinde deploy on kosullari:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
/opt/iklimco
|
/opt/iklimco
|
||||||
@ -128,13 +125,138 @@ DB node uzerinde manuel DB kurulumu icin minimum:
|
|||||||
/opt/iklimco/backup
|
/opt/iklimco/backup
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## StorageBox DAVFS Mount Role
|
||||||
|
|
||||||
|
Her iki node'a uygulanir (`iklim-app-01` ve `iklim-db-01`).
|
||||||
|
|
||||||
|
### Amac
|
||||||
|
|
||||||
|
Hetzner StorageBox'u WebDAV (DAVFS) protokolü üzerinden `/mnt/storagebox` olarak mount eder. Docker volume'lari bu dizine baglanarak veri kaliciligini ve yedeklemeyi saglar.
|
||||||
|
|
||||||
|
### Test Ortami Sub-Account
|
||||||
|
|
||||||
|
| Parametre | Degisken | Deger |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Ana hesap | `storagebox_account` | `u469968` |
|
||||||
|
| Sub-account | `storagebox_user` | `u469968-sub1` |
|
||||||
|
| WebDAV URL | `storagebox_url` | `https://u469968-sub1.your-storagebox.de/` |
|
||||||
|
| Mount point | `storagebox_mount_point` | `/mnt/storagebox` |
|
||||||
|
|
||||||
|
### Role Degiskenleri
|
||||||
|
|
||||||
|
`group_vars/all.yml` — tum ortamlar icin ortak:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
storagebox_account: "u469968"
|
||||||
|
```
|
||||||
|
|
||||||
|
`group_vars/test.yml` — test ortamina ozgu; user ve url account'tan turetilir:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
storagebox_user: "{{ storagebox_account }}-sub1"
|
||||||
|
storagebox_url: "https://{{ storagebox_user }}.your-storagebox.de/"
|
||||||
|
storagebox_password: "{{ vault_storagebox_password }}" # Ansible Vault ile saklanir
|
||||||
|
storagebox_mount_point: "/mnt/storagebox"
|
||||||
|
```
|
||||||
|
|
||||||
|
Prod ortaminda yalnizca suffix degisir (`sub1` → `sub2`), geri kalan her sey turetilir.
|
||||||
|
|
||||||
|
`vault_storagebox_password` degeri Ansible Vault ile sifreli `group_vars/test-vault.yml` icinde tutulur:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Sifreleme
|
||||||
|
ansible-vault encrypt group_vars/test-vault.yml
|
||||||
|
|
||||||
|
# Duzenleme
|
||||||
|
ansible-vault edit group_vars/test-vault.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
`test-vault.yml` icerigi:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
vault_storagebox_password: "SUB_ACCOUNT_PAROLASI"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Adimlar
|
||||||
|
|
||||||
|
1. **davfs2 kurulumu**
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Install davfs2
|
||||||
|
ansible.builtin.dnf:
|
||||||
|
name: davfs2
|
||||||
|
state: present
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Kimlik bilgileri dosyasi** (`/etc/davfs2/secrets`)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Configure davfs2 secrets
|
||||||
|
ansible.builtin.lineinfile:
|
||||||
|
path: /etc/davfs2/secrets
|
||||||
|
line: "{{ storagebox_url }} {{ storagebox_user }} {{ storagebox_password }}"
|
||||||
|
create: yes
|
||||||
|
mode: "0600"
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Mount point olustur**
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Create mount point
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ storagebox_mount_point }}"
|
||||||
|
state: directory
|
||||||
|
mode: "0755"
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **fstab kaydı**
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Add fstab entry
|
||||||
|
ansible.builtin.lineinfile:
|
||||||
|
path: /etc/fstab
|
||||||
|
line: >-
|
||||||
|
{{ storagebox_url }} {{ storagebox_mount_point }} davfs
|
||||||
|
_netdev,auto,user,rw,uid=root,gid=root 0 0
|
||||||
|
state: present
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Mount et**
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Mount StorageBox
|
||||||
|
ansible.builtin.command: mount {{ storagebox_mount_point }}
|
||||||
|
args:
|
||||||
|
creates: "{{ storagebox_mount_point }}/.mounted_marker"
|
||||||
|
```
|
||||||
|
|
||||||
|
Mount basarisi icin dizine bir marker dosyasi yazilabilir:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Write mount marker
|
||||||
|
ansible.builtin.copy:
|
||||||
|
content: "mounted by ansible"
|
||||||
|
dest: "{{ storagebox_mount_point }}/.mounted_marker"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Notlar
|
||||||
|
|
||||||
|
- `davfs2` paketi EPEL repository'sinde bulunur; base role'de `dnf install epel-release` yapilmalidir.
|
||||||
|
- StorageBox sifreleri asla plaintext olarak repository'e eklenmez; Ansible Vault zorunludur.
|
||||||
|
- Mount noktasi reboot'ta `_netdev` flag'i sayesinde network hazir olduktan sonra otomatik mount edilir.
|
||||||
|
- Docker volume'lari bu dizin altindaki bir alt klasore yonlendirilir, ornegin `/mnt/storagebox/volumes/`.
|
||||||
|
|
||||||
## Kabul Kriterleri
|
## Kabul Kriterleri
|
||||||
|
|
||||||
- `ansible -i inventory/generated/test.yml all -m ping` basarili olur.
|
- `ansible -i inventory/generated/test.yml all -m ping` basarili olur.
|
||||||
- `test-swarm-01` uzerinde `docker info` calisir.
|
- `iklim-app-01` uzerinde `docker info` calisir.
|
||||||
- `test-swarm-01` uzerinde Swarm active olur; node `AVAILABILITY=Active` (drain degil).
|
- `iklim-app-01` uzerinde Swarm active olur; node `AVAILABILITY=Active` (drain degil).
|
||||||
- `docker network ls` icinde `iklimco-net` gorulur.
|
- `docker network ls` icinde `iklimco-net` gorulur.
|
||||||
- `docker node inspect test-swarm-01 --format '{{.Spec.Labels}}'` ciktisi `map[type:service]` icerir.
|
- `docker node inspect iklim-app-01 --format '{{.Spec.Labels}}'` ciktisi `map[type:service]` icerir.
|
||||||
- `test-db-01` uzerinde public DB portu acik degildir.
|
- `iklim-db-01` uzerinde public DB portu acik degildir.
|
||||||
- Public portlar Hetzner firewall + UFW seviyesinde `22`, `80`, `443` ile sinirlidir.
|
- Public portlar Hetzner firewall + firewalld seviyesinde `22`, `80`, `443` ile sinirlidir.
|
||||||
|
- Her iki node'da `mount | grep storagebox` StorageBox mount'unu gosterir.
|
||||||
|
- `ls /mnt/storagebox/.mounted_marker` basarili olur.
|
||||||
|
- Reboot sonrasi mount otomatik olarak geri gelir.
|
||||||
|
|||||||
@ -8,7 +8,7 @@ Test ortaminda tek runner yeterlidir:
|
|||||||
|
|
||||||
| Host | Runner |
|
| Host | Runner |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `test-swarm-01` | `act_runner` systemd servisi |
|
| `iklim-app-01` | `act_runner` systemd servisi |
|
||||||
|
|
||||||
Runner Docker container olarak calistirilmayacak. `/var/run/docker.sock` bir runner container'ina mount edilmeyecek.
|
Runner Docker container olarak calistirilmayacak. `/var/run/docker.sock` bir runner container'ina mount edilmeyecek.
|
||||||
|
|
||||||
@ -51,7 +51,7 @@ Test runner label'lari:
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
test-runner
|
test-runner
|
||||||
test-swarm-01
|
iklim-app-01
|
||||||
ubuntu-24.04
|
ubuntu-24.04
|
||||||
docker
|
docker
|
||||||
swarm-manager
|
swarm-manager
|
||||||
@ -61,7 +61,7 @@ Mevcut workflow'larda `runs-on` degeri test icin bu label'lardan biriyle uyumlu
|
|||||||
|
|
||||||
## Deploy On Kosullari
|
## Deploy On Kosullari
|
||||||
|
|
||||||
Test deploy pipeline'lari icin `test-swarm-01` uzerinde bulunmasi gerekenler:
|
Test deploy pipeline'lari icin `iklim-app-01` uzerinde bulunmasi gerekenler:
|
||||||
|
|
||||||
- Docker Engine
|
- Docker Engine
|
||||||
- Docker Compose plugin
|
- Docker Compose plugin
|
||||||
|
|||||||
@ -15,15 +15,16 @@ Terraform prod ortaminda sunlari olusturur:
|
|||||||
- Private ingress: `07-private-network-port-matrisi.md` dosyasindaki prod kurallari
|
- Private ingress: `07-private-network-port-matrisi.md` dosyasindaki prod kurallari
|
||||||
- SSH key
|
- SSH key
|
||||||
- Placement groups:
|
- Placement groups:
|
||||||
- `prod-swarm-spread`
|
- `iklim-prod-app-spread`
|
||||||
- `prod-db-spread`
|
- `iklim-prod-db-spread`
|
||||||
|
- Floating IP: app entry point icin sabit IPv4 (`iklim-app-01`'e atanir)
|
||||||
- Servers:
|
- Servers:
|
||||||
- `prod-swarm-01`
|
- `iklim-app-01`
|
||||||
- `prod-swarm-02`
|
- `iklim-app-02`
|
||||||
- `prod-swarm-03`
|
- `iklim-app-03`
|
||||||
- `prod-db-01`
|
- `iklim-db-01`
|
||||||
- `prod-db-02`
|
- `iklim-db-02`
|
||||||
- `prod-db-03`
|
- `iklim-db-03`
|
||||||
- Ansible inventory output
|
- Ansible inventory output
|
||||||
|
|
||||||
DB cluster yazilimi Terraform ile kurulmayacak. DB node'lari sadece makine, network ve firewall seviyesinde hazirlanacak.
|
DB cluster yazilimi Terraform ile kurulmayacak. DB node'lari sadece makine, network ve firewall seviyesinde hazirlanacak.
|
||||||
@ -42,6 +43,7 @@ terraform/
|
|||||||
firewall.tf
|
firewall.tf
|
||||||
placement.tf
|
placement.tf
|
||||||
servers.tf
|
servers.tf
|
||||||
|
floating_ip.tf
|
||||||
outputs.tf
|
outputs.tf
|
||||||
terraform.tfvars.example
|
terraform.tfvars.example
|
||||||
```
|
```
|
||||||
@ -50,13 +52,14 @@ terraform/
|
|||||||
|
|
||||||
## Degiskenler
|
## Degiskenler
|
||||||
|
|
||||||
|
`environment` sabiti `locals.tf` icindedir; `tfvars` ile override edilmez.
|
||||||
|
|
||||||
Minimum degiskenler:
|
Minimum degiskenler:
|
||||||
|
|
||||||
```hcl
|
```hcl
|
||||||
hcloud_token = "secret"
|
hcloud_token = "secret"
|
||||||
environment = "prod"
|
|
||||||
location = "fsn1"
|
location = "fsn1"
|
||||||
image = "ubuntu-24.04"
|
image = "rocky-10"
|
||||||
server_type_swarm = "cx42"
|
server_type_swarm = "cx42"
|
||||||
server_type_db = "cx52"
|
server_type_db = "cx52"
|
||||||
admin_ssh_public_key_path = "~/.ssh/id_ed25519.pub"
|
admin_ssh_public_key_path = "~/.ssh/id_ed25519.pub"
|
||||||
@ -65,26 +68,26 @@ 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 type degerleri kapasiteye gore degisebilir. Bu dokuman topoloji ve guvenlik kararini tanimlar; sizing daha sonra revize edilebilir.
|
||||||
|
|
||||||
## Server Rolleri ve Private IP PlanI
|
## Server Rolleri ve Private IP Plani
|
||||||
|
|
||||||
| Server | Private IP | Rol |
|
| Server | Private IP | Rol |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `prod-swarm-01` | `10.20.10.11` | Swarm manager + app worker + runner |
|
| `iklim-app-01` | `10.20.10.11` | Swarm manager + app worker + runner (primary, FIP alir) |
|
||||||
| `prod-swarm-02` | `10.20.10.12` | Swarm manager + app worker + runner |
|
| `iklim-app-02` | `10.20.10.12` | Swarm manager + app worker + runner |
|
||||||
| `prod-swarm-03` | `10.20.10.13` | Swarm manager + app worker + runner |
|
| `iklim-app-03` | `10.20.10.13` | Swarm manager + app worker + runner |
|
||||||
| `prod-db-01` | `10.20.20.11` | Manuel DB cluster node |
|
| `iklim-db-01` | `10.20.20.11` | Manuel DB cluster node |
|
||||||
| `prod-db-02` | `10.20.20.12` | Manuel DB cluster node |
|
| `iklim-db-02` | `10.20.20.12` | Manuel DB cluster node |
|
||||||
| `prod-db-03` | `10.20.20.13` | Manuel DB cluster node |
|
| `iklim-db-03` | `10.20.20.13` | Manuel DB cluster node |
|
||||||
|
|
||||||
Private IP'ler sabit tanimlanmalidir.
|
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
|
## Placement Group Karari
|
||||||
|
|
||||||
Prod icin iki ayri spread placement group:
|
Prod icin iki ayri spread placement group:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
prod-swarm-spread: prod-swarm-01/02/03
|
iklim-prod-app-spread: iklim-app-01/02/03
|
||||||
prod-db-spread: prod-db-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.
|
Bu sayede Swarm quorum node'lari kendi aralarinda farkli fiziksel host'lara, DB node'lari da kendi aralarinda farkli fiziksel host'lara yerlestirilmeye calisilir.
|
||||||
@ -96,6 +99,10 @@ Notlar:
|
|||||||
- Farkli lokasyon/region felaket kurtarma bu asamada konu disidir.
|
- Farkli lokasyon/region felaket kurtarma bu asamada konu disidir.
|
||||||
- Ileride scale buyudugunde multi-location DR ayri tasarlanmalidir.
|
- 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 Firewall
|
||||||
|
|
||||||
Public ingress:
|
Public ingress:
|
||||||
@ -103,8 +110,8 @@ Public ingress:
|
|||||||
| Port | Kaynak | Hedef |
|
| Port | Kaynak | Hedef |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `22/tcp` | `admin_allowed_cidrs` | Tum prod node'lari |
|
| `22/tcp` | `admin_allowed_cidrs` | Tum prod node'lari |
|
||||||
| `80/tcp` | `0.0.0.0/0`, `::/0` | Prod gateway entrypoint |
|
| `80/tcp` | `0.0.0.0/0`, `::/0` | `iklim-app-*` (Floating IP uzerinden) |
|
||||||
| `443/tcp` | `0.0.0.0/0`, `::/0` | Prod gateway entrypoint |
|
| `443/tcp` | `0.0.0.0/0`, `::/0` | `iklim-app-*` (Floating IP uzerinden) |
|
||||||
|
|
||||||
Prod'da su portlar public acilmayacak:
|
Prod'da su portlar public acilmayacak:
|
||||||
|
|
||||||
@ -118,15 +125,78 @@ Prod'da su portlar public acilmayacak:
|
|||||||
- `9090/tcp` Prometheus
|
- `9090/tcp` Prometheus
|
||||||
- `3000/tcp` Grafana
|
- `3000/tcp` Grafana
|
||||||
|
|
||||||
Bu servisler gerekiyorsa private network, VPN, bastion veya admin CIDR ile sinirlandirilmis ek kural uzerinden erisilebilir. Varsayilan public politika kapali kalir.
|
## 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
|
## Kabul Kriterleri
|
||||||
|
|
||||||
- `terraform plan` sadece prod Hetzner Project token'i ile calisir.
|
- `terraform plan` sadece prod Hetzner Project token'i ile calisir.
|
||||||
- 6 server olusur.
|
- 6 server olusur (`iklim-app-01/02/03`, `iklim-db-01/02/03`).
|
||||||
- Swarm node'lari `prod-swarm-spread` placement group icindedir.
|
- Swarm node'lari `iklim-prod-app-spread` placement group icindedir.
|
||||||
- DB node'lari `prod-db-spread` placement group icindedir.
|
- DB node'lari `iklim-prod-db-spread` placement group icindedir.
|
||||||
- Public firewall sadece `22`, `80`, `443` ingress'e izin verir.
|
- Public firewall sadece `22`, `80`, `443` ingress'e izin verir.
|
||||||
- Private firewall `07-private-network-port-matrisi.md` ile uyumludur.
|
- 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.
|
- Terraform state ve secret tfvars commit edilmez.
|
||||||
|
|
||||||
|
|||||||
@ -6,12 +6,12 @@ Bu asamanin amaci Terraform ile olusturulan prod makinelerini Linux, security ha
|
|||||||
|
|
||||||
| Host | Rol |
|
| Host | Rol |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `prod-swarm-01` | Swarm manager + app worker |
|
| `iklim-app-01` | Swarm manager + app worker |
|
||||||
| `prod-swarm-02` | Swarm manager + app worker |
|
| `iklim-app-02` | Swarm manager + app worker |
|
||||||
| `prod-swarm-03` | Swarm manager + app worker |
|
| `iklim-app-03` | Swarm manager + app worker |
|
||||||
| `prod-db-01` | Manuel DB cluster node |
|
| `iklim-db-01` | Manuel DB cluster node |
|
||||||
| `prod-db-02` | Manuel DB cluster node |
|
| `iklim-db-02` | Manuel DB cluster node |
|
||||||
| `prod-db-03` | Manuel DB cluster node |
|
| `iklim-db-03` | Manuel DB cluster node |
|
||||||
|
|
||||||
## Onerilen Dosya Yapisi
|
## Onerilen Dosya Yapisi
|
||||||
|
|
||||||
@ -76,7 +76,7 @@ Hetzner Cloud Firewall asil perimeter kabul edilir. UFW host uzerinde ikinci sav
|
|||||||
|
|
||||||
## Docker Role
|
## Docker Role
|
||||||
|
|
||||||
Sadece `prod-swarm-*` node'larinda zorunludur.
|
Sadece `iklim-app-*` node'larinda zorunludur.
|
||||||
|
|
||||||
Kurulacak paketler:
|
Kurulacak paketler:
|
||||||
|
|
||||||
@ -94,27 +94,27 @@ DB node'larinda Docker zorunlu degildir. DB manuel kurulum stratejisi container
|
|||||||
|
|
||||||
Prod Swarm 3 manager ile kurulacak:
|
Prod Swarm 3 manager ile kurulacak:
|
||||||
|
|
||||||
1. `prod-swarm-01` uzerinde `docker swarm init`
|
1. `iklim-app-01` uzerinde `docker swarm init`
|
||||||
2. Advertise/data path addr: `10.20.10.11`
|
2. Advertise/data path addr: `10.20.10.11`
|
||||||
3. Manager join token alinir.
|
3. Manager join token alinir.
|
||||||
4. `prod-swarm-02` ve `prod-swarm-03` manager olarak join olur.
|
4. `iklim-app-02` ve `iklim-app-03` manager olarak join olur.
|
||||||
5. Overlay network olusturulur:
|
5. Overlay network olusturulur:
|
||||||
- `iklimco-net`
|
- `iklimco-net`
|
||||||
- driver: `overlay`
|
- driver: `overlay`
|
||||||
- attachable: `true`
|
- attachable: `true`
|
||||||
6. Tum 3 node `type=service` label'i ile isaretlenir:
|
6. Tum 3 node `type=service` label'i ile isaretlenir:
|
||||||
```bash
|
```bash
|
||||||
for node in prod-swarm-01 prod-swarm-02 prod-swarm-03; do
|
for node in iklim-app-01 iklim-app-02 iklim-app-03; do
|
||||||
docker node update --label-add type=service "$node"
|
docker node update --label-add type=service "$node"
|
||||||
done
|
done
|
||||||
```
|
```
|
||||||
7. Hicbir node drain edilmez. 3 node da `AVAILABILITY=Active` kalir; hem manager hem app worker olarak calisir.
|
7. Hicbir node drain edilmez. 3 node da `AVAILABILITY=Active` kalir; hem manager hem app worker olarak calisir.
|
||||||
|
|
||||||
> DB node'lari (`prod-db-*`) Swarm'a join ettirilmez. DB cluster ayri yonetilir.
|
> DB node'lari (`iklim-db-*`) Swarm'a join ettirilmez. DB cluster ayri yonetilir.
|
||||||
|
|
||||||
## Node Directory Role
|
## Node Directory Role
|
||||||
|
|
||||||
Tum `prod-swarm-*` node'larinda:
|
Tum `iklim-app-*` node'larinda:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
/opt/iklimco
|
/opt/iklimco
|
||||||
@ -138,7 +138,7 @@ DB node'larinda manuel DB kurulumu icin:
|
|||||||
- 3 Swarm node `docker node ls` icinde manager olarak gorunur; hepsi `AVAILABILITY=Active`.
|
- 3 Swarm node `docker node ls` icinde manager olarak gorunur; hepsi `AVAILABILITY=Active`.
|
||||||
- Manager quorum saglanir (3 manager, 1 kayip tolere edilir).
|
- Manager quorum saglanir (3 manager, 1 kayip tolere edilir).
|
||||||
- `iklimco-net` overlay network vardir.
|
- `iklimco-net` overlay network vardir.
|
||||||
- `docker node inspect prod-swarm-01 --format '{{.Spec.Labels}}'` ciktisi `map[type:service]` icerir.
|
- `docker node inspect iklim-app-01 --format '{{.Spec.Labels}}'` ciktisi `map[type:service]` icerir.
|
||||||
- DB node'lari `docker node ls` ciktisinda gorunmez.
|
- DB node'lari `docker node ls` ciktisinda gorunmez.
|
||||||
- Public firewall sadece `22`, `80`, `443` ingress'e izin verir.
|
- Public firewall sadece `22`, `80`, `443` ingress'e izin verir.
|
||||||
- DB node'lari public DB portu acmaz.
|
- DB node'lari public DB portu acmaz.
|
||||||
|
|||||||
@ -8,9 +8,9 @@ Tek runner fonksiyonel olarak yeterlidir, ancak HA degildir. Prod hedefi HA oldu
|
|||||||
|
|
||||||
| Host | Runner |
|
| Host | Runner |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `prod-swarm-01` | `act_runner` systemd |
|
| `iklim-app-01` | `act_runner` systemd |
|
||||||
| `prod-swarm-02` | `act_runner` systemd |
|
| `iklim-app-02` | `act_runner` systemd |
|
||||||
| `prod-swarm-03` | `act_runner` systemd |
|
| `iklim-app-03` | `act_runner` systemd |
|
||||||
|
|
||||||
Bu modelde herhangi bir manager/runner kaybedilirse diger runner'lar pipeline job'larini alabilir.
|
Bu modelde herhangi bir manager/runner kaybedilirse diger runner'lar pipeline job'larini alabilir.
|
||||||
|
|
||||||
@ -42,9 +42,9 @@ ubuntu-24.04
|
|||||||
Node-spesifik label'lar:
|
Node-spesifik label'lar:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
prod-swarm-01
|
iklim-app-01
|
||||||
prod-swarm-02
|
iklim-app-02
|
||||||
prod-swarm-03
|
iklim-app-03
|
||||||
```
|
```
|
||||||
|
|
||||||
Mevcut prod workflow'lari `runs-on: prod-runner` kullaniyorsa 3 runner'dan herhangi biri job'u alabilir. Belirli bir node'a sabitlemek gerekirse node-spesifik label kullanilir.
|
Mevcut prod workflow'lari `runs-on: prod-runner` kullaniyorsa 3 runner'dan herhangi biri job'u alabilir. Belirli bir node'a sabitlemek gerekirse node-spesifik label kullanilir.
|
||||||
|
|||||||
@ -10,14 +10,14 @@ Bu matris Terraform Hetzner firewall ve Ansible UFW kurallari icin kaynak kabul
|
|||||||
|
|
||||||
| Subnet | CIDR | Amac |
|
| Subnet | CIDR | Amac |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| App/Swarm | `10.10.10.0/24` | `test-swarm-01` |
|
| App/Swarm | `10.10.10.0/24` | `iklim-app-01` |
|
||||||
| DB | `10.10.20.0/24` | `test-db-01` |
|
| DB | `10.10.20.0/24` | `test-db-01` |
|
||||||
|
|
||||||
### Prod
|
### Prod
|
||||||
|
|
||||||
| Subnet | CIDR | Amac |
|
| Subnet | CIDR | Amac |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| App/Swarm | `10.20.10.0/24` | `prod-swarm-01/02/03` |
|
| App/Swarm | `10.20.10.0/24` | `iklim-app-01/02/03` |
|
||||||
| DB | `10.20.20.0/24` | `prod-db-01/02/03` |
|
| DB | `10.20.20.0/24` | `prod-db-01/02/03` |
|
||||||
|
|
||||||
## Public Ingress Standardi
|
## Public Ingress Standardi
|
||||||
@ -57,7 +57,7 @@ Docker Swarm node'lari arasinda zorunlu portlar:
|
|||||||
|
|
||||||
Testte bu portlar fiilen tek Swarm node icin gerekli olsa da ileride worker eklemeyi kolaylastirmak icin app subnet icinde tanimlanabilir.
|
Testte bu portlar fiilen tek Swarm node icin gerekli olsa da ileride worker eklemeyi kolaylastirmak icin app subnet icinde tanimlanabilir.
|
||||||
|
|
||||||
Prod'da `10.20.10.0/24` app/swarm subnet icinde bu portlar tum `prod-swarm-*` node'lari arasinda acik olmalidir.
|
Prod'da `10.20.10.0/24` app/swarm subnet icinde bu portlar tum `iklim-app-*` node'lari arasinda acik olmalidir.
|
||||||
|
|
||||||
Kaynak: Docker overlay network dokumani, https://docs.docker.com/engine/network/drivers/overlay/
|
Kaynak: Docker overlay network dokumani, https://docs.docker.com/engine/network/drivers/overlay/
|
||||||
|
|
||||||
|
|||||||
195
terraform/hetzner/prod/firewall.tf
Normal file
195
terraform/hetzner/prod/firewall.tf
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
# Swarm node firewall — public HTTP/HTTPS + private infra services
|
||||||
|
resource "hcloud_firewall" "swarm" {
|
||||||
|
name = "${local.name_prefix}-firewall-app"
|
||||||
|
|
||||||
|
# SSH — admin CIDRs only
|
||||||
|
rule {
|
||||||
|
direction = "in"
|
||||||
|
protocol = "tcp"
|
||||||
|
port = "22"
|
||||||
|
source_ips = var.admin_allowed_cidrs
|
||||||
|
}
|
||||||
|
|
||||||
|
# HTTP public
|
||||||
|
rule {
|
||||||
|
direction = "in"
|
||||||
|
protocol = "tcp"
|
||||||
|
port = "80"
|
||||||
|
source_ips = ["0.0.0.0/0", "::/0"]
|
||||||
|
}
|
||||||
|
|
||||||
|
# HTTPS public
|
||||||
|
rule {
|
||||||
|
direction = "in"
|
||||||
|
protocol = "tcp"
|
||||||
|
port = "443"
|
||||||
|
source_ips = ["0.0.0.0/0", "::/0"]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Docker Swarm control plane
|
||||||
|
rule {
|
||||||
|
direction = "in"
|
||||||
|
protocol = "tcp"
|
||||||
|
port = "2377"
|
||||||
|
source_ips = [local.app_subnet_cidr]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Docker Swarm node discovery (TCP)
|
||||||
|
rule {
|
||||||
|
direction = "in"
|
||||||
|
protocol = "tcp"
|
||||||
|
port = "7946"
|
||||||
|
source_ips = [local.app_subnet_cidr]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Docker Swarm node discovery (UDP)
|
||||||
|
rule {
|
||||||
|
direction = "in"
|
||||||
|
protocol = "udp"
|
||||||
|
port = "7946"
|
||||||
|
source_ips = [local.app_subnet_cidr]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Docker Swarm VXLAN overlay
|
||||||
|
rule {
|
||||||
|
direction = "in"
|
||||||
|
protocol = "udp"
|
||||||
|
port = "4789"
|
||||||
|
source_ips = [local.app_subnet_cidr]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Vault API — private only, never public
|
||||||
|
rule {
|
||||||
|
direction = "in"
|
||||||
|
protocol = "tcp"
|
||||||
|
port = "8200"
|
||||||
|
source_ips = [local.app_subnet_cidr]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Redis
|
||||||
|
rule {
|
||||||
|
direction = "in"
|
||||||
|
protocol = "tcp"
|
||||||
|
port = "6379"
|
||||||
|
source_ips = [local.app_subnet_cidr]
|
||||||
|
}
|
||||||
|
|
||||||
|
# RabbitMQ AMQP
|
||||||
|
rule {
|
||||||
|
direction = "in"
|
||||||
|
protocol = "tcp"
|
||||||
|
port = "5672"
|
||||||
|
source_ips = [local.app_subnet_cidr]
|
||||||
|
}
|
||||||
|
|
||||||
|
# RabbitMQ STOMP
|
||||||
|
rule {
|
||||||
|
direction = "in"
|
||||||
|
protocol = "tcp"
|
||||||
|
port = "61613"
|
||||||
|
source_ips = [local.app_subnet_cidr]
|
||||||
|
}
|
||||||
|
|
||||||
|
# RabbitMQ Web STOMP
|
||||||
|
rule {
|
||||||
|
direction = "in"
|
||||||
|
protocol = "tcp"
|
||||||
|
port = "15674"
|
||||||
|
source_ips = [local.app_subnet_cidr]
|
||||||
|
}
|
||||||
|
|
||||||
|
# RabbitMQ Management — private only, SWAG arkasindan 443 uzerinden
|
||||||
|
rule {
|
||||||
|
direction = "in"
|
||||||
|
protocol = "tcp"
|
||||||
|
port = "15672"
|
||||||
|
source_ips = [local.app_subnet_cidr]
|
||||||
|
}
|
||||||
|
|
||||||
|
# APISIX Dashboard — private only, SWAG arkasindan 443 uzerinden (IP kisitli)
|
||||||
|
rule {
|
||||||
|
direction = "in"
|
||||||
|
protocol = "tcp"
|
||||||
|
port = "9000"
|
||||||
|
source_ips = [local.app_subnet_cidr]
|
||||||
|
}
|
||||||
|
|
||||||
|
# APISIX Admin API — sadece Docker overlay icinden Dashboard erisir
|
||||||
|
rule {
|
||||||
|
direction = "in"
|
||||||
|
protocol = "tcp"
|
||||||
|
port = "9180"
|
||||||
|
source_ips = [local.app_subnet_cidr]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Prometheus — private only, Grafana Docker overlay uzerinden erisir
|
||||||
|
rule {
|
||||||
|
direction = "in"
|
||||||
|
protocol = "tcp"
|
||||||
|
port = "9090"
|
||||||
|
source_ips = [local.app_subnet_cidr]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Grafana — private only, SWAG arkasindan 443 uzerinden (IP kisitli)
|
||||||
|
rule {
|
||||||
|
direction = "in"
|
||||||
|
protocol = "tcp"
|
||||||
|
port = "3000"
|
||||||
|
source_ips = [local.app_subnet_cidr]
|
||||||
|
}
|
||||||
|
|
||||||
|
labels = {
|
||||||
|
environment = local.environment
|
||||||
|
role = "swarm"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# DB node firewall — SSH + DB ports from app/swarm subnet only
|
||||||
|
resource "hcloud_firewall" "db" {
|
||||||
|
name = "${local.name_prefix}-firewall-db"
|
||||||
|
|
||||||
|
# SSH — admin CIDRs only
|
||||||
|
rule {
|
||||||
|
direction = "in"
|
||||||
|
protocol = "tcp"
|
||||||
|
port = "22"
|
||||||
|
source_ips = var.admin_allowed_cidrs
|
||||||
|
}
|
||||||
|
|
||||||
|
# PostgreSQL from app/swarm subnet
|
||||||
|
rule {
|
||||||
|
direction = "in"
|
||||||
|
protocol = "tcp"
|
||||||
|
port = "5432"
|
||||||
|
source_ips = [local.app_subnet_cidr]
|
||||||
|
}
|
||||||
|
|
||||||
|
# PostgreSQL replication within DB subnet
|
||||||
|
rule {
|
||||||
|
direction = "in"
|
||||||
|
protocol = "tcp"
|
||||||
|
port = "5432"
|
||||||
|
source_ips = [local.db_subnet_cidr]
|
||||||
|
}
|
||||||
|
|
||||||
|
# MongoDB from app/swarm subnet
|
||||||
|
rule {
|
||||||
|
direction = "in"
|
||||||
|
protocol = "tcp"
|
||||||
|
port = "27017"
|
||||||
|
source_ips = [local.app_subnet_cidr]
|
||||||
|
}
|
||||||
|
|
||||||
|
# MongoDB replica set internal traffic
|
||||||
|
rule {
|
||||||
|
direction = "in"
|
||||||
|
protocol = "tcp"
|
||||||
|
port = "27017"
|
||||||
|
source_ips = [local.db_subnet_cidr]
|
||||||
|
}
|
||||||
|
|
||||||
|
labels = {
|
||||||
|
environment = local.environment
|
||||||
|
role = "db"
|
||||||
|
}
|
||||||
|
}
|
||||||
18
terraform/hetzner/prod/floating_ip.tf
Normal file
18
terraform/hetzner/prod/floating_ip.tf
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
resource "hcloud_floating_ip" "app" {
|
||||||
|
name = "${local.name_prefix}-app-fip"
|
||||||
|
type = "ipv4"
|
||||||
|
home_location = var.location
|
||||||
|
description = "Floating IP for ${local.environment} app entry point"
|
||||||
|
|
||||||
|
labels = {
|
||||||
|
environment = local.environment
|
||||||
|
role = "app"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Floating IP, iklim-app-01'e atanir (primary node).
|
||||||
|
# Failover gerekirse manuel veya otomasyon ile baska app node'una tasinabilir.
|
||||||
|
resource "hcloud_floating_ip_assignment" "app" {
|
||||||
|
floating_ip_id = hcloud_floating_ip.app.id
|
||||||
|
server_id = hcloud_server.swarm["iklim-app-01"].id
|
||||||
|
}
|
||||||
22
terraform/hetzner/prod/locals.tf
Normal file
22
terraform/hetzner/prod/locals.tf
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
locals {
|
||||||
|
environment = "prod"
|
||||||
|
hcloud_project = "iklim_prod"
|
||||||
|
name_prefix = "iklim-prod"
|
||||||
|
|
||||||
|
swarm_private_ips = {
|
||||||
|
"iklim-app-01" = "10.20.10.11"
|
||||||
|
"iklim-app-02" = "10.20.10.12"
|
||||||
|
"iklim-app-03" = "10.20.10.13"
|
||||||
|
}
|
||||||
|
|
||||||
|
db_private_ips = {
|
||||||
|
"iklim-db-01" = "10.20.20.11"
|
||||||
|
"iklim-db-02" = "10.20.20.12"
|
||||||
|
"iklim-db-03" = "10.20.20.13"
|
||||||
|
}
|
||||||
|
|
||||||
|
network_zone = "eu-central"
|
||||||
|
network_cidr = "10.20.0.0/16"
|
||||||
|
app_subnet_cidr = "10.20.10.0/24"
|
||||||
|
db_subnet_cidr = "10.20.20.0/24"
|
||||||
|
}
|
||||||
22
terraform/hetzner/prod/network.tf
Normal file
22
terraform/hetzner/prod/network.tf
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
resource "hcloud_network" "main" {
|
||||||
|
name = "${local.name_prefix}-net"
|
||||||
|
ip_range = local.network_cidr
|
||||||
|
|
||||||
|
labels = {
|
||||||
|
environment = local.environment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "hcloud_network_subnet" "app" {
|
||||||
|
network_id = hcloud_network.main.id
|
||||||
|
type = "cloud"
|
||||||
|
network_zone = local.network_zone
|
||||||
|
ip_range = local.app_subnet_cidr
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "hcloud_network_subnet" "db" {
|
||||||
|
network_id = hcloud_network.main.id
|
||||||
|
type = "cloud"
|
||||||
|
network_zone = local.network_zone
|
||||||
|
ip_range = local.db_subnet_cidr
|
||||||
|
}
|
||||||
52
terraform/hetzner/prod/outputs.tf
Normal file
52
terraform/hetzner/prod/outputs.tf
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
output "ansible_inventory_yaml" {
|
||||||
|
description = "Ansible inventory in YAML format — write to ansible/inventory/generated/prod.yml"
|
||||||
|
sensitive = false
|
||||||
|
value = yamlencode({
|
||||||
|
all = {
|
||||||
|
children = {
|
||||||
|
swarm = {
|
||||||
|
hosts = {
|
||||||
|
for name, server in hcloud_server.swarm : name => {
|
||||||
|
ansible_host = server.ipv4_address
|
||||||
|
private_ip = local.swarm_private_ips[name]
|
||||||
|
ansible_user = "root"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
db = {
|
||||||
|
hosts = {
|
||||||
|
for name, server in hcloud_server.db : name => {
|
||||||
|
ansible_host = server.ipv4_address
|
||||||
|
private_ip = local.db_private_ips[name]
|
||||||
|
ansible_user = "root"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
output "prod_private_ips" {
|
||||||
|
description = "Private IPs assigned to prod nodes"
|
||||||
|
sensitive = false
|
||||||
|
value = {
|
||||||
|
swarm = local.swarm_private_ips
|
||||||
|
db = local.db_private_ips
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output "prod_public_ips" {
|
||||||
|
description = "Public IPv4 addresses of prod nodes"
|
||||||
|
sensitive = false
|
||||||
|
value = {
|
||||||
|
swarm = { for name, server in hcloud_server.swarm : name => server.ipv4_address }
|
||||||
|
db = { for name, server in hcloud_server.db : name => server.ipv4_address }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output "prod_floating_ip" {
|
||||||
|
description = "Floating IP for prod swarm entry point — point DNS A records here"
|
||||||
|
sensitive = false
|
||||||
|
value = hcloud_floating_ip.app.ip_address
|
||||||
|
}
|
||||||
19
terraform/hetzner/prod/placement.tf
Normal file
19
terraform/hetzner/prod/placement.tf
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
resource "hcloud_placement_group" "app_spread" {
|
||||||
|
name = "${local.name_prefix}-app-spread"
|
||||||
|
type = "spread"
|
||||||
|
|
||||||
|
labels = {
|
||||||
|
environment = local.environment
|
||||||
|
role = "app"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "hcloud_placement_group" "db_spread" {
|
||||||
|
name = "${local.name_prefix}-db-spread"
|
||||||
|
type = "spread"
|
||||||
|
|
||||||
|
labels = {
|
||||||
|
environment = local.environment
|
||||||
|
role = "db"
|
||||||
|
}
|
||||||
|
}
|
||||||
5
terraform/hetzner/prod/providers.tf
Normal file
5
terraform/hetzner/prod/providers.tf
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# Hetzner Cloud Project: iklim_prod
|
||||||
|
# Token bu projeye ait olmalidir.
|
||||||
|
provider "hcloud" {
|
||||||
|
token = var.hcloud_token
|
||||||
|
}
|
||||||
76
terraform/hetzner/prod/servers.tf
Normal file
76
terraform/hetzner/prod/servers.tf
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
resource "hcloud_ssh_key" "admin" {
|
||||||
|
name = "${local.name_prefix}-admin-key"
|
||||||
|
public_key = file(var.admin_ssh_public_key_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "hcloud_server" "swarm" {
|
||||||
|
for_each = local.swarm_private_ips
|
||||||
|
|
||||||
|
name = each.key
|
||||||
|
server_type = var.server_type_swarm
|
||||||
|
image = var.image
|
||||||
|
location = var.location
|
||||||
|
ssh_keys = [hcloud_ssh_key.admin.id]
|
||||||
|
placement_group_id = hcloud_placement_group.app_spread.id
|
||||||
|
|
||||||
|
labels = {
|
||||||
|
environment = local.environment
|
||||||
|
role = "swarm"
|
||||||
|
type = "service"
|
||||||
|
}
|
||||||
|
|
||||||
|
lifecycle {
|
||||||
|
prevent_destroy = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "hcloud_server" "db" {
|
||||||
|
for_each = local.db_private_ips
|
||||||
|
|
||||||
|
name = each.key
|
||||||
|
server_type = var.server_type_db
|
||||||
|
image = var.image
|
||||||
|
location = var.location
|
||||||
|
ssh_keys = [hcloud_ssh_key.admin.id]
|
||||||
|
placement_group_id = hcloud_placement_group.db_spread.id
|
||||||
|
|
||||||
|
labels = {
|
||||||
|
environment = local.environment
|
||||||
|
role = "db"
|
||||||
|
type = "db"
|
||||||
|
}
|
||||||
|
|
||||||
|
lifecycle {
|
||||||
|
prevent_destroy = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "hcloud_server_network" "swarm" {
|
||||||
|
for_each = local.swarm_private_ips
|
||||||
|
|
||||||
|
server_id = hcloud_server.swarm[each.key].id
|
||||||
|
network_id = hcloud_network.main.id
|
||||||
|
ip = each.value
|
||||||
|
|
||||||
|
depends_on = [hcloud_network_subnet.app]
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "hcloud_server_network" "db" {
|
||||||
|
for_each = local.db_private_ips
|
||||||
|
|
||||||
|
server_id = hcloud_server.db[each.key].id
|
||||||
|
network_id = hcloud_network.main.id
|
||||||
|
ip = each.value
|
||||||
|
|
||||||
|
depends_on = [hcloud_network_subnet.db]
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "hcloud_firewall_attachment" "swarm" {
|
||||||
|
firewall_id = hcloud_firewall.swarm.id
|
||||||
|
server_ids = [for s in hcloud_server.swarm : s.id]
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "hcloud_firewall_attachment" "db" {
|
||||||
|
firewall_id = hcloud_firewall.db.id
|
||||||
|
server_ids = [for s in hcloud_server.db : s.id]
|
||||||
|
}
|
||||||
8
terraform/hetzner/prod/terraform.tfvars.example
Normal file
8
terraform/hetzner/prod/terraform.tfvars.example
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# Hetzner Cloud Project: iklim_prod
|
||||||
|
hcloud_token = "YOUR_HETZNER_PROD_PROJECT_API_TOKEN"
|
||||||
|
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 = ["1.2.3.4/32", "5.6.7.8/32"]
|
||||||
40
terraform/hetzner/prod/variables.tf
Normal file
40
terraform/hetzner/prod/variables.tf
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
variable "hcloud_token" {
|
||||||
|
type = string
|
||||||
|
sensitive = true
|
||||||
|
description = "Hetzner Cloud API token for the prod project"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "location" {
|
||||||
|
type = string
|
||||||
|
default = "fsn1"
|
||||||
|
description = "Hetzner Cloud datacenter location"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "image" {
|
||||||
|
type = string
|
||||||
|
default = "rocky-10"
|
||||||
|
description = "Server image"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "server_type_swarm" {
|
||||||
|
type = string
|
||||||
|
default = "cx42"
|
||||||
|
description = "Hetzner server type for Swarm nodes"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "server_type_db" {
|
||||||
|
type = string
|
||||||
|
default = "cx52"
|
||||||
|
description = "Hetzner server type for DB nodes"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "admin_ssh_public_key_path" {
|
||||||
|
type = string
|
||||||
|
default = "~/.ssh/id_ed25519.pub"
|
||||||
|
description = "Path to the admin SSH public key file"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "admin_allowed_cidrs" {
|
||||||
|
type = list(string)
|
||||||
|
description = "CIDR list for admin SSH access"
|
||||||
|
}
|
||||||
10
terraform/hetzner/prod/versions.tf
Normal file
10
terraform/hetzner/prod/versions.tf
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
terraform {
|
||||||
|
required_version = ">= 1.6"
|
||||||
|
|
||||||
|
required_providers {
|
||||||
|
hcloud = {
|
||||||
|
source = "hetznercloud/hcloud"
|
||||||
|
version = "~> 1.49"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
# Swarm node firewall — public HTTP/HTTPS + private infra services
|
# Swarm node firewall — public HTTP/HTTPS + private infra services
|
||||||
resource "hcloud_firewall" "swarm" {
|
resource "hcloud_firewall" "swarm" {
|
||||||
name = "${local.name_prefix}-firewall-swarm"
|
name = "${local.name_prefix}-firewall-app"
|
||||||
|
|
||||||
# SSH — admin CIDRs only
|
# SSH — admin CIDRs only
|
||||||
rule {
|
rule {
|
||||||
@ -98,40 +98,48 @@ resource "hcloud_firewall" "swarm" {
|
|||||||
source_ips = [local.app_subnet_cidr]
|
source_ips = [local.app_subnet_cidr]
|
||||||
}
|
}
|
||||||
|
|
||||||
# RabbitMQ Management — admin CIDRs only
|
# RabbitMQ Management — private only, SWAG arkasindan 443 uzerinden erisim
|
||||||
rule {
|
rule {
|
||||||
direction = "in"
|
direction = "in"
|
||||||
protocol = "tcp"
|
protocol = "tcp"
|
||||||
port = "15672"
|
port = "15672"
|
||||||
source_ips = var.admin_allowed_cidrs
|
source_ips = [local.app_subnet_cidr]
|
||||||
}
|
}
|
||||||
|
|
||||||
# APISIX Admin API — admin CIDRs only
|
# APISIX Dashboard — private only, SWAG arkasindan 443 uzerinden (IP kisitli)
|
||||||
|
rule {
|
||||||
|
direction = "in"
|
||||||
|
protocol = "tcp"
|
||||||
|
port = "9000"
|
||||||
|
source_ips = [local.app_subnet_cidr]
|
||||||
|
}
|
||||||
|
|
||||||
|
# APISIX Admin API — sadece Docker overlay icinden Dashboard erisir, insan erisimi gerekmez
|
||||||
rule {
|
rule {
|
||||||
direction = "in"
|
direction = "in"
|
||||||
protocol = "tcp"
|
protocol = "tcp"
|
||||||
port = "9180"
|
port = "9180"
|
||||||
source_ips = var.admin_allowed_cidrs
|
source_ips = [local.app_subnet_cidr]
|
||||||
}
|
}
|
||||||
|
|
||||||
# Prometheus — admin CIDRs only
|
# Prometheus — private only, SWAG arkasindan 443 uzerinden erisim
|
||||||
rule {
|
rule {
|
||||||
direction = "in"
|
direction = "in"
|
||||||
protocol = "tcp"
|
protocol = "tcp"
|
||||||
port = "9090"
|
port = "9090"
|
||||||
source_ips = var.admin_allowed_cidrs
|
source_ips = [local.app_subnet_cidr]
|
||||||
}
|
}
|
||||||
|
|
||||||
# Grafana — admin CIDRs only
|
# Grafana — private only, SWAG arkasindan 443 uzerinden erisim
|
||||||
rule {
|
rule {
|
||||||
direction = "in"
|
direction = "in"
|
||||||
protocol = "tcp"
|
protocol = "tcp"
|
||||||
port = "3000"
|
port = "3000"
|
||||||
source_ips = var.admin_allowed_cidrs
|
source_ips = [local.app_subnet_cidr]
|
||||||
}
|
}
|
||||||
|
|
||||||
labels = {
|
labels = {
|
||||||
environment = var.environment
|
environment = local.environment
|
||||||
role = "swarm"
|
role = "swarm"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -165,7 +173,7 @@ resource "hcloud_firewall" "db" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
labels = {
|
labels = {
|
||||||
environment = var.environment
|
environment = local.environment
|
||||||
role = "db"
|
role = "db"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
16
terraform/hetzner/test/floating_ip.tf
Normal file
16
terraform/hetzner/test/floating_ip.tf
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
resource "hcloud_floating_ip" "app" {
|
||||||
|
name = "${local.name_prefix}-app-fip"
|
||||||
|
type = "ipv4"
|
||||||
|
home_location = var.location
|
||||||
|
description = "Floating IP for ${local.environment} app entry point"
|
||||||
|
|
||||||
|
labels = {
|
||||||
|
environment = local.environment
|
||||||
|
role = "app"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "hcloud_floating_ip_assignment" "app" {
|
||||||
|
floating_ip_id = hcloud_floating_ip.app.id
|
||||||
|
server_id = hcloud_server.swarm.id
|
||||||
|
}
|
||||||
@ -1,9 +1,12 @@
|
|||||||
locals {
|
locals {
|
||||||
name_prefix = "iklim-${var.environment}"
|
environment = "test"
|
||||||
|
hcloud_project = "iklim_test"
|
||||||
|
name_prefix = "iklim-test"
|
||||||
|
|
||||||
swarm_private_ip = "10.10.10.11"
|
swarm_private_ip = "10.10.10.11"
|
||||||
db_private_ip = "10.10.20.11"
|
db_private_ip = "10.10.20.11"
|
||||||
|
|
||||||
|
network_zone = "eu-central"
|
||||||
network_cidr = "10.10.0.0/16"
|
network_cidr = "10.10.0.0/16"
|
||||||
app_subnet_cidr = "10.10.10.0/24"
|
app_subnet_cidr = "10.10.10.0/24"
|
||||||
db_subnet_cidr = "10.10.20.0/24"
|
db_subnet_cidr = "10.10.20.0/24"
|
||||||
|
|||||||
@ -3,20 +3,20 @@ resource "hcloud_network" "main" {
|
|||||||
ip_range = local.network_cidr
|
ip_range = local.network_cidr
|
||||||
|
|
||||||
labels = {
|
labels = {
|
||||||
environment = var.environment
|
environment = local.environment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "hcloud_network_subnet" "app" {
|
resource "hcloud_network_subnet" "app" {
|
||||||
network_id = hcloud_network.main.id
|
network_id = hcloud_network.main.id
|
||||||
type = "cloud"
|
type = "cloud"
|
||||||
network_zone = "eu-central"
|
network_zone = local.network_zone
|
||||||
ip_range = local.app_subnet_cidr
|
ip_range = local.app_subnet_cidr
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "hcloud_network_subnet" "db" {
|
resource "hcloud_network_subnet" "db" {
|
||||||
network_id = hcloud_network.main.id
|
network_id = hcloud_network.main.id
|
||||||
type = "cloud"
|
type = "cloud"
|
||||||
network_zone = "eu-central"
|
network_zone = local.network_zone
|
||||||
ip_range = local.db_subnet_cidr
|
ip_range = local.db_subnet_cidr
|
||||||
}
|
}
|
||||||
|
|||||||
@ -44,3 +44,9 @@ output "test_public_ips" {
|
|||||||
db_01 = hcloud_server.db.ipv4_address
|
db_01 = hcloud_server.db.ipv4_address
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
output "test_floating_ip" {
|
||||||
|
description = "Floating IP for test app entry point — point DNS A records here"
|
||||||
|
sensitive = false
|
||||||
|
value = hcloud_floating_ip.app.ip_address
|
||||||
|
}
|
||||||
|
|||||||
@ -3,6 +3,6 @@ resource "hcloud_placement_group" "test_spread" {
|
|||||||
type = "spread"
|
type = "spread"
|
||||||
|
|
||||||
labels = {
|
labels = {
|
||||||
environment = var.environment
|
environment = local.environment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
# Hetzner Cloud Project: iklim_test
|
||||||
|
# Token bu projeye ait olmalidir.
|
||||||
provider "hcloud" {
|
provider "hcloud" {
|
||||||
token = var.hcloud_token
|
token = var.hcloud_token
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@ resource "hcloud_ssh_key" "admin" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
resource "hcloud_server" "swarm" {
|
resource "hcloud_server" "swarm" {
|
||||||
name = "${var.environment}-swarm-01"
|
name = "iklim-app-01"
|
||||||
server_type = var.server_type_swarm
|
server_type = var.server_type_swarm
|
||||||
image = var.image
|
image = var.image
|
||||||
location = var.location
|
location = var.location
|
||||||
@ -12,7 +12,7 @@ resource "hcloud_server" "swarm" {
|
|||||||
placement_group_id = hcloud_placement_group.test_spread.id
|
placement_group_id = hcloud_placement_group.test_spread.id
|
||||||
|
|
||||||
labels = {
|
labels = {
|
||||||
environment = var.environment
|
environment = local.environment
|
||||||
role = "swarm"
|
role = "swarm"
|
||||||
type = "service"
|
type = "service"
|
||||||
}
|
}
|
||||||
@ -25,7 +25,7 @@ resource "hcloud_server" "swarm" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
resource "hcloud_server" "db" {
|
resource "hcloud_server" "db" {
|
||||||
name = "${var.environment}-db-01"
|
name = "iklim-db-01"
|
||||||
server_type = var.server_type_db
|
server_type = var.server_type_db
|
||||||
image = var.image
|
image = var.image
|
||||||
location = var.location
|
location = var.location
|
||||||
@ -33,7 +33,7 @@ resource "hcloud_server" "db" {
|
|||||||
placement_group_id = hcloud_placement_group.test_spread.id
|
placement_group_id = hcloud_placement_group.test_spread.id
|
||||||
|
|
||||||
labels = {
|
labels = {
|
||||||
environment = var.environment
|
environment = local.environment
|
||||||
role = "db"
|
role = "db"
|
||||||
type = "db"
|
type = "db"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
|
# Hetzner Cloud Project: iklim_test
|
||||||
hcloud_token = "YOUR_HETZNER_TEST_PROJECT_API_TOKEN"
|
hcloud_token = "YOUR_HETZNER_TEST_PROJECT_API_TOKEN"
|
||||||
environment = "test"
|
|
||||||
location = "fsn1"
|
location = "fsn1"
|
||||||
image = "ubuntu-24.04"
|
image = "rocky-10"
|
||||||
server_type_swarm = "cx32"
|
server_type_swarm = "cx32"
|
||||||
server_type_db = "cx42"
|
server_type_db = "cx42"
|
||||||
admin_ssh_public_key_path = "~/.ssh/id_ed25519.pub"
|
admin_ssh_public_key_path = "~/.ssh/id_ed25519.pub"
|
||||||
admin_allowed_cidrs = ["X.X.X.X/32"]
|
admin_allowed_cidrs = ["1.2.3.4/32", "5.6.7.8/32"]
|
||||||
|
|||||||
@ -4,12 +4,6 @@ variable "hcloud_token" {
|
|||||||
description = "Hetzner Cloud API token for the test project"
|
description = "Hetzner Cloud API token for the test project"
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "environment" {
|
|
||||||
type = string
|
|
||||||
default = "test"
|
|
||||||
description = "Environment name prefix for all resources"
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "location" {
|
variable "location" {
|
||||||
type = string
|
type = string
|
||||||
default = "fsn1"
|
default = "fsn1"
|
||||||
@ -18,7 +12,7 @@ variable "location" {
|
|||||||
|
|
||||||
variable "image" {
|
variable "image" {
|
||||||
type = string
|
type = string
|
||||||
default = "ubuntu-24.04"
|
default = "rocky-10"
|
||||||
description = "Server image"
|
description = "Server image"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user