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.
9.5 KiB
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:
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
cd Environment_Infrastructure/terraform/hetzner/prod
terraform init
terraform plan
terraform apply
Inventory Üretimi
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.
[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
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:
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):
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.
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:
storagebox_uid: "999"
storagebox_gid: "999"
Bootstrap Çalıştırma Sırası
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
# 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
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
# 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.