docs(infra): align DB stack and APISIX production guidance

Update Environment_Infrastructure to match the current root stack conventions for database images, shared secret names, and APISIX real IP handling.

- update test Ansible DB image defaults to PostGIS 18/PostGIS 3.6 and MongoDB 8.3.2

- align Patroni configuration with DATABASE_POSTGRES_* secret variable names

- document APISIX real IP template configuration and Harbor rebuild workflow

- replace the separate DB stack env file guidance with the shared .env.secrets.shared flow

- update production setup and roadmap snippets to use current PostGIS, MongoDB, and APISIX rebuild commands
This commit is contained in:
Murat ÖZDEMİR 2026-05-20 19:55:49 +03:00
parent 9e20f2fcf8
commit e3787d80f6
8 changed files with 54 additions and 64 deletions

View File

@ -40,7 +40,7 @@ bootstrap:
users:
postgres:
password: "${POSTGRES_PASSWORD}"
password: "${DATABASE_POSTGRES_ROOT_PASSWD}"
options:
- superuser
@ -52,10 +52,10 @@ postgresql:
authentication:
replication:
username: replicator
password: "${REPLICATOR_PASSWORD}"
password: "${DATABASE_POSTGRES_REPLICATOR_PASSWORD}"
superuser:
username: postgres
password: "${POSTGRES_PASSWORD}"
password: "${DATABASE_POSTGRES_ROOT_PASSWD}"
parameters:
unix_socket_directories: "/var/run/postgresql"

View File

@ -21,8 +21,8 @@ wireguard_clients:
allowed_ips: 10.8.0.2/32
# DB Stack
db_postgres_image: "postgis/postgis:17-3.5"
db_mongo_image: "mongo:8"
db_postgres_image: "postgis/postgis:18-3.6"
db_mongo_image: "mongo:8.3.2"
db_postgres_root_user: "{{ vault_postgres_root_user }}"
db_postgres_password: "{{ vault_postgres_password }}"
db_mongo_root_user: "{{ vault_mongo_root_user }}"

View File

@ -10,12 +10,11 @@ Changes made for test already apply to prod.
- [ ] `ssls/1` PUT block removed from `init/apisix-core/init.sh`
- [ ] `dev` SSL block removed or confirmed non-impactful for prod
- [ ] Custom APISIX image (`custom-apisix:3.12.0`) config.yaml contains `real_ip_header`
and `set_real_ip_from` for overlay CIDR (`10.0.0.0/8`)
- [ ] New image built and pushed to Harbor if config.yaml was changed:
- [ ] Custom APISIX image (`custom-apisix:3.12.0`) `template/apisix-core/config.yaml.template` contains
`real_ip_header`, `real_ip_recursive`, and `set_real_ip_from` (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`)
- [ ] New image built and pushed to Harbor if config.yaml.template was changed:
```bash
docker build -t registry.tarla.io/iklimco/custom-apisix:3.12.0 .
docker push registry.tarla.io/iklimco/custom-apisix:3.12.0
bash ops/push-harbor-custom-images.sh
```
## Prod-specific note

View File

@ -212,7 +212,7 @@ Insert **after** `Bootstrap SWAG Certificate` and **before** `Review Environment
echo "⏳ Waiting for PostgreSQL..."
until docker run --rm --network iklimco-net \
-e PGPASSWORD="${DATABASE_POSTGRES_ROOT_PASSWD}" \
postgis/postgis:17-3.5 \
postgis/postgis:18-3.6 \
pg_isready -h postgresql -U "${DATABASE_POSTGRES_ROOT_USER}" -q 2>/dev/null; do
sleep 5
done
@ -220,19 +220,19 @@ Insert **after** `Bootstrap SWAG Certificate` and **before** `Review Environment
echo "▶ $(basename "$sql_file")"
docker run --rm -i --network iklimco-net \
-e PGPASSWORD="${DATABASE_POSTGRES_ROOT_PASSWD}" \
postgis/postgis:17-3.5 \
postgis/postgis:18-3.6 \
psql -h postgresql -U "${DATABASE_POSTGRES_ROOT_USER}" < "$sql_file"
done
echo "⏳ Waiting for MongoDB..."
until docker run --rm --network iklimco-net mongo:8 \
until docker run --rm --network iklimco-net mongo:8.3.2 \
mongosh "mongodb://${DATABASE_MONGODB_ROOT_USER}:${DATABASE_MONGODB_ROOT_PASSWD}@mongodb/admin" \
--eval "db.runCommand({ping:1})" --quiet 2>/dev/null; do
sleep 5
done
for js_file in $(ls ./init/mongodb/*.js 2>/dev/null | sort); do
echo "▶ $(basename "$js_file")"
docker run --rm -i --network iklimco-net mongo:8 \
docker run --rm -i --network iklimco-net mongo:8.3.2 \
mongosh "mongodb://${DATABASE_MONGODB_ROOT_USER}:${DATABASE_MONGODB_ROOT_PASSWD}@mongodb/admin" \
--quiet < "$js_file"
done

View File

@ -47,7 +47,7 @@ APISIX's custom image (`registry.tarla.io/iklimco/custom-apisix:3.12.0`) include
`config.yaml`. That config must set real IP headers so APISIX sees real client IPs, not
SWAG's overlay IP.
Locate the APISIX `config.yaml` in the custom image build source and ensure it contains:
The source file is `template/apisix-core/config.yaml.template``ops/push-harbor-custom-images.sh` generates `build/apisix-core/config.yaml` from this template at build time and deletes it afterward. Ensure the template contains:
```yaml
nginx_config:
@ -63,8 +63,11 @@ nginx_config:
Docker Swarm overlay networks use `10.x.x.x` addressing. These CIDR ranges cover all
typical overlay subnet allocations.
If the custom image config does not have these, add them and rebuild+push the image to Harbor
before deploying.
If the template does not have these, add them and rebuild+push the image to Harbor before deploying:
```bash
bash ops/push-harbor-custom-images.sh
```
## Step 3 — Remove APISIX TLS upstream configs (if any)

View File

@ -258,15 +258,9 @@ Applied to `iklim-app-*` nodes. Gitea Act Runner is installed on each app node a
Applied to `iklim-db-*` nodes. On each DB node, it creates `/opt/iklimco/db` and `/opt/iklimco/backup` directories, as well as a local reference directory for MongoDB. The actual production configuration, including node-specific `mongod.conf`, replica set auth key, and Patroni configurations, is set up on StorageBox at `/mnt/storagebox/db/mongodb-0X/config/` and `/mnt/storagebox/db/postgresql-0X/config/` in the `08-prod-db-cluster-kurulum.md` step. etcd data is stored on local Docker named volumes (not StorageBox).
## /opt/iklimco/stacks/.env
## DB Stack Env Variables
Password variables required by the DB cluster stacks are stored in the `/opt/iklimco/stacks/.env` file. This file is stored on StorageBox as `prod/secrets/iklim.co/.env.stacks`. Before the first deploy, it is fetched on `iklim-app-01` with the following command:
```bash
scp -P 23 STORAGEBOX_USER@STORAGEBOX_USER.your-storagebox.de:prod/secrets/iklim.co/.env.stacks \
/opt/iklimco/stacks/.env
chmod 600 /opt/iklimco/stacks/.env
```
Password variables required by the DB cluster stack (`docker-stack-db.prod.yml`) — `DATABASE_POSTGRES_ROOT_PASSWD`, `DATABASE_POSTGRES_REPLICATOR_PASSWORD`, `DATABASE_MONGODB_ROOT_PASSWD` — are stored in `prod/secrets/iklim.co/.env.secrets.shared` on StorageBox, alongside the other shared secrets. No separate file is needed.
## StorageBox Directory Structure

View File

@ -238,7 +238,7 @@ MongoDB services are defined in `docker-stack-db.prod.yml` (repo root). Each ser
```yaml
mongodb-01:
image: mongo:8
image: mongo:8.3.2
volumes:
- mongodb-01-data:/data/db
- mongodb-01-log:/data/log
@ -268,8 +268,8 @@ Run **once** after the stack is deployed:
```bash
# On iklim-app-01 (overlay network erişimi için):
docker run --rm -it --network iklimco-net mongo:8 \
mongosh "mongodb://mongo-root:${MONGO_ROOT_PASSWORD}@mongodb-01/admin"
docker run --rm -it --network iklimco-net mongo:8.3.2 \
mongosh "mongodb://mongo-root:${DATABASE_MONGODB_ROOT_PASSWD}@mongodb-01/admin"
# Inside mongosh:
rs.initiate({
@ -293,12 +293,12 @@ Patroni coordinates PostgreSQL primary/standby roles through etcd. If the primar
### 5.1 Custom Image (Patroni + PostGIS)
Patroni is installed on top of the `postgis/postgis:17-3.5` image. This image is pushed to Harbor and used in the stack.
Patroni is installed on top of the `postgis/postgis:18-3.6` image. This image is pushed to Harbor and used in the stack.
`build/patroni-postgis/Dockerfile`:
```dockerfile
FROM postgis/postgis:17-3.5
FROM postgis/postgis:18-3.6
USER root
@ -328,9 +328,9 @@ Or manually:
```bash
cd build/patroni-postgis
docker build -t registry.tarla.io/iklimco/custom-patroni-postgis:17-3.5 .
docker build -t registry.tarla.io/iklimco/custom-patroni-postgis:18-3.6 .
echo "$HARBOR_CI_TOKEN" | docker login registry.tarla.io -u robot-ci-push-iklimco --password-stdin
docker push registry.tarla.io/iklimco/custom-patroni-postgis:17-3.5
docker push registry.tarla.io/iklimco/custom-patroni-postgis:18-3.6
```
### 5.2 etcd Cluster
@ -417,7 +417,7 @@ bootstrap:
users:
postgres:
password: "${POSTGRES_PASSWORD}"
password: "${DATABASE_POSTGRES_ROOT_PASSWD}"
options:
- superuser
@ -429,10 +429,10 @@ postgresql:
authentication:
replication:
username: replicator
password: "${REPLICATOR_PASSWORD}"
password: "${DATABASE_POSTGRES_REPLICATOR_PASSWORD}"
superuser:
username: postgres
password: "${POSTGRES_PASSWORD}"
password: "${DATABASE_POSTGRES_ROOT_PASSWD}"
parameters:
unix_socket_directories: "/var/run/postgresql"
@ -451,10 +451,10 @@ Patroni services are defined in `docker-stack-db.prod.yml`. Each service uses th
```yaml
patroni-01:
image: registry.tarla.io/iklimco/custom-patroni-postgis:17-3.5
image: registry.tarla.io/iklimco/custom-patroni-postgis:18-3.6
environment:
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
REPLICATOR_PASSWORD: "${REPLICATOR_PASSWORD}"
DATABASE_POSTGRES_ROOT_PASSWD: "${DATABASE_POSTGRES_ROOT_PASSWD}"
DATABASE_POSTGRES_REPLICATOR_PASSWORD: "${DATABASE_POSTGRES_REPLICATOR_PASSWORD}"
TZ: "Europe/Istanbul"
volumes:
- postgresql-01-data:/var/lib/postgresql/data
@ -512,28 +512,19 @@ All DB services (etcd, MongoDB, Patroni) are in the single `docker-stack-db.prod
### .env File
The `/opt/iklimco/stacks/.env` file is stored on StorageBox as `prod/secrets/iklim.co/.env.stacks`. Fetch it once before first deploy:
DB stack password variables (`DATABASE_POSTGRES_ROOT_PASSWD`, `DATABASE_POSTGRES_REPLICATOR_PASSWORD`, `DATABASE_MONGODB_ROOT_PASSWD`) are stored in `prod/secrets/iklim.co/.env.secrets.shared` on StorageBox. Fetch it to `iklim-app-01` before deploy:
```bash
scp -P 23 STORAGEBOX_USER@STORAGEBOX_USER.your-storagebox.de:prod/secrets/iklim.co/.env.stacks \
/opt/iklimco/stacks/.env
chmod 600 /opt/iklimco/stacks/.env
```
File content (`/opt/iklimco/stacks/.env`, not committed to the repo):
```env
DATABASE_POSTGRES_ROOT_USER=postgres
POSTGRES_PASSWORD=<strong-password>
REPLICATOR_PASSWORD=<strong-password>
MONGO_ROOT_PASSWORD=<strong-password>
scp -P 23 STORAGEBOX_USER@STORAGEBOX_USER.your-storagebox.de:prod/secrets/iklim.co/.env.secrets.shared \
/tmp/.env.secrets.shared
chmod 600 /tmp/.env.secrets.shared
```
### Deploy Steps
```bash
# On iklim-app-01, in the repo working directory:
export $(cat /opt/iklimco/stacks/.env | xargs)
set -a; . /tmp/.env.secrets.shared; set +a
# Automatic ETCD_INITIAL_CLUSTER_STATE detection:
DEPLOY_FILE="docker-stack-db.prod.yml"
@ -585,8 +576,8 @@ Run once after the stack is deployed:
```bash
# From iklim-app-01 via overlay network:
docker run --rm -it --network iklimco-net mongo:8 \
mongosh "mongodb://mongo-root:${MONGO_ROOT_PASSWORD}@mongodb-01/admin"
docker run --rm -it --network iklimco-net mongo:8.3.2 \
mongosh "mongodb://mongo-root:${DATABASE_MONGODB_ROOT_PASSWD}@mongodb-01/admin"
# Inside mongosh:
rs.initiate({

View File

@ -154,16 +154,19 @@ The prod stack uses the `registry.tarla.io/iklimco/custom-apisix:3.12.0` image.
nginx_config:
http:
real_ip_header: "X-Real-IP"
set_real_ip_from: "10.0.0.0/8"
real_ip_recursive: "on"
set_real_ip_from:
- "10.0.0.0/8"
- "172.16.0.0/12"
- "192.168.0.0/16"
```
`set_real_ip_from: 10.0.0.0/8` covers all container addresses in the Swarm overlay network; this skips SWAG's internal overlay IP and writes the real client IP to APISIX access logs.
These three CIDR ranges cover all typical Docker Swarm overlay subnet allocations. APISIX reads the real client IP from SWAG's `X-Real-IP` header instead of the overlay container IP.
If the image requires a rebuild because `config.yaml` changed:
If the image requires a rebuild because `config.yaml` changed, run from the project root:
```bash
docker build -t registry.tarla.io/iklimco/custom-apisix:3.12.0 .
docker push registry.tarla.io/iklimco/custom-apisix:3.12.0
bash ops/push-harbor-custom-images.sh
```
During deploy, `init/apisix-core/init.sh` is run once by the pipeline. It writes the APISIX configuration to Patroni etcd with the `/apisix` prefix; the 3 replicas in prod read this etcd state commonly, so no separate init per replica is required. Detail: `roadmap/prod-env/05-apisix-remove-ssl.md`.
@ -293,7 +296,7 @@ PostgreSQL and MongoDB init scripts run through Swarm overlay DNS service names
echo "⏳ Waiting for PostgreSQL..."
until docker run --rm --network iklimco-net \
-e PGPASSWORD="${DATABASE_POSTGRES_ROOT_PASSWD}" \
postgis/postgis:17-3.5 \
postgis/postgis:18-3.6 \
pg_isready -h postgresql -U "${DATABASE_POSTGRES_ROOT_USER}" -q 2>/dev/null; do
sleep 5
done
@ -301,19 +304,19 @@ PostgreSQL and MongoDB init scripts run through Swarm overlay DNS service names
echo "▶ $(basename "$sql_file")"
docker run --rm -i --network iklimco-net \
-e PGPASSWORD="${DATABASE_POSTGRES_ROOT_PASSWD}" \
postgis/postgis:17-3.5 \
postgis/postgis:18-3.6 \
psql -h postgresql -U "${DATABASE_POSTGRES_ROOT_USER}" < "$sql_file"
done
echo "⏳ Waiting for MongoDB..."
until docker run --rm --network iklimco-net mongo:8 \
until docker run --rm --network iklimco-net mongo:8.3.2 \
mongosh "mongodb://${DATABASE_MONGODB_ROOT_USER}:${DATABASE_MONGODB_ROOT_PASSWD}@mongodb/admin" \
--eval "db.runCommand({ping:1})" --quiet 2>/dev/null; do
sleep 5
done
for js_file in $(ls ./init/mongodb/*.js 2>/dev/null | sort); do
echo "▶ $(basename "$js_file")"
docker run --rm -i --network iklimco-net mongo:8 \
docker run --rm -i --network iklimco-net mongo:8.3.2 \
mongosh "mongodb://${DATABASE_MONGODB_ROOT_USER}:${DATABASE_MONGODB_ROOT_PASSWD}@mongodb/admin" \
--quiet < "$js_file"
done
@ -650,7 +653,7 @@ Expected: valid JSON weather response.
- After the first deploy, `docker exec $(docker ps -q -f name=iklimco_swag) nginx -t` succeeds and returns `syntax is ok`.
- The output of `cat /mnt/storagebox/swag/site-confs/api.conf | grep server_name` contains `server_name api.iklim.co;`.
- The `ssls/1` PUT block does not exist inside `init/apisix-core/init.sh`.
- The `registry.tarla.io/iklimco/custom-apisix:3.12.0` image exists in Harbor and its `config.yaml` contains `set_real_ip_from: 10.0.0.0/8` configuration.
- The `registry.tarla.io/iklimco/custom-apisix:3.12.0` image exists in Harbor and its `config.yaml` contains `real_ip_header`, `real_ip_recursive`, and `set_real_ip_from` (covering `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`) configuration.
- After the first deploy, real client IP appears in APISIX access logs, not the SWAG overlay IP: `docker exec $(docker ps -q -f name=iklimco_apisix | head -1) tail -5 /usr/local/apisix/logs/access.log`
- `docker service ps iklimco_cert-reloader` shows that the service is running.
- `docker service ls` does not contain `iklimco_etcd`, `iklimco_postgresql`, `iklimco_mongodb`, `iklimco_pg-proxy`, or `iklimco_mongo-proxy`; they are removed by the post-deploy step in `deploy-prod.yml` (base stack services superseded by the `iklim-db` stack or deprecated in prod).