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

363 lines
15 KiB
Markdown
Raw Permalink 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ı.
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 = "<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
```
## 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@<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
```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.<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.