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
*.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:
- 🧱 Terraform resources for Hetzner infrastructure (`test` and `prod`)
- 🤖 Ansible bootstrap playbooks, shared roles, and inventory targets
- 📚 End-to-end setup guides and roadmap documents (mostly Turkish)
- 📊 Sizing/cost analysis and supporting reference assets
Bu depo şunları kapsar:
- 🧱 Hetzner altyapısı için Terraform kaynakları (`test` ve `prod`)
- 🤖 Ansible bootstrap playbook'ları, paylaşımlı roller ve envanter hedefleri
- 📚 Uçtan uca kurulum rehberleri ve yol haritası dokümanları
- 📊 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)
- 🤖 **Ansible**: prepares OS, security hardening, Docker/Swarm, runner setup, and StorageBox mount workflows via in-repo playbooks and shared roles
- 🚀 **Application/stack deployment**: handled in related deployment workflows and stack manifests referenced by roadmap docs
- 🧱 **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**: 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
- 🚀 **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/prod`
- 🤖 Ansible automation assets for both environments:
- 🤖 Her iki ortam için Ansible otomasyon varlıkları:
- `ansible/test/test-bootstrap.yml`
- `ansible/prod/prod-bootstrap.yml`
- `ansible/roles/*`
- `ansible/test/group_vars/*` and `ansible/prod/group_vars/*`
- 📦 Inventory artifacts and output targets:
- `ansible/test/inventory/generated/test.yml` (tracked sample)
- `ansible/prod/inventory/generated/prod.yml` (expected output path)
- 📘 Detailed setup phases:
- `setup/00-genel-yol-haritasi.md` through `setup/09-prod-runner-ha-ve-swarm.md`
- 🛣️ Environment roadmap steps:
- `ansible/test/group_vars/*` ve `ansible/prod/group_vars/*`
- 📦 Envanter çıktıları ve hedef yollar:
- `ansible/test/inventory/generated/test.yml` (takip edilen örnek)
- `ansible/prod/inventory/generated/prod.yml` (beklenen çıktı yolu)
- 📘 Detaylı kurulum aşamaları:
- `setup/00-genel-yol-haritasi.md` `setup/09-prod-runner-ha-ve-swarm.md`
- 🛣️ Ortam yol haritası adımları:
- `roadmap/test-env/*`
- `roadmap/prod-env/*`
- 📈 Capacity planning and reference charts:
- 📈 Kapasite planlama ve referans grafikler:
- `hetzner-sizing-report.md`
- `test-app-graphs.png`
- `test-db-graphs.png`
## 🧭 Target Environment Topology
## 🧭 Hedef Ortam Topolojisi
### 🧪 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-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
| 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-03` | Swarm manager + app worker + runner | `10.20.10.13` | `cpx42` |
| `iklim-db-01` | DB cluster node | `10.20.20.11` | `cpx32` |
| `iklim-db-02` | DB cluster node | `10.20.20.12` | `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.
- Public ingress is limited to:
- `22/tcp` (admin CIDRs only)
- Test ve prod, ayrı Hetzner Cloud proje ve token'larıyla birbirinden yalıtılmıştır.
- Kamuya açık gelen trafik şunlarla sınırlıdır:
- `22/tcp` (yalnızca admin CIDR'ları)
- `80/tcp`
- `443/tcp`
- Critical services remain private-only (for example Vault `8200`, PostgreSQL `5432`, MongoDB `27017`, internal observability and broker ports).
- Placement groups are used for host-spread strategy.
- `prevent_destroy = true` is enabled on server resources to reduce accidental deletion risk.
- Terraform state and secret files must not be committed.
- Kritik servisler yalnızca private ağda erişilebilir (örneğin Vault `8200`, PostgreSQL `5432`, MongoDB `27017`, iç gözlemlenebilirlik ve broker portları).
- Host dağılımı stratejisi için placement group'lar kullanılmaktadır.
- Sunucu kaynaklarında yanlışlıkla silinmeye karşı `prevent_destroy = true` etkinleştirilmiştir.
- Terraform state ve gizli dosyalar commit'lenmemelidir.
See:
- `setup/01-private-network-port-matrisi.md`
Ayrıca bkz.:
- [[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/prod/firewall.tf`
## 🗂️ Repository Structure
## 🗂️ Depo Yapısı
```text
Environment_Infrastructure/
@ -123,35 +124,37 @@ Environment_Infrastructure/
│ └── hetzner/
│ ├── test/
│ └── prod/
├── facts/
│ └── firewall.md
├── hetzner-sizing-report.md
├── setup-vs-roadmap-map.md
├── test-app-graphs.png
└── test-db-graphs.png
```
## ✅ Prerequisites
## ✅ Ön Koşullar
- Terraform `>= 1.6`
- Hetzner Cloud account(s) and API token per environment
- SSH key pair (public key path used in Terraform variables)
- Linux/macOS shell tools (`bash`, `cp`, `sed` or editor of your choice)
- Optional but expected in later phases: Ansible, Docker, access to Gitea/Harbor/StorageBox
- Hetzner Cloud hesabı ve ortam başına API token
- SSH anahtar çifti (public key yolu Terraform değişkenlerinde kullanılır)
- Linux/macOS kabuk araçları (`bash`, `cp`, `sed` veya tercih edilen metin editörü)
- İlerleyen aşamalarda gerekli: Ansible, Docker, Gitea/Harbor/StorageBox erişimi
## 🛠️ Terraform Usage
## 🛠️ Terraform Kullanımı
### 1) 🧪 Test Infrastructure
### 1) 🧪 Test Altyapısı
```bash
cd terraform/hetzner/test
cp terraform.tfvars.example terraform.tfvars
```
Edit `terraform.tfvars` values:
`terraform.tfvars` değerlerini düzenle:
- `hcloud_token`
- `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
terraform init
@ -161,19 +164,19 @@ mkdir -p ../../../ansible/test/inventory/generated
terraform output -raw ansible_inventory_yaml > ../../../ansible/test/inventory/generated/test.yml
```
### 2) 🏭 Production Infrastructure
### 2) 🏭 Production Altyapısı
```bash
cd terraform/hetzner/prod
cp terraform.tfvars.example terraform.tfvars
```
Edit `terraform.tfvars` values:
`terraform.tfvars` değerlerini düzenle:
- `hcloud_token` (prod token)
- `admin_allowed_cidrs`
- optional overrides
- isteğe bağlı geçersiz kılmalar
Then run:
Ardından çalıştır:
```bash
terraform init
@ -183,79 +186,79 @@ mkdir -p ../../../ansible/prod/inventory/generated
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
2. `setup/01-private-network-port-matrisi.md` — private/public port policy
3. `setup/02-test-terraform-iaac.md` — test Terraform phase
4. `setup/03-test-ansible-bootstrap.md` — test OS/bootstrap/hardening
5. `setup/04-test-db-docker-kurulum.md` — test DB stack setup on Swarm
6. `setup/05-test-runner-ve-deploy-onkosullari.md` — test runner and deploy prerequisites
7. `setup/06-prod-terraform-iaac.md` — prod Terraform phase
8. `setup/07-prod-ansible-bootstrap.md` — prod OS/bootstrap/hardening
1. `setup/00-genel-yol-haritasi.md` — genel kararlar ve sınırlar
2. `setup/01-private-network-port-matrisi.md` — private/public port politikası
3. `setup/02-test-terraform-iaac.md` — test Terraform aşaması
4. `setup/03-test-ansible-bootstrap.md` — test OS/bootstrap/sertleştirme
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 ve deploy ön koşulları
7. `setup/06-prod-terraform-iaac.md` — prod Terraform aşaması
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)
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/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)
- **Prod:** `3 x cpx42` (app) + `3 x cpx32` (db)
Approximate monthly total in the report:
Rapordaki yaklaşık aylık toplam:
- Test: `$59.98`
- 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/`
- private keys, certificates, `.env` secrets
- runner tokens and vault password files
- private key'ler, sertifikalar, `.env` gizli bilgileri
- runner token'ları ve vault parola dosyaları
See `.gitignore` for enforced patterns.
Zorunlu kalıplar için `.gitignore` dosyasına bkz.
Recommended:
- keep runtime secrets in secure secret stores / encrypted vault files
- keep generated runtime artifacts outside version control unless explicitly sanitized
Önerilen:
- çalışma zamanı gizli bilgilerini güvenli gizli depolarda / şifreli vault dosyalarında tut
- ü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.
- Some roadmap steps target files under the broader `iklim.co` application repository, not this repository alone.
- `ansible/prod/inventory/generated/prod.yml` beklenen bir çıktı yoludur; üretilene kadar mevcut olmayabilir.
- 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
- floating IP exists and is attached
- firewalls expose only intended public ports
- placement groups are assigned
- generated inventory YAML is exported to `ansible/{test,prod}/inventory/generated/*.yml`
- sunucular beklenen isimler ve private IP'lerle oluşturulmuş
- floating IP mevcut ve bağlı
- firewall'lar yalnızca amaçlanan public portlarııyor
- placement group'lar atanmış
- ü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
- DB accessibility is private-only
- Vault/API gateways follow public/private exposure rules
- runner and deploy lock behavior align with environment policy
- Swarm durumu ve etiketleri dökümantasyonla eşleşiyor
- DB erişimi yalnızca private ağdan mümkün
- Vault/API gateway'leri public/private erişim kurallarına uyuyor
- Runner ve deploy kilit davranışı ortam politikasıyla örtüşüyor
## 🔗 References
## 🔗 Referanslar
- Hetzner Terraform Provider: https://registry.terraform.io/providers/hetznercloud/hcloud/latest
- 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
notify: Restart fail2ban
- name: Ensure fail2ban is running and enabled
ansible.builtin.service:
name: fail2ban
state: started
enabled: yes
- name: Ensure firewalld is running
ansible.builtin.service:
name: firewalld
@ -67,6 +73,12 @@
create_home: yes
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
ansible.builtin.lineinfile:
path: /etc/systemd/journald.conf

View File

@ -6,4 +6,13 @@ storagebox_password: "{{ vault_storagebox_password }}"
iklim_password: "{{ vault_iklim_password }}"
swarm_manager_ip: "10.10.10.11"
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"
# 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
32653536356331386232373033363738363336323461363432653031666166343462393737643730
3162386266326333386533373630663563386337613338310a376137623835333461363662323035
65636332376331643335323265336439613331613238393363626330313831653233373864313033
3430303335306366660a636139623264386437383763316665373230643939653039633330623834
64636564313232626462373638653538393261323031616563653164323961393664656439393639
37313335313739353564626364313663363038316132633739623338343436303337643162396635
34323838346664303464396438393534636265636262323364643364323163653464303931626130
37663138363966386530323133613661316230303362323937313132306236323339323839633139
34633733333531373233386436313837343364326334386535626262356537376137646163326666
38306238666639623639393137623266363465313264326566663839303664303233666335663731
32633232376164383265313835326433366134613230613164373034663931396161623631666236
37613631353233346464363236383539663461333739396432626638323134383230343163396335
64363333396130326463316538306162363034353936383063333531396233356437333064613230
63353337306632323364336233313836663365623436336532623239633434656563666637333636
31343836353230306461613936383766636138663361343864623466376235666536306133306435
65666338333465653434386166366633383539323566613935363434363735313231336166626364
65353033363366386432306434333135653862616635373837376233326262646330326363626134
32343735386137363334306464383935613531373533363330363635633236313930373865393738
38363564663066363439396532656236656636646365363038393535303632356364613538353737
32636434383766343765643634633464353262396466393265643963383634323730343162323837
39343139613538653531616466303638336133396165646138663463383238616431613563343031
39333139643033326630343630366630633766383861353663353534633436646363356334626438
62306661376339343437643732333032373362633062326365666430616634613537316635653465
64306362386339333562
63323534653836623136643065383166323661663339653335373139663436316433396137343739
3039383336313861616236336565323664613333383262610a363436376132316437656239633334
63633863643938373537333562346433313766393732616362306132313430333964373031313962
3230393837316434650a323933313934316334633931323165633730373933326564316661666332
36323735323736656662366463383431393433643331616134623662663333313364343232653138
61316165636431663230376234626532663431373632393731383434386435353638363365323861
33373462333966663033666463303032343961653533343932363462623637616232666561646335
36623039353633343035353765326339616233666562636438636137303835643736386633623139
66656137646461363165313937306266393035383339363233303066396564346638643539313361
38653930393766656634343334373961323031333938663636623138666661346133316631393231
36393237313439313435363866333364343231613330373439626462356139303061313632343338
38633364343663333139396431663938653263633739383036613935373332653666656633323331
37316535386139633263633266353739386164666564666136643665353135356231323633373363
36646464383766663532353132326162363734366337373537656132366139386362616438616531
34386234306231376162643934643565343364336530616562303739363935353636343633376464
34333835363666303236313363663665653566326365386439653362663462343136316664613036
37616163653561633030376534393533653466373839633661653338313166383332613234666164
37343737316139393930313739366562643539326233366334353566633339333130366263663030
63393437383566393932353230396534383330646364646561323631653338396132303461613636
37306533663732626532393130323933633365336233613334666330343436326137306666636234
30623831303632613866333734306337623230373433373332646534306135333039343231643536
30356238323263653236326662303365343464356264376565326432323335323862366365666561
32316633396430376234636661623464316135333862623531393661343332303033323636393736
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
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?**
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.
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
- `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).
- 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
@ -34,109 +34,54 @@ cd terraform/hetzner/test
terraform apply
```
## 2. DB Node'u Swarm'a Ekleme
## 2. Vault Güncellemesi
**iklim-app-01 üzerinde (Manager)** join token alın:
```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:
```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:
Şu değişkenleri ekle:
```yaml
storage:
dbPath: /data/db
journal:
enabled: true
systemLog:
destination: file
logAppend: true
path: /data/log/mongod.log
net:
port: 27017
bindIp: 0.0.0.0
security:
authorization: enabled
vault_postgres_root_user: "postgres"
vault_postgres_password: "GÜÇLÜ_ŞİFRE"
vault_mongo_root_user: "mongoadmin"
vault_mongo_root_password: "GÜÇLÜ_ŞİFRE"
```
## 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
# .env dosyasını oluşturun (Hassas veriler için)
# DATABASE_POSTGRES_ROOT_USER, POSTGRES_PASSWORD, DATABASE_MONGO_ROOT_USER, MONGO_ROOT_PASSWORD
docker stack deploy -c /opt/iklimco/stacks/db.yml iklim-db
cd ansible/test
ansible-playbook -i inventory/generated/test.yml test-db-post-stack.yml --ask-vault-pass
```
## 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 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.
- Reboot sonrası veriler `/mnt/storagebox` üzerinden korunur.
- Reboot sonrası veriler named volume'lardan korunur (`docker volume ls` ile kontrol).