# Prod Ortamı Kurulum Geçmişi Prod kurulum adımları ve mevcut yapı. Bu dosya kurulum geçmişini korur. Güncel prod deploy akışı için ana kaynak repo kökündeki `prod_env-ci_dc-pipeline.md` dosyasıdır. Aşağıdaki manuel deploy adımları, ilk kurulum ve sorun giderme geçmişi olarak tutulur; normal prod deploy artık root `.gitea/workflows/deploy-prod.yml` üzerinden yürür. ## Terraform ### Hetzner Cloud Yapılandırması Test ve prod ayrı Hetzner Cloud projelerinde çalışır; her proje için ayrı API token kullanılır. `terraform.tfvars.example` dosyasından kopyalanarak doldurulur: ```hcl hcloud_token = "" location = "fsn1" image = "rocky-10" server_type_app = "cpx42" server_type_db = "cpx32" admin_ssh_public_key_path = "~/.ssh/id_rsa.pub" admin_allowed_cidrs = ["78.187.87.109/32", "95.70.151.248/32"] ``` `admin_allowed_cidrs` Ansible `group_vars/all/vars.yml` ile birebir uyumlu olmalıdır. Prod sunucuları `lifecycle { prevent_destroy = true }` ile korunur. ### Apply ```bash cd Environment_Infrastructure/terraform/hetzner/prod terraform init terraform plan terraform apply ``` ### Inventory Üretimi ```bash mkdir -p ../../../ansible/prod/inventory/generated terraform output -raw ansible_inventory_yaml > ../../../ansible/prod/inventory/generated/prod.yml ``` ## Ansible ### ansible.cfg `roles_path = roles:../roles` ile prod'a özgü roller (`roles/`) yanı sıra ortak roller (`../roles/`) de kullanılır. ```ini [defaults] inventory = inventory/generated/prod.yml remote_user = root host_key_checking = False retry_files_enabled = False interpreter_python = auto_silent roles_path = roles:../roles [privilege_escalation] become = True become_method = sudo become_user = root become_ask_pass = False ``` ### group_vars/all/vars.yml ```yaml storagebox_account: "u469968" storagebox_user: "{{ storagebox_account }}-sub5" storagebox_url: "https://{{ storagebox_user }}.your-storagebox.de/" storagebox_mount_point: "/mnt/storagebox" storagebox_password: "{{ vault_storagebox_password }}" storagebox_managed_directories: - path: "{{ storagebox_mount_point }}/ssl" mode: "0755" - path: "{{ storagebox_mount_point }}/swag/config" mode: "0755" - path: "{{ storagebox_mount_point }}/swag/site-confs" mode: "0755" - path: "{{ storagebox_mount_point }}/grafana/data" mode: "0755" - path: "{{ storagebox_mount_point }}/precipitation/images" mode: "0755" iklim_password: "{{ vault_iklim_password }}" act_runner_labels: "prod-runner,ubuntu-24.04,{{ inventory_hostname }}" swarm_manager_ip: "10.20.10.11" mongodb_replset_name: "rs0" admin_allowed_cidrs: "78.187.87.109/32 95.70.151.248/32" admin_ssh_public_key_path: "~/.ssh/id_rsa.pub" timezone: "Europe/Istanbul" ``` `admin_allowed_cidrs` Terraform `terraform.tfvars` ile birebir uyumlu olmalıdır. ### group_vars/all/vault.yml Ansible Vault ile şifrelidir. Tanımlaması gereken değişken: ```yaml vault_storagebox_password: "" ``` ### group_vars/db/vars.yml DB node'larında StorageBox `uid/gid=999` ile mount edilir (MongoDB ve PostgreSQL container user'ı ile uyumlu): ```yaml storagebox_uid: "999" storagebox_gid: "999" ``` ### host_vars — Per-Host Vault `vault_iklim_password` her sunucu için ayrı `host_vars//vault.yml` dosyasında tanımlıdır. Hardening rolü bu değeri `iklim` OS kullanıcısının sistem parolası olarak uygular (`password_hash('sha512')`). Her sunucuya farklı parola verilebilir. ```text prod/ host_vars/ iklim-app-01/vault.yml ← vault_iklim_password: "" iklim-app-02/vault.yml iklim-app-03/vault.yml iklim-db-01/vault.yml iklim-db-02/vault.yml iklim-db-03/vault.yml ``` Her dosya ayrı şifrelenir: `ansible-vault encrypt host_vars//vault.yml` Hardening rolü `PermitRootLogin prohibit-password` uygular — key tabanlı root girişi açık, parola ile kapalıdır. ### StorageBox Mount StorageBox WebDAV mount (`/mnt/storagebox`) davfs2 ile yapılır. DB node'larında `uid=999,gid=999` parametreleriyle mount edilir (PostgreSQL/MongoDB container uid'i ile uyumlu). `group_vars/db/vars.yml` içinde tanımlanır: ```yaml storagebox_uid: "999" storagebox_gid: "999" ``` ### Bootstrap Çalıştırma Sırası ```bash cd Environment_Infrastructure/ansible/prod # 1. Tüm node'lar — base, hardening, docker, dizinler, storagebox ansible-playbook prod-bootstrap.yml \ --tags base,hardening,docker,node_dirs,storagebox,storagebox_ssh_key \ --vault-password-file=../.vault_pass # 2. Swarm kurulumu ansible-playbook prod-bootstrap.yml \ --tags swarm \ --vault-password-file=../.vault_pass # 3. DB node label'ları ansible-playbook prod-bootstrap.yml \ --tags db_labels \ --vault-password-file=../.vault_pass # 4. DB node konfigürasyonu (StorageBox dizinleri, patroni.yml, mongod.conf, keyfile) ansible-playbook prod-bootstrap.yml \ --tags db_stack \ --limit db \ --vault-password-file=../.vault_pass # 5. Act runner kurulumu ansible-playbook prod-bootstrap.yml \ --tags act_runner \ --vault-password-file=../.vault_pass ``` ## Güncel Production Deploy Kaynakları | Alan | Güncel kaynak | | --- | --- | | Root prod workflow | `.gitea/workflows/deploy-prod.yml` | | Detaylı CI/CD dokümanı | `prod_env-ci_dc-pipeline.md` | | Ana infra stack | `docker-stack-infra_db-prod.yml` | | Vault HA stack | `docker-stack-vault.yml` | | Vault bootstrap script | `init/vault/vault-bootstrap.sh` | | Prod env ve secret dosyaları | `prod/secrets/iklim.co/.env`, `.env.secrets.*` | Güncel yapıda `.deleted` suffix'li eski stack dosyaları yoktur ve prod akışında dikkate alınmaz. Ana infra stack `docker-stack-infra_db-prod.yml` dosyasıdır. Vault stack'i bu dosyanın içinde değildir; `vault-bootstrap.sh` tarafından `docker-stack-vault.yml` ile deploy edilir. ## Tarihsel Manuel DB Stack Deploy (2026-05-21) Bu bölüm ilk prod DB/infra kurulum geçmişini korumak için bırakılmıştır. Güncel normal akışta bu adımlar elle çalıştırılmaz; root prod workflow ana stack deploy, Vault bootstrap, MongoDB replica set init ve DB init scriptlerini yönetir. ### Custom Image Build `build/patroni-postgis/` altında PostGIS + Patroni imajı bulunur (`registry.tarla.io/iklimco/custom-patroni-postgis:18-3.6`). `ops/push-harbor-custom-images.sh` ile Harbor'a push edilir. ### Stack Deploy Tarihsel not: Bu komut bloğundaki `docker-stack-db-prod.yml` artık güncel stack dosyası değildir. Güncel ana stack `docker-stack-infra_db-prod.yml` dosyasıdır. ```bash # Lokal → app-01 scp ./docker-stack-* root@178.104.210.41:/home/iklim/ # app-01'de cd /home/iklim # password; 'https://passwords.tarla.io' içinde "tarla.io›[Hetzner] Utils Server" klasörünün altında scp -P 23 u469968@u469968.your-storagebox.de:prod/secrets/iklim.co/.env.secrets.shared /tmp/.env.secrets.shared scp -P 23 u469968@u469968.your-storagebox.de:prod/secrets/iklim.co/.env /tmp/.env chmod 600 /tmp/.env.secrets.shared chmod 600 /tmp/.env export $(grep -v '^\s*#' /tmp/.env.secrets.shared | grep -v '^\s*$' | xargs) export $(grep -v '^\s*#' /tmp/.env | grep -v '^\s*$' | xargs) docker stack deploy --with-registry-auth -c docker-stack-db-prod.yml iklimco # deploy başarılı bir şekilde tamamlanınca rm /tmp/.env rm /tmp/.env.secrets.shared history -c && history -w ``` ### MongoDB Replica Set Init Tarihsel not: İlk kurulumda `rs.initiate` elle verilmişti. Güncel root prod workflow içinde `Initialize MongoDB Replica Set` adımı replica set yoksa `rs.initiate()`, eksik üye varsa primary üzerinden `rs.add()` çalıştırır. ```bash ssh root@ # password; 'https://passwords.tarla.io' içinde "tarla.io›[Hetzner] Utils Server" klasörünün altında scp -P 23 u469968@u469968.your-storagebox.de:prod/secrets/iklim.co/.env.secrets.shared /tmp/.env.secrets.shared scp -P 23 u469968@u469968.your-storagebox.de:prod/secrets/iklim.co/.env /tmp/.env chmod 600 /tmp/.env.secrets.shared chmod 600 /tmp/.env export $(grep -v '^\s*#' /tmp/.env.secrets.shared | grep -v '^\s*$' | xargs) export $(grep -v '^\s*#' /tmp/.env | grep -v '^\s*$' | xargs) MONGO_CID=$(docker ps --filter name=iklimco_mongodb-01 --format "{{.ID}}" | head -1) docker exec -it $MONGO_CID mongosh \ -u "$DATABASE_MONGODB_ROOT_USER" \ -p "$DATABASE_MONGODB_ROOT_PASSWD" \ --authenticationDatabase admin --eval ' rs.initiate({ _id: "rs0", members: [ { _id: 0, host: "mongodb-01:27017" }, { _id: 1, host: "mongodb-02:27017" }, { _id: 2, host: "mongodb-03:27017" } ] })' # bir süre sonra docker exec -it $MONGO_CID mongosh \ -u "$DATABASE_MONGODB_ROOT_USER" \ -p "$DATABASE_MONGODB_ROOT_PASSWD" \ --authenticationDatabase admin --eval 'rs.status()' rm /tmp/.env rm /tmp/.env.secrets.shared history -c && history -w ``` ### Patroni Cluster Doğrulama ```bash # app-01'den curl -s http://10.20.20.11:8008/cluster | python3 -m json.tool ``` ## Tarihsel Durum (2026-05-21) | Adım | Durum | | ------------------------------------------------------- | ----- | | Terraform — 6 sunucu, ağ, firewall, floating IP | ✅ | | Ansible base + hardening + docker + node_dirs | ✅ | | Ansible storagebox + storagebox_ssh_key | ✅ | | Ansible swarm (3 manager app + 3 worker db) | ✅ | | Ansible db_labels | ✅ | | Ansible db_stack (StorageBox DB dizinleri + config) | ✅ | | Ansible act_runner (3 prod runner Gitea'da Idle) | ✅ | | DB stack deploy (etcd + MongoDB + Patroni) | ✅ | | MongoDB replica set init (rs0: 1 primary, 2 secondary) | ✅ | | Patroni HA cluster (1 leader, 2 replica, lag=0) | ✅ | | Ana infra stack deploy (docker-stack-infra_db-prod.yml) | ✅ | | MongoDB rs.initiate (ilk deploy sonrası elle) | ✅ | | Deploy pipeline ilk çalışma | ✅ | ## Güncel Durum (2026-06-15) | Alan | Güncel durum | | --- | --- | | Prod deploy kaynak dokümanı | `prod_env-ci_dc-pipeline.md` | | Root prod workflow | `.gitea/workflows/deploy-prod.yml` | | Ana infra stack | `docker-stack-infra_db-prod.yml` | | Vault HA stack | `docker-stack-vault.yml` | | Vault deploy yöntemi | `init/vault/vault-bootstrap.sh` tarafından bootstrap/deploy | | Eski `.deleted` stack dosyaları | Silindi, güncel akışta yok | | Prod env dosyası | StorageBox `prod/secrets/iklim.co/.env` -> workflow workspace `./.env` | | Shared secrets | StorageBox `prod/secrets/iklim.co/.env.secrets.shared` | | Service secrets | StorageBox `prod/secrets/iklim.co/.env.secrets.` | | SWAG secrets | StorageBox `prod/secrets/iklim.co/.env.secrets.swag` | | MongoDB replica set init | Workflow içinde otomatik/idempotent adım olarak yönetiliyor | | PostgreSQL init | Patroni primary beklenerek `./init/postgresql/*.sql` ile çalışıyor | | MongoDB init | Replica set hazırlandıktan sonra `./init/mongodb/*.js` ile çalışıyor | | DNS update | Workflow GoDaddy API ile `api`, `apigw`, `rabbitmq`, `grafana` A kayıtlarını güncelliyor | Güncel prod workflow ana hatlarıyla şu sırayı izler: 1. StorageBox'tan `.env`, `.env.secrets.shared`, service secret dosyaları ve `.env.secrets.swag` alınır. 2. PostgreSQL ve MongoDB init template'leri `./init/postgresql` ve `./init/mongodb` altına üretilir. 3. Harbor pull login yapılır. 4. SWAG DNS/site config dosyaları hazırlanır. 5. Vault için geçici TLS placeholder cert gerekirse oluşturulur. 6. `rabbitmq_erlang_cookie` Docker secret'ı oluşturulur veya mevcutsa korunur. 7. `docker-stack-infra_db-prod.yml` `iklimco` stack'ine deploy edilir. 8. Runner job container `iklimco-net` overlay network'üne bağlanır. 9. `init-infra-prod.sh` çalışır; bu script Vault bootstrap ve RabbitMQ prod hazırlığını yapar. 10. Vault AppRole ID/Secret ID değerleri ve Docker secrets üretilir. 11. Güncellenen `.env` ve `.env.secrets.*` dosyaları StorageBox'a yüklenir. 12. etcd, APISIX, SWAG certificate, MongoDB replica set, DB init scriptleri ve DNS kayıtları doğrulanır/güncellenir. ## Önemli Mimari Notlar ### Ana Infra Stack ve Vault Ayrımı (2026-06-15) Güncel durumda ana infra stack `docker-stack-infra_db-prod.yml` dosyasıdır. Bu stack Redis master/replica/sentinel, RabbitMQ cluster, APISIX, APISIX Dashboard, Prometheus, Grafana, SWAG, cert-reloader, cert-distributor, etcd, Patroni ve MongoDB replica set servislerini içerir. Vault ana infra stack içinde değildir. Vault HA cluster `docker-stack-vault.yml` dosyasıyla, `init/vault/vault-bootstrap.sh` tarafından deploy edilir. Bootstrap akışı placeholder `vault_unseal_key` oluşturur, `iklimco_vault` servisini deploy eder, Vault init/unseal işlemini yapar ve Docker secret'ı gerçek unseal key ile rotate eder. ### Tek Stack Yaklaşımı (2026-05-26) `docker-stack-infra-prod.yml` ve `docker-stack-db-prod.yml` tek dosyada birleştirildi: `docker-stack-infra_db-prod.yml`. Her iki dosya da aynı `iklimco` stack adına deploy edildiğinden servis isimleri değişmedi. **Neden birleştirildi:** External overlay network'lerde Docker embedded DNS hiçbir entry kaydetmez (servis VIP'leri, alias'lar dahil). Stack-owned network'te Docker DNS tam çalışır — vault `retry_join`, etcd alias'ları ve tüm servis discovery sorunları çözüldü. **Network:** `iklimco-net` artık stack tarafından oluşturulur (MTU=1400, attachable). Ansible `swarm` rolündeki network oluşturma task'ı kaldırıldı. **MongoDB rs.initiate:** Bu not ilk kurulum dönemine aittir. Güncel prod workflow `Initialize MongoDB Replica Set` adımında `rs.initiate()` ve gerektiğinde `rs.add()` işlemlerini yönetir. **Network silinirse:** Stack'i yeniden deploy et — `docker stack deploy -c docker-stack-infra_db-prod.yml iklimco` ### Vault retry_join (2026-05-25) `retry_join.leader_api_addr` olarak `iklimco_vault` (Swarm servis adı) kullanılır. Stack-owned network sayesinde Docker DNS bu VIP'i kayıt eder. `leader_tls_server_name: vault.iklim.co` ile `*.iklim.co` sertifikası TLS doğrulamasını geçer. Güncel Vault deploy akışında bu ayar `docker-stack-vault.yml` ve Vault template dosyaları üzerinden kullanılır. Vault stack deploy'u root workflow'da doğrudan değil, `init-infra-prod.sh` -> `init/vault/init-prod.sh` -> `init/vault/vault-bootstrap.sh` zinciriyle yapılır. ### Runner / iklimco-net (2026-05-26) Act runner config'de `container.network: "bridge"` kullanılır (önceki `iklimco-net`). Workflow'da "Connect Runner to Overlay Network" adımı "Deploy Swarm Stacks" sonrasına taşındı — böylece stack'in oluşturduğu `iklimco-net`'e runner job container bağlanabilir.