Implement Hetzner sizing report recommendations and detailed DB setups
- Add `hetzner-sizing-report.md` defining data-driven server type recommendations for test and prod environments.
- Update Terraform configurations to align with the recommended `CPX` server types and refine firewall rules for Docker Swarm and database interactions.
- Introduce comprehensive documentation and stack files for:
- Single-node PostgreSQL/MongoDB deployment on a test DB worker node.
- High-availability 3-node MongoDB replica set and Patroni+etcd PostgreSQL cluster for production.
- Enhance Ansible bootstrap roles with SELinux disabling, fail2ban configuration, and StorageBox SSH key management for CI/CD.
- Reorganize and rename setup documentation files for improved structure and clarity.
This commit is contained in:
parent
76f87aa2f9
commit
b115a4cbdf
2
.gitignore
vendored
2
.gitignore
vendored
@ -46,3 +46,5 @@ runner-config.secret.yaml
|
||||
.DS_Store
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
*.pdf
|
||||
|
||||
270
hetzner-sizing-report.md
Normal file
270
hetzner-sizing-report.md
Normal file
@ -0,0 +1,270 @@
|
||||
# iklim.co Hetzner Yeni Test ve Prod Sunucu Raporu
|
||||
|
||||
Tarih: 2026-05-11
|
||||
|
||||
## Yönetici Özeti
|
||||
|
||||
Hetzner Cloud cost-optimized `CX` modellerinde "limited availability" problemi olduğu için yeni test ve prod kurulumlarında `CPX` regular performance modellerine geçilmesi önerilir.
|
||||
|
||||
Mevcut elle kurulmuş test ortamı verileri, test app sunucusunda RAM'in ana sınırlayıcı kaynak olduğunu gösteriyor. Test app node üzerinde 10 mikroservis ve altyapı servisleri birlikte çalıştığı için `CPX32` yani 8 GB RAM risklidir. Bu nedenle yeni test app sunucusu için `CPX42` önerilir.
|
||||
|
||||
Test DB sunucusunda disk ve network kullanımı düşük, disk doluluğu da çok düşüktür. Ancak CPU grafiğinde 8 vCPU seviyesine yaklaşan kısa süreli yükler görüldüğü için test DB için de `CPX42` ile başlamak daha doğru ve daha az riskli seçenektir.
|
||||
|
||||
Prod ortamında app ve DB katmanları 3'er node olarak kurulacağı için tek node başına en yüksek kaynakla başlamak gerekli değildir. Başlangıç için app node'larda `CPX42`, DB node'larda `CPX32` önerilir. Bu tercih, app tarafındaki Java mikroservis RAM baskısını azaltırken DB tarafında ekonomik bir cluster başlangıcı sağlar. Büyüme görüldüğünde DB node'lar `CPX42` veya daha yüksek modellere rescale edilebilir.
|
||||
|
||||
Önerilen nihai başlangıç:
|
||||
|
||||
| Ortam | Rol | Önerilen Tip | Adet | Aylık Toplam |
|
||||
| --- | --- | --- | ---: | ---: |
|
||||
| Test | App / Swarm / runner | `CPX42` | 1 | $29.99 |
|
||||
| Test | DB | `CPX42` | 1 | $29.99 |
|
||||
| Prod | App / Swarm | `CPX42` | 3 | $89.97 |
|
||||
| Prod | DB cluster | `CPX32` | 3 | $49.47 |
|
||||
|
||||
Toplam başlangıç maliyeti:
|
||||
|
||||
| Kapsam | Aylık Maliyet |
|
||||
| --- | ---: |
|
||||
| Test | $59.98 / mo |
|
||||
| Prod | $139.44 / mo |
|
||||
| Test + Prod | $199.42 / mo |
|
||||
|
||||
## Kapsam ve Varsayımlar
|
||||
|
||||
Bu rapor şu veriler esas alınarak hazırlanmıştır:
|
||||
|
||||
- Mevcut elle kurulmuş test app ve test DB sunucularının CPU, disk, network, RAM ve disk doluluk verileri.
|
||||
- Test app sunucusunda çalışan 10 mikroservis ve altyapı servisleri.
|
||||
- Test DB sunucusunda çalışan PostgreSQL/PostGIS ve MongoDB container'ları.
|
||||
- Hetzner tarafında cost-optimized `CX` modellerinin alınabilir olmaması.
|
||||
- Kullanıcı tarafından paylaşılan `CPX` ve `CCX` model kaynak/fiyat bilgileri.
|
||||
- Prod ortamında 3 app node ve 3 DB node olacak şekilde cluster topolojisi.
|
||||
|
||||
Fiyatlar kullanıcı tarafından paylaşılan Hetzner fiyat tablosuna göre hesaplanmıştır. Vergi, backup, snapshot, floating IP, volume ve trafik aşım maliyetleri dahil edilmemiştir.
|
||||
|
||||
## Kullanılabilir Hetzner Modelleri
|
||||
|
||||
### Regular Performance CPX Modelleri
|
||||
|
||||
| Tip | CPU | RAM | SSD | Trafik | Saatlik | Aylık |
|
||||
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
|
||||
| `CPX22` | 2 AMD | 4 GB | 80 GB | 20 TB | $0.015 / h | $9.49 / mo |
|
||||
| `CPX32` | 4 AMD | 8 GB | 160 GB | 20 TB | $0.026 / h | $16.49 / mo |
|
||||
| `CPX42` | 8 AMD | 16 GB | 320 GB | 20 TB | $0.048 / h | $29.99 / mo |
|
||||
| `CPX52` | 12 AMD | 24 GB | 480 GB | 20 TB | $0.069 / h | $42.99 / mo |
|
||||
| `CPX62` | 16 AMD | 32 GB | 640 GB | 20 TB | $0.095 / h | $59.49 / mo |
|
||||
|
||||
### General Purpose CCX Modelleri
|
||||
|
||||
| Tip | CPU | RAM | SSD | Trafik | Saatlik | Aylık |
|
||||
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
|
||||
| `CCX13` | 2 AMD | 8 GB | 80 GB | 20 TB | $0.030 / h | $18.49 / mo |
|
||||
| `CCX23` | 4 AMD | 16 GB | 160 GB | 20 TB | $0.059 / h | $36.99 / mo |
|
||||
| `CCX33` | 8 AMD | 32 GB | 240 GB | 30 TB | $0.119 / h | $73.99 / mo |
|
||||
|
||||
### CPX ve CCX Seçim Notu
|
||||
|
||||
`CCX` modelleri dedicated CPU davranışı nedeniyle CPU tutarlılığı gereken senaryolarda avantajlıdır. Ancak bu proje için başlangıç aşamasında belirleyici risk app tarafında RAM, DB tarafında ise ekonomik cluster başlangıcıdır. Bu nedenle ilk tercih `CPX` modelleridir.
|
||||
|
||||
DB tarafında `CCX33` 32 GB RAM sunsa da 8 vCPU ve 240 GB disk ile `CPX42` veya `CPX62` kadar dengeli değildir. Başlangıçta dedicated CPU gereksinimi net olmadığı için prod DB'de `CCX` ile başlamak önerilmez.
|
||||
|
||||
## Mevcut Test Ortamı Bulguları
|
||||
|
||||
### Test App Sunucusu
|
||||
|
||||
Mevcut test app sunucusu üzerinde 10 mikroservis ve altyapı servisleri aynı node üzerinde çalışmaktadır. Çalışan servisler:
|
||||
|
||||
| Grup | Servisler |
|
||||
| --- | --- |
|
||||
| Mikroservisler | auth, account, lightning, thunderstorm, precipitation, nowcast-point-alarm, nowcast-geo-alarm, forecast, forecast-point-alarm, enroute |
|
||||
| Altyapı servisleri | RabbitMQ, Redis, Vault, Prometheus, Grafana, APISIX, APISIX Dashboard, etcd |
|
||||
|
||||
Kaynak özeti:
|
||||
|
||||
| Metrik | Mevcut Değer | Yorum |
|
||||
| -------------- | --------------------: | ----------------------------------- |
|
||||
| RAM toplam | 14 GiB | Yaklaşık 16 GB sınıfı sunucu |
|
||||
| RAM used | 10 GiB | App node için yüksek ve belirleyici |
|
||||
| RAM available | 4.0 GiB | Kalan alan sınırlı |
|
||||
| Swap | 0 B | OOM riskini artırır |
|
||||
| Root disk | 226 GB | Yaklaşık 240 GB sınıfı disk |
|
||||
| Disk used | 81 GB | %38 kullanım |
|
||||
| Disk available | 136 GB | Test için yeterli boşluk |
|
||||
| CPU | Düşük baz, kısa spike | CPU kritik sınır değil |
|
||||
| Disk I/O | Düşük | IOPS/throughput sınırlayıcı değil |
|
||||
| Network | Düşük | Trafik sınırlayıcı değil |
|
||||
![[test-app-graphs.png]]
|
||||
Değerlendirme:
|
||||
|
||||
- App sunucusunda ana risk RAM'dir.
|
||||
- `CPX32` 8 GB RAM sunduğu için mevcut 10 GiB used seviyesini karşılamaz.
|
||||
- JVM/container memory limitleri uygulanmadan `CPX32` OOM veya yoğun GC riski taşır.
|
||||
- Disk kullanımı 81 GB olduğu için 160 GB disk teorik olarak yeterli görünse de Docker image/layer birikimi, loglar ve deploy sırasındaki geçici alan ihtiyacı nedeniyle 320 GB daha güvenlidir.
|
||||
|
||||
Sonuç: yeni test app sunucusu için `CPX42` önerilir.
|
||||
|
||||
### Test DB Sunucusu
|
||||
|
||||
Mevcut test DB sunucusunda PostgreSQL/PostGIS ve MongoDB çalışmaktadır.
|
||||
|
||||
Kaynak özeti:
|
||||
|
||||
| Metrik | Mevcut Değer | Yorum |
|
||||
| -------------- | -----------------------: | --------------------------------------------- |
|
||||
| RAM toplam | 14 GiB | Yaklaşık 16 GB sınıfı sunucu |
|
||||
| RAM used | 7.6 GiB | Linux cache nedeniyle tek başına riskli değil |
|
||||
| RAM available | 7.4 GiB | RAM açısından rahat |
|
||||
| Swap | 0 B | Ani bellek baskısında risk |
|
||||
| Root disk | 226 GB | Yaklaşık 240 GB sınıfı disk |
|
||||
| Disk used | 16 GB | %8 kullanım, çok düşük |
|
||||
| Disk available | 201 GB | Fazlasıyla yeterli |
|
||||
| CPU | Kısa süreli yüksek spike | DB için asıl izlenmesi gereken metrik |
|
||||
| Disk I/O | Düşük | Disk throughput/IOPS sınırlayıcı görünmüyor |
|
||||
| Network | Düşük | Trafik sınırlayıcı değil |
|
||||
![[test-db-graphs.png]]
|
||||
Değerlendirme:
|
||||
|
||||
- RAM ve disk açısından `CPX32` yeterli görünebilir.
|
||||
- Ancak CPU grafiğinde 8 vCPU seviyesine yaklaşan kısa süreli yükler var.
|
||||
- Bu yükler PostGIS query, Mongo indexleme, import veya batch işlemlerinden kaynaklanıyorsa `CPX32`'ye düşmek testte yavaşlamaya neden olabilir.
|
||||
- Test DB tek node olduğu için prod DB cluster'ına göre daha az toleranslıdır.
|
||||
|
||||
Sonuç: yeni test DB sunucusu için de `CPX42` önerilir.
|
||||
|
||||
## Yeni Test Ortamı Gereksinimleri
|
||||
|
||||
Yeni test ortamında iki node hedeflenir:
|
||||
|
||||
| Sunucu | Rol | Önerilen Tip | CPU | RAM | SSD | Trafik | Aylık |
|
||||
| --- | --- | --- | ---: | ---: | ---: | ---: | ---: |
|
||||
| `iklim-app-01` | Swarm manager, app, runner, altyapı servisleri | `CPX42` | 8 AMD | 16 GB | 320 GB | 20 TB | $29.99 |
|
||||
| `iklim-db-01` | PostgreSQL/PostGIS, MongoDB | `CPX42` | 8 AMD | 16 GB | 320 GB | 20 TB | $29.99 |
|
||||
| **Toplam** | 2 sunucu | | **16 vCPU** | **32 GB** | **640 GB** | **40 TB** | **$59.98** |
|
||||
|
||||
### Test İçin Reddedilen Daha Ekonomik Alternatif
|
||||
|
||||
| Sunucu | Alternatif Tip | Neden Reddedildi |
|
||||
| --- | --- | --- |
|
||||
| `iklim-app-01` | `CPX32` | Mevcut app node 10 GiB used RAM seviyesinde; 8 GB RAM riskli |
|
||||
| `iklim-db-01` | `CPX32` | RAM/disk yeterli olabilir, ancak CPU spike'ları nedeniyle tek node DB'de riskli |
|
||||
|
||||
### Test Ortamı Operasyon Notları
|
||||
|
||||
- App node'da mikroservisler için JVM/container memory limitleri tanımlanmalıdır.
|
||||
- Swap varsayılan olarak yoksa düşük öncelikli acil durum swap'i değerlendirilebilir; bu performans çözümünden çok OOM etkisini yumuşatma aracıdır.
|
||||
- Docker image prune/log rotation politikası uygulanmalıdır.
|
||||
- Prometheus retention test ortamında sınırlandırılmalıdır.
|
||||
- DB ve app aynı node'a alınmamalıdır; mevcut veri ayrı DB node ihtiyacını desteklemektedir.
|
||||
|
||||
## Prod Ortamı Önerisi
|
||||
|
||||
Prod ortamında 3 app node ve 3 DB node olacak şekilde cluster topolojisi hedeflenir. Bu nedenle testteki tek node baskısı prod'da daha kontrollü dağıtılabilir.
|
||||
|
||||
### Önerilen Prod Başlangıç
|
||||
|
||||
| Sunucu | Rol | Önerilen Tip | CPU | RAM | SSD | Trafik | Aylık |
|
||||
| --- | --- | --- | ---: | ---: | ---: | ---: | ---: |
|
||||
| `iklim-app-01` | Swarm app node | `CPX42` | 8 AMD | 16 GB | 320 GB | 20 TB | $29.99 |
|
||||
| `iklim-app-02` | Swarm app node | `CPX42` | 8 AMD | 16 GB | 320 GB | 20 TB | $29.99 |
|
||||
| `iklim-app-03` | Swarm app node | `CPX42` | 8 AMD | 16 GB | 320 GB | 20 TB | $29.99 |
|
||||
| `iklim-db-01` | DB cluster node | `CPX32` | 4 AMD | 8 GB | 160 GB | 20 TB | $16.49 |
|
||||
| `iklim-db-02` | DB cluster node | `CPX32` | 4 AMD | 8 GB | 160 GB | 20 TB | $16.49 |
|
||||
| `iklim-db-03` | DB cluster node | `CPX32` | 4 AMD | 8 GB | 160 GB | 20 TB | $16.49 |
|
||||
| **Toplam** | 6 sunucu | | **36 vCPU** | **72 GB** | **1,440 GB** | **120 TB** | **$139.44** |
|
||||
|
||||
### Prod Gerekçesi
|
||||
|
||||
App katmanı:
|
||||
|
||||
- Test app verisi 16 GB sınıfı node'da 10 GiB used RAM göstermektedir.
|
||||
- Prod'da trafik ve deploy frekansı artacağı için app node'larda `CPX32` yerine `CPX42` ile başlamak daha güvenlidir.
|
||||
- 3 node sayesinde servisler dağıtılabilir; ancak Java mikroservislerin bellek karakteristiği nedeniyle node başına 16 GB RAM daha doğru başlangıçtır.
|
||||
|
||||
DB katmanı:
|
||||
|
||||
- Prod DB 3 node cluster olacağı için test DB'deki tek node riskinin aynısı birebir geçerli değildir.
|
||||
- Başlangıç veri hacmi ve trafik düşük/orta ise `CPX32` ekonomik ve savunulabilir başlangıçtır.
|
||||
- Büyüme görüldüğünde DB node'lar sırasıyla `CPX42`, `CPX52` veya `CPX62` modellerine rescale edilebilir.
|
||||
- Lokal NVMe disk DB ana veri dizini için varsayılan tercih olarak korunmalıdır.
|
||||
|
||||
### Prod Büyüme Yolu
|
||||
|
||||
| Tetikleyici | İlk Aksiyon | İkinci Aksiyon |
|
||||
| --- | --- | --- |
|
||||
| App RAM baskısı | Servis memory limitlerini düzenle | App node'ları `CPX52` veya yeni app node'a genişlet |
|
||||
| App CPU baskısı | Replica dağılımını düzenle | App node ekle veya `CPX52`'ye rescale et |
|
||||
| DB CPU baskısı | Query/index analizi yap | DB node'ları `CPX42` veya `CPX52`'ye rescale et |
|
||||
| DB RAM baskısı | Cache/config optimizasyonu yap | DB node'ları `CPX42` veya `CPX52`'ye rescale et |
|
||||
| DB disk kapasitesi | Retention/cleanup uygula | Önce rescale, sonra ölçümlü ek volume |
|
||||
| Disk IOPS baskısı | Query ve storage analizi yap | Lokal NVMe üzerinde daha büyük modele geç |
|
||||
|
||||
## Ek Volume Değerlendirmesi
|
||||
|
||||
DB ana veri dizinini başlangıçta Hetzner Volume üzerine taşımak önerilmez. Başlangıç için lokal NVMe disk daha güvenli varsayımdır.
|
||||
|
||||
Ek volume şu işler için daha uygundur:
|
||||
|
||||
- Backup dump dosyaları.
|
||||
- Arşiv verisi.
|
||||
- Düşük IOPS isteyen cold data.
|
||||
- Geçici export/import alanları.
|
||||
- Uygulama tarafında object-like dosya depolama ihtiyaçları.
|
||||
|
||||
DB ana data dizini için ek volume ancak aşağıdaki koşullarda değerlendirilmelidir:
|
||||
|
||||
- Volume performansı hedef DB workload'u ile ölçülmüş olmalı.
|
||||
- IOPS, latency ve throughput metrikleri lokal disk ile karşılaştırılmalı.
|
||||
- Backup/restore ve failover senaryoları netleştirilmeli.
|
||||
- DB engine tarafında tablespace/partition yaklaşımı bilinçli tasarlanmalı.
|
||||
|
||||
Kapasite yetmezliği durumunda ilk tercih genellikle ek volume değil, sunucuyu daha büyük `CPX` modele rescale etmek olmalıdır.
|
||||
|
||||
## Terraform Hedef Değerleri
|
||||
|
||||
Terraform'da Hetzner server type değerleri küçük harfle kullanılmalıdır.
|
||||
|
||||
### Test
|
||||
|
||||
| Terraform Değişkeni | Hedef Değer | Gerekçe |
|
||||
| --- | --- | --- |
|
||||
| `server_type_swarm` | `cpx42` | Test app node için 16 GB RAM gerekli |
|
||||
| `server_type_db` | `cpx42` | Tek node DB'de CPU spike riskini azaltır |
|
||||
|
||||
### Prod
|
||||
|
||||
| Terraform Değişkeni | Hedef Değer | Gerekçe |
|
||||
| --- | --- | --- |
|
||||
| `server_type_swarm` | `cpx42` | Java mikroservis yoğun app node'ları için dengeli başlangıç |
|
||||
| `server_type_db` | `cpx32` | 3 node DB cluster için ekonomik başlangıç |
|
||||
|
||||
## Maliyet Özeti
|
||||
|
||||
### Test Maliyeti
|
||||
|
||||
| Kalem | Tip | Adet | Birim Aylık | Aylık Toplam |
|
||||
| --- | --- | ---: | ---: | ---: |
|
||||
| App | `CPX42` | 1 | $29.99 | $29.99 |
|
||||
| DB | `CPX42` | 1 | $29.99 | $29.99 |
|
||||
| **Toplam** | | **2** | | **$59.98** |
|
||||
|
||||
### Prod Maliyeti
|
||||
|
||||
| Kalem | Tip | Adet | Birim Aylık | Aylık Toplam |
|
||||
| --- | --- | ---: | ---: | ---: |
|
||||
| App | `CPX42` | 3 | $29.99 | $89.97 |
|
||||
| DB | `CPX32` | 3 | $16.49 | $49.47 |
|
||||
| **Toplam** | | **6** | | **$139.44** |
|
||||
|
||||
### Genel Toplam
|
||||
|
||||
| Ortam | Sunucu Adedi | Aylık Toplam |
|
||||
| --- | ---: | ---: |
|
||||
| Test | 2 | $59.98 |
|
||||
| Prod | 6 | $139.44 |
|
||||
| **Genel Toplam** | **8** | **$199.42** |
|
||||
|
||||
## Sonuç
|
||||
|
||||
Yeni test ortamında `CPX42 + CPX42` seçimi mevcut test verileriyle uyumludur. App node için `CPX32` RAM nedeniyle risklidir; DB node için `CPX32` ise tek node CPU spike'ları nedeniyle gereksiz risk taşır.
|
||||
|
||||
Prod ortamında `3 x CPX42 app` ve `3 x CPX32 DB` ekonomik ama teknik olarak savunulabilir bir başlangıçtır. Bu seçim, app katmanındaki bellek baskısını azaltır ve DB katmanında cluster yapısının sağladığı yatay kapasiteyi kullanır. Büyüme görüldüğünde önce metriklerle doğrulama yapılmalı, ardından rescale veya node ekleme tercih edilmelidir.
|
||||
@ -72,7 +72,7 @@ Mevcut uygulama stack dosyalarinda bazi servisler host port publish ediyor olabi
|
||||
|
||||
## Private Network Politikasi
|
||||
|
||||
Private network icinde acilmasi gereken portlarin ayrintili matrisi `07-private-network-port-matrisi.md` dosyasindadir. Ajanlar firewall veya Ansible UFW kurali yazarken bu dosyayi kaynak kabul etmelidir.
|
||||
Private network icinde acilmasi gereken portlarin ayrintili matrisi `01-private-network-port-matrisi.md` dosyasindadir. Ajanlar firewall veya Ansible UFW kurali yazarken bu dosyayi kaynak kabul etmelidir.
|
||||
|
||||
## Gitea Actions Runner Karari
|
||||
|
||||
@ -97,12 +97,9 @@ Test icin tek runner yeterlidir:
|
||||
|
||||
## Deploy Lock Karari
|
||||
|
||||
Prod ortaminda 3 runner HA icin gereklidir; ancak ayni anda birden fazla deploy job'u
|
||||
calistirabilir. Bu nedenle prod deploy islemleri StorageBox uzerinde otomatik lock ile
|
||||
tekillestirilmelidir.
|
||||
Prod ortaminda 3 runner HA icin gereklidir; ancak ayni anda birden fazla deploy job'u calistirabilir. Bu nedenle prod deploy islemleri StorageBox uzerinde otomatik lock ile tekillestirilmelidir.
|
||||
|
||||
Lock dosyalari/klasorleri manuel olusturulmayacak. Workflow basinda atomik `mkdir`
|
||||
ile olusturulacak, deploy bitince `rmdir` ile silinecek.
|
||||
Lock dosyalari/klasorleri manuel olusturulmayacak. Workflow basinda atomik `mkdir` ile olusturulacak, deploy bitince `rmdir` ile silinecek.
|
||||
|
||||
Onerilen StorageBox path'leri:
|
||||
|
||||
@ -118,8 +115,7 @@ Baslangic icin en sade ve guvenli model tek global prod deploy lock'tur:
|
||||
prod/locks/prod-deploy.lock
|
||||
```
|
||||
|
||||
Bu model tum prod deploy'lari siraya sokar. Daha sonra ihtiyac olursa servis bazli
|
||||
lock modeline gecilebilir.
|
||||
Bu model tum prod deploy'lari siraya sokar. Daha sonra ihtiyac olursa servis bazli lock modeline gecilebilir.
|
||||
|
||||
Ornek akış:
|
||||
|
||||
@ -129,10 +125,7 @@ ssh storagebox 'mkdir -p prod/locks && mkdir prod/locks/prod-deploy.lock'
|
||||
ssh storagebox 'rmdir prod/locks/prod-deploy.lock'
|
||||
```
|
||||
|
||||
`mkdir` atomik oldugu icin lock zaten varsa komut fail olur; bu durumda job beklemeli
|
||||
veya temiz bir hata ile cikmalidir. Workflow fail olsa bile cleanup adimi lock'u silmeye
|
||||
calismalidir. Eski kalmis lock'lari tespit etmek icin lock klasoru icine timestamp,
|
||||
runner adi ve workflow bilgisi yazilabilir.
|
||||
`mkdir` atomik oldugu icin lock zaten varsa komut fail olur; bu durumda job beklemeli veya temiz bir hata ile cikmalidir. Workflow fail olsa bile cleanup adimi lock'u silmeye calismalidir. Eski kalmis lock'lari tespit etmek icin lock klasoru icine timestamp, runner adi ve workflow bilgisi yazilabilir.
|
||||
|
||||
## Hetzner Fiziksel Host Ayrimi
|
||||
|
||||
|
||||
@ -85,22 +85,17 @@ Mevcut `docker-stack-infra.yml` bazi servisleri host mode ile publish ediyor ola
|
||||
|
||||
DB altyapisi manuel kurulacagi icin kesin cluster teknolojisi bu dokumanin disindadir. Yine de firewall icin varsayilan portlar asagidadir.
|
||||
|
||||
### PostgreSQL / PostGIS
|
||||
### PostgreSQL / PostGIS (Patroni + etcd)
|
||||
|
||||
Prod ortami Patroni + etcd ile yonetilen PostgreSQL kullanir. Test ortaminda tek node oldugu icin replication ve HA portlari gerekmez.
|
||||
|
||||
| Port | Protocol | Kaynak | Hedef | Not |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `5432` | TCP | App/Swarm subnet | PostgreSQL node/cluster endpoint | Uygulama DB baglantisi |
|
||||
| `5432` | TCP | DB subnet | PostgreSQL node'lari | Streaming replication ayni portu kullanabilir |
|
||||
|
||||
Eger Patroni kullanilirsa ek portlar daha sonra DB runbook'unda netlestirilmelidir:
|
||||
|
||||
| Port | Protocol | Amac |
|
||||
| --- | --- | --- |
|
||||
| `8008` | TCP | Patroni REST API |
|
||||
| `2379-2380` | TCP | Patroni icin etcd kullanilirsa etcd client/peer |
|
||||
| `5000-5001` | TCP | HAProxy veya benzeri DB endpoint kullanilirsa |
|
||||
|
||||
Bu ek portlar ancak ilgili teknoloji secildiginde acilmalidir.
|
||||
| `5432` | TCP | App/Swarm subnet | PostgreSQL node'lari (Patroni yonetimli) | Uygulama JDBC — tum node'lara baglanir, driver primary'i bulur |
|
||||
| `5432` | TCP | DB subnet | PostgreSQL node'lari | Patroni replication (pg_basebackup ve wal streaming) |
|
||||
| `8008` | TCP | DB subnet | PostgreSQL node'lari | Patroni REST API — leader election, saglik kontrolu |
|
||||
| `2379` | TCP | DB subnet | etcd node'lari | etcd client — Patroni → etcd erisimi |
|
||||
| `2380` | TCP | DB subnet | etcd node'lari | etcd peer — etcd cluster icindeki raft protokolu |
|
||||
|
||||
### MongoDB
|
||||
|
||||
@ -126,17 +121,36 @@ Testte DB node tek oldugu icin DB subnet icindeki PostgreSQL/MongoDB replication
|
||||
|
||||
## Prod Private Kurallari
|
||||
|
||||
Prod ortaminda minimum:
|
||||
Prod ortaminda minimum (Patroni + etcd dahil):
|
||||
|
||||
App subnet (swarm firewall) — kendi icindeki trafik:
|
||||
|
||||
| Kaynak | Hedef | Portlar |
|
||||
| --- | --- | --- |
|
||||
| `10.20.10.0/24` | `10.20.10.0/24` | `2377/tcp`, `7946/tcp`, `7946/udp`, `4789/udp` |
|
||||
| `10.20.10.0/24` | `10.20.20.0/24` | `5432/tcp`, `27017/tcp` |
|
||||
| `10.20.20.0/24` | `10.20.20.0/24` | `5432/tcp`, `27017/tcp` |
|
||||
| `10.20.10.0/24` | `10.20.10.0/24` | `8200/tcp`, `6379/tcp`, `5672/tcp`, `61613/tcp`, `15674/tcp`, `2379/tcp` |
|
||||
| `10.20.10.0/24` | `10.20.10.0/24` | `2377/tcp`, `7946/tcp`, `7946/udp`, `4789/udp` (Swarm) |
|
||||
| `10.20.10.0/24` | `10.20.10.0/24` | `8200/tcp`, `6379/tcp`, `5672/tcp`, `61613/tcp`, `15674/tcp`, `2379/tcp` (uygulama servisleri) |
|
||||
| Admin CIDR veya VPN | `10.20.10.0/24` | `15672/tcp`, `9180/tcp`, `9090/tcp`, `3000/tcp` |
|
||||
|
||||
Patroni, HAProxy, Mongo sharding veya ayri monitoring agent mimarisi secilirse bu matrise ek portlar kontrollu sekilde eklenmelidir.
|
||||
App → DB trafigi (swarm firewall'da ilgili kural bulunmaz; db firewall'da izin verilir):
|
||||
|
||||
| Kaynak | Hedef | Portlar |
|
||||
| --- | --- | --- |
|
||||
| `10.20.10.0/24` | `10.20.20.0/24` | `5432/tcp`, `27017/tcp` (DB erisimi) |
|
||||
| `10.20.10.0/24` | `10.20.20.0/24` | `2377/tcp`, `7946/tcp`, `7946/udp`, `4789/udp` (Swarm — DB worker join) |
|
||||
|
||||
DB subnet (db firewall) — DB node'lari arasi trafik:
|
||||
|
||||
| Kaynak | Hedef | Portlar |
|
||||
| --- | --- | --- |
|
||||
| `10.20.20.0/24` | `10.20.20.0/24` | `5432/tcp`, `27017/tcp` (DB replication) |
|
||||
| `10.20.20.0/24` | `10.20.20.0/24` | `2379/tcp`, `2380/tcp` (etcd client/peer) |
|
||||
| `10.20.20.0/24` | `10.20.20.0/24` | `8008/tcp` (Patroni REST API) |
|
||||
|
||||
DB → App trafigi (swarm firewall'da izin verilir):
|
||||
|
||||
| Kaynak | Hedef | Portlar |
|
||||
| --- | --- | --- |
|
||||
| `10.20.20.0/24` | `10.20.10.0/24` | `2377/tcp`, `7946/tcp`, `7946/udp`, `4789/udp` (Swarm — manager portlari) |
|
||||
|
||||
## Kabul Kriterleri
|
||||
|
||||
@ -12,7 +12,7 @@ Terraform test ortaminda sunlari olusturur:
|
||||
- DB subnet: `10.10.20.0/24`
|
||||
- Firewall:
|
||||
- Public ingress: sadece `22/tcp`, `80/tcp`, `443/tcp`
|
||||
- Private ingress: `07-private-network-port-matrisi.md` dosyasindaki test kurallari
|
||||
- Private ingress: `01-private-network-port-matrisi.md` dosyasindaki test kurallari
|
||||
- SSH key
|
||||
- Placement group: `iklim-test-spread`
|
||||
- Floating IP: swarm entry point icin sabit IPv4
|
||||
@ -51,8 +51,8 @@ Minimum degiskenler:
|
||||
hcloud_token = "secret"
|
||||
location = "fsn1"
|
||||
image = "rocky-10"
|
||||
server_type_swarm = "cx32"
|
||||
server_type_db = "cx42"
|
||||
server_type_swarm = "cpx42"
|
||||
server_type_db = "cpx42"
|
||||
admin_ssh_public_key_path = "~/.ssh/id_ed25519.pub"
|
||||
admin_allowed_cidrs = ["X.X.X.X/32"]
|
||||
```
|
||||
@ -61,6 +61,11 @@ admin_allowed_cidrs = ["X.X.X.X/32"]
|
||||
|
||||
`location` icin tek lokasyonla baslanir. Farkli region/lokasyon felaket kurtarma bu asamada konu disidir; ileride dokumana eklenmelidir.
|
||||
|
||||
Server type karari `../hetzner-sizing-report.md` dokumanindaki mevcut test
|
||||
ortami metriklerine dayanir. Test app node uzerinde 10 mikroservis ve altyapi
|
||||
servisleri birlikte calistigi icin `cpx32` RAM acisindan riskli bulunmustur.
|
||||
Test DB node icin de tek node CPU spike riski nedeniyle `cpx42` onerilir.
|
||||
|
||||
## Server Rolleri
|
||||
|
||||
| Server | Private IP | Rol |
|
||||
@ -70,6 +75,14 @@ admin_allowed_cidrs = ["X.X.X.X/32"]
|
||||
|
||||
Private IP'ler Terraform icinde sabit tanimlanmalidir. Ansible inventory ve firewall kurallari deterministik kalir.
|
||||
|
||||
## Onerilen Kaynaklar ve Maliyet
|
||||
|
||||
| Server | Rol | Server Type | CPU | RAM | SSD | Aylik |
|
||||
| --- | --- | --- | ---: | ---: | ---: | ---: |
|
||||
| `iklim-app-01` | Swarm manager + app worker + Gitea runner | `cpx42` | 8 AMD | 16 GB | 320 GB | $29.99 |
|
||||
| `iklim-db-01` | PostgreSQL/PostGIS + MongoDB node | `cpx42` | 8 AMD | 16 GB | 320 GB | $29.99 |
|
||||
| **Toplam** | 2 server | | **16 vCPU** | **32 GB** | **640 GB** | **$59.98** |
|
||||
|
||||
## Firewall Kurallari
|
||||
|
||||
Public ingress:
|
||||
@ -82,21 +95,49 @@ Public ingress:
|
||||
|
||||
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 (app subnet `10.10.10.0/24` kaynakli):
|
||||
### App (swarm) Firewall — Private Ingress
|
||||
|
||||
App subnet kaynakli (iklim-app-01):
|
||||
|
||||
| Port | Servis | Erisim yontemi |
|
||||
| --- | --- | --- |
|
||||
| `2377/tcp` | Docker Swarm control plane | App subnet icinden |
|
||||
| `7946/tcp,udp` | Docker Swarm node discovery | App subnet icinden |
|
||||
| `4789/udp` | Docker Swarm VXLAN overlay | App subnet icinden |
|
||||
| `8200/tcp` | Vault | Docker overlay / private network |
|
||||
| `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 |
|
||||
| `15672/tcp` | RabbitMQ Management | 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 |
|
||||
| `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 |
|
||||
|
||||
DB subnet kaynakli (`iklim-db-01` Swarm'a worker olarak katildigi icin):
|
||||
|
||||
| Port | Servis | Kaynak |
|
||||
| --- | --- | --- |
|
||||
| `2377/tcp` | Docker Swarm control plane | `10.10.20.0/24` |
|
||||
| `7946/tcp,udp` | Docker Swarm node discovery | `10.10.20.0/24` |
|
||||
| `4789/udp` | Docker Swarm VXLAN overlay | `10.10.20.0/24` |
|
||||
|
||||
### DB Firewall — Private Ingress
|
||||
|
||||
| Port | Servis | Kaynak |
|
||||
| --- | --- | --- |
|
||||
| `22/tcp` | SSH | `admin_allowed_cidrs` |
|
||||
| `5432/tcp` | PostgreSQL | `10.10.10.0/24` (app subnet) |
|
||||
| `27017/tcp` | MongoDB | `10.10.10.0/24` (app subnet) |
|
||||
| `2377/tcp` | Docker Swarm control plane | `10.10.10.0/24` (app subnet) |
|
||||
| `7946/tcp,udp` | Docker Swarm node discovery | `10.10.10.0/24` (app subnet) |
|
||||
| `4789/udp` | Docker Swarm VXLAN overlay | `10.10.10.0/24` (app subnet) |
|
||||
|
||||
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.
|
||||
Diger private ingress kurallari icin `01-private-network-port-matrisi.md` kaynak alinacak.
|
||||
|
||||
## Placement Group
|
||||
|
||||
@ -172,4 +213,3 @@ Kasitli silmek icin once lifecycle blogunu gecici olarak kaldir.
|
||||
- Public internetten sadece `22`, `80`, `443` firewall seviyesinde aciktir.
|
||||
- Vault `8200` public'ten kapali kalir.
|
||||
- Terraform state repo'ya commit edilmez.
|
||||
|
||||
@ -36,12 +36,17 @@ ansible/
|
||||
Tum test node'larina uygulanir:
|
||||
|
||||
- `dnf update`
|
||||
- temel paketler:
|
||||
- temel paketler (sirasıyla kurulur):
|
||||
- `epel-release` — fail2ban ve davfs2 bu repo'dan gelir; once kurulur
|
||||
- `curl`
|
||||
- `wget`
|
||||
- `git`
|
||||
- `jq`
|
||||
- `tar`
|
||||
- `unzip`
|
||||
- `bash-completion`
|
||||
- `gettext` — envsubst icin; CI/CD deploy pipeline'larinda gerekli
|
||||
- `tree`
|
||||
- `ca-certificates`
|
||||
- `fail2ban`
|
||||
- `chrony`
|
||||
@ -67,6 +72,59 @@ Tum test node'larina uygulanir:
|
||||
- outgoing: allow
|
||||
- Public SSH sadece admin CIDR'dan acilir.
|
||||
|
||||
### SELinux Karari
|
||||
|
||||
Rocky Linux 10 SELinux enforcing modda gelir. Karar: **disabled**.
|
||||
|
||||
Gerekce:
|
||||
- Hetzner Cloud firewall (dis perimeter) + firewalld (host) iki katman ag guvenligini saglar.
|
||||
- Docker + davfs2 + firewalld kombinasyonu SELinux enforcing modda ek policy ve volume label yonetimi gerektirir.
|
||||
- Utils VPS'te de disabled yapilmis; tutarlilik saglanir.
|
||||
|
||||
```bash
|
||||
# /etc/selinux/config icinde:
|
||||
SELINUX=disabled
|
||||
# Degisiklik reboot sonrasi aktif olur
|
||||
reboot
|
||||
```
|
||||
|
||||
Ansible'da:
|
||||
|
||||
```yaml
|
||||
- name: Disable SELinux
|
||||
ansible.posix.selinux:
|
||||
state: disabled
|
||||
register: selinux_change
|
||||
|
||||
- name: Reboot if SELinux state changed
|
||||
ansible.builtin.reboot:
|
||||
when: selinux_change.changed
|
||||
```
|
||||
|
||||
### fail2ban Konfigurasyonu
|
||||
|
||||
`/etc/fail2ban/jail.local` icerigi:
|
||||
|
||||
```ini
|
||||
[DEFAULT]
|
||||
ignoreip = 127.0.0.1/8 {{ admin_allowed_cidrs }}
|
||||
bantime = 21600
|
||||
findtime = 300
|
||||
maxretry = 5
|
||||
banaction = iptables-multiport
|
||||
backend = systemd
|
||||
|
||||
[sshd]
|
||||
enabled = true
|
||||
```
|
||||
|
||||
- `bantime`: 6 saat ban
|
||||
- `findtime`: 5 dakika icinde
|
||||
- `maxretry`: 5 basarisiz giris → ban
|
||||
- `ignoreip`: admin CIDR'lari ban'dan muaf tutar
|
||||
|
||||
Ansible'da `admin_allowed_cidrs` listesi space-separated stringe donusturulup template'e basilir.
|
||||
|
||||
Not: Docker iptables kurallari firewalld ile etkilesebilir. Hetzner Cloud firewall asil dis perimeter kabul edilir; firewalld host icinde ikinci katman olarak kullanilir.
|
||||
|
||||
## Docker Role
|
||||
@ -243,10 +301,69 @@ vault_storagebox_password: "SUB_ACCOUNT_PAROLASI"
|
||||
|
||||
### Notlar
|
||||
|
||||
- `davfs2` paketi EPEL repository'sinde bulunur; base role'de `dnf install epel-release` yapilmalidir.
|
||||
- `davfs2` paketi EPEL repository'sinde bulunur; base role `epel-release`'i zaten kurar.
|
||||
- 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/`.
|
||||
- Docker Swarm servisleri `/mnt/storagebox/<env>/<service>/` altindaki dizinleri bind mount olarak kullanir.
|
||||
|
||||
## StorageBox SSH Key Role
|
||||
|
||||
Her iki node'a uygulanir (`iklim-app-01` ve `iklim-db-01`).
|
||||
|
||||
### Amac
|
||||
|
||||
Sunucu uzerinde ed25519 SSH anahtar cifti uretilir ve StorageBox ana hesabina yuklenir.
|
||||
Bu sayede CI/CD pipeline'lari `STORAGEBOX_SSH_PRIV` Gitea secret'ini kullanarak
|
||||
sifre girmeden StorageBox'a erisebilir.
|
||||
|
||||
### Adimlar
|
||||
|
||||
1. **SSH key uret** (eger yoksa)
|
||||
|
||||
```yaml
|
||||
- name: Generate SSH key for storagebox
|
||||
ansible.builtin.user:
|
||||
name: root
|
||||
generate_ssh_key: yes
|
||||
ssh_key_type: ed25519
|
||||
ssh_key_file: /root/.ssh/id_ed25519_storagebox
|
||||
ssh_key_comment: "{{ inventory_hostname }}-storagebox"
|
||||
```
|
||||
|
||||
2. **Public key'i StorageBox'a yukle**
|
||||
|
||||
Bu adim manuel yapilir (ilk kez sifre gerektirir):
|
||||
|
||||
```bash
|
||||
cat /root/.ssh/id_ed25519_storagebox.pub | ssh -p23 u469968-sub1@u469968-sub1.your-storagebox.de install-ssh-key
|
||||
```
|
||||
|
||||
Sonraki erisimler sifresiz calisir:
|
||||
|
||||
```bash
|
||||
sftp -P23 u469968-sub1@u469968-sub1.your-storagebox.de
|
||||
```
|
||||
|
||||
3. **Private ve public key'leri Gitea'ya ekle**
|
||||
|
||||
Gitea → Organization Settings → Actions → Secrets:
|
||||
|
||||
| Secret Adi | Deger |
|
||||
| --- | --- |
|
||||
| `STORAGEBOX_SSH_PRIV` | `/root/.ssh/id_ed25519_storagebox` icerigi |
|
||||
| `STORAGEBOX_SSH_PUB` | `/root/.ssh/id_ed25519_storagebox.pub` icerigi |
|
||||
|
||||
Key icerigini almak icin:
|
||||
|
||||
```bash
|
||||
cat /root/.ssh/id_ed25519_storagebox
|
||||
cat /root/.ssh/id_ed25519_storagebox.pub
|
||||
```
|
||||
|
||||
### Notlar
|
||||
|
||||
- Her sunucu icin ayri key uretilir; tum public key'ler StorageBox ana hesabina yuklenir.
|
||||
- Private key asla repo'ya commit edilmez; yalnizca Gitea secret olarak saklanir.
|
||||
|
||||
## Kabul Kriterleri
|
||||
|
||||
286
setup/04-test-db-docker-kurulum.md
Normal file
286
setup/04-test-db-docker-kurulum.md
Normal file
@ -0,0 +1,286 @@
|
||||
# 08 - Test DB Docker Kurulumu (Swarm)
|
||||
|
||||
Bu asamanin amaci `iklim-db-01` node'unu Swarm'a worker olarak eklemek ve PostgreSQL ile MongoDB'yi Swarm servisi olarak calistirmaktir. Veri kaliciligini StorageBox saglar.
|
||||
|
||||
DB yazilimi Ansible tarafindan kurulmaz; bu belge DB node uzerinde elle veya ayri bir Ansible role ile uygulanir. `03-test-ansible-bootstrap.md` tamamlandiktan sonra baslayiniz.
|
||||
|
||||
## Mimari
|
||||
|
||||
```
|
||||
iklim-app-01 (Swarm manager, 10.10.10.11)
|
||||
|
|
||||
|-- iklimco-net (overlay)
|
||||
|
|
||||
iklim-db-01 (Swarm worker, 10.10.20.11) [role=db]
|
||||
|-- postgresql-v17 (Swarm service, placement: role=db)
|
||||
|-- mongo-v8 (Swarm service, placement: role=db)
|
||||
|
|
||||
/mnt/storagebox/test/db/
|
||||
postgresql/data/
|
||||
mongodb/data/
|
||||
mongodb/log/
|
||||
mongodb/config/
|
||||
```
|
||||
|
||||
## On Kosullar
|
||||
|
||||
- `03-test-ansible-bootstrap.md` her iki node'da tamamlanmis olmali.
|
||||
- StorageBox `/mnt/storagebox` olarak her iki node'da mount edilmis olmali.
|
||||
- Docker `iklim-db-01` uzerinde kurulu olmali (bootstrap role bunu yapar).
|
||||
|
||||
## 1. Firewall Guncellemesi
|
||||
|
||||
`iklim-db-01`'in Swarm'a katilabilmesi icin ek kurallara ihtiyac var.
|
||||
`terraform/hetzner/test/firewall.tf` dosyasina asagidaki kurallari ekle:
|
||||
|
||||
`hcloud_firewall.swarm` icine (DB subnet'ten Swarm portlarina erisim):
|
||||
|
||||
```hcl
|
||||
rule {
|
||||
direction = "in"
|
||||
protocol = "tcp"
|
||||
port = "2377"
|
||||
source_ips = [local.db_subnet_cidr]
|
||||
description = "Docker Swarm control plane from DB subnet"
|
||||
}
|
||||
|
||||
rule {
|
||||
direction = "in"
|
||||
protocol = "tcp"
|
||||
port = "7946"
|
||||
source_ips = [local.db_subnet_cidr]
|
||||
description = "Docker Swarm node discovery (TCP) from DB subnet"
|
||||
}
|
||||
|
||||
rule {
|
||||
direction = "in"
|
||||
protocol = "udp"
|
||||
port = "7946"
|
||||
source_ips = [local.db_subnet_cidr]
|
||||
description = "Docker Swarm node discovery (UDP) from DB subnet"
|
||||
}
|
||||
|
||||
rule {
|
||||
direction = "in"
|
||||
protocol = "udp"
|
||||
port = "4789"
|
||||
source_ips = [local.db_subnet_cidr]
|
||||
description = "Docker Swarm VXLAN overlay from DB subnet"
|
||||
}
|
||||
```
|
||||
|
||||
`hcloud_firewall.db` icine (app subnet'ten Swarm portlarina erisim):
|
||||
|
||||
```hcl
|
||||
rule {
|
||||
direction = "in"
|
||||
protocol = "tcp"
|
||||
port = "2377"
|
||||
source_ips = [local.app_subnet_cidr]
|
||||
description = "Docker Swarm control plane from app subnet"
|
||||
}
|
||||
|
||||
rule {
|
||||
direction = "in"
|
||||
protocol = "tcp"
|
||||
port = "7946"
|
||||
source_ips = [local.app_subnet_cidr]
|
||||
description = "Docker Swarm node discovery (TCP) from app subnet"
|
||||
}
|
||||
|
||||
rule {
|
||||
direction = "in"
|
||||
protocol = "udp"
|
||||
port = "7946"
|
||||
source_ips = [local.app_subnet_cidr]
|
||||
description = "Docker Swarm node discovery (UDP) from app subnet"
|
||||
}
|
||||
|
||||
rule {
|
||||
direction = "in"
|
||||
protocol = "udp"
|
||||
port = "4789"
|
||||
source_ips = [local.app_subnet_cidr]
|
||||
description = "Docker Swarm VXLAN overlay from app subnet"
|
||||
}
|
||||
```
|
||||
|
||||
Sonra uygula:
|
||||
|
||||
```bash
|
||||
cd terraform/hetzner/test
|
||||
terraform apply
|
||||
```
|
||||
|
||||
## 2. DB Node'u Swarm'a Ekleme
|
||||
|
||||
**iklim-app-01 uzerinde** join token al:
|
||||
|
||||
```bash
|
||||
docker swarm join-token worker
|
||||
```
|
||||
|
||||
**iklim-db-01 uzerinde** Swarm'a katil:
|
||||
|
||||
```bash
|
||||
docker swarm join --token <TOKEN> 10.10.10.11:2377
|
||||
```
|
||||
|
||||
**iklim-app-01 uzerinde** node'u etiketle:
|
||||
|
||||
```bash
|
||||
docker node update --label-add role=db iklim-db-01
|
||||
docker node ls
|
||||
```
|
||||
|
||||
## 3. StorageBox Dizin Yapisi
|
||||
|
||||
**iklim-db-01 uzerinde:**
|
||||
|
||||
```bash
|
||||
mkdir -p /mnt/storagebox/test/db/postgresql/data
|
||||
mkdir -p /mnt/storagebox/test/db/mongodb/data
|
||||
mkdir -p /mnt/storagebox/test/db/mongodb/log
|
||||
mkdir -p /mnt/storagebox/test/db/mongodb/config
|
||||
```
|
||||
|
||||
## 4. PostgreSQL Stack
|
||||
|
||||
### mongod.conf
|
||||
|
||||
`/mnt/storagebox/test/db/mongodb/config/mongod.conf` dosyasini olustur:
|
||||
|
||||
```yaml
|
||||
processManagement:
|
||||
pidFilePath: "/data/db/mongod.pid"
|
||||
net:
|
||||
port: 27017
|
||||
storage:
|
||||
engine: "wiredTiger"
|
||||
dbPath: "/data/db"
|
||||
directoryPerDB: true
|
||||
systemLog:
|
||||
verbosity: 0
|
||||
timeStampFormat: "iso8601-local"
|
||||
destination: file
|
||||
path: "/data/log/mongo.log"
|
||||
logAppend: true
|
||||
logRotate: rename
|
||||
security:
|
||||
authorization: enabled
|
||||
```
|
||||
|
||||
### Stack Dosyasi
|
||||
|
||||
`/opt/iklimco/stacks/db.yml`:
|
||||
|
||||
```yaml
|
||||
version: "3.8"
|
||||
|
||||
networks:
|
||||
iklimco-net:
|
||||
external: true
|
||||
|
||||
services:
|
||||
postgresql:
|
||||
image: postgis/postgis:17-3.5
|
||||
environment:
|
||||
POSTGRES_USER: "${DATABASE_POSTGRES_ROOT_USER}"
|
||||
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
|
||||
POSTGRES_DB: postgres
|
||||
PGDATA: /var/lib/postgresql/data/pgdata
|
||||
TZ: "Europe/Istanbul"
|
||||
volumes:
|
||||
- /mnt/storagebox/test/db/postgresql/data:/var/lib/postgresql/data
|
||||
- /opt/iklimco/init/postgresql:/docker-entrypoint-initdb.d:ro
|
||||
networks:
|
||||
- iklimco-net
|
||||
deploy:
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.labels.role == db
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
|
||||
mongodb:
|
||||
image: mongo:8
|
||||
environment:
|
||||
MONGO_INITDB_ROOT_USERNAME: mongo-root
|
||||
MONGO_INITDB_ROOT_PASSWORD: "${MONGO_ROOT_PASSWORD}"
|
||||
volumes:
|
||||
- /mnt/storagebox/test/db/mongodb/data:/data/db
|
||||
- /mnt/storagebox/test/db/mongodb/log:/data/log
|
||||
- /mnt/storagebox/test/db/mongodb/config:/data/configdb
|
||||
networks:
|
||||
- iklimco-net
|
||||
command: ["--config", "/data/configdb/mongod.conf"]
|
||||
deploy:
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.labels.role == db
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
```
|
||||
|
||||
### .env Dosyasi
|
||||
|
||||
`/opt/iklimco/stacks/.env` (repo'ya commit edilmez):
|
||||
|
||||
```env
|
||||
DATABASE_POSTGRES_ROOT_USER=<kullanici-adi>
|
||||
POSTGRES_PASSWORD=<guclu-sifre>
|
||||
MONGO_ROOT_PASSWORD=<guclu-sifre>
|
||||
```
|
||||
|
||||
## 5. Deploy
|
||||
|
||||
```bash
|
||||
# iklim-app-01 uzerinde (Swarm manager)
|
||||
docker stack deploy --compose-file /opt/iklimco/stacks/db.yml \
|
||||
--with-registry-auth \
|
||||
$(set -a; source /opt/iklimco/stacks/.env; set +a; echo "") \
|
||||
iklim-db
|
||||
```
|
||||
|
||||
Alternatif olarak env-file destegi icin:
|
||||
|
||||
```bash
|
||||
docker stack deploy \
|
||||
--compose-file <(docker-compose -f /opt/iklimco/stacks/db.yml config) \
|
||||
iklim-db
|
||||
```
|
||||
|
||||
Kontrol:
|
||||
|
||||
```bash
|
||||
docker stack services iklim-db
|
||||
docker service logs iklim-db_postgresql
|
||||
docker service logs iklim-db_mongodb
|
||||
```
|
||||
|
||||
## 6. App Servislerinden Erisim
|
||||
|
||||
Overlay network (`iklimco-net`) uzerinden servis adlariyla erisim:
|
||||
|
||||
| Servis | Host (overlay DNS) | Port |
|
||||
| --- | --- | --- |
|
||||
| PostgreSQL | `iklim-db_postgresql` | `5432` |
|
||||
| MongoDB | `iklim-db_mongodb` | `27017` |
|
||||
|
||||
Spring Boot vb. uygulama konfigurasyon ornegi:
|
||||
|
||||
```
|
||||
spring.datasource.url=jdbc:postgresql://iklim-db_postgresql:5432/iklimdb
|
||||
spring.data.mongodb.uri=mongodb://mongo-root:<sifre>@iklim-db_mongodb:27017/iklimdb?authSource=admin
|
||||
```
|
||||
|
||||
## Kabul Kriterleri
|
||||
|
||||
- `docker stack services iklim-db` her iki servisi `1/1` olarak gosterir.
|
||||
- `iklim-app-01` uzerinden `postgresql` servisine TCP 5432 overlay ile ulasilabilir.
|
||||
- `iklim-app-01` uzerinden `mongodb` servisine TCP 27017 overlay ile ulasilabilir.
|
||||
- `/mnt/storagebox/test/db/postgresql/data/` dizininde veri dosyalari olusur.
|
||||
- Servis yeniden baslatildiginda veri korunur.
|
||||
- `5432` ve `27017` portlari public internet'ten kapalidir.
|
||||
@ -101,12 +101,49 @@ Runner kurulumu ve pipeline calismasi icin secret'lar:
|
||||
|
||||
- Gitea runner registration token
|
||||
- Harbor username/password veya token
|
||||
- StorageBox credential
|
||||
- StorageBox SSH key (priv + pub)
|
||||
- SSH deploy key
|
||||
- Hetzner token gerekmez; Terraform asamasinda kullanilir
|
||||
|
||||
Bu secret'lar repo'ya yazilmayacak.
|
||||
|
||||
## Gitea Organizasyon Secret'lari
|
||||
|
||||
Gitea → `git.tarla.io` → Organization → **Settings** → **Actions** → **Secrets** altina eklenir.
|
||||
|
||||
| Secret | Aciklama | Kaynak |
|
||||
| --- | --- | --- |
|
||||
| `STORAGEBOX_SSH_PRIV` | StorageBox erisimi icin private SSH key | Her sunucuda `/root/.ssh/id_ed25519_storagebox` |
|
||||
| `STORAGEBOX_SSH_PUB` | Eslesen public key | Her sunucuda `/root/.ssh/id_ed25519_storagebox.pub` |
|
||||
| `REPO_ACCESS_TOKEN` | Pipeline'in Gitea API'sini cagirabileceği token | Gitea → User Settings → Applications → Access Tokens |
|
||||
|
||||
Workflow'da kullanim ornegi:
|
||||
|
||||
```yaml
|
||||
- name: Set up StorageBox SSH key
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
echo "${{ secrets.STORAGEBOX_SSH_PRIV }}" > ~/.ssh/id_ed25519_storagebox
|
||||
echo "${{ secrets.STORAGEBOX_SSH_PUB }}" > ~/.ssh/id_ed25519_storagebox.pub
|
||||
chmod 600 ~/.ssh/id_ed25519_storagebox
|
||||
ssh-keyscan -p 23 u469968-sub1.your-storagebox.de >> ~/.ssh/known_hosts
|
||||
```
|
||||
|
||||
### Ansible Vault Secret'lari
|
||||
|
||||
CI/CD disindaki secret'lar `ansible/group_vars/test-vault.yml` icinde Ansible Vault ile sifrelenir:
|
||||
|
||||
```bash
|
||||
ansible-vault edit ansible/group_vars/test-vault.yml
|
||||
```
|
||||
|
||||
Icerik:
|
||||
|
||||
```yaml
|
||||
vault_storagebox_password: "SUB_ACCOUNT_PAROLASI"
|
||||
vault_gitea_runner_token: "RUNNER_REGISTRATION_TOKEN"
|
||||
```
|
||||
|
||||
## Kabul Kriterleri
|
||||
|
||||
- `systemctl status gitea-act-runner` active gorunur.
|
||||
@ -12,7 +12,7 @@ Terraform prod ortaminda sunlari olusturur:
|
||||
- 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
|
||||
- Private ingress: `01-private-network-port-matrisi.md` dosyasindaki prod kurallari
|
||||
- SSH key
|
||||
- Placement groups:
|
||||
- `iklim-prod-app-spread`
|
||||
@ -60,13 +60,18 @@ Minimum degiskenler:
|
||||
hcloud_token = "secret"
|
||||
location = "fsn1"
|
||||
image = "rocky-10"
|
||||
server_type_swarm = "cx42"
|
||||
server_type_db = "cx52"
|
||||
server_type_swarm = "cpx42"
|
||||
server_type_db = "cpx32"
|
||||
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 type karari `../hetzner-sizing-report.md` dokumanindaki mevcut test
|
||||
ortami metrikleri ve prod cluster topolojisi dikkate alinarak belirlenmistir.
|
||||
Prod app node'lar icin Java mikroservis bellek baskisi nedeniyle `cpx42`,
|
||||
prod DB node'lar icin ise 3 node cluster baslangici nedeniyle ekonomik
|
||||
`cpx32` onerilir. Kapasite ihtiyaci metriklerle dogrulandiginda node ekleme
|
||||
veya in-place rescale yapilabilir.
|
||||
|
||||
## Server Rolleri ve Private IP Plani
|
||||
|
||||
@ -81,6 +86,18 @@ Server type degerleri kapasiteye gore degisebilir. Bu dokuman topoloji ve guvenl
|
||||
|
||||
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.
|
||||
|
||||
## Onerilen Kaynaklar ve Maliyet
|
||||
|
||||
| Server | Rol | Server Type | CPU | RAM | SSD | Aylik |
|
||||
| --- | --- | --- | ---: | ---: | ---: | ---: |
|
||||
| `iklim-app-01` | Swarm manager + app worker + runner | `cpx42` | 8 AMD | 16 GB | 320 GB | $29.99 |
|
||||
| `iklim-app-02` | Swarm manager + app worker + runner | `cpx42` | 8 AMD | 16 GB | 320 GB | $29.99 |
|
||||
| `iklim-app-03` | Swarm manager + app worker + runner | `cpx42` | 8 AMD | 16 GB | 320 GB | $29.99 |
|
||||
| `iklim-db-01` | DB cluster node | `cpx32` | 4 AMD | 8 GB | 160 GB | $16.49 |
|
||||
| `iklim-db-02` | DB cluster node | `cpx32` | 4 AMD | 8 GB | 160 GB | $16.49 |
|
||||
| `iklim-db-03` | DB cluster node | `cpx32` | 4 AMD | 8 GB | 160 GB | $16.49 |
|
||||
| **Toplam** | 6 server | | **36 vCPU** | **72 GB** | **1,440 GB** | **$139.44** |
|
||||
|
||||
## Placement Group Karari
|
||||
|
||||
Prod icin iki ayri spread placement group:
|
||||
@ -127,30 +144,56 @@ Prod'da su portlar public acilmayacak:
|
||||
|
||||
## Private Firewall
|
||||
|
||||
Private ingress (app subnet `10.20.10.0/24` kaynakli):
|
||||
### App (swarm) Firewall — Private Ingress
|
||||
|
||||
App subnet kaynakli (`10.20.10.0/24`):
|
||||
|
||||
| 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 |
|
||||
| `7946/tcp,udp` | Docker Swarm node discovery | App subnet icinden |
|
||||
| `4789/udp` | Docker Swarm VXLAN overlay | App subnet icinden |
|
||||
| `8200/tcp` | Vault | Docker overlay / private network |
|
||||
| `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 |
|
||||
| `15672/tcp` | RabbitMQ Management | 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 |
|
||||
| `9090/tcp` | Prometheus | SWAG arkasinda `443` — IP kisitli |
|
||||
| `3000/tcp` | Grafana | SWAG arkasinda `443` — IP kisitli |
|
||||
|
||||
DB firewall ek kurallar (db subnet `10.20.20.0/24` kaynakli):
|
||||
DB subnet kaynakli (`iklim-db-*` node'lari Swarm'a worker olarak katildigi icin):
|
||||
|
||||
| Port | Servis | Kural |
|
||||
| Port | Servis | Kaynak |
|
||||
| --- | --- | --- |
|
||||
| `5432/tcp` | PostgreSQL replication | DB subnet icinden |
|
||||
| `27017/tcp` | MongoDB replica set | DB subnet icinden |
|
||||
| `2377/tcp` | Docker Swarm control plane | `10.20.20.0/24` |
|
||||
| `7946/tcp,udp` | Docker Swarm node discovery | `10.20.20.0/24` |
|
||||
| `4789/udp` | Docker Swarm VXLAN overlay | `10.20.20.0/24` |
|
||||
|
||||
### DB Firewall — Private Ingress
|
||||
|
||||
App subnet kaynakli (`10.20.10.0/24`):
|
||||
|
||||
| Port | Servis | Not |
|
||||
| --- | --- | --- |
|
||||
| `22/tcp` | SSH | `admin_allowed_cidrs` |
|
||||
| `5432/tcp` | PostgreSQL (Patroni primary) | App subnet erisimi |
|
||||
| `27017/tcp` | MongoDB replica set endpoint | App subnet erisimi |
|
||||
| `2377/tcp` | Docker Swarm control plane | App subnet icinden |
|
||||
| `7946/tcp,udp` | Docker Swarm node discovery | App subnet icinden |
|
||||
| `4789/udp` | Docker Swarm VXLAN overlay | App subnet icinden |
|
||||
|
||||
DB subnet icindeki karsilikli erisim (`10.20.20.0/24`):
|
||||
|
||||
| Port | Servis | Not |
|
||||
| --- | --- | --- |
|
||||
| `5432/tcp` | PostgreSQL Patroni replication | DB node'lari arasi |
|
||||
| `27017/tcp` | MongoDB replica set internal | DB node'lari arasi |
|
||||
| `2379/tcp` | etcd client | Patroni → etcd erisimi |
|
||||
| `2380/tcp` | etcd peer | etcd cluster internal |
|
||||
| `8008/tcp` | Patroni REST API | Patroni leader election ve saglik kontrolu |
|
||||
|
||||
IP kisitlamasi Hetzner firewall'da degil, SWAG nginx konfigurasyonunda yapilir.
|
||||
|
||||
@ -196,7 +239,7 @@ once lifecycle blogunu gecici olarak kaldir.
|
||||
- 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.
|
||||
- Private firewall `01-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.
|
||||
@ -39,17 +39,20 @@ ansible/
|
||||
Tum prod node'larina uygulanir:
|
||||
|
||||
- Paket cache update
|
||||
- Temel paketler:
|
||||
- Temel paketler (sirasıyla kurulur):
|
||||
- `epel-release` — fail2ban ve davfs2 bu repo'dan gelir; once kurulur
|
||||
- `curl`
|
||||
- `wget`
|
||||
- `git`
|
||||
- `jq`
|
||||
- `tar`
|
||||
- `unzip`
|
||||
- `bash-completion`
|
||||
- `gettext` — envsubst icin; CI/CD deploy pipeline'larinda gerekli
|
||||
- `tree`
|
||||
- `ca-certificates`
|
||||
- `gnupg`
|
||||
- `lsb-release`
|
||||
- `ufw`
|
||||
- `fail2ban`
|
||||
- `firewalld`
|
||||
- `chrony`
|
||||
- `python3`
|
||||
- `python3-pip`
|
||||
831
setup/08-prod-db-cluster-kurulum.md
Normal file
831
setup/08-prod-db-cluster-kurulum.md
Normal file
@ -0,0 +1,831 @@
|
||||
# 09 - Prod DB Cluster Kurulumu (Swarm)
|
||||
|
||||
Bu aşamanın amacı üç DB node'unu Docker Swarm'a worker olarak eklemek, MongoDB replica set ve Patroni + etcd ile yönetilen PostgreSQL yüksek erişilebilirlik konfigürasyonunu yapmaktır.
|
||||
|
||||
`07-prod-ansible-bootstrap.md` tüm DB node'larında tamamlanmış olmalıdır.
|
||||
|
||||
## Mimari
|
||||
|
||||
```
|
||||
iklim-app-01/02/03 (Swarm manager'lar, 10.20.10.11/12/13)
|
||||
|
|
||||
|-- iklimco-net (overlay)
|
||||
|
|
||||
iklim-db-01 (Swarm worker, 10.20.20.11)
|
||||
mongodb-01 [rs0 member 0 — preferred primary]
|
||||
etcd-01 [etcd cluster member]
|
||||
patroni-01 [Patroni + PostgreSQL — ilk primary adayı]
|
||||
|
||||
iklim-db-02 (Swarm worker, 10.20.20.12)
|
||||
mongodb-02 [rs0 member 1]
|
||||
etcd-02 [etcd cluster member]
|
||||
patroni-02 [Patroni + PostgreSQL — standby]
|
||||
|
||||
iklim-db-03 (Swarm worker, 10.20.20.13)
|
||||
mongodb-03 [rs0 member 2]
|
||||
etcd-03 [etcd cluster member]
|
||||
patroni-03 [Patroni + PostgreSQL — standby]
|
||||
```
|
||||
|
||||
DB container'ları birbirlerini overlay DNS adıyla değil, **Hetzner private IP üzerinden** tanıyor. Bu nedenle her servis portunu `host` modda yayımlar; replikasyon ve etcd trafiği doğrudan private network üzerinden gecer. Hetzner Cloud firewall ve prod `db` firewall zaten bu portlara izin vermektedir.
|
||||
|
||||
## 1. Firewall Güncellemesi
|
||||
|
||||
`terraform/hetzner/prod/firewall.tf` dosyasına aşağıdaki kuralları ekle.
|
||||
|
||||
`hcloud_firewall.swarm` içine (DB subnet'ten Swarm portlarına):
|
||||
|
||||
```hcl
|
||||
rule {
|
||||
direction = "in"
|
||||
protocol = "tcp"
|
||||
port = "2377"
|
||||
source_ips = [local.db_subnet_cidr]
|
||||
description = "Docker Swarm control plane from DB subnet"
|
||||
}
|
||||
|
||||
rule {
|
||||
direction = "in"
|
||||
protocol = "tcp"
|
||||
port = "7946"
|
||||
source_ips = [local.db_subnet_cidr]
|
||||
description = "Docker Swarm node discovery (TCP) from DB subnet"
|
||||
}
|
||||
|
||||
rule {
|
||||
direction = "in"
|
||||
protocol = "udp"
|
||||
port = "7946"
|
||||
source_ips = [local.db_subnet_cidr]
|
||||
description = "Docker Swarm node discovery (UDP) from DB subnet"
|
||||
}
|
||||
|
||||
rule {
|
||||
direction = "in"
|
||||
protocol = "udp"
|
||||
port = "4789"
|
||||
source_ips = [local.db_subnet_cidr]
|
||||
description = "Docker Swarm VXLAN overlay from DB subnet"
|
||||
}
|
||||
```
|
||||
|
||||
`hcloud_firewall.db` içine (app subnet'ten Swarm portlarına + overlay; DB subnet içi etcd/Patroni trafiği):
|
||||
|
||||
```hcl
|
||||
rule {
|
||||
direction = "in"
|
||||
protocol = "tcp"
|
||||
port = "2377"
|
||||
source_ips = [local.app_subnet_cidr]
|
||||
description = "Docker Swarm control plane from app subnet"
|
||||
}
|
||||
|
||||
rule {
|
||||
direction = "in"
|
||||
protocol = "tcp"
|
||||
port = "7946"
|
||||
source_ips = [local.app_subnet_cidr]
|
||||
description = "Docker Swarm node discovery (TCP) from app subnet"
|
||||
}
|
||||
|
||||
rule {
|
||||
direction = "in"
|
||||
protocol = "udp"
|
||||
port = "7946"
|
||||
source_ips = [local.app_subnet_cidr]
|
||||
description = "Docker Swarm node discovery (UDP) from app subnet"
|
||||
}
|
||||
|
||||
rule {
|
||||
direction = "in"
|
||||
protocol = "udp"
|
||||
port = "4789"
|
||||
source_ips = [local.app_subnet_cidr]
|
||||
description = "Docker Swarm VXLAN overlay from app subnet"
|
||||
}
|
||||
|
||||
rule {
|
||||
direction = "in"
|
||||
protocol = "tcp"
|
||||
port = "2379"
|
||||
source_ips = [local.db_subnet_cidr]
|
||||
description = "etcd client port within DB subnet"
|
||||
}
|
||||
|
||||
rule {
|
||||
direction = "in"
|
||||
protocol = "tcp"
|
||||
port = "2380"
|
||||
source_ips = [local.db_subnet_cidr]
|
||||
description = "etcd peer port within DB subnet"
|
||||
}
|
||||
|
||||
rule {
|
||||
direction = "in"
|
||||
protocol = "tcp"
|
||||
port = "8008"
|
||||
source_ips = [local.db_subnet_cidr]
|
||||
description = "Patroni REST API within DB subnet"
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
cd terraform/hetzner/prod
|
||||
terraform apply
|
||||
```
|
||||
|
||||
## 2. DB Node'larını Swarm'a Ekleme
|
||||
|
||||
**Swarm manager'lardan birinde** (iklim-app-01) join token al:
|
||||
|
||||
```bash
|
||||
docker swarm join-token worker
|
||||
```
|
||||
|
||||
**Her DB node'unda** (iklim-db-01, iklim-db-02, iklim-db-03):
|
||||
|
||||
```bash
|
||||
docker swarm join --token <TOKEN> 10.20.10.11:2377
|
||||
```
|
||||
|
||||
**iklim-app-01 üzerinde** node'ları etiketle:
|
||||
|
||||
```bash
|
||||
docker node update --label-add role=db --label-add db-index=01 iklim-db-01
|
||||
docker node update --label-add role=db --label-add db-index=02 iklim-db-02
|
||||
docker node update --label-add role=db --label-add db-index=03 iklim-db-03
|
||||
|
||||
docker node ls
|
||||
```
|
||||
|
||||
## 3. StorageBox Dizin Yapısı
|
||||
|
||||
Her DB node'unda (`/mnt/storagebox` zaten mount edilmiş olmalı):
|
||||
|
||||
```bash
|
||||
# iklim-db-01 üzerinde:
|
||||
mkdir -p /mnt/storagebox/prod/db/mongodb-01/{data,log,config}
|
||||
mkdir -p /mnt/storagebox/prod/db/postgresql-01/{data,config}
|
||||
mkdir -p /mnt/storagebox/prod/db/etcd-01/data
|
||||
|
||||
# iklim-db-02 üzerinde:
|
||||
mkdir -p /mnt/storagebox/prod/db/mongodb-02/{data,log,config}
|
||||
mkdir -p /mnt/storagebox/prod/db/postgresql-02/{data,config}
|
||||
mkdir -p /mnt/storagebox/prod/db/etcd-02/data
|
||||
|
||||
# iklim-db-03 üzerinde:
|
||||
mkdir -p /mnt/storagebox/prod/db/mongodb-03/{data,log,config}
|
||||
mkdir -p /mnt/storagebox/prod/db/postgresql-03/{data,config}
|
||||
mkdir -p /mnt/storagebox/prod/db/etcd-03/data
|
||||
```
|
||||
|
||||
## 4. MongoDB Replica Set
|
||||
|
||||
### mongod.conf
|
||||
|
||||
Her DB node'unda `/mnt/storagebox/prod/db/mongodb-0X/config/mongod.conf`:
|
||||
|
||||
```yaml
|
||||
net:
|
||||
port: 27017
|
||||
storage:
|
||||
engine: "wiredTiger"
|
||||
dbPath: "/data/db"
|
||||
directoryPerDB: true
|
||||
systemLog:
|
||||
verbosity: 0
|
||||
timeStampFormat: "iso8601-local"
|
||||
destination: file
|
||||
path: "/data/log/mongo.log"
|
||||
logAppend: true
|
||||
logRotate: rename
|
||||
replication:
|
||||
replSetName: "rs0"
|
||||
security:
|
||||
authorization: enabled
|
||||
keyFile: "/data/configdb/rs-auth.key"
|
||||
```
|
||||
|
||||
### Replica Set Auth Key
|
||||
|
||||
Tüm DB node'larında **aynı** key dosyası olmalıdır:
|
||||
|
||||
```bash
|
||||
# iklim-db-01 üzerinde oluştur:
|
||||
openssl rand -base64 756 > /mnt/storagebox/prod/db/mongodb-01/config/rs-auth.key
|
||||
chmod 400 /mnt/storagebox/prod/db/mongodb-01/config/rs-auth.key
|
||||
|
||||
# Aynı içeriği diğer node'lara kopyala:
|
||||
cat /mnt/storagebox/prod/db/mongodb-01/config/rs-auth.key \
|
||||
> /mnt/storagebox/prod/db/mongodb-02/config/rs-auth.key
|
||||
cat /mnt/storagebox/prod/db/mongodb-01/config/rs-auth.key \
|
||||
> /mnt/storagebox/prod/db/mongodb-03/config/rs-auth.key
|
||||
|
||||
chmod 400 /mnt/storagebox/prod/db/mongodb-0{2,3}/config/rs-auth.key
|
||||
```
|
||||
|
||||
### Stack Dosyası — MongoDB
|
||||
|
||||
`/opt/iklimco/stacks/prod-db-mongo.yml`:
|
||||
|
||||
```yaml
|
||||
version: "3.8"
|
||||
|
||||
networks:
|
||||
iklimco-net:
|
||||
external: true
|
||||
|
||||
services:
|
||||
mongodb-01:
|
||||
image: mongo:8
|
||||
environment:
|
||||
MONGO_INITDB_ROOT_USERNAME: mongo-root
|
||||
MONGO_INITDB_ROOT_PASSWORD: "${MONGO_ROOT_PASSWORD}"
|
||||
volumes:
|
||||
- /mnt/storagebox/prod/db/mongodb-01/data:/data/db
|
||||
- /mnt/storagebox/prod/db/mongodb-01/log:/data/log
|
||||
- /mnt/storagebox/prod/db/mongodb-01/config:/data/configdb
|
||||
networks:
|
||||
- iklimco-net
|
||||
ports:
|
||||
- target: 27017
|
||||
published: 27017
|
||||
protocol: tcp
|
||||
mode: host
|
||||
command: ["--config", "/data/configdb/mongod.conf"]
|
||||
deploy:
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.hostname == iklim-db-01
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
|
||||
mongodb-02:
|
||||
image: mongo:8
|
||||
environment:
|
||||
MONGO_INITDB_ROOT_USERNAME: mongo-root
|
||||
MONGO_INITDB_ROOT_PASSWORD: "${MONGO_ROOT_PASSWORD}"
|
||||
volumes:
|
||||
- /mnt/storagebox/prod/db/mongodb-02/data:/data/db
|
||||
- /mnt/storagebox/prod/db/mongodb-02/log:/data/log
|
||||
- /mnt/storagebox/prod/db/mongodb-02/config:/data/configdb
|
||||
networks:
|
||||
- iklimco-net
|
||||
ports:
|
||||
- target: 27017
|
||||
published: 27017
|
||||
protocol: tcp
|
||||
mode: host
|
||||
command: ["--config", "/data/configdb/mongod.conf"]
|
||||
deploy:
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.hostname == iklim-db-02
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
|
||||
mongodb-03:
|
||||
image: mongo:8
|
||||
environment:
|
||||
MONGO_INITDB_ROOT_USERNAME: mongo-root
|
||||
MONGO_INITDB_ROOT_PASSWORD: "${MONGO_ROOT_PASSWORD}"
|
||||
volumes:
|
||||
- /mnt/storagebox/prod/db/mongodb-03/data:/data/db
|
||||
- /mnt/storagebox/prod/db/mongodb-03/log:/data/log
|
||||
- /mnt/storagebox/prod/db/mongodb-03/config:/data/configdb
|
||||
networks:
|
||||
- iklimco-net
|
||||
ports:
|
||||
- target: 27017
|
||||
published: 27017
|
||||
protocol: tcp
|
||||
mode: host
|
||||
command: ["--config", "/data/configdb/mongod.conf"]
|
||||
deploy:
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.hostname == iklim-db-03
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
```
|
||||
|
||||
### Replica Set Başlangıç
|
||||
|
||||
Stack deploy edildikten sonra **bir kez** çalıştırılır:
|
||||
|
||||
```bash
|
||||
# iklim-db-01 üzerinde:
|
||||
docker exec -it $(docker ps -q -f name=iklim-db_mongodb-01) mongosh \
|
||||
-u mongo-root -p "${MONGO_ROOT_PASSWORD}" --authenticationDatabase admin
|
||||
|
||||
# mongosh içinde:
|
||||
rs.initiate({
|
||||
_id: "rs0",
|
||||
members: [
|
||||
{ _id: 0, host: "10.20.20.11:27017", priority: 2 },
|
||||
{ _id: 1, host: "10.20.20.12:27017", priority: 1 },
|
||||
{ _id: 2, host: "10.20.20.13:27017", priority: 1 }
|
||||
]
|
||||
})
|
||||
|
||||
# Durum kontrol:
|
||||
rs.status()
|
||||
```
|
||||
|
||||
`"stateStr": "PRIMARY"` ve iki `"SECONDARY"` görülünce replica set hazırdır.
|
||||
|
||||
## 5. PostgreSQL — Patroni + etcd
|
||||
|
||||
Patroni, PostgreSQL primary/standby rollerini etcd üzerinden koordine eder. Primary düşerse diğer node'lardan biri otomatik olarak seçim kazanır ve primary olur. Swarm servisi container'ı yeniden başlatır; Patroni kaldığı yerden devam eder.
|
||||
|
||||
### 5.1 Özel Image (Patroni + PostGIS)
|
||||
|
||||
`postgis/postgis:17-3.5` imajı üzerine Patroni kurulur. Bu imaj Harbor'a push edilip stack'te kullanılır.
|
||||
|
||||
`Environment_Infrastructure/docker/patroni-postgis/Dockerfile`:
|
||||
|
||||
```dockerfile
|
||||
FROM postgis/postgis:17-3.5
|
||||
|
||||
USER root
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
python3-pip \
|
||||
python3-dev \
|
||||
gcc \
|
||||
libpq-dev \
|
||||
&& pip3 install --no-cache-dir 'patroni[etcd3]' \
|
||||
&& apt-get purge -y gcc python3-dev \
|
||||
&& apt-get autoremove -y \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
USER postgres
|
||||
|
||||
ENTRYPOINT ["patroni", "/etc/patroni/patroni.yml"]
|
||||
```
|
||||
|
||||
Build ve push:
|
||||
|
||||
```bash
|
||||
cd Environment_Infrastructure/docker/patroni-postgis
|
||||
docker build -t registry.iklim.co/infra/patroni-postgis:17-3.5 .
|
||||
docker push registry.iklim.co/infra/patroni-postgis:17-3.5
|
||||
```
|
||||
|
||||
### 5.2 etcd Kümesi
|
||||
|
||||
#### Stack Dosyası — etcd
|
||||
|
||||
`/opt/iklimco/stacks/prod-db-etcd.yml`:
|
||||
|
||||
```yaml
|
||||
version: "3.8"
|
||||
|
||||
networks:
|
||||
iklimco-net:
|
||||
external: true
|
||||
|
||||
services:
|
||||
etcd-01:
|
||||
image: bitnami/etcd:3
|
||||
environment:
|
||||
ALLOW_NONE_AUTHENTICATION: "yes"
|
||||
ETCD_NAME: etcd-01
|
||||
ETCD_INITIAL_ADVERTISE_PEER_URLS: http://10.20.20.11:2380
|
||||
ETCD_LISTEN_PEER_URLS: http://0.0.0.0:2380
|
||||
ETCD_ADVERTISE_CLIENT_URLS: http://10.20.20.11:2379
|
||||
ETCD_LISTEN_CLIENT_URLS: http://0.0.0.0:2379
|
||||
ETCD_INITIAL_CLUSTER: "etcd-01=http://10.20.20.11:2380,etcd-02=http://10.20.20.12:2380,etcd-03=http://10.20.20.13:2380"
|
||||
ETCD_INITIAL_CLUSTER_STATE: new
|
||||
ETCD_INITIAL_CLUSTER_TOKEN: iklimco-etcd-prod
|
||||
volumes:
|
||||
- /mnt/storagebox/prod/db/etcd-01/data:/bitnami/etcd/data
|
||||
networks:
|
||||
- iklimco-net
|
||||
ports:
|
||||
- target: 2379
|
||||
published: 2379
|
||||
protocol: tcp
|
||||
mode: host
|
||||
- target: 2380
|
||||
published: 2380
|
||||
protocol: tcp
|
||||
mode: host
|
||||
deploy:
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.hostname == iklim-db-01
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
|
||||
etcd-02:
|
||||
image: bitnami/etcd:3
|
||||
environment:
|
||||
ALLOW_NONE_AUTHENTICATION: "yes"
|
||||
ETCD_NAME: etcd-02
|
||||
ETCD_INITIAL_ADVERTISE_PEER_URLS: http://10.20.20.12:2380
|
||||
ETCD_LISTEN_PEER_URLS: http://0.0.0.0:2380
|
||||
ETCD_ADVERTISE_CLIENT_URLS: http://10.20.20.12:2379
|
||||
ETCD_LISTEN_CLIENT_URLS: http://0.0.0.0:2379
|
||||
ETCD_INITIAL_CLUSTER: "etcd-01=http://10.20.20.11:2380,etcd-02=http://10.20.20.12:2380,etcd-03=http://10.20.20.13:2380"
|
||||
ETCD_INITIAL_CLUSTER_STATE: new
|
||||
ETCD_INITIAL_CLUSTER_TOKEN: iklimco-etcd-prod
|
||||
volumes:
|
||||
- /mnt/storagebox/prod/db/etcd-02/data:/bitnami/etcd/data
|
||||
networks:
|
||||
- iklimco-net
|
||||
ports:
|
||||
- target: 2379
|
||||
published: 2379
|
||||
protocol: tcp
|
||||
mode: host
|
||||
- target: 2380
|
||||
published: 2380
|
||||
protocol: tcp
|
||||
mode: host
|
||||
deploy:
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.hostname == iklim-db-02
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
|
||||
etcd-03:
|
||||
image: bitnami/etcd:3
|
||||
environment:
|
||||
ALLOW_NONE_AUTHENTICATION: "yes"
|
||||
ETCD_NAME: etcd-03
|
||||
ETCD_INITIAL_ADVERTISE_PEER_URLS: http://10.20.20.13:2380
|
||||
ETCD_LISTEN_PEER_URLS: http://0.0.0.0:2380
|
||||
ETCD_ADVERTISE_CLIENT_URLS: http://10.20.20.13:2379
|
||||
ETCD_LISTEN_CLIENT_URLS: http://0.0.0.0:2379
|
||||
ETCD_INITIAL_CLUSTER: "etcd-01=http://10.20.20.11:2380,etcd-02=http://10.20.20.12:2380,etcd-03=http://10.20.20.13:2380"
|
||||
ETCD_INITIAL_CLUSTER_STATE: new
|
||||
ETCD_INITIAL_CLUSTER_TOKEN: iklimco-etcd-prod
|
||||
volumes:
|
||||
- /mnt/storagebox/prod/db/etcd-03/data:/bitnami/etcd/data
|
||||
networks:
|
||||
- iklimco-net
|
||||
ports:
|
||||
- target: 2379
|
||||
published: 2379
|
||||
protocol: tcp
|
||||
mode: host
|
||||
- target: 2380
|
||||
published: 2380
|
||||
protocol: tcp
|
||||
mode: host
|
||||
deploy:
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.hostname == iklim-db-03
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
```
|
||||
|
||||
**Önemli:** etcd cluster ilk kez başlatılırken üç node aynı anda `ETCD_INITIAL_CLUSTER_STATE: new` ile deploy edilmelidir. Cluster kurulduktan sonra bu değer `existing` olarak güncellenmeli ve stack yeniden deploy edilmelidir (node yeniden başlasın diye `new` kalmamalı; aksi hâlde data dizini sıfırlanır).
|
||||
|
||||
### 5.3 Patroni Konfigürasyonu
|
||||
|
||||
Her node için ayrı bir `patroni.yml` dosyası oluşturulur. Farklılıklar yalnızca `name` ve `connect_address` alanlarındadır.
|
||||
|
||||
**Node 01** — `/mnt/storagebox/prod/db/postgresql-01/config/patroni.yml`:
|
||||
|
||||
```yaml
|
||||
scope: iklim-postgres
|
||||
namespace: /db/
|
||||
name: postgresql-01
|
||||
|
||||
restapi:
|
||||
listen: 0.0.0.0:8008
|
||||
connect_address: 10.20.20.11:8008
|
||||
|
||||
etcd3:
|
||||
hosts:
|
||||
- 10.20.20.11:2379
|
||||
- 10.20.20.12:2379
|
||||
- 10.20.20.13:2379
|
||||
|
||||
bootstrap:
|
||||
dcs:
|
||||
ttl: 30
|
||||
loop_wait: 10
|
||||
retry_timeout: 10
|
||||
maximum_lag_on_failover: 1048576
|
||||
postgresql:
|
||||
use_pg_rewind: true
|
||||
parameters:
|
||||
wal_level: replica
|
||||
hot_standby: "on"
|
||||
wal_keep_size: 512
|
||||
max_wal_senders: 5
|
||||
max_replication_slots: 5
|
||||
|
||||
initdb:
|
||||
- encoding: UTF8
|
||||
- data-checksums
|
||||
|
||||
pg_hba:
|
||||
- host replication replicator 10.20.20.0/24 scram-sha-256
|
||||
- host all all 10.20.10.0/24 scram-sha-256
|
||||
- host all all 10.20.20.0/24 scram-sha-256
|
||||
|
||||
users:
|
||||
postgres:
|
||||
password: "${POSTGRES_PASSWORD}"
|
||||
options:
|
||||
- superuser
|
||||
|
||||
postgresql:
|
||||
listen: 0.0.0.0:5432
|
||||
connect_address: 10.20.20.11:5432
|
||||
data_dir: /var/lib/postgresql/data/pgdata
|
||||
pgpass: /tmp/pgpass0
|
||||
authentication:
|
||||
replication:
|
||||
username: replicator
|
||||
password: "${REPLICATOR_PASSWORD}"
|
||||
superuser:
|
||||
username: postgres
|
||||
password: "${POSTGRES_PASSWORD}"
|
||||
parameters:
|
||||
unix_socket_directories: "/var/run/postgresql"
|
||||
|
||||
tags:
|
||||
nofailover: false
|
||||
noloadbalance: false
|
||||
clonefrom: false
|
||||
nosync: false
|
||||
```
|
||||
|
||||
**Node 02** — `/mnt/storagebox/prod/db/postgresql-02/config/patroni.yml`:
|
||||
|
||||
Node 01 ile aynı içerik, yalnızca şu alanlar farklı:
|
||||
|
||||
```yaml
|
||||
name: postgresql-02
|
||||
|
||||
restapi:
|
||||
connect_address: 10.20.20.12:8008
|
||||
|
||||
postgresql:
|
||||
connect_address: 10.20.20.12:5432
|
||||
data_dir: /var/lib/postgresql/data/pgdata
|
||||
```
|
||||
|
||||
**Node 03** — `/mnt/storagebox/prod/db/postgresql-03/config/patroni.yml`:
|
||||
|
||||
```yaml
|
||||
name: postgresql-03
|
||||
|
||||
restapi:
|
||||
connect_address: 10.20.20.13:8008
|
||||
|
||||
postgresql:
|
||||
connect_address: 10.20.20.13:5432
|
||||
data_dir: /var/lib/postgresql/data/pgdata
|
||||
```
|
||||
|
||||
### 5.4 Stack Dosyası — Patroni
|
||||
|
||||
`/opt/iklimco/stacks/prod-db-patroni.yml`:
|
||||
|
||||
```yaml
|
||||
version: "3.8"
|
||||
|
||||
networks:
|
||||
iklimco-net:
|
||||
external: true
|
||||
|
||||
services:
|
||||
patroni-01:
|
||||
image: registry.iklim.co/infra/patroni-postgis:17-3.5
|
||||
environment:
|
||||
DATABASE_POSTGRES_ROOT_USER: "${DATABASE_POSTGRES_ROOT_USER}"
|
||||
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
|
||||
REPLICATOR_PASSWORD: "${REPLICATOR_PASSWORD}"
|
||||
TZ: "Europe/Istanbul"
|
||||
volumes:
|
||||
- /mnt/storagebox/prod/db/postgresql-01/data:/var/lib/postgresql/data
|
||||
- /mnt/storagebox/prod/db/postgresql-01/config/patroni.yml:/etc/patroni/patroni.yml:ro
|
||||
networks:
|
||||
- iklimco-net
|
||||
ports:
|
||||
- target: 5432
|
||||
published: 5432
|
||||
protocol: tcp
|
||||
mode: host
|
||||
- target: 8008
|
||||
published: 8008
|
||||
protocol: tcp
|
||||
mode: host
|
||||
deploy:
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.hostname == iklim-db-01
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
|
||||
patroni-02:
|
||||
image: registry.iklim.co/infra/patroni-postgis:17-3.5
|
||||
environment:
|
||||
DATABASE_POSTGRES_ROOT_USER: "${DATABASE_POSTGRES_ROOT_USER}"
|
||||
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
|
||||
REPLICATOR_PASSWORD: "${REPLICATOR_PASSWORD}"
|
||||
TZ: "Europe/Istanbul"
|
||||
volumes:
|
||||
- /mnt/storagebox/prod/db/postgresql-02/data:/var/lib/postgresql/data
|
||||
- /mnt/storagebox/prod/db/postgresql-02/config/patroni.yml:/etc/patroni/patroni.yml:ro
|
||||
networks:
|
||||
- iklimco-net
|
||||
ports:
|
||||
- target: 5432
|
||||
published: 5432
|
||||
protocol: tcp
|
||||
mode: host
|
||||
- target: 8008
|
||||
published: 8008
|
||||
protocol: tcp
|
||||
mode: host
|
||||
deploy:
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.hostname == iklim-db-02
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
|
||||
patroni-03:
|
||||
image: registry.iklim.co/infra/patroni-postgis:17-3.5
|
||||
environment:
|
||||
DATABASE_POSTGRES_ROOT_USER: "${DATABASE_POSTGRES_ROOT_USER}"
|
||||
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
|
||||
REPLICATOR_PASSWORD: "${REPLICATOR_PASSWORD}"
|
||||
TZ: "Europe/Istanbul"
|
||||
volumes:
|
||||
- /mnt/storagebox/prod/db/postgresql-03/data:/var/lib/postgresql/data
|
||||
- /mnt/storagebox/prod/db/postgresql-03/config/patroni.yml:/etc/patroni/patroni.yml:ro
|
||||
networks:
|
||||
- iklimco-net
|
||||
ports:
|
||||
- target: 5432
|
||||
published: 5432
|
||||
protocol: tcp
|
||||
mode: host
|
||||
- target: 8008
|
||||
published: 8008
|
||||
protocol: tcp
|
||||
mode: host
|
||||
deploy:
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.hostname == iklim-db-03
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
```
|
||||
|
||||
### 5.5 Durum Kontrolü
|
||||
|
||||
```bash
|
||||
# Herhangi bir DB node'unda:
|
||||
docker exec -it $(docker ps -q -f name=iklim-patroni_patroni-01) \
|
||||
patronictl -c /etc/patroni/patroni.yml list
|
||||
```
|
||||
|
||||
Beklenen çıktı: bir `Leader` ve iki `Replica` satırı, hepsinin `State` sütunu `running`.
|
||||
|
||||
```bash
|
||||
# etcd cluster sağlığı:
|
||||
docker exec -it $(docker ps -q -f name=iklim-etcd_etcd-01) \
|
||||
etcdctl endpoint health \
|
||||
--endpoints=http://10.20.20.11:2379,http://10.20.20.12:2379,http://10.20.20.13:2379
|
||||
```
|
||||
|
||||
```bash
|
||||
# Mevcut primary'i öğren:
|
||||
docker exec -it $(docker ps -q -f name=iklim-patroni_patroni-01) \
|
||||
patronictl -c /etc/patroni/patroni.yml topology
|
||||
```
|
||||
|
||||
## 6. Deploy
|
||||
|
||||
Sıra önemlidir: önce etcd, ardından MongoDB ve Patroni stack'leri.
|
||||
|
||||
```bash
|
||||
# iklim-app-01 üzerinde (Swarm manager):
|
||||
export $(cat /opt/iklimco/stacks/.env | xargs)
|
||||
|
||||
# 1. etcd cluster:
|
||||
docker stack deploy \
|
||||
--compose-file /opt/iklimco/stacks/prod-db-etcd.yml \
|
||||
--with-registry-auth \
|
||||
iklim-etcd
|
||||
|
||||
# etcd cluster'ın kurulmasını bekle (yaklaşık 30 saniye):
|
||||
sleep 30
|
||||
|
||||
# etcd sağlığını doğrula:
|
||||
docker exec -it $(docker ps -q -f name=iklim-etcd_etcd-01) \
|
||||
etcdctl endpoint health \
|
||||
--endpoints=http://10.20.20.11:2379,http://10.20.20.12:2379,http://10.20.20.13:2379
|
||||
|
||||
# 2. MongoDB:
|
||||
docker stack deploy \
|
||||
--compose-file /opt/iklimco/stacks/prod-db-mongo.yml \
|
||||
--with-registry-auth \
|
||||
iklim-db
|
||||
|
||||
# 3. Patroni (PostgreSQL):
|
||||
docker stack deploy \
|
||||
--compose-file /opt/iklimco/stacks/prod-db-patroni.yml \
|
||||
--with-registry-auth \
|
||||
iklim-patroni
|
||||
|
||||
docker stack services iklim-etcd
|
||||
docker stack services iklim-db
|
||||
docker stack services iklim-patroni
|
||||
```
|
||||
|
||||
**etcd'yi ikinci kez deploy etmeden önce** `ETCD_INITIAL_CLUSTER_STATE` değerini `new` yerine `existing` olarak güncelle; aksi hâlde veri dizini sıfırlanır. İlk deploy başarılı olduktan sonra `prod-db-etcd.yml` içindeki her serviste bu değeri `existing` yap ve `docker stack deploy` ile güncelle.
|
||||
|
||||
### .env Dosyası
|
||||
|
||||
`/opt/iklimco/stacks/.env` (repo'ya commit edilmez):
|
||||
|
||||
```env
|
||||
DATABASE_POSTGRES_ROOT_USER=postgres
|
||||
POSTGRES_PASSWORD=<güçlü-şifre>
|
||||
REPLICATOR_PASSWORD=<güçlü-şifre>
|
||||
MONGO_ROOT_PASSWORD=<güçlü-şifre>
|
||||
```
|
||||
|
||||
### MongoDB Replica Set Başlatma
|
||||
|
||||
MongoDB stack deploy edildikten sonra bir kez çalıştırılır:
|
||||
|
||||
```bash
|
||||
docker exec -it $(docker ps -q -f name=iklim-db_mongodb-01) mongosh \
|
||||
-u mongo-root -p "${MONGO_ROOT_PASSWORD}" --authenticationDatabase admin
|
||||
|
||||
# mongosh içinde:
|
||||
rs.initiate({
|
||||
_id: "rs0",
|
||||
members: [
|
||||
{ _id: 0, host: "10.20.20.11:27017", priority: 2 },
|
||||
{ _id: 1, host: "10.20.20.12:27017", priority: 1 },
|
||||
{ _id: 2, host: "10.20.20.13:27017", priority: 1 }
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
## 7. App Servislerinden Erişim
|
||||
|
||||
### MongoDB Replica Set Connection String
|
||||
|
||||
```
|
||||
mongodb://mongo-root:<SIFRE>@10.20.20.11:27017,10.20.20.12:27017,10.20.20.13:27017/<db>?replicaSet=rs0&authSource=admin
|
||||
```
|
||||
|
||||
### PostgreSQL — Patroni
|
||||
|
||||
Patroni her an primary olan node'u yönetir. Uygulama katmanı tüm üç IP'yi vererek primary'e yazabilir, secondary'den okuyabilir:
|
||||
|
||||
```
|
||||
# Yazma — sadece primary kabul eder:
|
||||
jdbc:postgresql://10.20.20.11:5432,10.20.20.12:5432,10.20.20.13:5432/iklimdb?targetServerType=primary
|
||||
|
||||
# Okuma (yük dengeleme):
|
||||
jdbc:postgresql://10.20.20.11:5432,10.20.20.12:5432,10.20.20.13:5432/iklimdb?targetServerType=preferSecondary
|
||||
```
|
||||
|
||||
PostgreSQL JDBC sürücüsü `targetServerType=primary` ile bağlanmaya çalışacağı tüm node'lara bağlanır ve primary olanı otomatik bulur.
|
||||
|
||||
### Patroni REST API
|
||||
|
||||
Patroni, 8008 portundan HTTP endpoint sunar. Bu endpoint HAProxy veya benzeri bir load balancer ile kullanılarak primary'i otomatik yönlendirme sağlanabilir:
|
||||
|
||||
```bash
|
||||
# Primary kontrolü (HTTP 200 = primary, HTTP 503 = replica):
|
||||
curl -s http://10.20.20.11:8008/primary
|
||||
```
|
||||
|
||||
## Kabul Kriterleri
|
||||
|
||||
- `docker stack services iklim-etcd` — üç servis `1/1`
|
||||
- `docker stack services iklim-db` — üç MongoDB servisi `1/1`
|
||||
- `docker stack services iklim-patroni` — üç Patroni servisi `1/1`
|
||||
- `patronictl list` — 1 `Leader`, 2 `Replica`, hepsi `running`
|
||||
- `etcdctl endpoint health` — üç endpoint `healthy`
|
||||
- `rs.status()` — 1 PRIMARY, 2 SECONDARY
|
||||
- App node'larından MongoDB ve PostgreSQL'e erişim sağlanır
|
||||
- `5432`, `27017`, `2379`, `2380`, `8008` portları public internet'ten kapalıdır
|
||||
- Bir DB node yeniden başlatıldığında Patroni otomatik seçim yapar, yeni primary belirlenir
|
||||
- Patroni primary geçişi sırasında eski primary standby olarak re-join olur (split-brain yoktur)
|
||||
Loading…
x
Reference in New Issue
Block a user