This commit is contained in:
2026-01-29 14:27:03 +01:00
parent 90e6750501
commit f08e71b54d
8 changed files with 514 additions and 112 deletions

View File

@@ -1,31 +1,87 @@
- name: Wait for PostgreSQL socket ---
# Tworzenie bazy danych i użytkownika dla MailArchiver
# Ta rola uruchamia się TYLKO na primary
- name: Wait for PostgreSQL to be ready
wait_for: wait_for:
path: "/var/run/postgresql/{{ pg_version }}-{{ pg_cluster }}/.s.PGSQL.5432" path: "/var/run/postgresql/.s.PGSQL.5432"
timeout: 60 timeout: 60
- name: Create db user # --- Utworzenie użytkownika mailuser ---
become: true - name: Create mailuser database user
become_user: postgres
postgresql_user: postgresql_user:
name: mailuser name: mailuser
password: "{{ mailuser_password }}" password: "{{ mailuser_password }}"
login_host: "/var/run/postgresql/{{ pg_version }}-{{ pg_cluster }}/.s.PGSQL.5432" role_attr_flags: CREATEDB
login_unix_socket_directory: /var/run/postgresql
- name: Create database db: postgres
become: true become: true
become_user: postgres become_user: postgres
# --- Utworzenie bazy mailarchiver ---
- name: Create mailarchiver database
postgresql_db: postgresql_db:
name: mailarchiver name: mailarchiver
owner: mailuser owner: mailuser
login_host: "/var/run/postgresql/{{ pg_version }}-{{ pg_cluster }}/.s.PGSQL.5432" encoding: UTF-8
lc_collate: en_US.UTF-8
- name: Grant schema rights lc_ctype: en_US.UTF-8
login_unix_socket_directory: /var/run/postgresql
become: true become: true
become_user: postgres become_user: postgres
# --- Przyznanie uprawnień ---
- name: Grant privileges on schema public
postgresql_query:
db: mailarchiver
query: |
GRANT USAGE ON SCHEMA public TO mailuser;
GRANT CREATE ON SCHEMA public TO mailuser;
login_unix_socket_directory: /var/run/postgresql
become: true
become_user: postgres
# --- Default privileges dla tabel ---
- name: Set default privileges for tables
postgresql_query: postgresql_query:
db: mailarchiver db: mailarchiver
query: | query: |
GRANT ALL ON SCHEMA public TO mailuser;
ALTER DEFAULT PRIVILEGES IN SCHEMA public ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT ALL ON TABLES TO mailuser; GRANT ALL ON TABLES TO mailuser;
login_host: "/var/run/postgresql/{{ pg_version }}-{{ pg_cluster }}/.s.PGSQL.5432"
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT ALL ON SEQUENCES TO mailuser;
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT ALL ON FUNCTIONS TO mailuser;
login_unix_socket_directory: /var/run/postgresql
become: true
become_user: postgres
# --- Sprawdzenie ---
- name: Verify mailarchiver database creation
postgresql_query:
db: mailarchiver
query: "SELECT datname, pg_database.datdba::regrole FROM pg_database WHERE datname = 'mailarchiver'"
login_unix_socket_directory: /var/run/postgresql
become: true
become_user: postgres
register: db_verify
- name: Display database info
debug:
msg: "Database mailarchiver created: {{ db_verify.query_result }}"
- name: Verify mailuser permissions
postgresql_query:
db: mailarchiver
query: "SELECT * FROM information_schema.role_table_grants WHERE grantee='mailuser' LIMIT 5"
login_unix_socket_directory: /var/run/postgresql
become: true
become_user: postgres
register: user_perms
ignore_errors: true
- name: Display user permissions
debug:
msg: "Mailuser permissions: {{ user_perms.query_result | default('No permissions yet') }}"

View File

@@ -1,37 +1,92 @@
- name: Install pgBackRest ---
apt: # Instalacja i konfiguracja pgBackRest
name: pgbackrest # Full backup: niedziela 2:00
state: present # Incremental backup: codziennie 2:00 (oprócz niedzieli)
# Repozytorium backupu na pg2 (replica)
- name: Repo dir # --- Auto-detect PostgreSQL version ---
- name: Detect PostgreSQL version
shell: "ls -d /etc/postgresql/*/ | head -1 | xargs basename"
register: pg_version_detect
changed_when: false
- name: Set PostgreSQL variables from detection
set_fact:
pg_version: "{{ pg_version_detect.stdout | trim }}"
pg_cluster: "main"
pg_data: "/var/lib/postgresql/{{ pg_version_detect.stdout | trim }}/main"
pg_bin_path: "/usr/lib/postgresql/{{ pg_version_detect.stdout | trim }}/bin"
- name: Install pgBackRest and dependencies
apt:
name:
- pgbackrest
state: present
update_cache: yes
# --- Repozytorium backupu na replica ---
- name: Create backup repository directory (on replica)
file: file:
path: /pgbackrest path: /pgbackrest
state: directory state: directory
owner: postgres owner: postgres
mode: "750" group: postgres
mode: "0750"
when: inventory_hostname in groups['replica'] when: inventory_hostname in groups['replica']
- name: Config # --- Konfiguracja pgBackRest ---
- name: Deploy pgBackRest configuration
template: template:
src: pgbackrest.conf.j2 src: pgbackrest.conf.j2
dest: /etc/pgbackrest.conf dest: /etc/pgbackrest.conf
owner: postgres
group: postgres
mode: "0640"
- name: Create stanza # --- Utworzenie stanza na primary ---
- name: Create pgBackRest stanza (on primary)
shell: pgbackrest --stanza=main stanza-create
become: true
become_user: postgres become_user: postgres
command: pgbackrest --stanza=main stanza-create when: inventory_hostname in groups['primary']
register: stanza_create
changed_when: "'CREATED' in stanza_create.stdout or 'already exists' not in stanza_create.stdout"
ignore_errors: true
# --- Full backup - niedziela 2:00 ---
- name: Schedule full backup weekly (Sunday 2:00 AM)
cron:
name: "pgBackRest full backup"
weekday: "0"
hour: "2"
minute: "0"
user: postgres
job: "pgbackrest --stanza=main backup --type=full"
state: present
when: inventory_hostname in groups['primary'] when: inventory_hostname in groups['primary']
- name: Full backup weekly # --- Incremental backup - codziennie 2:00 (ale nie w niedzielę) ---
- name: Schedule incremental backup daily (Monday-Saturday 2:00 AM)
cron: cron:
name: "pgbackrest full" name: "pgBackRest incremental backup"
weekday: 0 weekday: "1-6"
hour: 2 hour: "2"
minute: 0 minute: "0"
job: "pgbackrest --stanza=main backup --type=full" user: postgres
- name: Incremental backup daily
cron:
name: "pgbackrest incr"
hour: 2
minute: 0
job: "pgbackrest --stanza=main backup --type=incr" job: "pgbackrest --stanza=main backup --type=incr"
state: present
when: inventory_hostname in groups['primary']
# --- Test configuration ---
- name: Test pgBackRest configuration (on primary)
shell: pgbackrest --stanza=main check
become: true
become_user: postgres
when: inventory_hostname in groups['primary']
register: pgbackrest_check
ignore_errors: true
- name: Display pgBackRest check result
debug:
msg: "{{ pgbackrest_check.stdout }}"
when: inventory_hostname in groups['primary']

View File

@@ -1,11 +1,26 @@
# pgBackRest Configuration
# Backup storage on replica (pg2)
# Archive destination: /pgbackrest
[main] [main]
pg1-path=/var/lib/postgresql/16/main pg1-path={{ pg_data }}
[global] [global]
{% if inventory_hostname in groups['primary'] %}
repo1-host=pg2
{% endif %}
repo1-path=/pgbackrest repo1-path=/pgbackrest
repo1-retention-full=2 repo1-retention-full=2
repo1-retention-diff=7 repo1-retention-diff=7
repo1-retention-incr=7
repo1-bundle=y
start-fast=y start-fast=y
log-level-console=info
log-level-file=debug
archive-async=y
archive-timeout=60
check-strict=y
delta=y
recovery-option=recovery_target_timeline=latest
{% if inventory_hostname in groups['primary'] %}
repo1-host=pg2
repo1-host-user=postgres
{% endif %}

View File

@@ -1,96 +1,111 @@
- name: Install PostgreSQL packages ---
# Instalacja PostgreSQL (najnowsza wersja) i konfiguracja klastra
# Role: primary i replica
- name: Install PostgreSQL and contrib packages
apt: apt:
name: name:
- postgresql - postgresql
- postgresql-contrib - postgresql-contrib
- python3-psycopg2
update_cache: yes update_cache: yes
state: present
# --- Detect PostgreSQL version and cluster --- # --- Auto-detect PostgreSQL version ---
- name: Detect PostgreSQL version - name: Detect installed PostgreSQL version
shell: "ls -d /etc/postgresql/*/ | head -n1 | xargs basename" shell: "ls -d /etc/postgresql/*/ | head -1 | xargs basename"
register: pg_version_result register: pg_version_detect
changed_when: false changed_when: false
failed_when: pg_version_detect.rc != 0
- name: Detect cluster name - name: Set PostgreSQL version variable
shell: "ls -d /var/lib/postgresql/*/main/ 2>/dev/null | head -n1 | xargs -I {} basename $(dirname {}) | tail -n1"
register: pg_cluster_result
changed_when: false
- name: Set PostgreSQL version and cluster facts
set_fact: set_fact:
pg_version: "{{ pg_version_result.stdout | trim }}" pg_version: "{{ pg_version_detect.stdout | trim }}"
pg_cluster: "main" pg_cluster: "main"
pg_data: "/var/lib/postgresql/{{ pg_version_detect.stdout | trim }}/main"
pg_config_path: "/etc/postgresql/{{ pg_version_detect.stdout | trim }}/main"
pg_bin_path: "/usr/lib/postgresql/{{ pg_version_detect.stdout | trim }}/bin"
- name: Define PostgreSQL cluster path - name: Display detected PostgreSQL version
set_fact: debug:
pg_cluster_path: "/etc/postgresql/{{ pg_version }}/{{ pg_cluster }}" msg: "PostgreSQL {{ pg_version }} detected. Data path: {{ pg_data }}"
pg_data_path: "/var/lib/postgresql/{{ pg_version }}/{{ pg_cluster }}"
# --- Stop cluster before config --- # --- Tworzenie logów PostgreSQL ---
- name: Stop PostgreSQL cluster - name: Ensure log directory exists
shell: "pg_ctlcluster {{ pg_version }} {{ pg_cluster }} stop"
ignore_errors: true
# --- Remove broken cluster configuration if exists ---
- name: Remove broken cluster configuration
shell: "rm -rf {{ pg_cluster_path }} {{ pg_data_path }}"
ignore_errors: true
# --- Ensure data directory exists and is owned by postgres ---
- name: Create PostgreSQL data directory
file: file:
path: "{{ pg_data_path }}" path: /var/log/postgresql
state: directory
owner: postgres
group: postgres
mode: "0755"
# --- Inicjalizacja klastra ---
- name: Check if cluster data directory exists
stat:
path: "{{ pg_data }}"
register: pg_data_stat
- name: Create cluster data directory if not exists
file:
path: "{{ pg_data }}"
state: directory state: directory
owner: postgres owner: postgres
group: postgres group: postgres
mode: "0700" mode: "0700"
when: not pg_data_stat.stat.exists
# --- Initialize database cluster ---
- name: Initialize PostgreSQL cluster - name: Initialize PostgreSQL cluster
shell: "sudo -u postgres /usr/lib/postgresql/{{ pg_version }}/bin/initdb -D {{ pg_data_path }} --encoding=UTF8 --locale=en_US.UTF-8" shell: "{{ pg_bin_path }}/initdb -D {{ pg_data }} --encoding=UTF8 --locale=en_US.UTF-8"
ignore_errors: true become: true
become_user: postgres
when: not pg_data_stat.stat.exists
register: initdb_result
# --- Ensure cluster config directory exists --- - name: Create config directory for cluster
- name: Ensure cluster config directory exists
file: file:
path: "{{ pg_cluster_path }}" path: "{{ pg_config_path }}"
state: directory state: directory
owner: postgres owner: postgres
group: postgres group: postgres
mode: "0700" mode: "0755"
# --- Configure PostgreSQL --- # --- Konfiguracja PostgreSQL ---
- name: postgresql.conf - name: Deploy postgresql.conf
template: template:
src: postgresql.conf.j2 src: postgresql.conf.j2
dest: "{{ pg_cluster_path }}/postgresql.conf" dest: "{{ pg_config_path }}/postgresql.conf"
owner: postgres owner: postgres
group: postgres group: postgres
mode: "0600" mode: "0600"
notify: restart postgresql
- name: pg_hba.conf - name: Deploy pg_hba.conf
template: template:
src: pg_hba.conf.j2 src: pg_hba.conf.j2
dest: "{{ pg_cluster_path }}/pg_hba.conf" dest: "{{ pg_config_path }}/pg_hba.conf"
owner: postgres owner: postgres
group: postgres group: postgres
mode: "0600" mode: "0600"
notify: restart postgresql
# --- Start cluster --- # --- Uruchomienie PostgreSQL ---
- name: Start PostgreSQL cluster - name: Enable and start PostgreSQL service
shell: "sudo -u postgres /usr/lib/postgresql/{{ pg_version }}/bin/pg_ctl -D {{ pg_data_path }} -l /var/log/postgresql/postgresql-{{ pg_version }}-{{ pg_cluster }}.log start" systemd:
ignore_errors: true name: postgresql
enabled: yes
state: started
daemon_reload: yes
# --- Wait for socket to be ready --- # --- Czekanie na socket ---
- name: Wait for PostgreSQL socket - name: Wait for PostgreSQL socket to be ready
wait_for: wait_for:
path: "/var/run/postgresql/{{ pg_version }}-{{ pg_cluster }}/.s.PGSQL.5432" path: "/var/run/postgresql/.s.PGSQL.5432"
timeout: 60 timeout: 60
# --- Install Python PostgreSQL libraries ---
- name: Install python postgres libraries
apt:
name:
- python3-psycopg2
state: present state: present
update_cache: yes
# Handlery
- name: restart postgresql
systemd:
name: postgresql
state: restarted
listen: "restart postgresql"

View File

@@ -0,0 +1,15 @@
# PostgreSQL 16 pg_hba.conf - Host Based Authentication
# TYPE DATABASE USER ADDRESS METHOD
# Local connections
local all postgres peer
local all all peer
# Replication from pg2 (10.0.0.2)
host replication replicator 10.0.0.2/32 md5
# Applications and MailArchiver
host all all 10.0.0.0/24 md5
# IPv6 local
host all all ::1/128 md5

View File

@@ -0,0 +1,51 @@
# PostgreSQL Configuration for Production
# Debian 13 - najnowsza dostępna wersja
# Primary: pg1, Replica: pg2
# --- Network ---
listen_addresses = '*'
max_connections = 200
port = 5432
# --- Memory (dla 8GB RAM) ---
shared_buffers = 2GB
effective_cache_size = 6GB
work_mem = 64MB
maintenance_work_mem = 1GB
# --- WAL and Logging ---
wal_level = replica
wal_compression = on
max_wal_senders = 10
max_replication_slots = 10
wal_keep_size = 10GB
# --- Archiving (dla pgBackRest) ---
archive_mode = on
archive_command = 'pgbackrest --stanza=main archive-push %p'
archive_timeout = 300
# --- Checkpoints ---
checkpoint_timeout = 15min
checkpoint_completion_target = 0.9
max_wal_size = 64GB
min_wal_size = 16GB
# --- Logging ---
log_directory = '/var/log/postgresql'
log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log'
log_truncate_on_rotation = on
log_rotation_age = 1d
log_rotation_size = 100MB
log_min_duration_statement = 1000
log_connections = on
log_disconnections = on
log_duration = off
# --- Replication slots ---
max_replication_slots = 10
# --- Performance ---
random_page_cost = 1.1
effective_io_concurrency = 200
synchronous_commit = local

View File

@@ -1,28 +1,120 @@
- name: Create replicator user (on primary) ---
# Konfiguracja replikacji PostgreSQL
# Ta rola uruchamia się na replica (pg2)
# Tworzy użytkownika replicator i base backup z primary
# --- Auto-detect PostgreSQL version na primary ---
- name: Detect PostgreSQL version on primary
shell: "ls -d /etc/postgresql/*/ | head -1 | xargs basename"
register: pg_version_detect
changed_when: false
delegate_to: pg1 delegate_to: pg1
become_user: postgres run_once: true
- name: Set PostgreSQL variables from detection
set_fact:
pg_version: "{{ pg_version_detect.stdout | trim }}"
pg_cluster: "main"
pg_data: "/var/lib/postgresql/{{ pg_version_detect.stdout | trim }}/main"
pg_bin_path: "/usr/lib/postgresql/{{ pg_version_detect.stdout | trim }}/bin"
- name: Wait for PostgreSQL on primary to be ready
wait_for:
host: pg1
port: 5432
timeout: 300
delegate_to: pg1
# --- Utworzenie użytkownika replicator na primary ---
- name: Create replicator user on primary
postgresql_user: postgresql_user:
name: replicator name: replicator
password: "{{ replicator_password }}" password: "{{ replicator_password }}"
role_attr_flags: REPLICATION,LOGIN role_attr_flags: REPLICATION,LOGIN
login_unix_socket_directory: /var/run/postgresql
db: postgres
become: true
become_user: postgres
delegate_to: pg1
- name: Stop postgres # --- Przygotowanie replica ---
service: - name: Stop PostgreSQL on replica
systemd:
name: postgresql name: postgresql
state: stopped state: stopped
- name: Remove old data - name: Remove existing cluster data on replica
file: file:
path: "{{ pg_data }}" path: "{{ pg_data }}"
state: absent state: absent
- name: Base backup # --- Base backup z primary ---
- name: Create replication slot on primary
postgresql_query:
db: postgres
query: "SELECT pg_create_physical_replication_slot('pg2_slot')"
login_unix_socket_directory: /var/run/postgresql
become: true
become_user: postgres become_user: postgres
command: > delegate_to: pg1
pg_basebackup -h pg1 -D {{ pg_data }} ignore_errors: true
-U replicator -Fp -Xs -P -R
- name: Start postgres - name: Perform base backup from primary to replica
service: shell: |
pg_basebackup \
-h pg1 \
-D {{ pg_data }} \
-U replicator \
-Fp \
-Xs \
-P \
-R \
-S pg2_slot
become: true
become_user: postgres
register: basebackup_result
- name: Fix permissions on data directory
file:
path: "{{ pg_data }}"
owner: postgres
group: postgres
mode: "0700"
recurse: yes
become: true
- name: Configure replication settings
lineinfile:
path: "{{ pg_data }}/standby.signal"
line: ""
create: yes
owner: postgres
group: postgres
mode: "0644"
# --- Uruchomienie replica ---
- name: Start PostgreSQL on replica
systemd:
name: postgresql name: postgresql
state: started state: started
enabled: yes
- name: Wait for PostgreSQL replica to be ready
wait_for:
path: "/var/run/postgresql/.s.PGSQL.5432"
timeout: 60
state: present
- name: Verify replication on primary
postgresql_query:
db: postgres
query: "SELECT application_name, client_addr, state FROM pg_stat_replication"
login_unix_socket_directory: /var/run/postgresql
become: true
become_user: postgres
delegate_to: pg1
register: replication_status
- name: Display replication status
debug:
msg: "{{ replication_status.query_result }}"

View File

@@ -1,19 +1,122 @@
- name: Stop postgres on primary ---
service: # Przywrócenie primary z pg2 (replica)
# Używany w przypadku awarii pg1
# --- Auto-detect PostgreSQL version na replica ---
- name: Detect PostgreSQL version on replica
shell: "ls -d /etc/postgresql/*/ | head -1 | xargs basename"
register: pg_version_detect
changed_when: false
delegate_to: pg2
run_once: true
- name: Set PostgreSQL variables from detection
set_fact:
pg_version: "{{ pg_version_detect.stdout | trim }}"
pg_cluster: "main"
pg_data: "/var/lib/postgresql/{{ pg_version_detect.stdout | trim }}/main"
pg_bin_path: "/usr/lib/postgresql/{{ pg_version_detect.stdout | trim }}/bin"
- name: Wait for replica to be accessible
wait_for:
host: pg2
port: 5432
timeout: 300
# --- Promowanie replica ---
- name: Promote replica to primary
shell: "{{ pg_bin_path }}/pg_ctl promote -D {{ pg_data }}"
become: true
become_user: postgres
delegate_to: pg2
register: promote_result
- name: Wait for promoted replica to be ready
wait_for:
host: pg2
port: 5432
timeout: 60
# --- Przywrócenie na pg1 ---
- name: Stop PostgreSQL on primary (pg1)
systemd:
name: postgresql name: postgresql
state: stopped state: stopped
ignore_errors: true
- name: Remove old data on primary - name: Remove old cluster data on primary
file: file:
path: "{{ pg_data }}" path: "{{ pg_data }}"
state: absent state: absent
become: true
- name: Restore base backup from replica # --- Base backup z nowego primary (pg2) ---
- name: Perform base backup from new primary (pg2)
shell: |
pg_basebackup \
-h pg2 \
-D {{ pg_data }} \
-U replicator \
-Fp \
-Xs \
-P \
-R
become: true
become_user: postgres become_user: postgres
command: > register: restore_basebackup
pg_basebackup -h pg2 -D {{ pg_data }} -U replicator -Fp -Xs -P -R
- name: Start postgres on primary - name: Fix permissions on restored data directory
service: file:
path: "{{ pg_data }}"
owner: postgres
group: postgres
mode: "0700"
recurse: yes
become: true
- name: Configure recovery settings
lineinfile:
path: "{{ pg_data }}/standby.signal"
line: ""
create: yes
owner: postgres
group: postgres
mode: "0644"
# --- Uruchomienie pg1 ---
- name: Start PostgreSQL on primary
systemd:
name: postgresql name: postgresql
state: started state: started
enabled: yes
- name: Wait for PostgreSQL to start
wait_for:
path: "/var/run/postgresql/.s.PGSQL.5432"
timeout: 60
# --- Weryfikacja ---
- name: Verify primary is accessible
postgresql_query:
db: postgres
query: "SELECT version()"
login_unix_socket_directory: /var/run/postgresql
become: true
become_user: postgres
register: version_check
- name: Display restoration result
debug:
msg: "Primary restored successfully. PostgreSQL: {{ version_check.query_result[0].version }}"
- name: Display manual failover instructions
debug:
msg: |
Restoration complete!
Next steps:
1. Verify pg1 and pg2 connectivity
2. Update application connection strings if needed
3. Monitor replication status:
SELECT * FROM pg_stat_replication;
4. Schedule regular test restores