feat: 纳管共享 etcd 与 k3s 外 PostgreSQL 高可用及备份
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env python3
|
||||
"""将真实 pgBackRest 备份恢复到临时目录,仅用私有 Unix socket 启动并验证。"""
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
|
||||
def run(args, **kwargs):
|
||||
return subprocess.run(['sudo', '-n', '-u', 'pgprod', *args],
|
||||
check=True, capture_output=True, text=True, timeout=240, **kwargs)
|
||||
|
||||
|
||||
def main():
|
||||
if sys.argv[1:] != ['--run']:
|
||||
raise SystemExit('需显式 --run;仅恢复到新的临时目录,不接触原 PGDATA。')
|
||||
root = Path(tempfile.mkdtemp(prefix='homelab-pg-restore-', dir='/tmp'))
|
||||
data = root / 'data'
|
||||
started = False
|
||||
try:
|
||||
(root / 'pg_hba.conf').write_text('local all pgprod peer\n')
|
||||
(root / 'test.conf').write_text(
|
||||
f"data_directory='{data}'\nhba_file='{root}/pg_hba.conf'\n"
|
||||
f"unix_socket_directories='{root}'\nunix_socket_permissions=0700\n"
|
||||
"listen_addresses=''\nport=55432\nssl=off\narchive_mode=off\n"
|
||||
"shared_buffers=32MB\nmax_connections=80\nmax_wal_senders=5\n"
|
||||
"hot_standby=on\nprimary_conninfo=''\n")
|
||||
subprocess.run(['sudo', '-n', 'chown', '-R', 'pgprod:pgprod', str(root)], check=True)
|
||||
run(['pgbackrest', '--config=/etc/homelab-postgresql/prod/pgbackrest.conf', '--stanza=prod',
|
||||
'--pg1-path=' + str(data), '--type=immediate', '--target-action=promote',
|
||||
'--archive-mode=off', 'restore'])
|
||||
print('真实仓库备份已恢复到临时目录', flush=True)
|
||||
run(['/usr/lib/postgresql/18/bin/pg_ctl', '-D', str(data), '-l', str(root / 'server.log'),
|
||||
'-o', '-c config_file=' + str(root / 'test.conf'), '-w', '-t', '60', 'start'])
|
||||
started = True
|
||||
# pg_ctl ready 可早于 WAL recovery 完成;等待恢复结束并可写。
|
||||
for _ in range(60):
|
||||
result = run(['/usr/lib/postgresql/18/bin/psql', '-X', '-At', '-h', str(root), '-p', '55432',
|
||||
'-d', 'postgres', '-c',
|
||||
"SELECT NOT pg_is_in_recovery() AND NOT rolsuper AND rolcreatedb AND rolcreaterole "
|
||||
"FROM pg_roles WHERE rolname='ayatori'"])
|
||||
if result.stdout.strip() == 't':
|
||||
break
|
||||
time.sleep(1)
|
||||
else:
|
||||
raise RuntimeError('恢复后 SQL/管理角色验证未通过')
|
||||
print('PASS:恢复实例可写、Ayatori 管理角色属性正确;仅私有 Unix socket', flush=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(e.stderr, file=sys.stderr)
|
||||
raise
|
||||
finally:
|
||||
# pg_ctl start 超时也可能已经创建 postmaster,清理前必须先确认停止。
|
||||
if started or (data / 'postmaster.pid').exists():
|
||||
run(['/usr/lib/postgresql/18/bin/pg_ctl', '-D', str(data), '-m', 'immediate', '-w', 'stop'])
|
||||
subprocess.run(['sudo', '-n', 'chown', '-R', str(os.getuid()) + ':' + str(os.getgid()), str(root)], check=True)
|
||||
shutil.rmtree(root)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user