feat: 纳管共享 etcd 与 k3s 外 PostgreSQL 高可用及备份
yaml / yaml (pull_request) Successful in 41s
ansible / collection-test (pull_request) Successful in 2m41s
terraform / validate (pull_request) Successful in 2m41s
ansible / lint (pull_request) Successful in 4m36s

This commit is contained in:
2026-09-25 19:34:48 +00:00
parent acd4b55722
commit 3a2fe5fa0c
92 changed files with 4211 additions and 0 deletions
@@ -0,0 +1,14 @@
[Unit]
Description=Shared PostgreSQL primary-aware full backup
After=network-online.target
RequiresMountsFor={{ pg_repo_path }}
[Service]
Type=oneshot
User=pgbackup
Group=pgbackup
ExecStart=/usr/bin/pgbackrest --config=/etc/homelab-pgbackrest/repository.conf --stanza=prod --type=full backup
TimeoutStartSec=2h
UMask=0077
Nice=10
IOSchedulingClass=best-effort
IOSchedulingPriority=7
@@ -0,0 +1,8 @@
[Unit]
Description=Daily shared PostgreSQL full backup
[Timer]
OnCalendar=*-*-* 03:40:00 UTC
RandomizedDelaySec=10min
Persistent=true
[Install]
WantedBy=timers.target
@@ -0,0 +1,19 @@
# 参考 Pigsty v4.5.0 的 Patroni 角色检查与旧连接摘除行为。
global
maxconn 256
log stdout format raw local0
stats socket /tmp/homelab-pg-haproxy.sock mode 600 level admin
defaults
mode tcp
timeout connect 3s
timeout client 1h
timeout server 1h
timeout check 3s
listen pg-prod
bind {{ pg_proxy_bind }}
option httpchk GET /primary
http-check expect status 200
default-server inter 2s fall 3 rise 2 on-marked-down shutdown-sessions
{% for member in pg_proxy_members %}
server {{ member.name }} {{ member.address }}:{{ member.port }} check port {{ member.api_port }}
{% endfor %}
@@ -0,0 +1,33 @@
[Unit]
Description=Homelab PostgreSQL {{ pg_instance }}{{ ' through Patroni' if pg_instance == 'prod' else '' }}
After=network-online.target
Wants=network-online.target
RequiresMountsFor={{ pg_parent_dir }}
[Service]
User={{ pg_profile.user }}
Group={{ pg_profile.user }}
UMask=0077
RuntimeDirectory=homelab-postgresql-{{ pg_instance }}
RuntimeDirectoryMode=0700
{% if pg_instance == 'prod' %}
Type=simple
ExecStart={{ pg_patroni_bin }} {{ pg_config_dir }}/patroni.yml
KillMode=process
{% else %}
Type=simple
ExecStart={{ pg_bin_dir }}/postgres -D {{ pg_data_dir }} -c config_file={{ pg_config_dir }}/postgresql.conf
KillSignal=SIGINT
{% endif %}
Restart=on-failure
RestartSec=5
TimeoutStopSec=120
MemoryHigh={{ pg_profile.memory_high }}
MemoryMax={{ pg_profile.memory_max }}
OOMPolicy=stop
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
ReadWritePaths={{ pg_parent_dir }} {{ pg_config_dir }} {{ pg_socket_dir }}
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,99 @@
# 实例化部署;不引用 Pigsty 的全局 /pg 或宿主内存自动调优。
name: {{ pg_member | to_json }}
namespace: /homelab/patroni/
scope: pg-prod
restapi:
listen: {{ pg_address }}:{{ pg_profile.api_port }}
connect_address: {{ pg_address }}:{{ pg_profile.api_port }}
authentication:
username: patroni
password: {{ pg_credentials.rest_password | to_json }}
etcd3:
hosts: {{ pg_etcd_endpoints | to_json }}
protocol: https
username: {{ pg_etcd_credentials.username | to_json }}
password: {{ pg_etcd_credentials.password | to_json }}
cacert: {{ pg_config_dir }}/ca.crt
cert: {{ pg_config_dir }}/etcd.crt
key: {{ pg_config_dir }}/etcd.key
bootstrap:
dcs:
ttl: 30
loop_wait: 10
retry_timeout: 10
maximum_lag_on_failover: 1048576
check_timeline: true
synchronous_mode: false
failsafe_mode: false
postgresql:
use_pg_rewind: true
use_slots: true
parameters:
max_connections: {{ pg_profile.max_connections }}
wal_level: replica
hot_standby: 'on'
wal_log_hints: 'on'
max_wal_senders: 5
max_replication_slots: 5
max_slot_wal_keep_size: 2GB
max_wal_size: 1GB
min_wal_size: 80MB
archive_mode: 'on'
archive_timeout: 300s
archive_command: "pgbackrest --config={{ pg_config_dir }}/pgbackrest.conf --stanza=prod archive-push %p"
initdb:
- encoding: UTF8
- locale: C.UTF-8
- data-checksums
postgresql:
listen: {{ pg_address }}:{{ pg_profile.port }}
connect_address: {{ pg_address }}:{{ pg_profile.port }}
data_dir: {{ pg_data_dir }}
bin_dir: {{ pg_bin_dir }}
pgpass: {{ pg_parent_dir }}/pgpass
use_unix_socket: true
use_unix_socket_repl: true
authentication:
superuser:
username: {{ pg_profile.user }}
password: {{ pg_credentials.superuser_password | to_json }}
replication:
username: replicator
password: {{ pg_credentials.replication_password | to_json }}
sslmode: verify-full
sslrootcert: {{ pg_config_dir }}/ca.crt
parameters:
unix_socket_directories: {{ pg_socket_dir }}
unix_socket_permissions: '0700'
password_encryption: scram-sha-256
shared_buffers: {{ pg_profile.shared_buffers }}
work_mem: 4MB
maintenance_work_mem: 64MB
ssl: 'on'
ssl_cert_file: {{ pg_config_dir }}/server.crt
ssl_key_file: {{ pg_config_dir }}/server.key
ssl_ca_file: {{ pg_config_dir }}/ca.crt
log_statement: none
log_min_error_statement: panic
pg_hba:
- local all {{ pg_profile.user }} peer
- local replication {{ pg_profile.user }} peer
- local replication replicator scram-sha-256
{% for address in pg_replication_addresses %}
- hostssl replication replicator {{ address }} scram-sha-256
- hostssl all {{ pg_profile.user }} {{ address }} scram-sha-256
{% endfor %}
{% for cidr in pg_client_cidrs %}
- hostssl all all {{ cidr }} scram-sha-256
{% endfor %}
- host all all 0.0.0.0/0 reject
- host all all ::/0 reject
remove_data_directory_on_rewind_failure: false
remove_data_directory_on_diverged_timelines: false
watchdog:
mode: 'off'
tags:
nofailover: false
noloadbalance: false
clonefrom: false
nosync: false
@@ -0,0 +1,6 @@
local all {{ pg_profile.user }} peer
{% for cidr in pg_client_cidrs %}
hostssl all all {{ cidr }} scram-sha-256
{% endfor %}
host all all 0.0.0.0/0 reject
host all all ::/0 reject
@@ -0,0 +1,22 @@
[global]
{% if pg_repo_remote | default(true) %}
repo1-host={{ pg_repo_host }}
repo1-host-user={{ pg_repo_user }}
repo1-host-config=/etc/homelab-pgbackrest/repository.conf
{% endif %}
repo1-path={{ pg_repo_path }}
repo1-retention-full=3
process-max=1
compress-type=zst
compress-level=3
start-fast=y
log-level-console=warn
log-level-file=off
lock-path={{ pg_parent_dir }}/backup-lock
spool-path={{ pg_parent_dir }}/backup-spool
# 不设置 archive-push-queue-max;超限丢弃 WAL 会破坏恢复链。
[prod]
pg1-path={{ pg_data_dir }}
pg1-port={{ pg_profile.port }}
pg1-socket-path={{ pg_socket_dir }}
pg1-user={{ pg_profile.user }}
@@ -0,0 +1,18 @@
listen_addresses = '{{ pg_address }}'
port = {{ pg_profile.port }}
unix_socket_directories = '{{ pg_socket_dir }}'
unix_socket_permissions = 0700
hba_file = '{{ pg_config_dir }}/pg_hba.conf'
password_encryption = 'scram-sha-256'
shared_buffers = '{{ pg_profile.shared_buffers }}'
max_connections = {{ pg_profile.max_connections }}
work_mem = '4MB'
maintenance_work_mem = '32MB'
max_wal_size = '512MB'
ssl = on
ssl_cert_file = '{{ pg_config_dir }}/server.crt'
ssl_key_file = '{{ pg_config_dir }}/server.key'
ssl_ca_file = '{{ pg_config_dir }}/ca.crt'
archive_mode = off
log_statement = none
log_min_error_statement = panic
@@ -0,0 +1,21 @@
# 备份从仓库发起,发现实际 primary,避免切换后定时器仍依赖旧主。
[global]
repo1-path={{ pg_repo_path }}
repo1-retention-full=3
process-max=1
compress-type=zst
compress-level=3
start-fast=y
log-level-console=info
log-level-file=off
lock-path={{ pg_repo_path }}/lock
[prod]
{% for member in pg_backup_members %}
pg{{ loop.index }}-host={{ member.address }}
pg{{ loop.index }}-host-user=pgprod
pg{{ loop.index }}-host-config=/etc/homelab-postgresql/prod/pgbackrest.conf
pg{{ loop.index }}-path=/var/lib/homelab-postgresql/prod/data
pg{{ loop.index }}-port=5432
pg{{ loop.index }}-socket-path=/run/homelab-postgresql-prod
pg{{ loop.index }}-user=pgprod
{% endfor %}