# Test — Docker Swarm Node Recovery Test ortamında tek manager (`iklim-app-01`) ve tek worker (`iklim-db-01`) bulunur. Hangi node'un yeniden kurulduğuna göre recovery süreci farklılaşır. ## Senaryo 1: `iklim-app-01` (Manager) Yeniden Kurulur ### Sorun Yeni `iklim-app-01` üzerinde `docker swarm init` farklı cluster ID ile yeni bir küme başlatır. `iklim-db-01` hâlâ eski kümeye bağlıdır. Ansible `swarm` role'u `iklim-db-01`'de Swarm'ı `active` görür, join denemez. İki node iki ayrı kümede kalır. ### Çözüm ```bash # 1. iklim-db-01 üzerinde — eski kümeden çık docker swarm leave --force # 2. Ansible ile her iki node'u yeniden kur cd ansible/test ansible-playbook -i inventory/generated/test.yml test-bootstrap.yml --ask-vault-pass # 3. DB stack'i yeniden deploy et ansible-playbook -i inventory/generated/test.yml test-db-post-stack.yml --ask-vault-pass ``` DB verileri `iklim-db-01`'deki named volume'larda korunur, kayıp yaşanmaz. --- ## Senaryo 2: `iklim-db-01` (Worker) Yeniden Kurulur ### Durum Yeni `iklim-db-01` Swarm'dan habersiz başlar (`inactive`). Manager (`iklim-app-01`) eski dead node kaydını tutar. > ⚠️ **Veri kaybı:** `iklim-db-01` yeniden kurulduğu için tüm named volume'lar silinmiştir. 3. adım öncesinde backup'tan restore yapılması zorunludur. ### Çözüm ```bash # 1. iklim-app-01 üzerinde — eski dead node kaydını temizle (bootstrap'tan ÖNCE yapılmalı) docker node ls # eski node ID'yi bul docker node rm # 2. Ansible bootstrap — yeni node otomatik join olur cd ansible/test ansible-playbook -i inventory/generated/test.yml test-bootstrap.yml --ask-vault-pass # 3. DB stack'i yeniden deploy et (backup'tan restore sonrası) ansible-playbook -i inventory/generated/test.yml test-db-post-stack.yml --ask-vault-pass ``` Ansible `swarm` role'u `inactive` durumu gördüğü için token alıp join eder, `role=db` label'ını uygular. DB servisleri placement constraint sayesinde yeni node'a schedule edilir. --- ## Senaryo 3: Her İki Node Yeniden Kurulur Her şey sıfırdan kurulur, Swarm uyumsuzluğu yaşanmaz. ```bash cd ansible/test ansible-playbook -i inventory/generated/test.yml test-bootstrap.yml --ask-vault-pass ansible-playbook -i inventory/generated/test.yml test-db-post-stack.yml --ask-vault-pass ``` --- ## Özet | Senaryo | Manuel Adım | Ansible Yeterli mi? | |---|---|---| | Manager (`iklim-app-01`) ölür | `docker swarm leave --force` (worker'da) | Sonrasında evet | | Worker (`iklim-db-01`) ölür | `docker node rm ` (manager'da, bootstrap'tan önce) | Hayır — backup restore gerekir | | Her ikisi ölür | Yok | Evet | ## Neden Prod'da Bu Sorun Yok Prod ortamında birden fazla manager node (en az 3) çalıştırılır. Tek manager düşse diğerleri liderliği devralır, küme sağlıklı kalmaya devam eder. --- # Prod — Monitoring & SWAG Failover SWAG, cert-reloader, Prometheus ve Grafana cluster-native (replicated) değildir; her zaman tek instance çalışırlar ve varsayılan olarak `iklim-app-01`'e (Floating IP node) sabitlenmişlerdir. `iklim-app-01` çöktüğünde bu servisler durur; DNS/HTTPS erişimi ve izleme (monitoring) kesilir. Swarm quorum 2 manager ile devam eder; mikroservisler ve Vault başka node'lara taşınır. `cert-distributor` bu kuralın dışındadır: `mode: global` ile `node.labels.type == service` olan tüm node'larda çalışır; StorageBox'tan sertifikayı node-lokal `/opt/iklimco/ssl`'e kopyalar (Vault FUSE mount kısıtlaması nedeniyle). `iklim-app-01` düştüğünde diğer node'lardaki `cert-distributor` instance'ları çalışmaya devam eder — failover gerektirmez. Tüm bu servislerin verileri ve konfigürasyonları StorageBox'ta tutulur: - **SWAG:** `/mnt/storagebox/swag/config` - **SSL:** `/mnt/storagebox/ssl` - **Prometheus:** `/mnt/storagebox/prometheus/data` - **Grafana:** `/mnt/storagebox/grafana/data` ## Prod Senaryo: `iklim-app-01` Çöktü ### 1. Servisleri Başka Node'a Taşı SWAG ve cert-reloader birlikte taşınmalıdır. Prometheus ve Grafana da bağımsız olarak veya aynı anda taşınabilir. `cert-distributor` global mode'da çalıştığından taşıma gerekmez. ```bash # iklim-app-02 veya iklim-app-03 üzerinde (aktif manager): # SWAG & Cert-Reloader taşıma (replicas=1 olduğundan taşıma sırasında kısa kesinti yaşanır) docker service update --constraint-add "node.hostname == iklim-app-02" --constraint-rm "node.hostname == iklim-app-01" iklimco_swag docker service update --constraint-add "node.hostname == iklim-app-02" --constraint-rm "node.hostname == iklim-app-01" iklimco_cert-reloader # Prometheus & Grafana taşıma docker service update --constraint-add "node.hostname == iklim-app-02" --constraint-rm "node.hostname == iklim-app-01" iklimco_prometheus docker service update --constraint-add "node.hostname == iklim-app-02" --constraint-rm "node.hostname == iklim-app-01" iklimco_grafana ``` ### 2. Floating IP'yi Yeni Node'a Taşı **CLI ile:** ```bash hcloud floating-ip assign ``` **Hetzner Cloud Console (web) ile:** 1. [console.hetzner.cloud](https://console.hetzner.cloud) adresine giriş yap. 2. Sol menüden ilgili projeyi seç (`iklim_prod`). 3. Sol menüden **Floating IPs** sekmesine gir. 4. `iklim-prod-app-fip` satırının sağındaki **⋮** (üç nokta) menüsünü aç → **Reassign**. 5. Açılan listeden **`iklim-app-02`**'yi seç → **Reassign** butonuna tıkla. > **Not:** Floating IP Hetzner panelinde yeniden atandıktan sonra `iklim-app-02`'nin network interface'inde de aktif olması gerekir. Ansible bootstrap bu konfigürasyonu yapıyorsa otomatiktir; emin olmak için `ip addr show` ile Floating IP'nin bind edildiğini doğrula. ### 3. Doğrula SWAG başlama ve sertifika kontrolü birkaç saniye sürebilir; servis `Running` görünse de ilk `curl` başarısız dönebilir. Birkaç saniye bekleyip tekrar dene. ```bash docker service ls | grep -E 'swag|cert-reloader|prometheus|grafana' curl -si https://api.iklim.co/health ``` ### `iklim-app-01` Geri Döndüğünde Node Swarm'a yeniden katıldıktan sonra tüm servisleri tekrar `iklim-app-01`'e taşıyıp Floating IP'yi geri aktarabilirsiniz. ```bash # Önce node'un Swarm'a gerçekten katıldığını doğrula (STATUS = Ready olmalı) docker node ls # Servisleri geri taşı for svc in iklimco_swag iklimco_cert-reloader iklimco_prometheus iklimco_grafana; do docker service update --constraint-add "node.hostname == iklim-app-01" --constraint-rm "node.hostname == iklim-app-02" $svc done # Floating IP'yi iklim-app-01'e geri ata hcloud floating-ip assign ``` ## Özet | Bileşen | Failover davranışı | |---------|-------------------| | Swarm quorum | Otomatik — 2 manager yeterli | | Vault, mikroservisler | Otomatik — `node.labels.type == service` constraint ile başka node'a schedule edilir | | SWAG, cert-reloader | Manuel — `docker service update --constraint-*` + Floating IP taşıma | | cert-distributor | Otomatik — `mode: global`, tüm servis node'larında zaten çalışır | | Prometheus, Grafana | Manuel — `docker service update --constraint-*` | | Veriler & Konfig | StorageBox'ta; failover node hemen erişir, veri kaybı yaşanmaz | --- # Prod — DB Node Recovery Her DB node'u (`iklim-db-01`, `iklim-db-02`, `iklim-db-03`) aynı servis üçlüsünü barındırır: | Node | Servisler | |------|-----------| | `iklim-db-01` | `etcd-01`, `patroni-01`, `mongodb-01` | | `iklim-db-02` | `etcd-02`, `patroni-02`, `mongodb-02` | | `iklim-db-03` | `etcd-03`, `patroni-03`, `mongodb-03` | ## Senaryo A: Node Geçici Olarak Çöker (Volume'lar Korunur) etcd, Patroni ve MongoDB'nin tamamı 3 üyeli HA cluster'lardır; quorum için 2 node yeterlidir. | Servis | Etki | Otomatik İyileşme | |--------|------|-------------------| | etcd | 2/3 node ile quorum devam eder | Node geri dönünce cluster'a otomatik katılır | | Patroni | Replica düşerse primary devam eder; primary düşerse etcd üzerinden yeni primary seçilir | Node geri dönünce replica olarak otomatik katılır | | MongoDB | 2/3 node ile quorum devam eder; gerekirse yeni primary seçilir | Node geri dönünce primary'den initial sync ile güncellenir | **Manuel adım gerekmez.** Docker Swarm `restart_policy: on-failure` servisleri otomatik başlatır. ## Senaryo B: Node Yeniden Kurulur (Volume'lar Silinir) etcd named volume'ları node-lokal olduğundan node yeniden kurulunca kaybolur. Patroni ve MongoDB kendi kendine iyileşir; etcd manuel müdahale gerektirir. ```bash # Aktif bir etcd container'ından — eski üyeyi cluster'dan çıkar docker exec -it $(docker ps -q -f name=iklimco_etcd-01) \ etcdctl member list --endpoints=http://etcd-01:2379,http://etcd-02:2379,http://etcd-03:2379 # Çıktıdan yeniden kurulan node'un 'sini al: docker exec -it $(docker ps -q -f name=iklimco_etcd-01) \ etcdctl member remove --endpoints=http://etcd-01:2379,http://etcd-02:2379,http://etcd-03:2379 # Servisleri yeniden başlat (etcd boş volume ile existing cluster'a katılır; # Patroni primary'den pg_basebackup ile otomatik clone alır; # MongoDB hostname değişmediyse primary'den otomatik initial sync yapar) docker service update --force iklimco_etcd-0N docker service update --force iklimco_patroni-0N docker service update --force iklimco_mongodb-0N ``` > **MongoDB hostname değişirse:** Replica set konfigürasyonu eski hostname'i tutar. `mongosh` ile `rs.remove(":27017")` ardından `rs.add(":27017")` çalıştır. > **etcd `ETCD_INITIAL_CLUSTER_STATE`:** Stack dosyasında `new` olarak tanımlıdır (ilk kurulum için). Yeniden kurulum senaryosunda Swarm servisi `--force` ile güncellenince etcd boş volume ile başlar ve mevcut cluster'a `existing` modunda katılmaya çalışır. Bitnami etcd image'ı bunu otomatik algılar; sorun yaşanırsa stack dosyasında ilgili node'un `ETCD_INITIAL_CLUSTER_STATE` değerini geçici olarak `existing` yapıp redeploy et, ardından geri al. ## Özet | Servis | Geçici çöküş | Yeniden kurulum | |--------|-------------|-----------------| | etcd | Otomatik | Manuel: `member remove` → `service update --force` | | Patroni | Otomatik | Otomatik: boş dir'den primary'yi clone alır | | MongoDB | Otomatik | Otomatik (aynı hostname); hostname değişirse `rs.remove` + `rs.add` |