Files
homelab-infra/infrastructure/shared-postgresql

k3s 外共享 PostgreSQL

面向 homelab 的 PG 18 + Patroni + pgBackRest,复用现有 Bao、shared etcd、VyOS 和监控栈。 开发与生产共用 laptop,但使用独立 OS 用户、数据集、端口与配置。没有部署 Pigsty 全栈。

2026-09-25 已上线新生产主从、独立开发实例、稳定入口与本地备份。 现有 CNPG 和应用数据未迁移。Ayatori 按维护者决定沿用独立控制面规划,本轮只备齐接入材料, 未向现有 k3s 部署 Ayatori 或 Instance CR,也不能宣称 Instance Ready。

入口与拓扑

承载 角色 数据目录/入口 初始预算
laptop prod 正常主库 / Patroni /var/lib/homelab-postgresql/prod,192.168.10.127:5432 ZFS quota 32 GiB;PG MemoryMax 1 GiB
pve1 LXC150 prod standby,与 etcd 共置 同 prod 目录,10.60.0.20:5432 HDD mp0 16 GiB;容器 1536 MiB
laptop dev 独立实例 /var/lib/homelab-postgresql/dev,pg-dev.ad.ddupan.top:5433 ZFS quota 8 GiB;MemoryMax 384 MiB
pve2 LXC151 pgBackRest 仓库,与 etcd 共置 /var/lib/homelab-pgbackrest HDD mp0 32 GiB;容器 768 MiB
VyOS 生产稳定 TCP 入口 pg-prod.ad.ddupan.top:5432 → 192.168.10.2:5432 独立 HAProxy service,MemoryMax 64 MiB

两个 LXC 的 rootfs 保持 pve-rg SSD DRBD,数据盘为 pve-rg-hdd。 laptop ZFS 使用 recordsize=16K、lz4、atime=off。预算不是容量或 SLA 承诺,standby 的 16 GiB 小于主库 quota,扩容应以较小容量为约束。PG prod/dev 分别为 pgprod/pgdev,配置和 socket 隔离。 初始 prod shared_buffers=128 MiB / max_connections=80;dev 为 32 MiB / 30。 现场版本为 PostgreSQL 18.6、Patroni 4.1.5、pgBackRest 2.59.1。

客户端必须使用自己的业务凭据及 sslmode=verify-full。本轮仅建立 Ayatori 非 superuser 管理账号(CREATEDB/CREATEROLE),未交付普通应用账号。公开中央 CA 位于 ayatori/ca.crt。 例如,在已获管理身份的受控终端用交互密码提示验证,不在命令行填写密码:

psql 'host=pg-prod.ad.ddupan.top hostaddr=192.168.10.2 port=5432 dbname=postgres user=ayatori sslmode=verify-full sslrootcert=ayatori/ca.crt' -W

生产采用异步复制,自动切换可能损失未复制的已提交事务;最大候选落后阈值 1 MiB 不是精确 RPO。 Patroni REST 绑定内网 8008,写操作有独立认证。HAProxy 校验 /primary,摘除旧主时关闭旧会话; 角色变换与代理收敛期间可能出现短暂连接错误或只读响应,应用应使用适当重试。 没有硬件 watchdog/fencing,尚未验收进程冻结、网络分区或整站故障,不能把服务停止演练当作覆盖所有故障。

IaC 与秘密

本目录 Terraform 独立 state 为 shared-postgresql/terraform.tfstate,使用现有 S3 terraform 身份;run.py 在内存读取 kv/k8s/seaweedfs-s3 的 AK/SK。首次 PKI/JWT role/policy 管理由有效 Bao 管理会话 plan/apply,业务部署改用受限 SPIFFE role homelab-postgresql。

python3 run.py terraform plan
PG_BAO_SPIFFE_ROLE=homelab-postgresql python3 run.py --spiffe ansible credentials.yml

--spiffe 从本机 Workload API 取得 JWT-SVID,交换短期 Bao token,执行后撤销;失败不回退到 旧 CLI token,不写 ~/.vault-token。专用 role 绑定 spiffe://ddupan.top/dev/panxiao81,只允许 所需签发路径、实例 KV 与 Ayatori 交付路径,既有 etcd 消费者只读;没有 PKI/auth 管理或 KV 删除权限。 Terraform provider 直接使用包装器的 token(skip_child_token),不另外创建子 token。

原始实例秘密位于 kv/infra/postgresql/{prod,dev};initialize-secrets.yml 仅在元数据明确不存在、 数据目录尚未初始化时 CAS=0 创建。403、软删除、密码缺失或漂移均不能触发隐式重置。 Ayatori 单独使用 kv/infra/postgresql/ayatori/{prod,dev},仅有 username/password,由 ayatori-credentials.yml CAS 创建及回读核对。未来 ESO 只读这两个路径,不读取完整实例秘密。 涉及秘密的任务 no_log,禁止对凭据 play 使用 diff。旧 Ansible Vault 未迁移。

部署与维护入口

  1. storage.yml / pve-storage.yml 准备专属卷和预算;preflight.yml 拒绝未知数据或错误挂载。
  2. packages.yml 预装软件,阻止 apt 自动初始化默认 main;只刷新 Ubuntu 与本项目 PGDG 源, 仍校验签名,不接管 laptop 无关第三方仓库故障。
  3. Terraform 准备 PKI 与身份,initialize-secrets.yml -e pg_allow_initialize=true 首次创建密码。
  4. site.yml 写入实例配置和证书;不初始化、不自动重启。backup.yml 建立专属仓库与 SSH 传输。
  5. bootstrap.yml -e pg_allow_initialize=true 显式初始化空目录,先 laptop 后 standby;未知数据不接管。
  6. bootstrap-backup.yml 验证 WAL 与首备成功后启用每日 timer;proxy.yml 要求唯一 primary 才发布入口。
  7. DNS 由 infrastructure/dns/records.yml 与 Samba provision-dc.yml --tags dns 管理。
  8. controller.yml 安装 PG 证书每日续签调度,预检通过才启用。
  9. Ayatori 独立控制面就绪后按 接入说明 注册;CNPG 迁移另行按应用验收。

普通 site.yml 不代表所有配置已在线激活;尚未实现通用参数变更的 rolling restart。 专用 renew-certificates.yml 只处理证书,60 天有效期、剩余不足 14 天续签;逐主机检查服务, 需要时签发并向 Patroni/PostgreSQL 主进程发送 HUP,最后检查 SQL。homelab-postgresql-renew.timer 每日 04:30 UTC 加随机延迟;日志由同名 service journal 查询。程序与 Ansible 副本位于 /opt/homelab-postgresql-controller,由 root 管理,以本机开发身份取短期 token。 续签依赖现有 SPIRE(在 k3s)和 Bao;数据库与 etcd 的正常运行仅用本地证书,不实时依赖 k3s。

备份与恢复

仓库主机发起备份,发现实际 primary;每天 03:40 UTC 加随机延迟执行 full,保留 3 个 full, 本地连续归档 WAL。WAL archive_timeout=300s,未设置超限丢弃 WAL 的选项。 生产两节点各持有独立 SSH key,仓库和数据库账号相互授权,固定 host key,关闭 SSH 自动追加 host keys(UpdateHostKeys=no),不允许 PTY/转发; 专用账号仍能运行所需远端命令。密钥在目标文件系统原地生成,避免 ext4 属性复制到 ZFS 失败。

查看仓库 homelab-postgresql-backup.timer/service 与 pgbackrest --config=/etc/homelab-pgbackrest/repository.conf --stanza=prod info/check。 首次实际 full 的 DB 22.7 MB、仓库备份集约 2.7 MB,仅代表当前空实例,不是未来业务容量估算。 独立的 dev 实例当前不备份。异地备份按维护者决定后置;本地 PVE 仓库不是异地或离线副本。

tests/live_restore.py --run 从真实仓库恢复到新临时目录,用私有 Unix socket 验证恢复实例可写与 Ayatori 角色,随后停库清理;不覆盖生产 PGDATA。全站恢复及接管旧 CNPG 不由此脚本自动执行。

监控与验收

现有 VictoriaMetrics/Alertmanager 已采集生产两节点 Patroni 原生指标,规则位于 platform/observability/metrics/{scrapes,rules}/shared-postgresql.yaml。覆盖成员不可采集、无可见主库、 多主、replica 未流复制及 PG 进程停止。没有部署另一套监控栈;数据库 SQL 级 exporter、dev 运行告警、 备份年龄与证书到期告警尚待补齐,不能宣称监控完整。

2026-09-25 已验证:

  • 真实 prod 主从、dev 初始化;Ayatori 管理账号 verify-full TLS/权限校验;生产稳定入口 SQL 写入。
  • 配置第二次执行两主机 changed=0;备份配置三主机与代理重跑均 changed=0;实例秘密及 Ayatori 独立凭据重复执行不变更。
  • 真实 SSH WAL 归档、首个 full、每日 timer;真实备份恢复到隔离实例并验证可写。
  • tests/live_failover.py --run 停止主库服务,自动提升/入口写入、旧主重加入、计划回切后复制、探针清理全部通过; 完整通过的一次恢复写入约 9.2 秒(首次观察 15.1 秒),是低负载演练结果,不是 SLA。
  • 隔离 Docker 测试使用实际模板,另覆盖时间点恢复、DCS 多数派丢失后拒绝写入及管理权限合同。
  • Terraform 最终 plan 无变更、Ansible lint、短期会话撤销测试与 Ayatori Kustomize 渲染通过。
  • PG 续签 systemd 首次运行 Result=success,生产两主机证书检查与 SQL 验收均 changed=0;尚未强制演练证书临期轮换。

新 HDD 卷同步限制为各 10 MiB/s,入口 limit-resync.yml;同步已完成,副本 UpToDate。 此前 CT150 根卷 DRBD quorum 抖动导致 emergency_ro,已离线修复;详见 etcd runbook。 限速后未再观察到同类故障,长期稳定性与唯一根因尚未确认。