From 65edf17dc7f2567ac15275f585cf9ac253ddbc2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BBbikowski?= Date: Thu, 29 Jan 2026 12:55:01 +0100 Subject: [PATCH] v1.0 --- .gitignore | 2 + INSTALL.md | 160 ++++++++++++++++++ README.md | 59 +++++++ group_vars/all/secrets.yml.example | 2 + inventory.ini.example | 11 ++ roles/common/tasks/main.yml | 8 + roles/mailarchiver_db/tasks/main.yml | 20 +++ roles/pgbackrest/tasks/main.yml | 37 ++++ roles/pgbackrest/templates/pgbackrest.conf.j2 | 11 ++ roles/postgres/tasks/main.yml | 47 +++++ roles/postgres/tasks/templates/pg_hba.conf.j2 | 3 + .../tasks/templates/postgresql.conf.j2 | 13 ++ roles/replication/tasks/main.yml | 28 +++ roles/restore_primary/tasks/main.yml | 19 +++ site.yml | 15 ++ 15 files changed, 435 insertions(+) create mode 100644 .gitignore create mode 100644 INSTALL.md create mode 100644 README.md create mode 100644 group_vars/all/secrets.yml.example create mode 100644 inventory.ini.example create mode 100644 roles/common/tasks/main.yml create mode 100644 roles/mailarchiver_db/tasks/main.yml create mode 100644 roles/pgbackrest/tasks/main.yml create mode 100644 roles/pgbackrest/templates/pgbackrest.conf.j2 create mode 100644 roles/postgres/tasks/main.yml create mode 100644 roles/postgres/tasks/templates/pg_hba.conf.j2 create mode 100644 roles/postgres/tasks/templates/postgresql.conf.j2 create mode 100644 roles/replication/tasks/main.yml create mode 100644 roles/restore_primary/tasks/main.yml create mode 100644 site.yml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0b6b5f4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +secrets.yml +inventory.ini \ No newline at end of file diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 0000000..09108d9 --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,160 @@ +# PostgreSQL Production Deployment Guide via Ansible + +## 1. Instalacja serwerów + +Dwa serwery Debian 12: + +| Serwer | Rola | IP | +| ------ | ------- | -------- | +| pg-1 | Primary | 10.0.0.1 | +| pg-2 | Replica | 10.0.0.2 | + +Na każdym serwerze wykonaj: + +```bash +sudo apt update +sudo apt upgrade -y +sudo apt install -y sudo vim curl gnupg rsync unzip +``` + +Opcjonalnie utwórz konto do Ansible: + +```bash +sudo adduser ansible +sudo usermod -aG sudo ansible +``` + +--- + +## 2. Konfiguracja IP i DNS + +Edytuj `/etc/hosts` na obu serwerach: + +**pg-1 (/etc/hosts)** + +``` +10.0.0.1 pg1 +10.0.0.2 pg2 +``` + +**pg-2 (/etc/hosts)** + +``` +10.0.0.1 pg1 +10.0.0.2 pg2 +``` + +Upewnij się, że hostnames `pg1` i `pg2` są rozpoznawane w sieci. + +--- + +## 3. Przygotowanie repozytorium Ansible na pg-1 + +Skopiuj paczkę Ansible na pg-1: + +```bash +scp postgres_ansible_prod_full.zip user@pg-1:/home/user/ +ssh pg-1 +unzip postgres_ansible_prod_full.zip -d ~/postgres_ansible_prod +cd ~/postgres_ansible_prod +``` + +Edytuj `inventory.ini`, jeśli IP lub hostnames są inne: + +```ini +[primary] +pg1 ansible_host=10.0.0.1 + +[replica] +pg2 ansible_host=10.0.0.2 + +[all:vars] +ansible_user=root +pg_version=16 +pg_cluster=main +pg_data=/var/lib/postgresql/16/main +``` + +Ustaw hasła w `group_vars/all/secrets.yml`: + +```yaml +replicator_password: "SILNE_HASLO_REPL" +mailuser_password: "SILNE_HASLO_DB" +``` + +Opcjonalnie zaszyfruj je Ansible Vault: + +```bash +ansible-vault encrypt group_vars/all/secrets.yml +``` + +--- + +## 4. Uruchomienie playbooka + +Zainstaluj Ansible na pg-1: + +```bash +sudo apt install -y ansible +ansible --version +``` + +Test połączenia z pg-2: + +```bash +ansible -i inventory.ini all -m ping +``` + +Uruchomienie wdrożenia: + +```bash +ansible-playbook -i inventory.ini site.yml +``` + +Playbook automatycznie: + +- Instaluje PostgreSQL 16 na obu serwerach +- Konfiguruje primary i replica +- Tworzy użytkownika `mailuser` i bazę `mailarchiver` +- Ustawia pgBackRest i cron backupów (full i incremental) + +--- + +## 5. Test działania + +Na pg-1: + +```bash +sudo -u postgres psql -c "\l" +sudo -u postgres psql -c "\du" +``` + +Sprawdź replikację: + +```bash +sudo -u postgres psql -c "SELECT * FROM pg_stat_replication;" +``` + +Test backupów (pgBackRest): + +```bash +sudo -u postgres pgbackrest --stanza=main info +``` + +--- + +## 6. Przywracanie w razie awarii + +Jeśli pg-1 padnie, przywracasz z pg-2: + +```bash +ansible-playbook -i inventory.ini restore_primary.yml +``` + +Ręczny failover (promowanie pg-2 na primary): + +```bash +sudo -u postgres pg_ctl promote +``` + +Następnie zmień DNS lub connection string w aplikacji, aby wskazywał nowy primary. diff --git a/README.md b/README.md new file mode 100644 index 0000000..8a3830b --- /dev/null +++ b/README.md @@ -0,0 +1,59 @@ +# README.md + +# PostgreSQL Production Setup via Ansible + +Kompletne wdrożenie PostgreSQL 16 z repliką, backupami, PITR i bazą MailArchiver. + +## Struktura repo + +``` +ansible/ +├── inventory.ini +├── site.yml +├── group_vars/all/secrets.yml +└── roles/ + ├── common/ + │ └── tasks/main.yml + ├── postgres/ + │ ├── tasks/main.yml + │ └── templates/ + │ ├── postgresql.conf.j2 + │ └── pg_hba.conf.j2 + ├── replication/ + │ └── tasks/main.yml + ├── pgbackrest/ + │ ├── tasks/main.yml + │ └── templates/pgbackrest.conf.j2 + └── mailarchiver_db/ + └── tasks/main.yml +``` + +## Instalacja i konfiguracja + +[INSTALL.md](INSTALL.md) + +## Backup + +- Full backup: niedziela 2:00 +- Incremental backup: codziennie 2:00 +- WAL archiving dla PITR + +## Failover ręczny + +Na pg2: + +```bash +sudo -u postgres pg_ctl promote +``` + +Następnie zmień aplikację, aby łączyła się z nowym primary. + +--- + +## Uwagi produkcyjne + +- Aplikacja musi łączyć się tylko z primary +- Repozytorium backupu na pg2 +- Monitoruj `/pgbackrest` i WAL +- Wersja z Ansible Vault chroni hasła +- Test restore co tydzień zalecany diff --git a/group_vars/all/secrets.yml.example b/group_vars/all/secrets.yml.example new file mode 100644 index 0000000..272a0e6 --- /dev/null +++ b/group_vars/all/secrets.yml.example @@ -0,0 +1,2 @@ +replicator_password: "SILNE_HASLO_REPL" +mailuser_password: "SILNE_HASLO_DB" diff --git a/inventory.ini.example b/inventory.ini.example new file mode 100644 index 0000000..1e0b867 --- /dev/null +++ b/inventory.ini.example @@ -0,0 +1,11 @@ +[primary] +pg1 ansible_host=10.0.0.1 + +[replica] +pg2 ansible_host=10.0.0.2 + +[all:vars] +ansible_user=root +pg_version=16 +pg_cluster=main +pg_data=/var/lib/postgresql/16/main diff --git a/roles/common/tasks/main.yml b/roles/common/tasks/main.yml new file mode 100644 index 0000000..1b18570 --- /dev/null +++ b/roles/common/tasks/main.yml @@ -0,0 +1,8 @@ +- name: Basic packages + apt: + name: + - vim + - curl + - gnupg + - rsync + update_cache: yes diff --git a/roles/mailarchiver_db/tasks/main.yml b/roles/mailarchiver_db/tasks/main.yml new file mode 100644 index 0000000..82b5bc9 --- /dev/null +++ b/roles/mailarchiver_db/tasks/main.yml @@ -0,0 +1,20 @@ +- name: Create db user + become_user: postgres + postgresql_user: + name: mailuser + password: "{{ mailuser_password }}" + +- name: Create database + become_user: postgres + postgresql_db: + name: mailarchiver + owner: mailuser + +- name: Grant schema rights + become_user: postgres + postgresql_query: + db: mailarchiver + query: | + GRANT ALL ON SCHEMA public TO mailuser; + ALTER DEFAULT PRIVILEGES IN SCHEMA public + GRANT ALL ON TABLES TO mailuser; diff --git a/roles/pgbackrest/tasks/main.yml b/roles/pgbackrest/tasks/main.yml new file mode 100644 index 0000000..0a0e5cc --- /dev/null +++ b/roles/pgbackrest/tasks/main.yml @@ -0,0 +1,37 @@ +- name: Install pgBackRest + apt: + name: pgbackrest + state: present + +- name: Repo dir + file: + path: /pgbackrest + state: directory + owner: postgres + mode: "750" + when: inventory_hostname in groups['replica'] + +- name: Config + template: + src: pgbackrest.conf.j2 + dest: /etc/pgbackrest.conf + +- name: Create stanza + become_user: postgres + command: pgbackrest --stanza=main stanza-create + when: inventory_hostname in groups['primary'] + +- name: Full backup weekly + cron: + name: "pgbackrest full" + weekday: 0 + hour: 2 + minute: 0 + job: "pgbackrest --stanza=main backup --type=full" + +- name: Incremental backup daily + cron: + name: "pgbackrest incr" + hour: 2 + minute: 0 + job: "pgbackrest --stanza=main backup --type=incr" diff --git a/roles/pgbackrest/templates/pgbackrest.conf.j2 b/roles/pgbackrest/templates/pgbackrest.conf.j2 new file mode 100644 index 0000000..c7271c7 --- /dev/null +++ b/roles/pgbackrest/templates/pgbackrest.conf.j2 @@ -0,0 +1,11 @@ +[main] +pg1-path=/var/lib/postgresql/16/main + +[global] +{% if inventory_hostname in groups['primary'] %} +repo1-host=pg2 +{% endif %} +repo1-path=/pgbackrest +repo1-retention-full=2 +repo1-retention-diff=7 +start-fast=y diff --git a/roles/postgres/tasks/main.yml b/roles/postgres/tasks/main.yml new file mode 100644 index 0000000..12a2057 --- /dev/null +++ b/roles/postgres/tasks/main.yml @@ -0,0 +1,47 @@ +- name: Install PostgreSQL + apt: + name: + - postgresql-{{ pg_version }} + - postgresql-contrib + update_cache: yes + +- name: Stop postgres before config + service: + name: postgresql + state: stopped + +- name: postgresql.conf + template: + src: postgresql.conf.j2 + dest: /etc/postgresql/{{ pg_version }}/main/postgresql.conf + +- name: pg_hba.conf + template: + src: pg_hba.conf.j2 + dest: /etc/postgresql/{{ pg_version }}/main/pg_hba.conf + +- name: TLS cert dir + file: + path: /etc/postgresql/ssl + state: directory + owner: postgres + mode: "700" + +- name: Copy TLS cert + copy: + src: pg.crt + dest: /etc/postgresql/ssl/pg.crt + owner: postgres + mode: "600" + +- name: Copy TLS key + copy: + src: pg.key + dest: /etc/postgresql/ssl/pg.key + owner: postgres + mode: "600" + +- name: Start postgres + service: + name: postgresql + state: started diff --git a/roles/postgres/tasks/templates/pg_hba.conf.j2 b/roles/postgres/tasks/templates/pg_hba.conf.j2 new file mode 100644 index 0000000..8cb98d8 --- /dev/null +++ b/roles/postgres/tasks/templates/pg_hba.conf.j2 @@ -0,0 +1,3 @@ +local all postgres peer +host all all 10.0.0.0/24 md5 +host replication replicator 10.0.0.2/32 md5 diff --git a/roles/postgres/tasks/templates/postgresql.conf.j2 b/roles/postgres/tasks/templates/postgresql.conf.j2 new file mode 100644 index 0000000..ea12db4 --- /dev/null +++ b/roles/postgres/tasks/templates/postgresql.conf.j2 @@ -0,0 +1,13 @@ +listen_addresses = '*' + +shared_buffers = 2GB +effective_cache_size = 6GB +work_mem = 64MB +maintenance_work_mem = 1GB + +wal_compression = on +checkpoint_timeout = 15min +max_wal_size = 64GB + +archive_mode = on +archive_command = 'pgbackrest --stanza=main archive-push %p' \ No newline at end of file diff --git a/roles/replication/tasks/main.yml b/roles/replication/tasks/main.yml new file mode 100644 index 0000000..e4000ef --- /dev/null +++ b/roles/replication/tasks/main.yml @@ -0,0 +1,28 @@ +- name: Create replicator user (on primary) + delegate_to: pg1 + become_user: postgres + postgresql_user: + name: replicator + password: "{{ replicator_password }}" + role_attr_flags: REPLICATION,LOGIN + +- name: Stop postgres + service: + name: postgresql + state: stopped + +- name: Remove old data + file: + path: "{{ pg_data }}" + state: absent + +- name: Base backup + become_user: postgres + command: > + pg_basebackup -h pg1 -D {{ pg_data }} + -U replicator -Fp -Xs -P -R + +- name: Start postgres + service: + name: postgresql + state: started diff --git a/roles/restore_primary/tasks/main.yml b/roles/restore_primary/tasks/main.yml new file mode 100644 index 0000000..84c598c --- /dev/null +++ b/roles/restore_primary/tasks/main.yml @@ -0,0 +1,19 @@ +- name: Stop postgres on primary + service: + name: postgresql + state: stopped + +- name: Remove old data on primary + file: + path: "{{ pg_data }}" + state: absent + +- name: Restore base backup from replica + become_user: postgres + command: > + pg_basebackup -h pg2 -D {{ pg_data }} -U replicator -Fp -Xs -P -R + +- name: Start postgres on primary + service: + name: postgresql + state: started diff --git a/site.yml b/site.yml new file mode 100644 index 0000000..adcfd84 --- /dev/null +++ b/site.yml @@ -0,0 +1,15 @@ +- hosts: all + roles: + - common + +- hosts: primary + roles: + - postgres + - mailarchiver_db + - pgbackrest + +- hosts: replica + roles: + - postgres + - replication + - pgbackrest