Implement: Gitea Actions runner, automated DB stack, and Turkish localization

*   Introduces an Ansible role for installing and registering `act_runner` for Gitea Actions.
*   Automates PostgreSQL and MongoDB deployment on Docker Swarm in the test environment, leveraging Docker named volumes for data persistence.
*   Translates core documentation, including `README.md` and `setup/04-test-db-docker-kurulum.md`, to Turkish.
*   Adds comprehensive documentation for firewall architecture (`facts/firewall.md`) and Docker Swarm node recovery (`facts/swarm-node-recovery.md`).
*   Enhances security hardening by ensuring `fail2ban` is enabled and streamlining admin SSH key management via Ansible.
*   Updates Ansible vault structure to support new secret variables and adds `.vault_pass` to `.gitignore`.
This commit is contained in:
Murat ÖZDEMİR 2026-05-12 18:34:24 +03:00
parent bbeaf97815
commit 2198f932cd
21 changed files with 647 additions and 217 deletions

2
.gitignore vendored
View File

@ -48,3 +48,5 @@ runner-config.secret.yaml
*.swo *.swo
*.pdf *.pdf
ansible/.vault_pass

199
README.md
View File

@ -1,86 +1,87 @@
# 🌍 Environment Infrastructure # 🌍 Sunucu Ortam ve Altyapıları
Infrastructure-as-code and operational runbook repository for `iklim.co` test and production environments on Hetzner Cloud. `iklim.co` test ve prod ortamları için Hetzner Cloud üzerinde Infrastructure-as-code ve operasyonel runbook deposu.
This repository defines: Bu depo şunları kapsar:
- 🧱 Terraform resources for Hetzner infrastructure (`test` and `prod`) - 🧱 Hetzner altyapısı için Terraform kaynakları (`test` ve `prod`)
- 🤖 Ansible bootstrap playbooks, shared roles, and inventory targets - 🤖 Ansible bootstrap playbook'ları, paylaşımlı roller ve envanter hedefleri
- 📚 End-to-end setup guides and roadmap documents (mostly Turkish) - 📚 Uçtan uca kurulum rehberleri ve yol haritası dokümanları
- 📊 Sizing/cost analysis and supporting reference assets - 📊 Boyutlandırma/maliyet analizi ve referans kaynakları
## 🎯 Scope ## 🎯 Kapsam
The main goal is to standardize and document infrastructure provisioning with clear responsibility boundaries: Temel amaç, sorumluluk sınırları net biçimde tanımlanmış, standart ve belgelenmiş bir altyapı provisioning süreci oluşturmaktır:
- 🧱 **Terraform**: creates cloud infrastructure (servers, private networks, firewalls, placement groups, floating IPs, SSH key registration, inventory output) - 🧱 **Terraform**: bulut altyapısını oluşturur (sunucular, private ağlar, firewall'lar, placement group'lar, floating IP'ler, SSH key kaydı, envanter çıktısı)
- 🤖 **Ansible**: prepares OS, security hardening, Docker/Swarm, runner setup, and StorageBox mount workflows via in-repo playbooks and shared roles - 🤖 **Ansible**: OS hazırlığı, güvenlik sertleştirme, Docker/Swarm, runner kurulumu ve StorageBox mount süreçlerini depodaki playbook ve paylaşımlı roller aracılığıyla yönetir
- 🚀 **Application/stack deployment**: handled in related deployment workflows and stack manifests referenced by roadmap docs - 🚀 **Uygulama/stack dağıtımı**: yol haritası dokümanlarında referans verilen ilgili deployment workflow'ları ve stack manifest'leri tarafından yönetilir
## 📌 Current Repository Status ## 📌 Mevcut Depo Durumu
As of now, this repository primarily contains: Bu depo şu an ağırlıklı olarak şunları içermektedir:
- 🧱 Ready Terraform code for: - 🧱 Hazır Terraform kodu:
- `terraform/hetzner/test` - `terraform/hetzner/test`
- `terraform/hetzner/prod` - `terraform/hetzner/prod`
- 🤖 Ansible automation assets for both environments: - 🤖 Her iki ortam için Ansible otomasyon varlıkları:
- `ansible/test/test-bootstrap.yml` - `ansible/test/test-bootstrap.yml`
- `ansible/prod/prod-bootstrap.yml` - `ansible/prod/prod-bootstrap.yml`
- `ansible/roles/*` - `ansible/roles/*`
- `ansible/test/group_vars/*` and `ansible/prod/group_vars/*` - `ansible/test/group_vars/*` ve `ansible/prod/group_vars/*`
- 📦 Inventory artifacts and output targets: - 📦 Envanter çıktıları ve hedef yollar:
- `ansible/test/inventory/generated/test.yml` (tracked sample) - `ansible/test/inventory/generated/test.yml` (takip edilen örnek)
- `ansible/prod/inventory/generated/prod.yml` (expected output path) - `ansible/prod/inventory/generated/prod.yml` (beklenen çıktı yolu)
- 📘 Detailed setup phases: - 📘 Detaylı kurulum aşamaları:
- `setup/00-genel-yol-haritasi.md` through `setup/09-prod-runner-ha-ve-swarm.md` - `setup/00-genel-yol-haritasi.md` `setup/09-prod-runner-ha-ve-swarm.md`
- 🛣️ Environment roadmap steps: - 🛣️ Ortam yol haritası adımları:
- `roadmap/test-env/*` - `roadmap/test-env/*`
- `roadmap/prod-env/*` - `roadmap/prod-env/*`
- 📈 Capacity planning and reference charts: - 📈 Kapasite planlama ve referans grafikler:
- `hetzner-sizing-report.md` - `hetzner-sizing-report.md`
- `test-app-graphs.png` - `test-app-graphs.png`
- `test-db-graphs.png` - `test-db-graphs.png`
## 🧭 Target Environment Topology ## 🧭 Hedef Ortam Topolojisi
### 🧪 Test ### 🧪 Test
| Node | Role | Private IP | Suggested Type | | Node | Rol | Private IP | Önerilen Tip |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| `iklim-app-01` | Swarm manager + app worker + test runner | `10.10.10.11` | `cpx42` | | `iklim-app-01` | Swarm manager + app worker + test runner | `10.10.10.11` | `cpx42` |
| `iklim-db-01` | DB host (manual/stack-based DB setup path) | `10.10.20.11` | `cpx42` | | `iklim-db-01` | DB host (manuel/stack tabanlı DB kurulum yolu) | `10.10.20.11` | `cpx42` |
### 🏭 Production ### 🏭 Production
| Node | Role | Private IP | Suggested Type | | Node | Rol | Private IP | Önerilen Tip |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| `iklim-app-01` | Swarm manager + app worker + runner (primary FIP target) | `10.20.10.11` | `cpx42` | | `iklim-app-01` | Swarm manager + app worker + runner (birincil FIP hedefi) | `10.20.10.11` | `cpx42` |
| `iklim-app-02` | Swarm manager + app worker + runner | `10.20.10.12` | `cpx42` | | `iklim-app-02` | Swarm manager + app worker + runner | `10.20.10.12` | `cpx42` |
| `iklim-app-03` | Swarm manager + app worker + runner | `10.20.10.13` | `cpx42` | | `iklim-app-03` | Swarm manager + app worker + runner | `10.20.10.13` | `cpx42` |
| `iklim-db-01` | DB cluster node | `10.20.20.11` | `cpx32` | | `iklim-db-01` | DB cluster node | `10.20.20.11` | `cpx32` |
| `iklim-db-02` | DB cluster node | `10.20.20.12` | `cpx32` | | `iklim-db-02` | DB cluster node | `10.20.20.12` | `cpx32` |
| `iklim-db-03` | DB cluster node | `10.20.20.13` | `cpx32` | | `iklim-db-03` | DB cluster node | `10.20.20.13` | `cpx32` |
## 🔐 Security and Network Baseline ## 🔐 Güvenlik ve Ağ Temeli
Core decisions reflected in Terraform and setup docs: Terraform ve kurulum dokümanlarına yansıtılan temel kararlar:
- Test and prod are separated into different Hetzner Cloud projects/tokens. - Test ve prod, ayrı Hetzner Cloud proje ve token'larıyla birbirinden yalıtılmıştır.
- Public ingress is limited to: - Kamuya açık gelen trafik şunlarla sınırlıdır:
- `22/tcp` (admin CIDRs only) - `22/tcp` (yalnızca admin CIDR'ları)
- `80/tcp` - `80/tcp`
- `443/tcp` - `443/tcp`
- Critical services remain private-only (for example Vault `8200`, PostgreSQL `5432`, MongoDB `27017`, internal observability and broker ports). - Kritik servisler yalnızca private ağda erişilebilir (örneğin Vault `8200`, PostgreSQL `5432`, MongoDB `27017`, iç gözlemlenebilirlik ve broker portları).
- Placement groups are used for host-spread strategy. - Host dağılımı stratejisi için placement group'lar kullanılmaktadır.
- `prevent_destroy = true` is enabled on server resources to reduce accidental deletion risk. - Sunucu kaynaklarında yanlışlıkla silinmeye karşı `prevent_destroy = true` etkinleştirilmiştir.
- Terraform state and secret files must not be committed. - Terraform state ve gizli dosyalar commit'lenmemelidir.
See: Ayrıca bkz.:
- `setup/01-private-network-port-matrisi.md` - [[facts/firewall.md]] — tüm firewall kurallarının araç bazında özet dökümantasyonu
- [[setup/01-private-network-port-matrisi.md]]
- `terraform/hetzner/test/firewall.tf` - `terraform/hetzner/test/firewall.tf`
- `terraform/hetzner/prod/firewall.tf` - `terraform/hetzner/prod/firewall.tf`
## 🗂️ Repository Structure ## 🗂️ Depo Yapısı
```text ```text
Environment_Infrastructure/ Environment_Infrastructure/
@ -123,35 +124,37 @@ Environment_Infrastructure/
│ └── hetzner/ │ └── hetzner/
│ ├── test/ │ ├── test/
│ └── prod/ │ └── prod/
├── facts/
│ └── firewall.md
├── hetzner-sizing-report.md ├── hetzner-sizing-report.md
├── setup-vs-roadmap-map.md ├── setup-vs-roadmap-map.md
├── test-app-graphs.png ├── test-app-graphs.png
└── test-db-graphs.png └── test-db-graphs.png
``` ```
## ✅ Prerequisites ## ✅ Ön Koşullar
- Terraform `>= 1.6` - Terraform `>= 1.6`
- Hetzner Cloud account(s) and API token per environment - Hetzner Cloud hesabı ve ortam başına API token
- SSH key pair (public key path used in Terraform variables) - SSH anahtar çifti (public key yolu Terraform değişkenlerinde kullanılır)
- Linux/macOS shell tools (`bash`, `cp`, `sed` or editor of your choice) - Linux/macOS kabuk araçları (`bash`, `cp`, `sed` veya tercih edilen metin editörü)
- Optional but expected in later phases: Ansible, Docker, access to Gitea/Harbor/StorageBox - İlerleyen aşamalarda gerekli: Ansible, Docker, Gitea/Harbor/StorageBox erişimi
## 🛠️ Terraform Usage ## 🛠️ Terraform Kullanımı
### 1) 🧪 Test Infrastructure ### 1) 🧪 Test Altyapısı
```bash ```bash
cd terraform/hetzner/test cd terraform/hetzner/test
cp terraform.tfvars.example terraform.tfvars cp terraform.tfvars.example terraform.tfvars
``` ```
Edit `terraform.tfvars` values: `terraform.tfvars` değerlerini düzenle:
- `hcloud_token` - `hcloud_token`
- `admin_allowed_cidrs` - `admin_allowed_cidrs`
- optional overrides (`location`, image, server types, key path) - isteğe bağlı geçersiz kılmalar (`location`, image, sunucu tipleri, key yolu)
Then run: Ardından çalıştır:
```bash ```bash
terraform init terraform init
@ -161,19 +164,19 @@ mkdir -p ../../../ansible/test/inventory/generated
terraform output -raw ansible_inventory_yaml > ../../../ansible/test/inventory/generated/test.yml terraform output -raw ansible_inventory_yaml > ../../../ansible/test/inventory/generated/test.yml
``` ```
### 2) 🏭 Production Infrastructure ### 2) 🏭 Production Altyapısı
```bash ```bash
cd terraform/hetzner/prod cd terraform/hetzner/prod
cp terraform.tfvars.example terraform.tfvars cp terraform.tfvars.example terraform.tfvars
``` ```
Edit `terraform.tfvars` values: `terraform.tfvars` değerlerini düzenle:
- `hcloud_token` (prod token) - `hcloud_token` (prod token)
- `admin_allowed_cidrs` - `admin_allowed_cidrs`
- optional overrides - isteğe bağlı geçersiz kılmalar
Then run: Ardından çalıştır:
```bash ```bash
terraform init terraform init
@ -183,79 +186,79 @@ mkdir -p ../../../ansible/prod/inventory/generated
terraform output -raw ansible_inventory_yaml > ../../../ansible/prod/inventory/generated/prod.yml terraform output -raw ansible_inventory_yaml > ../../../ansible/prod/inventory/generated/prod.yml
``` ```
## 🧱 Setup Flow (Canonical Order) ## 🧱 Kurulum Akışı (Kanonik Sıra)
Use setup documents in this sequence: Kurulum dokümanlarını bu sırayla kullan:
1. `setup/00-genel-yol-haritasi.md` — global decisions and boundaries 1. `setup/00-genel-yol-haritasi.md` — genel kararlar ve sınırlar
2. `setup/01-private-network-port-matrisi.md` — private/public port policy 2. `setup/01-private-network-port-matrisi.md` — private/public port politikası
3. `setup/02-test-terraform-iaac.md` — test Terraform phase 3. `setup/02-test-terraform-iaac.md` — test Terraform aşaması
4. `setup/03-test-ansible-bootstrap.md` — test OS/bootstrap/hardening 4. `setup/03-test-ansible-bootstrap.md` — test OS/bootstrap/sertleştirme
5. `setup/04-test-db-docker-kurulum.md` — test DB stack setup on Swarm 5. `setup/04-test-db-docker-kurulum.md` — test DB stack kurulumu (Swarm üzerinde)
6. `setup/05-test-runner-ve-deploy-onkosullari.md` — test runner and deploy prerequisites 6. `setup/05-test-runner-ve-deploy-onkosullari.md` — test runner ve deploy ön koşulları
7. `setup/06-prod-terraform-iaac.md` — prod Terraform phase 7. `setup/06-prod-terraform-iaac.md` — prod Terraform aşaması
8. `setup/07-prod-ansible-bootstrap.md` — prod OS/bootstrap/hardening 8. `setup/07-prod-ansible-bootstrap.md` — prod OS/bootstrap/sertleştirme
9. `setup/08-prod-db-cluster-kurulum.md` — prod DB cluster stack (MongoDB + Patroni/etcd) 9. `setup/08-prod-db-cluster-kurulum.md` — prod DB cluster stack (MongoDB + Patroni/etcd)
10. `setup/09-prod-runner-ha-ve-swarm.md` — prod runner HA and deploy lock model 10. `setup/09-prod-runner-ha-ve-swarm.md` — prod runner HA ve deploy kilit modeli
## 🛣️ Roadmap Documents ## 🛣️ Yol Haritası Dokümanları
The roadmap folders track integration work for Swarm stacks, SWAG, APISIX, pipeline updates, and verification checklists: Yol haritası klasörleri; Swarm stack'leri, SWAG, APISIX, pipeline güncellemeleri ve doğrulama kontrol listeleri için entegrasyon çalışmalarını takip eder:
- `roadmap/test-env/*` - `roadmap/test-env/*`
- `roadmap/prod-env/*` - `roadmap/prod-env/*`
These documents often reference files from related repositories (for example application root repo workflows and stack files). Treat them as implementation guidance aligned with this infrastructure baseline. Bu dokümanlar zaman zaman ilgili repolardan (örneğin uygulama ana deposu workflow ve stack dosyaları) dosyalara referans verir. Bu altyapı temeliyle hizalanmış uygulama rehberleri olarak değerlendirilmelidir.
## 💰 Sizing and Cost Snapshot ## 💰 Boyutlandırma ve Maliyet Özeti
Reference: `hetzner-sizing-report.md` Referans: `hetzner-sizing-report.md`
Suggested baseline: Önerilen temel yapı:
- **Test:** `2 x cpx42` (app + db) - **Test:** `2 x cpx42` (app + db)
- **Prod:** `3 x cpx42` (app) + `3 x cpx32` (db) - **Prod:** `3 x cpx42` (app) + `3 x cpx32` (db)
Approximate monthly total in the report: Rapordaki yaklaşık aylık toplam:
- Test: `$59.98` - Test: `$59.98`
- Prod: `$139.44` - Prod: `$139.44`
- Combined: `$199.42` - Toplam: `$199.42`
## 🔑 Secrets and State Management ## 🔑 Gizli Bilgi ve State Yönetimi
Never commit: Kesinlikle commit'lenmemeli:
- `terraform.tfvars`, `*.tfvars`, `*.tfstate`, `.terraform/` - `terraform.tfvars`, `*.tfvars`, `*.tfstate`, `.terraform/`
- private keys, certificates, `.env` secrets - private key'ler, sertifikalar, `.env` gizli bilgileri
- runner tokens and vault password files - runner token'ları ve vault parola dosyaları
See `.gitignore` for enforced patterns. Zorunlu kalıplar için `.gitignore` dosyasına bkz.
Recommended: Önerilen:
- keep runtime secrets in secure secret stores / encrypted vault files - çalışma zamanı gizli bilgilerini güvenli gizli depolarda / şifreli vault dosyalarında tut
- keep generated runtime artifacts outside version control unless explicitly sanitized - üretilen çalışma zamanı artifakt'larını, açıkça temizlenmedikçe versiyon kontrolü dışında tut
## ⚠️ Known Gaps / Notes ## ⚠️ Bilinen Eksikler / Notlar
- `ansible/prod/inventory/generated/prod.yml` is an expected output path but may not exist until generated. - `ansible/prod/inventory/generated/prod.yml` beklenen bir çıktı yoludur; üretilene kadar mevcut olmayabilir.
- Some roadmap steps target files under the broader `iklim.co` application repository, not this repository alone. - Bazı yol haritası adımları, yalnızca bu depo değil, daha geniş kapsamlı `iklim.co` uygulama deposundaki dosyaları hedef alır.
## ✅ Quick Validation Checklist ## ✅ Hızlı Doğrulama Kontrol Listesi
After Terraform apply: Terraform apply sonrası:
- servers are created with expected names and private IPs - sunucular beklenen isimler ve private IP'lerle oluşturulmuş
- floating IP exists and is attached - floating IP mevcut ve bağlı
- firewalls expose only intended public ports - firewall'lar yalnızca amaçlanan public portlarııyor
- placement groups are assigned - placement group'lar atanmış
- generated inventory YAML is exported to `ansible/{test,prod}/inventory/generated/*.yml` - üretilen envanter YAML `ansible/{test,prod}/inventory/generated/*.yml` yoluna aktarılmış
After bootstrap/deploy phases: Bootstrap/deploy aşamaları sonrası:
- Swarm state and labels match documentation - Swarm durumu ve etiketleri dökümantasyonla eşleşiyor
- DB accessibility is private-only - DB erişimi yalnızca private ağdan mümkün
- Vault/API gateways follow public/private exposure rules - Vault/API gateway'leri public/private erişim kurallarına uyuyor
- runner and deploy lock behavior align with environment policy - Runner ve deploy kilit davranışı ortam politikasıyla örtüşüyor
## 🔗 References ## 🔗 Referanslar
- Hetzner Terraform Provider: https://registry.terraform.io/providers/hetznercloud/hcloud/latest - Hetzner Terraform Provider: https://registry.terraform.io/providers/hetznercloud/hcloud/latest
- Hetzner Networks: https://docs.hetzner.com/cloud/networks/overview/ - Hetzner Networks: https://docs.hetzner.com/cloud/networks/overview/

View File

@ -0,0 +1,8 @@
---
act_runner_version: "0.2.12"
act_runner_arch: "linux-amd64"
act_runner_gitea_url: "https://git.tarla.io"
act_runner_name: "iklim-test-app"
act_runner_labels: "ubuntu-latest,ubuntu-22.04,ubuntu-20.04"
# Gitea'dan alınan tek seferlik registration token; kayıt olmadıysa boş bırakılır.
act_runner_registration_token: "{{ vault_gitea_runner_token | default('') }}"

View File

@ -0,0 +1,6 @@
---
- name: restart act_runner
ansible.builtin.systemd:
name: gitea-act-runner
state: restarted
daemon_reload: true

View File

@ -0,0 +1,86 @@
---
- name: Install deploy prerequisites
ansible.builtin.dnf:
name:
- gettext
- jq
- git
state: present
- name: Create gitea-runner system user
ansible.builtin.user:
name: gitea-runner
system: true
shell: /bin/bash
create_home: true
home: /var/lib/gitea-runner
groups: docker
append: true
- name: Download act_runner binary
ansible.builtin.get_url:
url: "https://dl.gitea.com/act_runner/{{ act_runner_version }}/act_runner-{{ act_runner_version }}-{{ act_runner_arch }}"
dest: /usr/local/bin/act_runner
mode: "0755"
owner: root
group: root
- name: Create act_runner config directory
ansible.builtin.file:
path: /etc/gitea-act-runner
state: directory
owner: gitea-runner
group: gitea-runner
mode: "0750"
# Kayıt öncesinde varsayılan config.yaml üretilir; dosya varsa tekrar yazılmaz.
- name: Generate default config.yaml
ansible.builtin.shell:
cmd: /usr/local/bin/act_runner generate-config > /etc/gitea-act-runner/config.yaml
creates: /etc/gitea-act-runner/config.yaml
become_user: gitea-runner
- name: Fix config.yaml ownership
ansible.builtin.file:
path: /etc/gitea-act-runner/config.yaml
owner: gitea-runner
group: gitea-runner
mode: "0640"
# .runner dosyası varsa runner zaten kayıtlıdır; creates ile idempotent hale gelir.
- name: Register runner with Gitea
ansible.builtin.command:
argv:
- /usr/local/bin/act_runner
- register
- --instance
- "{{ act_runner_gitea_url }}"
- --token
- "{{ act_runner_registration_token }}"
- --no-interactive
- --name
- "{{ act_runner_name }}"
- --config
- /etc/gitea-act-runner/config.yaml
- --labels
- "{{ act_runner_labels }}"
args:
chdir: /var/lib/gitea-runner
creates: /var/lib/gitea-runner/.runner
become_user: gitea-runner
when: act_runner_registration_token | length > 0
no_log: true
- name: Deploy gitea-act-runner systemd service
ansible.builtin.template:
src: gitea-act-runner.service.j2
dest: /etc/systemd/system/gitea-act-runner.service
mode: "0644"
notify: restart act_runner
- name: Enable and start gitea-act-runner
ansible.builtin.systemd:
name: gitea-act-runner
enabled: true
state: started
daemon_reload: true

View File

@ -0,0 +1,16 @@
[Unit]
Description=Gitea Actions runner
After=network.target docker.service
[Service]
ExecStart=/usr/local/bin/act_runner daemon --config /etc/gitea-act-runner/config.yaml
ExecReload=/bin/kill -s HUP $MAINPID
WorkingDirectory=/var/lib/gitea-runner
User=gitea-runner
Group=gitea-runner
TimeoutSec=0
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,17 @@
---
- name: Stack durumunu kontrol et
ansible.builtin.shell: docker stack ls | grep iklim-db
register: stack_status
failed_when: false
changed_when: false
- name: DB stack dosyasını oluştur
ansible.builtin.template:
src: db.stack.yml.j2
dest: /opt/iklimco/stacks/db.yml
mode: '0600'
register: stack_file
- name: DB stack'i deploy et
ansible.builtin.shell: docker stack deploy -c /opt/iklimco/stacks/db.yml iklim-db
when: stack_status.rc != 0 or stack_file.changed

View File

@ -0,0 +1,12 @@
---
- name: MongoDB config dizinini oluştur
ansible.builtin.file:
path: /opt/iklimco/db/mongodb/config
state: directory
mode: '0750'
- name: MongoDB konfigürasyon dosyasını oluştur
ansible.builtin.template:
src: mongod.conf.j2
dest: /opt/iklimco/db/mongodb/config/mongod.conf
mode: '0644'

View File

@ -0,0 +1,6 @@
---
- include_tasks: db_node.yml
when: inventory_hostname in groups['db']
- include_tasks: app_node.yml
when: inventory_hostname in groups['app']

View File

@ -0,0 +1,42 @@
version: "3.8"
networks:
iklimco-net:
external: true
volumes:
postgresql_data:
mongodb_data:
services:
postgresql:
image: {{ db_postgres_image }}
environment:
POSTGRES_USER: "{{ db_postgres_root_user }}"
POSTGRES_PASSWORD: "{{ db_postgres_password }}"
POSTGRES_DB: postgres
PGDATA: /var/lib/postgresql/data/pgdata
volumes:
- postgresql_data:/var/lib/postgresql/data
networks:
- iklimco-net
deploy:
placement:
constraints:
- node.labels.role == db
mongodb:
image: {{ db_mongo_image }}
environment:
MONGO_INITDB_ROOT_USERNAME: "{{ db_mongo_root_user }}"
MONGO_INITDB_ROOT_PASSWORD: "{{ db_mongo_root_password }}"
volumes:
- mongodb_data:/data/db
- /opt/iklimco/db/mongodb/config/mongod.conf:/etc/mongod.conf
command: ["--config", "/etc/mongod.conf"]
networks:
- iklimco-net
deploy:
placement:
constraints:
- node.labels.role == db

View File

@ -0,0 +1,7 @@
storage:
dbPath: /data/db
net:
port: 27017
bindIp: 0.0.0.0
security:
authorization: enabled

View File

@ -38,6 +38,12 @@
dest: /etc/fail2ban/jail.local dest: /etc/fail2ban/jail.local
notify: Restart fail2ban notify: Restart fail2ban
- name: Ensure fail2ban is running and enabled
ansible.builtin.service:
name: fail2ban
state: started
enabled: yes
- name: Ensure firewalld is running - name: Ensure firewalld is running
ansible.builtin.service: ansible.builtin.service:
name: firewalld name: firewalld
@ -67,6 +73,12 @@
create_home: yes create_home: yes
state: present state: present
- name: Add SSH key to iklim user
ansible.posix.authorized_key:
user: iklim
state: present
key: "{{ lookup('file', admin_ssh_public_key_path) }}"
- name: Configure journald log limits - name: Configure journald log limits
ansible.builtin.lineinfile: ansible.builtin.lineinfile:
path: /etc/systemd/journald.conf path: /etc/systemd/journald.conf

View File

@ -6,4 +6,13 @@ storagebox_password: "{{ vault_storagebox_password }}"
iklim_password: "{{ vault_iklim_password }}" iklim_password: "{{ vault_iklim_password }}"
swarm_manager_ip: "10.10.10.11" swarm_manager_ip: "10.10.10.11"
admin_allowed_cidrs: "78.187.87.109/32 95.70.151.248/32" 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" timezone: "Europe/Istanbul"
# DB Stack
db_postgres_image: "postgis/postgis:17-3.5"
db_mongo_image: "mongo:8"
db_postgres_root_user: "{{ vault_postgres_root_user }}"
db_postgres_password: "{{ vault_postgres_password }}"
db_mongo_root_user: "{{ vault_mongo_root_user }}"
db_mongo_root_password: "{{ vault_mongo_root_password }}"

View File

@ -1,25 +1,30 @@
$ANSIBLE_VAULT;1.1;AES256 $ANSIBLE_VAULT;1.1;AES256
32653536356331386232373033363738363336323461363432653031666166343462393737643730 63323534653836623136643065383166323661663339653335373139663436316433396137343739
3162386266326333386533373630663563386337613338310a376137623835333461363662323035 3039383336313861616236336565323664613333383262610a363436376132316437656239633334
65636332376331643335323265336439613331613238393363626330313831653233373864313033 63633863643938373537333562346433313766393732616362306132313430333964373031313962
3430303335306366660a636139623264386437383763316665373230643939653039633330623834 3230393837316434650a323933313934316334633931323165633730373933326564316661666332
64636564313232626462373638653538393261323031616563653164323961393664656439393639 36323735323736656662366463383431393433643331616134623662663333313364343232653138
37313335313739353564626364313663363038316132633739623338343436303337643162396635 61316165636431663230376234626532663431373632393731383434386435353638363365323861
34323838346664303464396438393534636265636262323364643364323163653464303931626130 33373462333966663033666463303032343961653533343932363462623637616232666561646335
37663138363966386530323133613661316230303362323937313132306236323339323839633139 36623039353633343035353765326339616233666562636438636137303835643736386633623139
34633733333531373233386436313837343364326334386535626262356537376137646163326666 66656137646461363165313937306266393035383339363233303066396564346638643539313361
38306238666639623639393137623266363465313264326566663839303664303233666335663731 38653930393766656634343334373961323031333938663636623138666661346133316631393231
32633232376164383265313835326433366134613230613164373034663931396161623631666236 36393237313439313435363866333364343231613330373439626462356139303061313632343338
37613631353233346464363236383539663461333739396432626638323134383230343163396335 38633364343663333139396431663938653263633739383036613935373332653666656633323331
64363333396130326463316538306162363034353936383063333531396233356437333064613230 37316535386139633263633266353739386164666564666136643665353135356231323633373363
63353337306632323364336233313836663365623436336532623239633434656563666637333636 36646464383766663532353132326162363734366337373537656132366139386362616438616531
31343836353230306461613936383766636138663361343864623466376235666536306133306435 34386234306231376162643934643565343364336530616562303739363935353636343633376464
65666338333465653434386166366633383539323566613935363434363735313231336166626364 34333835363666303236313363663665653566326365386439653362663462343136316664613036
65353033363366386432306434333135653862616635373837376233326262646330326363626134 37616163653561633030376534393533653466373839633661653338313166383332613234666164
32343735386137363334306464383935613531373533363330363635633236313930373865393738 37343737316139393930313739366562643539326233366334353566633339333130366263663030
38363564663066363439396532656236656636646365363038393535303632356364613538353737 63393437383566393932353230396534383330646364646561323631653338396132303461613636
32636434383766343765643634633464353262396466393265643963383634323730343162323837 37306533663732626532393130323933633365336233613334666330343436326137306666636234
39343139613538653531616466303638336133396165646138663463383238616431613563343031 30623831303632613866333734306337623230373433373332646534306135333039343231643536
39333139643033326630343630366630633766383861353663353534633436646363356334626438 30356238323263653236326662303365343464356264376565326432323335323862366365666561
62306661376339343437643732333032373362633062326365666430616634613537316635653465 32316633396430376234636661623464316135333862623531393661343332303033323636393736
64306362386339333562 37653830393665363330393135616465363161623861343531636130636132663263613836646264
66373962313263643334373664303338636232643535313164343434653532303261346566623232
30363662393764316534393030303137663534613564656531616466396232323561623939303733
30383637376432346138333365376431396236356263333634613462316131343634663861356564
39613965333334303036626237373534333337633261353065336534373732306166643666323435
36666239653536653838623864396635663764313738303533323163633261613665

View File

@ -0,0 +1,7 @@
$ANSIBLE_VAULT;1.1;AES256
35336562303132663037626538366261663764666633306237386564303161633730666336643237
6563346339646130666531323165316561653861346663610a353036636565333138373139383438
33386634306666373932353134323830343666653266343262373236306631613735636430663838
3338643164613532370a323935383938383331303036353238376332616130383935623362626235
35653566383764303765373866376636323461383332346661363866623962643439376439363432
3335613733386536613765396136316364333934643465653234

View File

@ -0,0 +1,7 @@
$ANSIBLE_VAULT;1.1;AES256
37303034646630363834303561383932623131306331326530383861633838353039633364326162
6237613235633062356633346663336137353331653866640a333663646539646435363031343034
30633662633838376130656462646566366633333734333761616563333861386166313832333332
3733363866323036660a373966373766393965623231656266323630376133303963363639383631
37393539363765336632333162623065363537613862356261666636366261313136373266623364
3338646231343632336234303436636235633363646661656339

View File

@ -0,0 +1,19 @@
---
# 05 · Test runner ve deploy ön koşulları
#
# Ön koşul: Gitea arayüzünden (Organization → Settings → Actions → Runners)
# bir Registration Token alın ve group_vars/all/vault.yml içindeki
# vault_gitea_runner_token değişkenine ekleyin.
#
# ansible-playbook test-app-post-stack.yml --vault-password-file=.vault_pass
#
# Token tanımlı değilse kurulum tamamlanır ancak kayıt adımı atlanır.
# Sonraki çalıştırmada .runner dosyası varsa kayıt tekrar yapılmaz (idempotent).
- name: "App Node -Gitea runner ve deploy ön koşulları"
hosts: app
become: true
roles:
- role: act_runner
tags: [act_runner]

View File

@ -0,0 +1,14 @@
---
- name: DB Node - StorageBox Dizinleri ve MongoDB Konfigürasyonu
hosts: db
become: yes
roles:
- role: db_stack
tags: [db_stack]
- name: App Node - DB Stack Deploy
hosts: app
become: yes
roles:
- role: db_stack
tags: [db_stack]

131
facts/firewall.md Normal file
View File

@ -0,0 +1,131 @@
# Firewall Mimarisi
## Genel Bakış
Trafik filtreleme iki katmanda uygulanıyor. Bir paket sunucuya ulaşmadan önce Hetzner Cloud Firewall'dan geçmek zorunda; geçse bile sunucu içinde firewalld tarafından tekrar denetleniyor.
```
İnternet → Hetzner Cloud Firewall (Terraform) → firewalld (Ansible) → Uygulama
```
| Katman | Araç | Yönetim | Kapsam |
|--------|------|---------|--------|
| 1. Hetzner Cloud Firewall | Terraform | `terraform/hetzner/{test,prod}/firewall.tf` | Ağ seviyesi, sunucuya ulaşmadan drop |
| 2. firewalld | Ansible | `ansible/roles/hardening/tasks/main.yml` | İşletim sistemi seviyesi, default zone: drop |
Admin IP'leri her iki katmanda da aynı değişkenden besleniyor:
- **Terraform:** `var.admin_allowed_cidrs``terraform.tfvars`
- **Ansible:** `admin_allowed_cidrs``group_vars/all/vars.yml`
Mevcut admin CIDR'ları: `78.187.87.109/32`, `95.70.151.248/32`
---
## Katman 1 — Hetzner Cloud Firewall (Terraform)
Her ortam için iki ayrı firewall tanımı var: `app` ve `db` node'larına uygulanıyor.
### Test Ortamı
Subnet'ler: app `10.10.10.0/24`, db `10.10.20.0/24`
#### App Firewall (`iklim-test-firewall-app`)
| Port | Protokol | Kaynak | Açıklama |
|------|----------|--------|----------|
| 22 | TCP | admin CIDRs | SSH |
| 80 | TCP | 0.0.0.0/0, ::/0 | HTTP public |
| 443 | TCP | 0.0.0.0/0, ::/0 | HTTPS public |
| 2377 | TCP | 10.10.10.0/24, 10.10.20.0/24 | Docker Swarm control plane |
| 7946 | TCP/UDP | 10.10.10.0/24, 10.10.20.0/24 | Docker Swarm node discovery |
| 4789 | UDP | 10.10.10.0/24, 10.10.20.0/24 | Docker Swarm VXLAN overlay |
| 8200 | TCP | 10.10.10.0/24 | Vault API |
| 6379 | TCP | 10.10.10.0/24 | Redis |
| 5672 | TCP | 10.10.10.0/24 | RabbitMQ AMQP |
| 61613 | TCP | 10.10.10.0/24 | RabbitMQ STOMP |
| 15674 | TCP | 10.10.10.0/24 | RabbitMQ Web STOMP |
| 15672 | TCP | 10.10.10.0/24 | RabbitMQ Management (SWAG üzerinden 443) |
| 9000 | TCP | 10.10.10.0/24 | APISIX Dashboard (SWAG üzerinden 443, IP kısıtlı) |
| 9180 | TCP | 10.10.10.0/24 | APISIX Admin API (sadece Docker overlay) |
| 9090 | TCP | 10.10.10.0/24 | Prometheus (SWAG üzerinden 443) |
| 3000 | TCP | 10.10.10.0/24 | Grafana (SWAG üzerinden 443, IP kısıtlı) |
#### DB Firewall (`iklim-test-firewall-db`)
| Port | Protokol | Kaynak | Açıklama |
|------|----------|--------|----------|
| 22 | TCP | admin CIDRs | SSH |
| 5432 | TCP | 10.10.10.0/24 | PostgreSQL (app'ten) |
| 27017 | TCP | 10.10.10.0/24 | MongoDB (app'ten) |
| 2377 | TCP | 10.10.10.0/24 | Docker Swarm control plane |
| 7946 | TCP/UDP | 10.10.10.0/24 | Docker Swarm node discovery |
| 4789 | UDP | 10.10.10.0/24 | Docker Swarm VXLAN overlay |
### Prod Ortamı
Subnet'ler: app `10.20.10.0/24`, db `10.20.20.0/24`
App firewall test ile aynı kurallara sahip (kaynak IP'ler prod subnet'leri).
#### DB Firewall (`iklim-prod-firewall-db`) — test'ten farklı kurallar
| Port | Protokol | Kaynak | Açıklama |
|------|----------|--------|----------|
| 5432 | TCP | 10.20.20.0/24 | PostgreSQL replikasyon (DB subnet içi) |
| 27017 | TCP | 10.20.20.0/24 | MongoDB replica set internal |
| 2379 | TCP | 10.20.20.0/24 | etcd client |
| 2380 | TCP | 10.20.20.0/24 | etcd peer |
| 8008 | TCP | 10.20.20.0/24 | Patroni REST API |
---
## Katman 2 — firewalld (Ansible)
Tüm ortamlarda `hardening` role tarafından uygulanıyor. Default zone `drop` — listelenmeyen her trafik reddedilir.
| Port | Protokol | Kaynak | Açıklama |
|------|----------|--------|----------|
| 22 | TCP | admin CIDRs | SSH (rich rule, zone: drop) |
SSH rich rule örneği:
```
rule family="ipv4" source address="78.187.87.109/32" service name="ssh" accept
```
### SSH Sertleştirme (sshd_config)
| Parametre | Değer | Açıklama |
|-----------|-------|----------|
| PasswordAuthentication | no | Sadece key ile giriş |
| PermitRootLogin | prohibit-password | Root key ile girebilir, şifre ile giremez |
| PermitEmptyPasswords | no | Boş şifre yasak |
| MaxAuthTries | 3 | 3 başarısız denemede bağlantı kesilir |
> **Not:** `MaxAuthTries 3` ssh-agent'ta birden fazla key varken "Too many authentication failures" hatasına yol açar. `~/.ssh/config`'de `IdentitiesOnly yes` ile doğru key'i belirtmek gerekir.
---
## Kural Güncelleme Rehberi
### Admin IP değiştiğinde
İki dosyayı güncelle:
```
terraform/hetzner/test/terraform.tfvars → admin_allowed_cidrs
ansible/test/group_vars/all/vars.yml → admin_allowed_cidrs
```
Sonra uygula:
```bash
# Terraform
cd terraform/hetzner/test && terraform apply
# Ansible
cd ansible/test && ansible-playbook test-bootstrap.yml --tags hardening
```
### Yeni port açılacaksa
Hetzner seviyesinde `firewall.tf`'e kural ekle, Terraform ile uygula. Sunucu içinde firewalld'da açmak gerekiyorsa hardening role'una da ekle.

View File

@ -0,0 +1,76 @@
# Docker Swarm — Node Recovery
Test ortamında tek manager (`iklim-app-01`) ve tek worker (`iklim-db-01`) bulunur. Hangi node'un yeniden kurulduğuna göre recovery süreci farklılaşır.
## Senaryo 1: `iklim-app-01` (Manager) Yeniden Kurulur
### Sorun
Yeni `iklim-app-01` üzerinde `docker swarm init` farklı cluster ID ile yeni bir küme başlatır. `iklim-db-01` hâlâ eski kümeye bağlıdır. Ansible `swarm` role'u `iklim-db-01`'de Swarm'ı `active` görür, join denemez. İki node iki ayrı kümede kalır.
### Çözüm
```bash
# 1. iklim-db-01 üzerinde — eski kümeden çık
docker swarm leave --force
# 2. Ansible ile her iki node'u yeniden kur
cd ansible/test
ansible-playbook -i inventory/generated/test.yml test-bootstrap.yml --ask-vault-pass
# 3. DB stack'i yeniden deploy et
ansible-playbook -i inventory/generated/test.yml test-db-post-stack.yml --ask-vault-pass
```
DB verileri `iklim-db-01`'deki named volume'larda korunur, kayıp yaşanmaz.
---
## Senaryo 2: `iklim-db-01` (Worker) Yeniden Kurulur
### Durum
Yeni `iklim-db-01` Swarm'dan habersiz başlar (`inactive`). Manager (`iklim-app-01`) eski dead node kaydını tutar.
### Çözüm
```bash
# 1. Ansible bootstrap — yeni node otomatik join olur
cd ansible/test
ansible-playbook -i inventory/generated/test.yml test-bootstrap.yml --ask-vault-pass
# 2. iklim-app-01 üzerinde — eski dead node kaydını temizle
docker node ls # eski node ID'yi bul
docker node rm <eski-node-id>
# 3. DB stack'i yeniden deploy et (backup'tan restore sonrası)
ansible-playbook -i inventory/generated/test.yml test-db-post-stack.yml --ask-vault-pass
```
Ansible `swarm` role'u `inactive` durumu gördüğü için token alıp join eder, `role=db` label'ını uygular. DB servisleri placement constraint sayesinde yeni node'a schedule edilir.
---
## Senaryo 3: Her İki Node Yeniden Kurulur
Her şey sıfırdan kurulur, Swarm uyumsuzluğu yaşanmaz.
```bash
cd ansible/test
ansible-playbook -i inventory/generated/test.yml test-bootstrap.yml --ask-vault-pass
ansible-playbook -i inventory/generated/test.yml test-db-post-stack.yml --ask-vault-pass
```
---
## Özet
| Senaryo | Manuel Adım | Ansible Yeterli mi? |
|---|---|---|
| Manager (`iklim-app-01`) ölür | `docker swarm leave --force` (worker'da) | Sonrasında evet |
| Worker (`iklim-db-01`) ölür | `docker node rm <id>` (manager'da) | Büyük ölçüde evet |
| Her ikisi ölür | Yok | Evet |
## Neden Prod'da Bu Sorun Yok
Prod ortamında birden fazla manager node (en az 3) çalıştırılır. Tek manager düşse diğerleri liderliği devralır, küme sağlıklı kalmaya devam eder.

View File

@ -4,18 +4,18 @@ Bu aşamanın amacı `iklim-db-01` node'unu Swarm'a worker olarak eklemek ve Pos
## Mimari Karar ## Mimari Karar
Yol haritasında DB'lerin "manuel" kurulacağı belirtilmiştir. Test ortamında bu "manuel" süreç, DB'lerin işletim sistemine doğrudan kurulması yerine, **Swarm Worker** üzerinde Docker konteynerleri olarak ayağa kaldırılması şeklinde uygulanacaktır. Yol haritasında DB'lerin "manuel" kurulacağı belirtilmiştir. Test ortamında bu "manuel" süreç, DB'lerin işletim sistemine doğrudan kurulması yerine, **Swarm Worker** üzerinde Docker konteynerleri olarak ayağa kaldırılması şeklinde uygulanacaktır. Kurulum **Ansible** ile otomatize edilmiştir (`test-db-post-stack.yml`).
**Neden?** **Neden?**
1. **Yönetim Kolaylığı:** Docker ile versiyon geçişleri ve konfigürasyon yönetimi çok daha hızlıdır. 1. **Yönetim Kolaylığı:** Docker ile versiyon geçişleri ve konfigürasyon yönetimi çok daha hızlıdır.
2. **Overlay Network:** Uygulama servisleri (`iklim-app-01`), DB'lere `iklimco-net` overlay network üzerinden şifreli ve izole bir şekilde erişebilir. 2. **Overlay Network:** Uygulama servisleri (`iklim-app-01`), DB'lere `iklimco-net` overlay network üzerinden şifreli ve izole bir şekilde erişebilir.
3. **Veri Kalıcılığı:** Veriler Hetzner StorageBox (`/mnt/storagebox`) üzerinde saklanarak host bağımsızlığı sağlanır. 3. **Veri Kalıcılığı:** Veriler `iklim-db-01` üzerindeki Docker named volume'larında saklanır. StorageBox yalnızca backup için kullanılır.
## Ön Koşullar ## Ön Koşullar
- `03-test-ansible-bootstrap.md` her iki node'da tamamlanmış olmalı. - `03-test-ansible-bootstrap.md` her iki node'da tamamlanmış olmalı.
- StorageBox `/mnt/storagebox` olarak her iki node'da mount edilmiş olmalı.
- Docker `iklim-db-01` üzerinde kurulu olmalı (Bootstrap role bunu yapar). - Docker `iklim-db-01` üzerinde kurulu olmalı (Bootstrap role bunu yapar).
- Ansible vault'unda `vault_postgres_root_user`, `vault_postgres_password`, `vault_mongo_root_user`, `vault_mongo_root_password` tanımlı olmalı.
## 1. Firewall Güncellemesi ## 1. Firewall Güncellemesi
@ -34,109 +34,54 @@ cd terraform/hetzner/test
terraform apply terraform apply
``` ```
## 2. DB Node'u Swarm'a Ekleme ## 2. Vault Güncellemesi
**iklim-app-01 üzerinde (Manager)** join token alın:
```bash ```bash
docker swarm join-token worker cd ansible/test
ansible-vault edit group_vars/all/vault.yml
``` ```
**iklim-db-01 üzerinde (Worker)** Swarm'a katılın: Şu değişkenleri ekle:
```bash
docker swarm join --token <TOKEN> 10.10.10.11:2377
```
**iklim-app-01 üzerinde** node'u etiketleyin:
```bash
docker node update --label-add role=db iklim-db-01
```
## 3. StorageBox Dizin Yapısı
**iklim-db-01 üzerinde:**
```bash
mkdir -p /mnt/storagebox/test/db/postgresql/data
mkdir -p /mnt/storagebox/test/db/mongodb/data
mkdir -p /mnt/storagebox/test/db/mongodb/log
mkdir -p /mnt/storagebox/test/db/mongodb/config
```
## 4. Veritabanı Konfigürasyonları
### MongoDB Config (`mongod.conf`)
`/mnt/storagebox/test/db/mongodb/config/mongod.conf` dosyasını oluşturun:
```yaml ```yaml
storage: vault_postgres_root_user: "postgres"
dbPath: /data/db vault_postgres_password: "GÜÇLÜ_ŞİFRE"
journal: vault_mongo_root_user: "mongoadmin"
enabled: true vault_mongo_root_password: "GÜÇLÜ_ŞİFRE"
systemLog:
destination: file
logAppend: true
path: /data/log/mongod.log
net:
port: 27017
bindIp: 0.0.0.0
security:
authorization: enabled
``` ```
## 5. DB Stack Kurulumu ## 3. Ansible ile Kurulum
`/opt/iklimco/stacks/db.yml` (iklim-app-01 üzerinde):
```yaml
version: "3.8"
networks:
iklimco-net:
external: true
services:
postgresql:
image: postgis/postgis:17-3.5
environment:
POSTGRES_USER: "${DATABASE_POSTGRES_ROOT_USER}"
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
POSTGRES_DB: postgres
PGDATA: /var/lib/postgresql/data/pgdata
volumes:
- /mnt/storagebox/test/db/postgresql/data:/var/lib/postgresql/data
networks:
- iklimco-net
deploy:
placement:
constraints:
- node.labels.role == db
mongodb:
image: mongo:8
environment:
MONGO_INITDB_ROOT_USERNAME: "${DATABASE_MONGO_ROOT_USER}"
MONGO_INITDB_ROOT_PASSWORD: "${MONGO_ROOT_PASSWORD}"
volumes:
- /mnt/storagebox/test/db/mongodb/data:/data/db
- /mnt/storagebox/test/db/mongodb/log:/data/log
- /mnt/storagebox/test/db/mongodb/config/mongod.conf:/etc/mongod.conf
command: ["--config", "/etc/mongod.conf"]
networks:
- iklimco-net
deploy:
placement:
constraints:
- node.labels.role == db
```
### Deploy
```bash ```bash
# .env dosyasını oluşturun (Hassas veriler için) cd ansible/test
# DATABASE_POSTGRES_ROOT_USER, POSTGRES_PASSWORD, DATABASE_MONGO_ROOT_USER, MONGO_ROOT_PASSWORD ansible-playbook -i inventory/generated/test.yml test-db-post-stack.yml --ask-vault-pass
docker stack deploy -c /opt/iklimco/stacks/db.yml iklim-db
``` ```
## 6. Kabul Kriterleri **Playbook ne yapar?**
`iklim-db-01` üzerinde:
- `/opt/iklimco/db/mongodb/config/` dizinini oluşturur
- `mongod.conf` dosyasını yerleştirir
`iklim-app-01` üzerinde:
- `/opt/iklimco/stacks/db.yml` stack dosyasını oluşturur (şifreler vault'tan enjekte edilir)
- `docker stack deploy` ile PostgreSQL ve MongoDB servislerini başlatır
## 4. Volume ve Veri Yapısı
DB verileri `iklim-db-01` üzerindeki Docker named volume'larında tutulur:
| Volume | İçerik |
|---|---|
| `iklim-db_postgresql_data` | PostgreSQL veri dosyaları |
| `iklim-db_mongodb_data` | MongoDB veri dosyaları |
MongoDB log'ları stdout'a yazılır (`docker logs` ile izlenir). Konfigürasyon: `/opt/iklimco/db/mongodb/config/mongod.conf`
> StorageBox DB verisi için **kullanılmaz**. Yalnızca backup stratejisinde görev alır.
## 5. Kabul Kriterleri
- `docker node ls` komutunda `iklim-db-01` Ready ve Active görünür. - `docker node ls` komutunda `iklim-db-01` Ready ve Active görünür.
- `docker stack services iklim-db` her iki servisi 1/1 replica ile gösterir. - `docker stack services iklim-db` her iki servisi 1/1 replica ile gösterir.
- Uygulama node'undan `iklim-db_postgresql` ve `iklim-db_mongodb` DNS isimleriyle erişim sağlanır. - Uygulama node'undan `iklim-db_postgresql` ve `iklim-db_mongodb` DNS isimleriyle erişim sağlanır.
- Reboot sonrası veriler `/mnt/storagebox` üzerinden korunur. - Reboot sonrası veriler named volume'lardan korunur (`docker volume ls` ile kontrol).