- Anglicized setup and facts markdown file names for better consistency. - Updated 01-swarm-init-multinode.md to highlight Ansible automation of Swarm initialization and labeling. - Overhauled 03-infra-stack-changes.md to describe the single monolithic file strategy and reflect current Redis, RabbitMQ, and etcd cluster configurations. - Fixed minor overrides and typos in Patroni templates and Ansible bootstrap documents. - Restructured README and roadmap mapping to align with the renamed setup documents.
15 KiB
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:
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
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.
# 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.
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
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
# 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 | ⏳ bekliyor |
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.<svc> |
| 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:
- StorageBox'tan
.env,.env.secrets.shared, service secret dosyaları ve.env.secrets.swagalınır. - PostgreSQL ve MongoDB init template'leri
./init/postgresqlve./init/mongodbaltına üretilir. - Harbor pull login yapılır.
- SWAG DNS/site config dosyaları hazırlanır.
- Vault için geçici TLS placeholder cert gerekirse oluşturulur.
rabbitmq_erlang_cookieDocker secret'ı oluşturulur veya mevcutsa korunur.docker-stack-infra_db-prod.ymliklimcostack'ine deploy edilir.- Runner job container
iklimco-netoverlay network'üne bağlanır. init-infra-prod.shçalışır; bu script Vault bootstrap ve RabbitMQ prod hazırlığını yapar.- Vault AppRole ID/Secret ID değerleri ve Docker secrets üretilir.
- Güncellenen
.envve.env.secrets.*dosyaları StorageBox'a yüklenir. - 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.