feat(infra): update environment infrastructure configurations

- Synchronized environment-specific settings with the new isolated architecture.
- Updated network and storage definitions to match the latest Swarm stack requirements.
- Harmonized configuration templates for consistent cross-environment deployment.
This commit is contained in:
Murat ÖZDEMİR 2026-05-22 21:40:21 +03:00
parent c568e31515
commit ff9837ec54
9 changed files with 111 additions and 29 deletions

View File

@ -16,7 +16,7 @@ storagebox_managed_directories:
mode: "0755" mode: "0755"
iklim_password: "{{ vault_iklim_password }}" iklim_password: "{{ vault_iklim_password }}"
act_runner_labels: "prod-runner,ubuntu-24.04,{{ inventory_hostname }}" act_runner_labels: "prod-runner:docker://catthehacker/ubuntu:act-22.04,ubuntu-24.04,{{ inventory_hostname }}"
swarm_manager_ip: "10.20.10.11" swarm_manager_ip: "10.20.10.11"
mongodb_replset_name: "rs0" mongodb_replset_name: "rs0"
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"

View File

@ -0,0 +1,3 @@
---
storagebox_dir_mode: "0777"
storagebox_file_mode: "0666"

View File

@ -1,5 +1,5 @@
--- ---
act_runner_version: "0.2.12" act_runner_version: "0.6.1"
act_runner_arch: "linux-amd64" act_runner_arch: "linux-amd64"
act_runner_gitea_url: "https://git.tarla.io" act_runner_gitea_url: "https://git.tarla.io"
act_runner_name: "{{ inventory_hostname }}" act_runner_name: "{{ inventory_hostname }}"

View File

@ -24,6 +24,8 @@
mode: "0755" mode: "0755"
owner: root owner: root
group: root group: root
force: true
notify: restart act_runner
- name: Create act_runner config directory - name: Create act_runner config directory
ansible.builtin.file: ansible.builtin.file:

View File

@ -26,13 +26,12 @@ container:
network: "iklimco-net" network: "iklimco-net"
enable_ipv6: false enable_ipv6: false
privileged: false privileged: false
options: "" options: "-v /mnt/storagebox:/mnt/storagebox"
workdir_parent: "" workdir_parent: ""
valid_volumes: valid_volumes:
- "/var/run/docker.sock" - "/mnt/storagebox"
# docker_host set edilince act_runner socket'i tek seferlik mount eder ve # Docker 29.5.2 ile /var/run -> /run symlink kaynaklı "mkdirat var/run: file exists"
# DOCKER_HOST env'ini job container'a iletir; options'daki manuel mount ile # hatası giderildi; socket job container'lara mount edilebilir hale geldi.
# çakışıp "Duplicate mount point" hatasına yol açmaz.
docker_host: "unix:///var/run/docker.sock" docker_host: "unix:///var/run/docker.sock"
force_pull: false force_pull: false
force_rebuild: false force_rebuild: false

View File

@ -22,7 +22,7 @@
- name: Add fstab entry for StorageBox - name: Add fstab entry for StorageBox
ansible.builtin.lineinfile: ansible.builtin.lineinfile:
path: /etc/fstab path: /etc/fstab
line: "{{ storagebox_url }} {{ storagebox_mount_point }} davfs _netdev,auto,user,rw,uid={{ storagebox_uid | default('root') }},gid={{ storagebox_gid | default('root') }} 0 0" line: "{{ storagebox_url }} {{ storagebox_mount_point }} davfs _netdev,auto,user,rw,uid={{ storagebox_uid | default('root') }},gid={{ storagebox_gid | default('root') }}{% if storagebox_dir_mode is defined %},dir_mode={{ storagebox_dir_mode }}{% endif %}{% if storagebox_file_mode is defined %},file_mode={{ storagebox_file_mode }}{% endif %} 0 0"
regexp: "^{{ storagebox_url | regex_escape() }}" regexp: "^{{ storagebox_url | regex_escape() }}"
state: present state: present
@ -31,7 +31,7 @@
path: "{{ storagebox_mount_point }}" path: "{{ storagebox_mount_point }}"
src: "{{ storagebox_url }}" src: "{{ storagebox_url }}"
fstype: davfs fstype: davfs
opts: "_netdev,auto,user,rw,uid={{ storagebox_uid | default('root') }},gid={{ storagebox_gid | default('root') }}" opts: "_netdev,auto,user,rw,uid={{ storagebox_uid | default('root') }},gid={{ storagebox_gid | default('root') }}{% if storagebox_dir_mode is defined %},dir_mode={{ storagebox_dir_mode }}{% endif %}{% if storagebox_file_mode is defined %},file_mode={{ storagebox_file_mode }}{% endif %}"
state: mounted state: mounted
- name: Write mount marker - name: Write mount marker

View File

@ -6,37 +6,114 @@ Prod kurulum adımları ve mevcut yapı.
### Hetzner Cloud Yapılandırması ### 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/hetzner/prod/terraform.tfvars` içindeki `hcloud_token` değeri `iklim_prod` projesinin token'ına aittir. 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:
Prod sunucuları `lifecycle { prevent_destroy = true }` ile korunur. ```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 ### Inventory Üretimi
```bash ```bash
cd Environment_Infrastructure/terraform/hetzner/prod
mkdir -p ../../../ansible/prod/inventory/generated 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
``` ```
## Ansible ## Ansible
### Yapılandırma Notları ### ansible.cfg
`group_vars/all/vars.yml` içindeki `admin_allowed_cidrs` tüm admin IP'lerini boşlukla ayrılmış string olarak içerir ve Terraform `terraform.tfvars` ile birebir uyumludur: `roles_path = roles:../roles` ile prod'a özgü roller (`roles/`) yanı sıra ortak roller (`../roles/`) de kullanılır.
```yaml ```ini
admin_allowed_cidrs: "78.187.87.109/32 95.70.151.248/32" [defaults]
admin_ssh_public_key_path: "~/.ssh/id_rsa.pub" 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
``` ```
Hardening rolü `PermitRootLogin prohibit-password` uygular — key tabanlı root girişi açık, parola ile root girişi kapalıdır. ### group_vars/all/vars.yml
`vault_iklim_password` per-host `host_vars/<hostname>/vault.yml` dosyalarında tanımlıdır, her sunucu farklı şifreye sahiptir: ```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 ```text
prod/ prod/
host_vars/ host_vars/
iklim-app-01/vault.yml iklim-app-01/vault.yml ← vault_iklim_password: "<iklim OS kullanıcısı parolası>"
iklim-app-02/vault.yml iklim-app-02/vault.yml
iklim-app-03/vault.yml iklim-app-03/vault.yml
iklim-db-01/vault.yml iklim-db-01/vault.yml
@ -44,6 +121,10 @@ prod/
iklim-db-03/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 Mount
StorageBox WebDAV mount (`/mnt/storagebox`) davfs2 ile yapılır. DB node'larında `uid=999,gid=999` parametreleriyle mount edilir (PostgreSQL/MongoDB container uid'i ile uyumlu). `group_vars/db/vars.yml` içinde tanımlanır: StorageBox 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:
@ -103,7 +184,7 @@ cd /home/iklim
scp -P 23 u469968@u469968.your-storagebox.de:prod/secrets/iklim.co/.env.secrets.shared \ scp -P 23 u469968@u469968.your-storagebox.de:prod/secrets/iklim.co/.env.secrets.shared \
/tmp/.env.secrets.shared /tmp/.env.secrets.shared
chmod 600 /tmp/.env.secrets.shared chmod 600 /tmp/.env.secrets.shared
scp -P 23 u469968@u469968.your-storagebox.de:prod/secrets/iklim.co/.env.secrets \ scp -P 23 u469968@u469968.your-storagebox.de:prod/secrets/iklim.co/.env \
/tmp/.env /tmp/.env
chmod 600 /tmp/.env chmod 600 /tmp/.env

View File

@ -242,7 +242,7 @@ Insert **after** `Bootstrap SWAG Certificate` and **before** `Review Environment
> **Prod-specific:** DB hostnames are `postgresql` and `mongodb` (Swarm VIP service names). > **Prod-specific:** DB hostnames are `postgresql` and `mongodb` (Swarm VIP service names).
> Test pipeline uses `postgresql` / `mongodb` (unqualified aliases within the same stack). > Test pipeline uses `postgresql` / `mongodb` (unqualified aliases within the same stack).
> SQL and JS files are generated by `Prepare Init Files` step via `init_postgresql` / `init_mongodb` functions in `common-functions.sh`. > SQL and JS files are generated by `Prepare Init Files` step via `init_postgresql` / `init_mongodb` functions in `common-functions-prod.sh`.
> Step is idempotent — scripts use `CREATE IF NOT EXISTS` / `createCollection` semantics. > Step is idempotent — scripts use `CREATE IF NOT EXISTS` / `createCollection` semantics.
## Step 8 — Microservice prod deploy overlay ## Step 8 — Microservice prod deploy overlay

View File

@ -93,9 +93,6 @@ For DNS automation, `PROD_FLOATING_IP` must be defined as a Gitea project variab
Before the infra stack is deployed, the following Docker secrets must be created on `iklim-app-01`. These secrets are referenced by `docker-stack-infra.prod.yml`; if they do not exist, stack deploy fails. Before the infra stack is deployed, the following Docker secrets must be created on `iklim-app-01`. These secrets are referenced by `docker-stack-infra.prod.yml`; if they do not exist, stack deploy fails.
```bash ```bash
# Redis password, used by Redis master, replica, and sentinel:
openssl rand -hex 32 | docker secret create redis_password -
# RabbitMQ Erlang cluster cookie; must be the same on all RabbitMQ nodes: # RabbitMQ Erlang cluster cookie; must be the same on all RabbitMQ nodes:
openssl rand -hex 32 | docker secret create rabbitmq_erlang_cookie - openssl rand -hex 32 | docker secret create rabbitmq_erlang_cookie -
``` ```
@ -108,7 +105,7 @@ Verify secrets:
```bash ```bash
docker secret ls docker secret ls
# redis_password and rabbitmq_erlang_cookie rows must appear # rabbitmq_erlang_cookie row must appear
``` ```
### SWAG Nginx Configuration Templates ### SWAG Nginx Configuration Templates
@ -198,7 +195,7 @@ All prod deploy workflows, including infra and microservices, must use the same
| 3 | Set up SSH Key and Add to known_hosts | | | 3 | Set up SSH Key and Add to known_hosts | |
| 4 | Update Apt Repository and Install Required Tools | `gettext tree jq``jq` is required for the GoDaddy DNS API | | 4 | Update Apt Repository and Install Required Tools | `gettext tree jq``jq` is required for the GoDaddy DNS API |
| 5 | Fetch Service Secret Files | Fetch `.env.secrets.*` from StorageBox | | 5 | Fetch Service Secret Files | Fetch `.env.secrets.*` from StorageBox |
| 6 | Initialize Workspace | Fetch `.env` and `.env.secrets.shared` from StorageBox; run `init-base.sh` | | 6 | Initialize Workspace | Fetch `.env` and `.env.secrets.shared` from StorageBox; run `init-infra-dev.sh` |
| 7 | Upload Updated Secrets to Storagebox | | | 7 | Upload Updated Secrets to Storagebox | |
| 8 | Provision Vault AppRole IDs and Docker Secrets | | | 8 | Provision Vault AppRole IDs and Docker Secrets | |
| 9 | Upload Updated Env to Storagebox | | | 9 | Upload Updated Env to Storagebox | |
@ -325,7 +322,7 @@ PostgreSQL and MongoDB init scripts run through Swarm overlay DNS service names
``` ```
- `postgresql` and `mongodb`: Swarm VIP service names, resolved on the `iklimco-net` overlay; Patroni primary automatic routing happens at VIP level - `postgresql` and `mongodb`: Swarm VIP service names, resolved on the `iklimco-net` overlay; Patroni primary automatic routing happens at VIP level
- SQL files `./init/postgresql/*.sql` and JS files `./init/mongodb/*.js` are created in the `Prepare Init Files` step by the `init_postgresql`/`init_mongodb` functions in `common-functions.sh` - SQL files `./init/postgresql/*.sql` and JS files `./init/mongodb/*.js` are created in the `Prepare Init Files` step by the `init_postgresql`/`init_mongodb` functions in `common-functions-prod.sh`
- Idempotent: `CREATE IF NOT EXISTS` / `createCollection` semantics; runs safely again on later deploys - Idempotent: `CREATE IF NOT EXISTS` / `createCollection` semantics; runs safely again on later deploys
## Swarm Service Distribution ## Swarm Service Distribution
@ -646,7 +643,7 @@ Expected: valid JSON weather response.
- Public ingress is limited to only `22`, `80`, and `443`. - Public ingress is limited to only `22`, `80`, and `443`.
- `prod/secrets/iklim.co/.env.secrets.swag` exists on StorageBox and contains valid GoDaddy credentials. - `prod/secrets/iklim.co/.env.secrets.swag` exists on StorageBox and contains valid GoDaddy credentials.
- `PROD_FLOATING_IP` project variable is defined in Gitea. - `PROD_FLOATING_IP` project variable is defined in Gitea.
- `redis_password` and `rabbitmq_erlang_cookie` appear in `docker secret ls`. - `rabbitmq_erlang_cookie` appears in `docker secret ls`.
- The `ssl`, `swag/config`, `swag/site-confs`, `grafana/data`, and `precipitation/images` directories exist on StorageBox; see `07-prod-ansible-bootstrap.md` — StorageBox Directory Structure. - The `ssl`, `swag/config`, `swag/site-confs`, `grafana/data`, and `precipitation/images` directories exist on StorageBox; see `07-prod-ansible-bootstrap.md` — StorageBox Directory Structure.
- The `swag/site-confs/default.conf`, `api.conf.tpl`, `apigw.conf.tpl`, `rabbitmq.conf.tpl`, and `grafana.conf.tpl` template files exist in the repo. - The `swag/site-confs/default.conf`, `api.conf.tpl`, `apigw.conf.tpl`, `rabbitmq.conf.tpl`, and `grafana.conf.tpl` template files exist in the repo.
- StorageBox `prod/secrets/iklim.co/.env.prod` has correct values for `API_SUBDOMAIN`, `APIGW_SUBDOMAIN`, `RABBITMQ_SUBDOMAIN`, `GRAFANA_SUBDOMAIN`, `RESTRICTED_IPS`, `SWAG_CERT_DIR`, `SWAG_CONFIG_DIR`, and `SWAG_SITE_CONFS_DIR`. - StorageBox `prod/secrets/iklim.co/.env.prod` has correct values for `API_SUBDOMAIN`, `APIGW_SUBDOMAIN`, `RABBITMQ_SUBDOMAIN`, `GRAFANA_SUBDOMAIN`, `RESTRICTED_IPS`, `SWAG_CERT_DIR`, `SWAG_CONFIG_DIR`, and `SWAG_SITE_CONFS_DIR`.