Environment_Infrastructure/facts/prod-kurulum-gecmisi.md
Murat ÖZDEMİR 87df1b3a5c docs(prod): mark initial deployment pipeline completed
Update the production installation history to show the initial deployment pipeline as completed.

Normalize the historical status table formatting while keeping the recorded setup milestones unchanged.
2026-06-18 19:32:34 +03:00

15 KiB
Raw Blame History

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

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:

  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.