Environment_Infrastructure/facts/prod-kurulum-gecmisi.md
Murat ÖZDEMİR 51933afea6 feat(infra): Refactor Swarm networking for reliable DNS and stack ownership
Moves `iklimco-net` overlay network creation to be managed by the Docker Swarm stack, ensuring reliable embedded DNS resolution for inter-service communication. This resolves issues where services on external overlay networks failed to discover each other via Docker DNS.

This refactoring includes:
*   Removing the manual `iklimco-net` creation from the Ansible `swarm` role.
*   Adjusting `act_runner` configuration to connect job containers to `iklimco-net` only after the stack has deployed and created the network.
*   Setting `storagebox_file_mode` to `0600` for DB nodes to prevent "too open" errors with MongoDB keyfiles.
*   Provisioning dedicated bind mount directories for MongoDB and PostgreSQL on DB nodes with correct ownership and permissions.
*   Updating documentation to reflect the consolidated stack and network changes.
2026-05-26 01:08:12 +03:00

288 lines
9.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Prod Ortamı Kurulum Geçmişi
Prod kurulum adımları ve mevcut yapı.
## 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 = "<iklim_prod proje 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: "<storagebox-sub-user-parolası>"
```
### 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/<hostname>/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 OS kullanıcısı parolası>"
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/<hostname>/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
```
## DB Stack Deploy
### 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
```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
chmod 600 /tmp/.env.secrets.shared
scp -P 23 u469968@u469968.your-storagebox.de:prod/secrets/iklim.co/.env \
/tmp/.env
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
```bash
ssh root@<db-01-ip>
# 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
chmod 600 /tmp/.env.secrets.shared
scp -P 23 u469968@u469968.your-storagebox.de:prod/secrets/iklim.co/.env \
/tmp/.env
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
```
## Mevcut 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-stac-infra_db-prod.yml) | ⏳ bekliyor |
| MongoDB rs.initiate (ilk deploy sonrası elle) | ⏳ bekliyor |
| Deploy pipeline ilk çalışma | ⏳ bekliyor |
## Önemli Mimari Notlar
### Tek Stack Yaklaşımı (2026-05-26)
`docker-stack-infra-prod.yml` ve `docker-stack-db-prod.yml` tek dosyada birleştirildi: `docker-stac-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:** İlk deploy sonrası `rs.initiate` elle verilmeli (DB Stack Deploy bölümüne bakınız).
**Network silinirse:** Stack'i yeniden deploy et — `docker stack deploy -c docker-stac-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.
### 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.