diff --git a/ansible/prod/group_vars/all/vars.yml b/ansible/prod/group_vars/all/vars.yml index 07b0f19..f2fbfeb 100644 --- a/ansible/prod/group_vars/all/vars.yml +++ b/ansible/prod/group_vars/all/vars.yml @@ -16,7 +16,7 @@ storagebox_managed_directories: mode: "0755" 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" mongodb_replset_name: "rs0" admin_allowed_cidrs: "78.187.87.109/32 95.70.151.248/32" diff --git a/ansible/prod/group_vars/app/vars.yml b/ansible/prod/group_vars/app/vars.yml new file mode 100644 index 0000000..847692b --- /dev/null +++ b/ansible/prod/group_vars/app/vars.yml @@ -0,0 +1,3 @@ +--- +storagebox_dir_mode: "0777" +storagebox_file_mode: "0666" diff --git a/ansible/roles/act_runner/defaults/main.yml b/ansible/roles/act_runner/defaults/main.yml index 010162a..81e75f7 100644 --- a/ansible/roles/act_runner/defaults/main.yml +++ b/ansible/roles/act_runner/defaults/main.yml @@ -1,5 +1,5 @@ --- -act_runner_version: "0.2.12" +act_runner_version: "0.6.1" act_runner_arch: "linux-amd64" act_runner_gitea_url: "https://git.tarla.io" act_runner_name: "{{ inventory_hostname }}" diff --git a/ansible/roles/act_runner/tasks/main.yml b/ansible/roles/act_runner/tasks/main.yml index b4d8a3c..20b7e17 100644 --- a/ansible/roles/act_runner/tasks/main.yml +++ b/ansible/roles/act_runner/tasks/main.yml @@ -24,6 +24,8 @@ mode: "0755" owner: root group: root + force: true + notify: restart act_runner - name: Create act_runner config directory ansible.builtin.file: diff --git a/ansible/roles/act_runner/templates/config.yaml.j2 b/ansible/roles/act_runner/templates/config.yaml.j2 index 2e62880..e061d9e 100644 --- a/ansible/roles/act_runner/templates/config.yaml.j2 +++ b/ansible/roles/act_runner/templates/config.yaml.j2 @@ -26,13 +26,12 @@ container: network: "iklimco-net" enable_ipv6: false privileged: false - options: "" + options: "-v /mnt/storagebox:/mnt/storagebox" workdir_parent: "" valid_volumes: - - "/var/run/docker.sock" - # docker_host set edilince act_runner socket'i tek seferlik mount eder ve - # DOCKER_HOST env'ini job container'a iletir; options'daki manuel mount ile - # çakışıp "Duplicate mount point" hatasına yol açmaz. + - "/mnt/storagebox" + # Docker 29.5.2 ile /var/run -> /run symlink kaynaklı "mkdirat var/run: file exists" + # hatası giderildi; socket job container'lara mount edilebilir hale geldi. docker_host: "unix:///var/run/docker.sock" force_pull: false force_rebuild: false diff --git a/ansible/roles/storagebox/tasks/main.yml b/ansible/roles/storagebox/tasks/main.yml index a7698b4..d8e7ebd 100644 --- a/ansible/roles/storagebox/tasks/main.yml +++ b/ansible/roles/storagebox/tasks/main.yml @@ -22,7 +22,7 @@ - name: Add fstab entry for StorageBox ansible.builtin.lineinfile: 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() }}" state: present @@ -31,7 +31,7 @@ path: "{{ storagebox_mount_point }}" src: "{{ storagebox_url }}" 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 - name: Write mount marker diff --git a/facts/prod-kurulum-gecmisi.md b/facts/prod-kurulum-gecmisi.md index 84b6c09..1ef9334 100644 --- a/facts/prod-kurulum-gecmisi.md +++ b/facts/prod-kurulum-gecmisi.md @@ -6,37 +6,114 @@ Prod kurulum adımları ve mevcut yapı. ### 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 = "" +location = "fsn1" +image = "rocky-10" +server_type_app = "cpx42" +server_type_db = "cpx32" +admin_ssh_public_key_path = "~/.ssh/id_rsa.pub" +admin_allowed_cidrs = ["78.187.87.109/32", "95.70.151.248/32"] +``` + +`admin_allowed_cidrs` Ansible `group_vars/all/vars.yml` ile birebir uyumlu olmalıdır. Prod sunucuları `lifecycle { prevent_destroy = true }` ile korunur. + +### Apply + +```bash +cd Environment_Infrastructure/terraform/hetzner/prod +terraform init +terraform plan +terraform apply +``` ### Inventory Üretimi ```bash -cd Environment_Infrastructure/terraform/hetzner/prod mkdir -p ../../../ansible/prod/inventory/generated terraform output -raw ansible_inventory_yaml > ../../../ansible/prod/inventory/generated/prod.yml ``` ## 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 -admin_allowed_cidrs: "78.187.87.109/32 95.70.151.248/32" -admin_ssh_public_key_path: "~/.ssh/id_rsa.pub" +```ini +[defaults] +inventory = inventory/generated/prod.yml +remote_user = root +host_key_checking = False +retry_files_enabled = False +interpreter_python = auto_silent +roles_path = roles:../roles + +[privilege_escalation] +become = True +become_method = sudo +become_user = root +become_ask_pass = False ``` -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//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: "" +``` + +### 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//vault.yml` dosyasında tanımlıdır. Hardening rolü bu değeri `iklim` OS kullanıcısının sistem parolası olarak uygular (`password_hash('sha512')`). Her sunucuya farklı parola verilebilir. ```text prod/ host_vars/ - iklim-app-01/vault.yml + iklim-app-01/vault.yml ← vault_iklim_password: "" iklim-app-02/vault.yml iklim-app-03/vault.yml iklim-db-01/vault.yml @@ -44,6 +121,10 @@ prod/ iklim-db-03/vault.yml ``` +Her dosya ayrı şifrelenir: `ansible-vault encrypt host_vars//vault.yml` + +Hardening rolü `PermitRootLogin prohibit-password` uygular — key tabanlı root girişi açık, parola ile kapalıdır. + ### StorageBox Mount StorageBox WebDAV mount (`/mnt/storagebox`) davfs2 ile yapılır. DB node'larında `uid=999,gid=999` parametreleriyle mount edilir (PostgreSQL/MongoDB container uid'i ile uyumlu). `group_vars/db/vars.yml` içinde tanımlanır: @@ -103,7 +184,7 @@ cd /home/iklim scp -P 23 u469968@u469968.your-storagebox.de:prod/secrets/iklim.co/.env.secrets.shared \ /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 chmod 600 /tmp/.env diff --git a/roadmap/prod-env/08-deploy-pipeline-update.md b/roadmap/prod-env/08-deploy-pipeline-update.md index b941a18..69d4acb 100644 --- a/roadmap/prod-env/08-deploy-pipeline-update.md +++ b/roadmap/prod-env/08-deploy-pipeline-update.md @@ -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). > 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 8 — Microservice prod deploy overlay diff --git a/setup/09-prod-runner-ha-ve-swarm.md b/setup/09-prod-runner-ha-ve-swarm.md index f70010a..4f118df 100644 --- a/setup/09-prod-runner-ha-ve-swarm.md +++ b/setup/09-prod-runner-ha-ve-swarm.md @@ -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. ```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: openssl rand -hex 32 | docker secret create rabbitmq_erlang_cookie - ``` @@ -108,7 +105,7 @@ Verify secrets: ```bash docker secret ls -# redis_password and rabbitmq_erlang_cookie rows must appear +# rabbitmq_erlang_cookie row must appear ``` ### 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 | | | 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 | -| 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 | | | 8 | Provision Vault AppRole IDs and Docker Secrets | | | 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 -- 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 ## Swarm Service Distribution @@ -646,7 +643,7 @@ Expected: valid JSON weather response. - 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_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 `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`.