- 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.
363 lines
15 KiB
Markdown
363 lines
15 KiB
Markdown
# 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 | ⏳ 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:
|
||
|
||
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.
|