这是本节的多页打印视图。 .
文档
-
1: 平台支持
-
2: 快速开始
-
3: 命名空间隔离
-
4: SQL 命令
-
5: 备份
-
6: 副本(只读备用)
-
7: 故障转移:副本提升
-
8: 管理
-
9: 克隆
- 10: 扩展
-
11: 数据导入/导出
-
12: 恢复
-
13: HA 集群
- 13.1: Patroni 高可用
- 13.2: Patroni 动态配置
- 13.3: Patroni REST API
- 13.4: HA 集群扩展安装
- 13.5: Patroni 集群备份
- 13.6: Patroni 集群恢复
- 13.7: HA Exec / psql
- 13.8: 示例:HA 集群 + 自签 CA 的 MinIO
- 13.9: 生成证书 —— pg cert
- 13.10: S3 存储高可用方案
-
14: 自签证书
-
15: 开机自启
-
16: 插件 (Addons)
- 16.1: PgBouncer
- 16.2: Etcd
- 16.3: PgDog
- 16.4: Garage
- 16.5: PostgREST
- 16.6: SeaweedFS
- 16.7: RustFS
- 16.8: HAProxy
- 16.9: Silo
- 16.10: MinIO
- 16.11: Redis
- 16.12: Predixy
- 16.13: pgAdmin 4
- 16.14: Nginx
- 16.15: Grafana
- 16.16: Prometheus
- 16.17: Alertmanager
- 16.18: Gitea
- 16.19: VictoriaMetrics
- 16.20: BeeGFS
- 16.21: VMCluster
- 16.22: JuiceFS
- 16.23: VictoriaLogs
- 16.24: vlagent
- 16.25: VLCluster
- 16.26: ZeroFS
- 16.27: DuckDB
- 16.28: Node_exporter
- 16.29: InfluxDB
- 16.30: Pg_exporter
- 16.31: influx2tsdb
- 16.32: ClickHouse
- 16.33: Kafka
- 16.34: RabbitMQ
- 16.35: RocketMQ
-
17: 健康巡检
-
18: 配置调优
-
19: KV 缓存
-
20: 工具箱
-
20.1: ZFS 存储管理
-
20.2: PGMQ 消息队列
-
20.3: SQLite + Litestream
-
20.4: TimescaleDB
-
20.1: ZFS 存储管理
-
21: 日志
-
22: 销毁
1 - 平台支持
pgcli 在 Linux 和 macOS 上都是驱动 Podman 容器运行的。 两者的根本区别在于容器如何获得网络:
- Linux 原生运行 Podman,容器共享宿主网络栈(
--network host),彼此通过127.0.0.1互访。这是零开销路径,也是唯一支持跨主机拓扑的路径。 - macOS 把 Podman 跑在
podman machine虚拟机里。此处的 host 网络绑定的是 虚拟机的回环,Mac 看不到;于是 pgcli 改为加入共享 bridge 网络 (pgcli-net)并发布各端口 —— Mac 通过 gvproxy 用127.0.0.1:<port>访问,容器之间则通过 bridge 上的容器名互访。这适用于单主机 (开发/测试),不是跨主机 HA 路径。
下表是当前支持矩阵。“macOS(单主机)“表示单机完全可用;那些必须向其它机器 广播可路由地址、或依赖 Linux 专属容器内部机制的功能,仍是 仅 Linux。
支持矩阵
| 组件 / 功能 | Linux | macOS |
|---|---|---|
实例生命周期 —— pg create / start / stop / restart / destroy / status |
✅ | ✅ 单主机 |
pg psql / pg exec |
✅ | ✅ |
SQL 命令参考(pg exec、管理查询) |
✅ | ✅ |
日志 —— pg logs |
✅ | ✅ |
命名空间隔离(pg.yaml 的 namespace) |
✅ | ✅ |
数据导入 / 导出 —— pg import / pg export |
✅ | ✅ |
克隆 —— pg clone(流式 pg_dump | pg_restore) |
✅ | ✅ |
扩展 —— pg extension install / list |
✅ | ✅ |
备份 / 恢复 —— pg backup / pg restore(pgBackRest) |
✅ | ✅ 单主机 |
物理副本 —— pg replica |
✅ | ✅ 单主机 |
故障切换 —— pg failover(副本提升) |
✅ | ✅ 单主机 |
开机自启 —— pg autostart |
✅ systemd user unit | ✅ launchd(登录后) |
| 插件 —— PgBouncer | ✅ 含跨主机连接池 | ✅ 单主机 dev/test |
| 插件 —— PgDog | ✅ | ✅ 单主机 dev/test |
| 插件 —— etcd | ✅ 含跨主机集群 | ❌ 仅 Linux |
| 插件 —— HAProxy | ✅ | ❌ 仅 Linux |
| 插件 —— MinIO | ✅ host 网络 | ✅ bridge,发布端口 |
| 插件 —— silo | ✅ host 网络 | ✅ bridge,发布端口 |
| 插件 —— rustfs | ✅ host 网络 | ⚠️ bridge 路径已实现但未实测 —— 暂按仅 Linux 对待 |
| 插件 —— Redis | ✅ host 网络 | ⚠️ bridge 路径已实现但未实测 —— 暂按仅 Linux 对待 |
| 插件 —— Predixy | ✅ host 网络(单机与跨机集群皆已验证) | ❌ 仅 Linux |
| 插件 —— Garage | ✅ host 网络 | ⚠️ bridge 路径已实现但未实测 —— 暂按仅 Linux 对待 |
| 插件 —— SeaweedFS | ✅ host 网络 | ⚠️ bridge 路径已实现但未实测 —— 暂按仅 Linux 对待 |
| 插件 —— pgAdmin 4 | ✅ host 网络 | ⚠️ bridge 路径已实现但未实测 —— 暂按仅 Linux 对待 |
| 插件 —— Grafana | ✅ host 网络 | ⚠️ bridge 路径已实现但未实测 —— 暂按仅 Linux 对待 |
| 插件 —— Prometheus | ✅ host 网络 | ⚠️ bridge 路径已实现但未实测 —— 暂按仅 Linux 对待 |
| 插件 —— Alertmanager | ✅ host 网络 | ⚠️ bridge 路径已实现但未实测 —— 暂按仅 Linux 对待 |
| 插件 —— Gitea | ✅ host 网络 | ⚠️ bridge 路径已实现但未实测 —— 暂按仅 Linux 对待 |
| 插件 —— nginx | ✅ host 网络 | ✅ bridge,发布端口(轻度实测) |
客户端 —— pg mc(MinIO 客户端) |
✅ host 网络 | ✅ bridge(见下) |
高可用 —— Patroni(pg ha) |
✅ 含跨主机 | ❌ 仅 Linux |
图例:✅ 支持 · ✅ 备注 支持但有所述限制 · ⚠️ 该平台上已实现但未实测 · ❌ 不支持。
实例类功能
所有构建在受管 PostgreSQL 实例之上的功能在两个平台都可用 —— 生命周期命令、
pg psql / pg exec、日志、命名空间、导入/导出、克隆、扩展、备份/恢复
(pgBackRest)、物理副本、故障切换。macOS 的 bridge 路径(发布端口 + 容器名
DNS)最早由实例、副本/备份路径验证;代理插件复用的正是这一套。
macOS 上这些都是单主机:跨主机副本、跨多机的连接池属于 Linux 的
--network host + LAN 地址能力。
开机自启
两个平台都支持,但机制不同:
- Linux —— systemd 的 user unit。
- macOS —— launchd 的 LaunchAgent。LaunchAgent 在用户登录时运行,而 非系统启动时 —— 因为 rootless podman 无法在有人登录前启动。请预期容器在你 登录后才拉起。见开机自启。
插件
各组件的网络模型不同:
- PgBouncer 与 PgDog ——
纯代理,macOS 上支持单主机 dev/test。macOS 下它们加入
pgcli-net并发布端 口;客户端连接仍是127.0.0.1:<port>。后端地址的注意事项(remote/后端主机 必须是从 Mac 可达的地址,而不是127.0.0.1)见各自页面的"平台支持"一 节。 - etcd —— 仅 Linux。成员以 host 网络运行,并把 client/peer URL 作为永久的集群状态写进 raft 成员列表;要移植 macOS 的 bridge + 容器名模型就得重写这份状态,在完成该设计之前保持仅 Linux。
- Patroni(
pg ha)—— 仅 Linux。成员依赖 podman 的 host 网络;root 和 rootless podman 均可使用。podman machine虚拟机的 uid 映射与 host 网络模型不吻合。这些命令在 macOS 上会快速失 败并给出清晰提示。见 Patroni 高可用。 - HAProxy —— 仅 Linux。它通过主机网络代理 Patroni 成员,而 podman machine 虚拟机不提供该能力;manager 在 macOS 上快速 失败。
- MinIO —— 两个平台都支持。对象存储,单机或跨
主机分布式纠删码集群皆可。Linux 上经主机网络提供服务;macOS 上加入
pgcli-net并发布 API 与控制台端 口,Mac 通过127.0.0.1:<port>即可访问(与代理插件同一条路径)。公开镜像 为双架构(amd64 + arm64)。 - silo —— 两个平台都支持。Pigsty 的 MinIO 分支,
网络模型与 MinIO 完全一致:Linux 走 host 网络,macOS 加入
pgcli-net并发布 端口。公开镜像为双架构(amd64 + arm64)。 - rustfs —— 实际上仅 Linux。pgcli 运行一个定制
wrapper 镜像(
ghcr.io/mars-base/pgcli/pgcli-rustfs),其 entrypoint 在容器 内部先把 bind 挂载的数据/证书目录 chown 成 rustfs 固定的 uid 10001,再降权到 非特权用户。wrapper 镜像为双架构构建,Go manager 也有与 MinIO/silo 相同的 macOS bridge 路径(发布端口),故 macOS 预期可用 —— 但本插件的 e2e 覆盖只在 Linux 上跑过,在 Mac 上实测之前请按仅 Linux 对待。 - Redis —— Linux 已实测;macOS 预期可用。纯上游
docker.io/library/redis镜像,没有 wrapper、也没有固定容器 uid,因此不需要 rustfs 那套属主机制。Linux 上走 host 网络(端口按listen落在全部网卡或仅 回环);macOS 上加入pgcli-net并发布端口,pg redis-cli拨host.containers.internal—— 与对象存储相同的 bridge 路径,代码完备但尚未在 Mac 上跑过,e2e 覆盖目前仅限 Linux。 - Predixy —— 仅 Linux。 与 HAProxy 一样,它需要
host 网络才能拨通后端的集群节点(单机与跨机集群都已验证)。wrapper 镜像只有
amd64 —— 上游只发 amd64 二进制 —— 因此 arm64 主机需要
--platform linux/amd64模拟运行,而 macOS 的 podman machine 路径根本不存在。 - pgAdmin 4 —— Linux 已实测;macOS 预期可用。纯
上游
docker.io/dpage/pgadmin4镜像,没有 wrapper —— 镜像自己的 entrypoint 把/var/lib/pgadminchown 给 uid 5050 并降权,所以 pgcli 传--user 0、从不在 宿主上 chown(与 rustfs 不同)。Linux 上走 host 网络,端口取自pgadmin_start_port(默认仅回环);macOS 上加入pgcli-net并发布端口、listen放宽到0.0.0.0—— 与 redis/对象存储相同的 bridge 路径,代码完备但 尚未在 Mac 上跑过,e2e 覆盖目前仅限 Linux。 - Grafana —— Linux 已实测;macOS 预期可用。纯上
游
docker.io/grafana/grafana镜像,没有 wrapper。镜像固化USER 472,且 entrypoint 完全不做任何属主管理,所以 pgcli 在创建容器前自己把宿主数据目录 chown 成472:0—— rootless podman 下经podman unshare chown,pgcli 以 root 跑在 rootful 宿主上则直接递归chown。Linux 上走 host 网络,端口取自grafana_start_port(默认开放全部网 卡);macOS 上加入pgcli-net并发布端口、listen放宽到0.0.0.0—— 与 pgAdmin/redis 相同的 bridge 路径,代码完备但尚未在 Mac 上跑过,e2e 覆盖目前 仅限 Linux。 - Prometheus —— Linux 已实测;macOS 预期可用。
纯上游
docker.io/prom/prometheus镜像,没有 wrapper,属主形态与 Grafana 一致(固化USER nobody,uid/gid65534,entrypoint 不做任何属主管理)—— pgcli 在创建容器前先把宿主 TSDB 目录 chown 过去:rootless podman 下经podman unshare chown 65534:65534,pgcli 以 root 跑在 rootful 宿主上则直接 递归chown。与 Grafana 不同,它靠一份 bind mount 的prometheus.yml(渲染 而来,或从--conf-file拷贝)加镜像名之后的 CLI flag 配置,而非环境变量。 Linux 上走 host 网络,端口取自prometheus_start_port(默认开放全部网卡)—— 且该默认背后没有任何鉴权:不像 Grafana 的0.0.0.0有密码兜底,这里的 暴露面只靠网络边界。macOS 上加入pgcli-net并发布端口 —— 与 Grafana 相同的 bridge 路径,代码完备但尚未在 Mac 上跑过,e2e 覆盖目前仅限 Linux。 - Alertmanager —— Linux;macOS 代码完备、未
实测。Prometheus 的下游告警路由端,也是它的近乎完全同构体:纯上游
docker.io/prom/alertmanager镜像,属主形态一致(固化USER nobody,uid/gid65534,entrypoint 不做属主管理——pgcli 预 chown 宿主状态目录),靠一份 bind mount 的alertmanager.yml(渲染而来,或从--conf-file拷贝)加镜像名之后的 CLI flag 配置。两个 Alertmanager 特有的事实:--cluster.listen-address=始终以 空串传入,所以单实例永不开启 HA gossip 端口(TCP+UDP9094);而与 Prometheus 不同,镜像里那条无鉴权的POST /-/reload路由无法关闭——pgcli 用 SIGHUP 重载、从而永远不依赖那条路由,但路由本身照样敞开。这叠在一个比 Prometheus 更 尖锐的暴露面上:Listen默认0.0.0.0且完全无鉴权,能连到端口即可创建 silence——悄悄压掉 oncall 期待的每一条通知。macOS 上加入pgcli-net并发布 端口 —— 同一条 bridge 路径,代码完备但尚未在 Mac 上跑过。 - Gitea —— Linux;macOS 代码完备、未实测。自托管
Git 服务,跑纯上游
docker.io/gitea/gitea镜像(rootful 构建——rootless 变体的 数据布局互不兼容,pgcli 永不选它)。属主形态与 Grafana 同构只是数字不同:守护 进程 su-exec 降到固化的git用户,uid/gid1000(经USER_UID/USER_GID钉死,宿主预 chown 无从漂移),不带--userflag;pgcli 同样预 chown 宿主/data目录。配置只经环境变量(GITEA__*每次启动折叠进持久化的app.ini)——这也正是SECRET_KEY/INTERNAL_TOKEN绝不注入的原因:重新生成的 secret 会在重装时静默重设幸存数据库的密钥。安装免向导(INSTALL_LOCK),admin 随后经容器内 CLI 创建。单端口惯例之外有两张可选面孔:--dsn把元数据搬到外置 PostgreSQL(pgcli 从不建库也从不删库);git-over-ssh 跑在镜像自带的 OpenSSHsshd上,而这个 sshd 在每个实例上都无条件在跑(s6 服务关不掉)。--ssh-port N让它变成 N 端口上的活克隆端点;默认关时它只是 parked 在池端口上 ——但无论如何都得给它 park 个地方:宿主网络下若留在 22 就会永远跟宿主 sshd 死磕。Listen默认0.0.0.0(Git 服务天然要给别的机器克隆),由自动生成的 admin 密码与关闭自助 注册兜底。macOS 上加入pgcli-net并发布 Web 端口、以及启用 SSH 时的 SSH 端口;代码完备但尚未在 Mac 上跑过。 - BeeGFS —— 仅 Linux;macOS 上直接拒绝安装。
首个多容器插件:一条配置 = 一个单机收敛文件系统——三个容器(
mgmtd+meta+storage)按依赖顺序启动、以集群为单位管理。它是唯一没有 bridge 路径、也没有 bridge 兜底的插件:BeeGFS 说的是自家 TCP/UDP 协议(不是 HTTP),守护进程跑--network host、直接绑协议原生端口——而 8.4 的 meta(8005)与 storage(8003)根本没有监听口覆盖手段,所以每台宿主只容得下 一个文件系统(守卫拦截,而非端口池)。macOS 没有可 bridge 过去的等价形态: 没有 host 网络就没有正确的 BeeGFS 拓扑,所以 Mac 上pg addon install beegfs快速失败而不是半可用。属主形态与 Gitea/Grafana 相反:镜像以 root 运行并 自属 bind 挂载目录,pgcli 从不 chown 任何东西。客户端侧是内核模块(DKMS), 无法容器化——pgcli 刻意从不安装、从不挂载,改用pg addon beegfs mount-hint输出逐字的客户端配置。 - nginx —— 两个平台都支持(macOS 轻度实测)。HTTP
反向代理,Linux 上走 host 网络;macOS 上加入
pgcli-net并发布端口,与对象 存储同一条 bridge 路径 —— 已跑通过但未经负载考验。上游镜像多架构 (amd64 + arm64)。 pg mc(MinIO 客户端)—— 两个平台都支持。它在一次性容器里运行mc:宿主的~/.mc/config.json按其原生默认路径挂载,cp/mirror/diff的本地文件参数也会按真实路径动态 挂载 —— macOS 上要求位于家目录之下(podman machine 只共享家目录)。Linux 上走 host 网络,127.0.0.1别名可直达本机 addon 实例;macOS 上容器在 bridge 网络里,127.0.0.1是容器自己的回环 —— 本机 addon 请用host.containers.internal:<port>,远端存储用可路由地址。
确认当前平台
pg start 会在横幅里打印检测到的平台,便于确认当前走的是哪条路径:
2 - 快速开始
几分钟内让 pgcli 运行起来。
安装
使用单条命令安装 pgcli:
此脚本将:
- 为你的平台下载最新的 pgcli 二进制文件
- 安装到
/usr/local/bin(如果没有 sudo 则安装到~/.local/bin) - 将 pgcli 添加到你的 PATH
初始化配置
这会在 ~/.pgcli/pg.yaml 创建带有合理默认值的配置,包括:
- 名为
default的默认实例 - 数据目录
~/pg/default - 从 35432 开始自动分配的端口
--backup-dir 可选但生产环境推荐:它将备份数据和日志放在与实例数据不同的
路径上,防止数据盘故障连累备份丢失。
启动实例
输出会显示连接 URL、管理员密码和备份状态。
连接到数据库
基本操作
多实例管理
多配置文件(隔离环境)
为同一主机上的隔离测试环境或每个项目一个配置,
使用不同的 --namespace 和不重叠的端口范围生成独立的配置文件。
| 参数 | 默认值 | 含义 |
|---|---|---|
--namespace |
default |
容器名称前缀:pgcli-pg-<namespace>-<instance> |
--base-dir |
~/.pgcli |
所有数据路径的基础目录 |
--backup-dir |
(在 --base-dir 下) |
备份数据/日志的独立基础目录 |
--pg-start-port |
35432 |
第一个 PG 主机端口;实例按顺序分配 |
--pg-ssh-port |
42201 |
第一个 SSH 主机端口;按顺序分配 |
规划建议:
- 始终使用显式的
--namespace以避免容器名称冲突。 - 同一主机上的端口范围不能重叠。
- 命名空间在创建时固化到容器名称中。 后期更改需要
pg destroy后重新初始化。
交互式 psql 会话
在交互式 psql 中你可以使用:
- 带历史记录和 tab 补全的 SQL 查询
- psql 元命令(
\dt、\du、\l等) \q退出
容器 Shell
Shell 以 root 身份在容器内运行,可以完全访问:
- PostgreSQL 数据目录(
/var/lib/postgresql/data) - 配置文件
- 日志文件(
/var/log/postgresql/) - 所有系统工具和实用程序
下一步
3 - 命名空间隔离
命名空间允许你在单台主机上创建完全隔离的环境,非常适合将生产、开发和测试环境分开,避免容器名称冲突或端口冲突。
什么是命名空间?
命名空间是应用于配置文件中所有容器名称的前缀。这种隔离机制确保:
- 容器名称不冲突:每个命名空间有自己的容器前缀
- 端口范围分离:每个配置文件从自己的范围分配端口
- 备份容器隔离:每个命名空间有自己的 pgBackRest 容器
- 配置文件独立:每个环境使用单独的配置文件
使用场景:生产和开发环境
一个常见场景是在同一台服务器上运行生产和开发环境:
这会创建两个完全隔离的环境:
| 环境 | 配置文件 | 容器前缀 | PG 端口范围 | SSH 端口范围 |
|---|---|---|---|---|
| 生产 | ~/.pgcli-prod/pg.yaml |
pgcli-pg-prod-* |
35432+ | 42201+ |
| 开发 | ~/.pgcli-dev/pg.yaml |
pgcli-pg-dev-* |
38000+ | 43000+ |
管理多个环境
使用 -c 标志指定要使用的配置文件:
工作原理
容器命名
命名空间为 prod,实例为 app-db:
- 实例容器:
pgcli-pg-prod-app-db - 备份容器:
pgcli-backup-prod - 网络:
pgcli-net-prod(如果使用独立网络)
没有命名空间(或 --namespace ""):
- 实例容器:
pgcli-pg-default-app-db - 备份容器:
pgcli-backup-default
端口分配
配置文件中的每个实例获得顺序端口:
- 第一个实例:
pg_start_port(例如 35432) - 第二个实例:
pg_start_port + 1(例如 35433) - 依此类推…
SSH 端口同理。
配置持久化
命名空间和端口范围保存在配置文件中:
最佳实践
1. 始终使用显式命名空间
在一台主机上运行多个配置时,永远不要依赖默认命名空间:
2. 使用不重叠的端口范围
确保配置之间的端口范围不重叠:
为每个环境中的多个实例预留足够的端口空间。
3. 命名空间在创建时固化
命名空间在创建时嵌入容器名称。之后更改会破坏关联:
4. 使用 Shell 别名简化操作
创建别名避免重复输入 -c:
高级用法:带副本的多环境
你甚至可以设置隔离的复制环境:
每个环境维护自己的复制槽、备份 stanza 和数据目录。
故障排除
容器名称冲突
原因:两个配置使用了相同的命名空间。
解决:使用不同的命名空间,或先销毁冲突的实例。
端口已被占用
原因:配置之间的端口范围重叠。
解决:使用不重叠的端口范围,并预留足够间距。
更改命名空间后找不到实例
原因:创建实例后更改了配置文件中的命名空间。
解决:销毁并重建实例,或恢复命名空间更改。
总结
命名空间为单台主机上的多环境提供完全隔离:
- 独立配置:每个环境有自己的
pg.yaml - 不同命名空间:防止容器名称冲突
- 不重叠端口:避免端口冲突
- 独立操作:每个环境使用
-c单独管理
非常适合在同一台服务器上运行生产、开发、测试和预发布环境,互不干扰。
4 - SQL 命令
两种对实例运行 SQL 的方式:pg exec 用于一次性 SQL 或容器命令,pg psql 用于交互式会话,pg shell 用于在容器内打开交互式 bash shell。
pg exec
SQL 模式(默认)
没有 -- 的参数作为 SQL 通过 psql 执行,使用实例配置的用户和数据库。
容器命令模式(– 之后)
-- 之后的参数直接在容器内运行(以 root 身份)。
远程数据库(–dsn)
通过连接字符串对任何可达数据库执行 SQL,使用临时容器。--dsn 仅支持 SQL 模式;容器命令需要本地实例。
pg psql
交互式会话
在 shell 内你获得完整的 psql 功能:带历史记录和 tab 补全的 SQL、元命令(\dt、\du、\l),以及 \q 退出。
非交互式(脚本)
切换到 postgres 超级用户
某些管理任务(例如创建某些扩展、修改系统级设置)需要 postgres 超级用户权限。使用 -- 传递 psql 参数并切换用户:
推荐方法:日常操作使用实例默认用户(admin),仅在需要超级用户权限时切换到 postgres。这比修改配置文件或重启容器更安全更方便。
示例场景:
远程数据库(–dsn)
pg shell
在 PostgreSQL 容器内打开交互式 bash shell。
使用场景:
- 检查 PostgreSQL 数据目录和日志文件
- 运行容器内部命令(如
psql、pg_isready、pg_repack) - 调试扩展安装和配置文件
规则
--dsn和--instance互斥:连接字符串确定主机、端口和数据库,因此-i被拒绝以避免静默误用。- 使用
--dsn时,数据库是 URL 的路径部分:postgres://user:pass@host:5432/mydb连接到mydb。要使用另一个数据库,更改路径。 - 使用本地实例时,
--传递原始 psql 参数(包括-d/-U),覆盖实例默认值。
5 - 备份
快照
快照类型:
full— 完整备份(默认,自包含)diff— 自上次完整备份以来的更改incr— 自上次备份以来的更改
共享备份容器
所有实例共享单个 pgbackrest 容器;每个实例在存储库中都有自己的 stanza。
一份配置一个仓库。 这个容器只服务一个仓库——
data_dir下的本地目 录,或配置了backup.repo.s3时那唯一的一个 S3 端点——环境里的每个 stanza (普通实例与 Patroni 集群一视同仁)都推给它。不存在按实例或按集群选仓库: 于是一个 pgcli 环境就只备份到一个 store。若想把第二个 store(比如再pg addon install minio一个)只喂给部分集群,就跑第二份配置 (pg config init --namespace …)、配上它自己的备份容器——完整走法见 示例:HA 集群 + 自签 CA 的 MinIO。
备份基础设施(网络、镜像、目录、配置、容器)在 pg start 时自动准备;手动运行 pg backup setup 重新初始化,例如更改基础目录之后。
S3 对象存储仓库
Patroni 集群的备份与 WAL 归档可以推送到任意 S3 兼容存储(MinIO、silo、rustfs、
garage、AWS S3……)。配置写在 pg.yaml 的 backup.repo.s3 下:
然后(重)跑 setup,它会自动完成归档编排:
- HTTPS 是硬性要求。 pgBackRest 拒绝明文 S3(上游明确不实现),端点必须
提供 TLS。pgcli 自带的 MinIO 用
pg addon install minio --tls开启原生 HTTPS —— pgcli 会生成自签 CA,并把ca.crt路径填进上面的ca_file。 外部端点(真实 AWS S3 等公签证书)留空ca_file即可;实在无法提供 CA 时 可用verify_tls: false逃生(不做证书校验)。 - 远端存储主机取 CA——无需 scp。 MinIO 在另一台机器时,用一次 TLS 握手就
能取回信任锚,不必拷贝文件:
pg backup fetch-ca <存储主机>:9002从端点下发 的证书里取出信任锚,存到<base-dir>/backup/repo-ca/下并打印 SHA-256 指纹——这本质是"首次使用即信任"(TOFU),所以信任前先把指纹与存储主机对拍 一次,再把文件交给pg backup setup --s3-ca-file <路径>。它覆盖 pgcli 会提供 的两种 TLS 形态:开--tls的 MinIO(下发的是生成好的"叶+CA"链,取出的是 签名根);以及喂了pg cert自签叶证书的 MinIO(叶本身就是信任锚,原样保存 即可)。公共 CA 签发的端点根本不需要ca_file,此命令对它们没有意义。 - 跨机 HA 免手工分发。 Patroni 集群分散在多台主机时,
ca_file与备份 SSH 公钥只需在一台主机配好:pg backup setup把 CA 发布进集群的 etcd 注册表,ca_file留空的加入者自动拉取;各主机的备份公钥在pg ha create时发布, 由setup合并进每个成员的 authorized_keys。注册表里只放公开材料(CA 证书、SSH 公钥),绝不含私钥或 S3 secret_key。详见 HA 文档"备份跨机 leader"一节。 - 归档自动化。 setup 检测到 Patroni 成员的归档配置过期时,会 pause 集群、
按"副本先、leader 后"重建成员容器(recreate 即重启,
archive_mode是 postmaster 级参数)、resume。每个成员的 patroni.yml 会注入同一份archive_command(每个集群一个 stanza,成员互为pg*-host,pgBackRest 自动定位 primary),WAL 从此持续 push 到 S3。 - 作用范围。 S3 仓库只作用于 Patroni 集群的 stanza;
pg直接管理的普通 实例备份仍走本地仓库,互不影响。
等价的一次性 flag 写法(secret_key 建议手编 pg.yaml,避免落入 shell 历史):
6 - 副本(只读备用)
创建现有实例的只读物理副本。副本通过 PostgreSQL 物理复制从其主实例持续流式传输 WAL,并提供只读查询服务——适用于读写分离、报告或作为热备。
发生了什么
- 预检 — 主实例必须正在运行(在任何配置写入之前验证;停止的主实例会失败但没有副作用)
- 注册 — 添加新实例条目,使用主实例的数据库名称和密码(参见注释),禁用 PITR,
replica_of设置为主实例 - 复制设置 — 在主实例上:
pg_hba.conf为回环地址和 RFC1918 范围添加host replication条目(幂等)- 创建物理复制槽
pgcli_r_<name>,保留 WAL 以确保副本不会落后于 WAL 回收
- 基础备份 —
pg_basebackup -R将主实例的数据目录复制到副本的数据目录,写入primary_conninfo(带密码)和standby.signal,使副本以备用模式启动 - 启动 — 副本容器启动并持续流式传输 WAL
验证
销毁
销毁副本是两步操作:
步骤 1 必须在步骤 2 之前运行:PostgreSQL 拒绝删除仍在流式传输的槽(replication slot is active),因此必须先销毁副本以关闭其流式连接。
注意: 如果主实例是本地 pgcli 管理的实例(同一主机),可以跳过步骤 2 —
destroy会自动清理槽。当从副本主机无法访问主实例时需要步骤 2。
跨网络副本
上述同主机流程假设主实例和副本共享一个服务器(一个 podman 守护进程,一个网络)。对于在另一台主机上的副本,pgcli 在每一侧运行一个命令——无需 SSH,唯一的跨机器信息就是你作为参数传递的内容:
获取主实例 DSN
如果主实例是 pgcli 管理的实例,使用 pg status 获取其连接信息:
然后将 127.0.0.1 替换为从副本主机看到的主实例主机 IP(例如 10.0.0.20)——用户、密码和数据库按原样使用。注意主实例主机必须接受从副本主机到该端口的 TCP 连接(防火墙/安全组)。
每一侧的作用:
- 主实例侧(
--replica-host <ip|hostname>):仅准备主实例——本地不创建任何内容。它将host replication all <addr>条目追加到pg_hba.conf并创建物理槽pgcli_r_<name>,然后打印要在副本主机上运行的确切--primary-dsn命令。IP 地址获得/32(IPv6 为/128)掩码;主机名按原样写入。已在托管 RFC1918 范围(10.0.0.0/8、172.16.0.0/12、192.168.0.0/16)内的 IP 会被跳过去重。幂等——重新运行不会添加重复行。 - 副本侧(
--primary-dsn):首先验证主实例上存在槽(验证连接性和顺序——在主实例侧之前运行会失败并带有可操作的错误消息且没有副作用),然后注册实例,通过网络从 DSN 运行pg_basebackup(主机网络),并启动备用实例。
副本侧仅在副本主机上运行:副本实例的用户、数据库和密码来自 DSN(物理复制复制 pg_authid,因此本地密码必须等于主实例的密码)。主实例名称使用 --primary-name 给出并记录为 replica_of;它不必存在于副本主机的配置中,-i 不用于远程主实例——如果给出,它保持其严格含义并且必须引用真实的本地实例。
销毁是对称的,每个主机一个命令——按此顺序:
步骤 1 必须在步骤 2 之前运行:PostgreSQL 拒绝删除仍在流式传输的槽(replication slot is active),因此必须先销毁副本以关闭其流式连接。
自动槽清理: 当跨主机副本设置了 PrimaryDSN 时,destroy 会自动尝试通过 DSN 删除远程主实例上的复制槽。如果主实例可达,则无需手动步骤 2。replica drop 是幂等的——当槽已不存在时重新运行会作为无操作成功。
非 pgcli 主实例
主实例不必由 pgcli 管理——只要主实例侧已手动准备(槽检查仅验证槽是否存在,不验证谁创建了它),副本侧可以对任何 PostgreSQL 服务器工作:
- 在
pg_hba.conf中允许从副本主机复制,然后重新加载(SELECT pg_reload_conf()): - 使用确切名称
pgcli_r_<replica-name>创建物理槽(副本侧检查此名称):需要wal_level = replica(或logical)和具有REPLICATION权限的用户——DSN 用户。
然后副本侧命令不变:
销毁时,主实例侧没有 pgcli——在 pg destroy -i ro1 之后,手动删除槽:
如果基础备份失败(例如网络中断),销毁副本并重新运行副本侧命令——主实例侧的槽和 hba 条目保持有效。
注释
- 只读 — 副本拒绝所有写入(
cannot execute INSERT in a read-only transaction)。要使其可写你需要提升它(pg_ctl promote),这尚未作为 pgcli 命令暴露 - 相同数据,相同密码 — 物理复制是主实例的逐字节复制,包括
pg_authid。因此副本的管理员密码和数据库名称与主实例相同;只有容器名称、端口和数据目录不同。使用--dsn风格的连接时使用副本的端口 - 副本上禁用 PITR — 备用实例不归档任何内容,也不在 pgBackRest 备份容器中注册;备份在主实例上运行
- 主实例必须正在运行 — 初始创建(
pg_basebackup)和持续流式传输都需要;如果主实例重启,副本会自动重新连接(槽显示为active) - 延迟显示 —
replica list延迟(now() - pg_last_xact_replay_timestamp())在主实例空闲时会增长;它会在下一个复制的事务时降回零。这是预期的空闲行为,不是漂移 - 幂等启动 — 重复的
pg start -i ro1在数据目录已初始化时跳过基础备份
7 - 故障转移:副本提升
当当前主实例失败时,将副本提升为新的主实例。pgcli 提供 3 步手动故障转移工作流——每个步骤在其各自的主机上运行,不自动检测同主机与跨主机拓扑。
概述
故障转移后(提升 ro1 → 新主实例):
3 步故障转移
每个步骤都是独立的命令。按顺序在各自的主机上运行它们。
步骤 1:pg replica promote <name>
在被提升为主实例的副本主机上运行。
发生了什么:
- 验证实例是副本(
ReplicaOf已设置)且容器正在运行 - 调用
pg_promote()(PostgreSQL 12+ 原生提升——无需容器重启) - 等待恢复结束(通常亚秒级)
- 通过
ALTER SYSTEM RESET从postgresql.auto.conf清理primary_conninfo - 更新配置:清除
ReplicaOf和PrimaryDSN,启用PITR - 自动初始化 PITR:
- pgBackRest stanza 创建
archive_mode/archive_command配置- PostgreSQL 重启以应用 postmaster 级别参数
- 打印下一步指令
幂等: 如果副本已被提升(例如通过手动 pg_ctl promote),命令会跳到配置更新。
步骤 2:pg replica drop <name> -i <old-primary>
在旧主实例主机上运行以清理复制槽。此步骤仅在特定场景中需要。
这会在旧主实例上删除物理复制槽 pgcli_r_ro1。没有清理,槽会无限期保留 WAL,直到主实例磁盘空间耗尽。
何时运行:
| 场景 | 操作 | 原因 |
|---|---|---|
| 旧主实例永久丢失 | 跳过 | 槽随服务器一起消失 |
| 计划将旧主实例降级为副本 | 跳过 | repoint 销毁数据目录(包括 pg_replslot/),所有槽被隐式移除 |
| 旧主实例已恢复,继续作为独立主实例运行 | 必须运行 | 槽无限期保留 WAL;没有清理磁盘最终会填满 |
| 旧主实例已恢复但将被关闭 | 可选 | 如果实例不再运行,跳过无害 |
保持旧主实例不变? 如果你想保留旧主实例及其原始数据(例如用于取证分析或作为只读归档),你可以简单地不理会它——不要在其上运行
drop或repoint。旧主实例继续作为具有陈旧数据的独立实例运行。只是要注意被提升副本的复制槽仍然存在并会累积 WAL;你可能想要仅删除那个特定的槽(pg replica drop ro1 -i pg01)同时保持其他一切不变。
步骤 3:pg replica repoint <name> --primary-dsn <dsn> --primary-name <name>
在每个剩余副本主机上运行以将其重新指向新主实例。
发生了什么:
- 通过 DSN 查询新主实例的扩展(
pg_extension目录) - 如果存在非内置扩展(例如 pg_cron、timescaledb),构建本地
-ext镜像并匹配包 - 停止旧副本容器并销毁其数据目录
- 通过 DSN 在新主实例上创建复制槽
- 更新配置:
ReplicaOf、PrimaryDSN、ImageTag、Extensions,禁用PITR - 通过
pg_basebackup -R从新主实例重新初始化 - 以备用模式启动副本容器
为什么销毁 + 重建而不是 ALTER SYSTEM SET?
提升后,新主实例进入新时间线。旧时间线上的其他副本不能简单地更改 primary_conninfo——PostgreSQL 会拒绝连接:
唯一安全的方法是从新主实例进行完整的 pg_basebackup。
获取主实例 DSN
从被提升副本主机上的 pg status 获取新主实例的连接字符串:
将 127.0.0.1 替换为从副本主机可达的新主实例主机 IP(例如 10.0.0.11)。
降级旧主实例
当旧主实例恢复时,你可以使用相同的 repoint 命令将其作为新主实例的副本重新加入:
即使 pg01 曾是主实例(未设置 ReplicaOf)这也有效。该命令:
- 停止 pg01 并销毁其数据(包括旧的 PITR stanza)
- 在新主实例上为 pg01 创建复制槽
- 设置
ReplicaOf = "ro1",PITR.Enabled = false - 通过
pg_basebackup从新主实例重新初始化
重新指向后,pg01 作为只读副本从新主实例流式传输 WAL——没有 WAL 归档,没有备份。
扩展同步
当副本被重新指向新主实例时,pgcli 会自动同步扩展:
- 查询 — 通过 DSN 连接到新主实例并查询
pg_extension获取已安装的扩展 - 过滤 — 识别非内置扩展(需要外部包的扩展,例如 pg_cron、pgmq、timescaledb)
- 构建 — 如果存在非内置扩展,构建本地
-ext镜像:- 如果本地
-ext镜像已存在,在其上安装缺失的包(重用 Pigsty 仓库——快速) - 如果没有
-ext镜像,从基础镜像构建并设置 Pigsty 仓库 apt-get install是幂等的——安装已存在的包是无操作
- 如果本地
- 应用 — 副本启动时,
ApplyExtensions将shared_preload_libraries写入postgresql.conf - 跳过 CREATE EXTENSION — 副本是只读的;扩展通过
pg_basebackup+ WAL 流式传输从主实例复制
这确保副本容器具有 postgresql.auto.conf 中引用的所需共享库(例如 pg_cron)。
同主机 vs 跨主机
pgcli 不自动检测拓扑。你选择在每个命令运行的位置:
| 场景 | 步骤 1 | 步骤 2 | 步骤 3 |
|---|---|---|---|
| 全部在同一主机 | pg replica promote ro1 |
pg replica drop ro1 -i pg01 |
pg replica repoint ro2 --primary-dsn "postgres://...@127.0.0.1:..." --primary-name ro1 |
| 主实例 + 副本分散在多个主机 | 在副本主机 | 在旧主实例主机 | 在每个副本主机上使用新主实例的网络 IP |
| 混合 | 在各自的主机 | 在旧主实例主机 | 在每个副本主机 |
--primary-dsn 必须使用从 repoint 运行所在主机可达的 IP/主机名。
完整示例
跨主机示例
级联复制
副本本身可以作为下游副本的主实例,形成级联链。这减少主实例的负载并启用分层拓扑。
工作原理
-
创建副本的副本:使用副本作为
-i目标 -
WAL 传播:
- ra2 从 ra3 流式传输 WAL
- ra2_ro1 从 ra2 流式传输 WAL
- 数据流:ra3 → ra2 → ra2_ro1
-
复制槽:每个链接维护自己的槽
- ra3 有槽
pgcli_r_ra2 - ra2 有槽
pgcli_r_ra2_ro1
- ra3 有槽
优势
- 减少主实例负载:只有直接副本连接到主实例
- 地理分布:主实例 → 区域副本 → 本地副本
- 网络效率:本地副本可以共享区域上游
限制
- 增加延迟:每一跳增加复制延迟
- 级联故障:如果 ra2 失败,ra2_ro1 失去其上游
- 提升复杂性:提升 ra2_ro1 需要将其重新指向新主实例
验证级联
级联故障转移
如果 ra2(中间节点)失败:
如果 ra3(主实例)失败且 ra2 被提升:
注释
- pg_promote() — PostgreSQL 12+ 原生函数,无需容器重启。实例就地退出恢复并立即变为可读写
- 时间线分歧 — 提升后,新主实例在新时间线上。其他副本不能用
ALTER SYSTEM SET primary_conninfo重新指向——它们必须通过pg_basebackup重建 - 被提升副本上的 PITR — 提升后,pgBackRest stanza 和 WAL 归档自动初始化(无需手动执行
pg start)。被提升的副本没有先前的备份历史 - 复制槽 — 旧主实例为被提升副本的槽在提升后变得陈旧。
pg replica drop清理它。如果旧主实例被降级为副本,repoint销毁旧数据且陈旧的槽不再被引用 - 扩展 — 副本容器通过
postgresql.auto.conf从主实例继承shared_preload_libraries。repoint 命令确保本地镜像在重建副本之前具有所需的扩展包 - 跳过 CREATE EXTENSION — 副本是只读的;
pg_basebackup从主实例复制扩展元数据,因此不需要CREATE EXTENSION(且会失败 “cannot execute CREATE EXTENSION in a read-only transaction”)
8 - 管理
Shell 补全
为命令、标志和实例名称启用 tab 补全。
Bash
Zsh
Fish
PowerShell
PostgreSQL 配置
通过 pg exec 使用 ALTER SYSTEM 修改 PostgreSQL 运行时参数,然后重新加载:
注意: 某些参数(例如 shared_buffers、max_connections)需要重启而不是重新加载。使用 pg stop && pg start 应用这些更改。
配置文件管理
检查或验证配置文件(默认为 ~/.pgcli/pg.yaml,使用 -c 覆盖)。
Init 生成默认配置;--add 在同一文件中创建命名实例,-o 写入自定义路径:
--backup-dir 将备份数据和日志放在与实例数据不同的路径上。这是典型的生产环境
配置:PG 数据在一块磁盘,备份在另一块,防止数据盘故障连累备份丢失。
在一个主机上运行多个配置的隔离参数(参见快速开始进行规划):
| 参数 | 默认值 | 含义 |
|---|---|---|
--namespace |
default |
容器名称前缀:pgcli-pg-<namespace>-<instance>,备份容器 pgcli-backup-<namespace>。传递 --namespace "" 保持旧式名称不带前缀 |
--base-dir |
~/.pgcli |
所有数据路径的基础目录(实例数据、插件配置、备份) |
--backup-dir |
(在 --base-dir 下) |
备份数据/日志的独立基础目录。覆盖 --base-dir 设置的备份路径 |
--pg-start-port |
35432 |
分配范围中的第一个 PG 主机端口 |
--pg-ssh-port |
42201 |
分配范围中的第一个 SSH 主机端口 |
所有都保存到配置文件中(namespace、pg_start_port、pg_ssh_port);跨配置使用不重叠的端口范围,这样分配永远不会冲突。
9 - 克隆
创建新实例,其数据从现有实例复制,直接流式传输——磁盘上无临时文件。
发生了什么
- 预检 — 在创建任何内容之前验证源:
- 本地实例:容器必须正在运行
--dsn:认证的SELECT 1必须成功(捕获错误密码、不可达主机)
- 创建 — 新实例条目添加到配置,带有随机密码、自己的容器名称、数据目录和自动分配的端口
- 启动 — 新实例启动(与
pg start相同的工作流) - 流式传输 — 源数据管道传输到目标,每秒显示一次实时传输进度
注释
- 源实例必须正在运行(或
--dsn目标可达);错误的源会立即失败且无副作用 - 新实例名称在配置中不能已存在
--dsn和--instance互斥:使用--dsn时连接字符串确定主机、端口和数据库- 新实例获得新的随机密码——在克隆输出或
pg status -i <name>中查找 - 仅逻辑复制(模式 + 数据);对于大型数据库,物理方法可能更快
10 - 扩展
pgcli 支持从 Pigsty DEB 仓库安装和管理 PostgreSQL 扩展。
工作原理
扩展被烘焙到派生容器镜像中:
pg extension install构建新镜像(基于当前镜像 + Pigsty 仓库 + 扩展包)- 停止并移除旧容器
- 从新镜像重新创建容器(主机数据卷被保留)
- 更新配置文件中的
image_tag
这种方法的优势:
- 扩展在容器重建后仍然存在(烘焙到镜像层)
pg start不需要在每次启动时运行apt-get install- 扩展文件是持久的并与容器生命周期解耦
命令
安装扩展
重启确认: 需要 shared_preload_libraries 的扩展(例如 pg_stat_statements、pg_cron)需要 PostgreSQL 重启。默认情况下,会提示你确认:
如果你拒绝重启,可以稍后应用更改:
列出已安装扩展
示例输出:
- managed:由 pgcli 跟踪(记录在配置中,包含在镜像中)
- unmanaged:手动安装的扩展(不在配置中跟踪)
移除扩展
工作流:
DROP EXTENSION IF EXISTS pgmq- 更新配置和
shared_preload_libraries - 无镜像重建 —
-ext镜像在实例间共享,包永远不会被卸载
重启确认: 如果移除需要 shared_preload_libraries 的扩展(例如 pg_stat_statements、pg_cron),会在重启前提示你确认:
如果你拒绝重启,可以稍后应用更改:
查看可用扩展
列出所有 440 个已知扩展:
- 45 个内置(contrib,已在基础镜像中——无需镜像构建)
- 395 个 Pigsty 目录(来自 Pigsty DEB 仓库,需要镜像构建)
扩展目录
参见扩展目录获取 45 个内置扩展完整列表(无需安装)。
参见常用扩展获取常用扩展详细使用示例。
目录外的扩展
只有目录中的扩展(builtin + Pigsty)可以通过 pg extension install 安装。未知扩展名称会在构建开始前被拒绝:
完整目录:https://pigsty.cc/ext/list/
配置
安装扩展后,配置会被更新:
image_tag 指向包含所有已安装扩展的派生镜像。
共享预加载库
需要 shared_preload_libraries 的扩展会在 postgresql.conf 中自动配置:
必须最先加载的扩展(如 citus、timescaledb)会自动放在位置 0,不管安装
顺序如何 —— 如果它们出现在其他位置,PostgreSQL 会 FATAL。
这是 postmaster 级别参数;更改后必须重启 PostgreSQL。
故障排除
扩展安装失败
原因:扩展名称不在内置 contrib 列表或 Pigsty 目录中。
解决方法:
- 验证扩展名称:
pg extension available - 检查 Pigsty 目录:https://pigsty.cc/ext/list/
- 注意确切的 SQL 扩展名称(例如
vector而不是pgvector)
CREATE EXTENSION 失败
扩展已安装但未在配置中跟踪。你可以安全地忽略这个,或手动将其添加到配置:
共享预加载库冲突
如果 shared_preload_libraries 在 postgresql.conf 中被手动编辑,pgcli 的标记块会覆盖它。
解决方法:删除手动配置并让 pgcli 管理它。
注释
- 扩展数量:45 个内置(contrib)+ 395 个 Pigsty 目录 = 440 个已知扩展
- 镜像大小:每个扩展增加 10-50MB 到镜像,但 Pigsty 包已优化
- 构建时间:首次扩展安装需要 1-3 分钟(下载 + 构建);后续安装更快(缓存命中)
- 副本行为:副本可以安装扩展,但
CREATE EXTENSION会被拒绝(只读)。在主实例上安装;副本通过物理复制同步 - 扩展升级:
ALTER EXTENSION ... UPDATE TO ...尚不支持;通过pg exec手动运行
10.1 - 扩展目录
本页列出基础镜像中所有内置扩展。使用示例请参见常用扩展。
内置扩展(45 个)
这些扩展已包含在 PostgreSQL 基础镜像中,无需镜像构建或 pg extension install。直接运行 CREATE EXTENSION 即可启用。
需要 shared_preload_libraries
这些扩展启用后需要重启 PostgreSQL。通过 pg extension install 安装时,pgcli 会自动处理重启。
| 扩展 | 描述 |
|---|---|
pg_stat_statements |
SQL 性能分析——跟踪查询执行次数、时间和资源使用 |
pg_cron |
数据库内置 Cron 定时任务调度器 |
pg_hint_plan |
查询提示,影响查询计划器 |
pg_stat_monitor |
高级性能监控,包含查询计划 |
pg_qualstats |
查询谓词统计,用于索引推荐 |
pg_stat_kcache |
内核级 CPU/IO 性能统计 |
pg_wait_sampling |
等待事件采样,用于性能分析 |
pg_track_settings |
跟踪配置变更历史 |
timescaledb |
时序数据库扩展,支持超级表 |
pg_prewarm |
重启后将表数据预加载到共享缓冲区 |
无需重启
这些扩展可以通过 CREATE EXTENSION 立即启用,无需重启。
| 扩展 | 描述 |
|---|---|
uuid-ossp |
UUID 生成函数(v1/v3/v4/v5) |
hstore |
单列键值对存储 |
pgcrypto |
哈希(bcrypt/sha256)、加密/解密、随机数生成 |
pg_trgm |
三元组相似性匹配,用于模糊搜索 |
unaccent |
去除字符重音,用于国际化文本搜索 |
fuzzystrmatch |
模糊字符串匹配(Levenshtein、Soundex、Metaphone) |
intarray |
整数数组操作(去重、排序、交集) |
isn |
ISBN/ISSN/EAN 标准数字类型及验证 |
pg_repack |
在线表重组,无需排他锁 |
pg_squeeze |
表空间回收 |
pg_partman |
自动分区管理 |
pgvector (Pigsty) |
向量相似性搜索,用于 AI 应用 |
postgis (Pigsty) |
地理空间数据支持 |
pgmq (Pigsty) |
轻量级消息队列 |
tablefunc |
交叉表/透视表函数 |
btree_gist |
GiST 索引的 B-tree 支持 |
btree_gin |
GIN 索引的 B-tree 支持 |
citext |
不区分大小写的文本类型 |
cube |
多维立方体数据类型 |
ltree |
层次化标签树数据类型 |
seg |
浮点区间数据类型 |
earthdistance |
地球表面大圆距离计算 |
postgres_fdw |
将外部 PostgreSQL 服务器作为本地表查询 |
file_fdw |
将服务器文件作为外部表读取 |
dblink |
连接其他 PostgreSQL 数据库 |
amcheck |
验证 B-tree 索引完整性 |
pageinspect |
底层页面检查,用于调试 |
pg_buffercache |
查看共享缓冲区内容 |
pg_freespacemap |
查看表的空闲空间映射 |
pg_visibility |
查看表的可见性映射 |
pg_walinspect |
检查 WAL 记录 |
pgstattuple |
表级元组统计 |
pgrowlocks |
显示行级锁信息 |
pg_surgery |
底层堆元组操作 |
xml2 |
XML 解析和 XPath 函数 |
dict_int |
整数文本搜索字典 |
dict_xsyn |
扩展同义词文本搜索字典 |
bloom |
布隆过滤器索引访问方法 |
autoinc |
自增触发器函数 |
insert_username |
插入用户名触发器函数 |
moddatetime |
自动更新时间戳触发器 |
refint |
引用完整性触发器函数 |
tcn |
触发器变更通知 |
sslinfo |
SSL 连接信息函数 |
lo |
大对象类型支持 |
intagg |
整数数组聚合/枚举 |
tsm_system_rows |
按行数进行表采样 |
tsm_system_time |
按时间进行表采样 |
pg_logicalinspect |
逻辑复制槽检查 |
10.2 - 常用扩展
最常用 PostgreSQL 扩展的使用示例,已在 PG 18 + pgcli 环境下测试通过。
性能与监控
| 扩展 | 安装 | 描述 |
|---|---|---|
pg_stat_statements |
pg extension install pg_stat_statements |
SQL 性能分析。生产环境必备。 |
pg_repack |
pg extension install pg_repack |
在线表重组,回收膨胀空间。 |
pg_prewarm |
pg extension install pg_prewarm |
重启后预加载表数据。 |
数据类型与标识符
| 扩展 | 安装 | 描述 |
|---|---|---|
uuid-ossp |
内置 | UUID 生成(v1/v3/v5)。PG 18 已内置 gen_random_uuid() (v4) 和 uuidv7() (v7)——无需安装扩展。 |
hstore |
内置 | 键值对存储。 |
citext |
内置 | 不区分大小写的文本。Foo = foo。 |
搜索与文本
| 扩展 | 安装 | 描述 |
|---|---|---|
pg_trgm |
内置 | 三元组相似性,用于模糊搜索和自动补全。 |
unaccent |
内置 | 去除重音,用于灵活的国际化文本搜索。 |
安全与加密
| 扩展 | 安装 | 描述 |
|---|---|---|
pgcrypto |
内置 | 哈希、加密/解密、随机数生成。 |
pgaudit |
pg extension install pgaudit |
审计日志。合规(GDPR、HIPAA、SOX)必需。 |
地理空间
| 扩展 | 安装 | 描述 |
|---|---|---|
postgis |
pg extension install postgis |
完整空间数据库:几何类型、空间索引、距离/面积计算。 |
AI 与向量搜索
| 扩展 | 安装 | 描述 |
|---|---|---|
pgvector |
pg extension install vector |
存储和搜索嵌入向量。AI 应用标准。 |
时序数据
| 扩展 | 安装 | 描述 |
|---|---|---|
timescaledb |
pg extension install timescaledb |
优化的时序存储,支持超级表。 |
定时任务与分布式
| 扩展 | 安装 | 描述 |
|---|---|---|
pg_cron |
pg extension install pg_cron |
Cron 定时任务调度器。pgcli 自动配置。 |
citus |
pg extension install citus |
分布式 PostgreSQL,支持分片。 |
外部数据
| 扩展 | 安装 | 描述 |
|---|---|---|
postgres_fdw |
内置 | 将外部 PostgreSQL 服务器作为本地表查询。 |
pg_duckdb |
pg extension install pg_duckdb |
嵌入式 DuckDB 分析引擎。读取 Parquet/CSV,OLAP 查询加速。 |
兼容性说明
| 扩展 | PG 18 状态 | 备注 |
|---|---|---|
pgml |
不可用 | 仅支持 PG 14-17(无 PG 18 包) |
uuid-ossp |
部分需要 | PG 18 内置:gen_random_uuid() (v4)、uuidv7() (v7)。扩展仅在需要 v1/v3/v5 时安装 |
11 - 数据导入/导出
导出和导入数据库到转储文件。支持自定义格式(推荐)和纯 SQL,带有自动 gzip 压缩。还支持在实例间管道传输。
格式比较:
| 特性 | 自定义(.dump) |
SQL(.sql) |
|---|---|---|
| 导入速度 | 更快(二进制格式) | 更慢(文本格式) |
| 文件大小 | 更小(压缩) | 更大(纯文本) |
| 人类可读 | 否 | 是 |
| 选择性恢复 | 是(特定表) | 否 |
| 最适合 | 迁移、备份、大型数据库 | 版本控制、CI 种子数据、手动编辑 |
格式检测: 使用魔数(基于内容)配合扩展名回退。
- 以
PGDMP开头的文件 → 自定义格式 .sql或.sql.gz→ 纯 SQL 格式.gz扩展名或 gzip 魔数(0x1f 0x8b) → 自动解压- 如果内容检测失败则使用扩展名作为回退
远程数据库(–dsn): 使用连接字符串连接到任何 PostgreSQL 实例。
- 无需本地 PostgreSQL 安装
- 适用于本地到远程、远程到本地和远程到远程迁移
- 支持与本地实例相同的所有标志(
-o、-d、--clean、-v、--compress)
关于现有数据的说明: 导入到包含现有表的数据库会失败,除非你使用 --clean 标志,它会在恢复前删除对象。导入到已包含数据的数据库时使用 --clean。
用例:
- 在实例间迁移数据:
pg export -i proj01 | pg import -i proj02 - 跨主机迁移:
pg export -i proj01 | pg import --dsn postgres://user:pass@remote:5432/db - 与团队共享数据库:
pg export -i proj01 -o dump.dump.gz(压缩,更小文件) - 重大更改前备份:
pg export -i proj01 -o pre-migration.sql.gz - CI/CD 管道:导出测试数据,导入到新的测试数据库
12 - 恢复
时间点恢复(PITR)
恢复到首次备份后的任意时间点。
时间格式:
2026-08-26 15:30:00+08:00— 带时区偏移2026-08-26 15:30:00+08— 仅时区小时2026-08-26 15:30:00Z— UTC2026-08-26 15:30:00— 假定为 UTC
恢复工作流: 停止 → 恢复 → 启动 → WAL 重放到目标时间
注意: --promote 之后,在进行进一步的 PITR 之前需要创建新的完整快照。
Patroni 集群
Patroni HA 集群用 pg ha restore 恢复,是上面单实例命令的集群版对应物。它接受
相同的 --time(全部格式),并支持 --dry-run、--tail-logs、--force。
原理: 先 pause 集群(冻结 failover,免得本机停掉 leader 时远端副本被
promote 抢走 leadership),再移除集群的 DCS 身份,然后让一个本机成员通过
Patroni 的自定义 bootstrap 方法,从 pgBackRest 仓库把(已清空的)数据目录恢复到目标
时间点。该成员在新时间线上启动并自动 promote 成可写 leader;其余成员——本机的、
跨主机的——都经 DCS 重新加入它。前提是该 stanza 已配好 WAL 归档,由带 S3 仓库的
pg backup setup 预置(见 HA 备份插件)。
与单实例恢复的差异:
- 总是 promote。 没有"挂起只读、检查、再换个时间重试"的两步——集群在目标点
直接以可写状态起来。执行前用
--dry-run确认目标时间。 - 必须在拥有成员的主机上运行,且动手前先检测 leader。
pg ha restore在执行 任何破坏性操作之前,会先从 DCS 读取当前 leader,判断它是否是本机的可控成员。 真实执行时若 leader 不在本机(远端成员,或本机视图里根本没有的跨主机成员),会 直接拒绝;--dry-run则只是告警。原因:破坏性路径会清掉 DCS 身份并让本机 成员重新 bootstrap,若本机停不掉当前 leader,它会在 旧时间线 上抢先重新抢回 DCS,与 bootstrap 竞态。先把 leader 切到本机(pg ha switchover/failover), 或到 leader 所在主机执行。leader 读不到/为空时不阻断(覆盖首次 bootstrap 的场景)。 - 没有
--promoteflag —— promote 是自动的。
恢复工作流: 预检(leader 必须是本机成员)→ pause 集群 → 停本机成员 → 清 DCS → 清空并用仓库 bootstrap 一个成员 → 它 promote 成新时间线的 leader → 其余成员作为副本 重新加入。
恢复之后: 建一个新的完整快照以给新时间线重新定位——
pg ha snapshot create app --type full。这步通常直接成功:同一 stanza 的
pgBackRest 恢复在时间线切换时保持 system-id 不变,所以首个快照不会报
[051] system-id ... do not match stanza,也不需要 stanza-upgrade。若有副本没有
自动重新加入,用 pg ha ctl app -- reinit app-<ns> <member> --force 重建它。
13 - HA 集群
Patroni 是 PostgreSQL 高可用的事实标准:它掌管每个
postmaster 的生命周期、在成员间流式复制、并在 leader 失联时执行自动 failover。pgcli
把 Patroni 作为独立的顶级命令 pg ha 暴露出来——它是一种独立的模式,既不是普通的 pg
实例,也不是用 pg addon install 安装的插件。
关于归属。 Patroni 相关页面原先挂在 插件 Addons 下。现在它们独立成 HA 集群这一节,因为
pg ha并不通过插件系统安装——插件索引里提到它只是为了方便 检索。etcd DCS 与 HAProxy 负载均衡仍是插件页面(etcd、 HAProxy);本节会在相关处链回去。
子页面
| 页面 | 内容 |
|---|---|
| Patroni HA | pg ha 命令集:创建、status、switchover/failover、pause、跨主机成员、密码、namespace 与 DCS 布局 |
| 动态配置 | pg ha edit-config / pg ha ctl —— 存在 DCS 里的运行时配置,以及为什么它绝不重建容器 |
| REST API | 每个成员的 Patroni REST API:健康检查、leader 重定向、HAProxy 探测什么 |
| HA 集群扩展 | 在集群范围内安装/卸载 PostgreSQL 扩展(滚动修改 shared_preload_libraries) |
| 集群备份 | pg backup setup + pg ha snapshot —— stanza、WAL 归档到 S3、跨主机的备份 SSH 通道 |
| 集群恢复 | pg ha restore —— 走自定义 bootstrap 机制的集群 PITR、leader 本机性预检、恢复后的重新基线 |
| Exec / psql | pg ha exec / pg ha psql —— 直连 leader 或任意成员跑 SQL,免 dsn、免进容器 |
| 示例:HA 集群 + 自签 CA 的 MinIO | 实测通过的端到端流程:集群备份到一台服务自带证书的 MinIO,全程在完全隔离的第二套环境里 |
| 生成证书 | pg cert —— 签发带域名与 IP SAN 的自签证书,服务于任何不想走 CA 又需要 TLS 的场合(开发测试服务器、内部端点,或服务自带证书的 MinIO):flag 一览,以及单张自签叶证书如何充当自己的信任锚 |
| S3 存储高可用 | 让仓库本身(MinIO/silo)也容错:pgcli 暴露的 SNSD 与 MNSD 两种形态及为何只有这两种,加上在数据目录之下用 ZFS —— 单机 raidz、分布式集群每节点各自建池、异构节点 |
典型路径
相关
- 插件:etcd(DCS)、HAProxy (可扛故障切换的稳定客户端端点,可选读写分离)、MinIO(备份所在的 S3 仓库)
- 恢复 → Patroni 集群:与单实例恢复共通的 PITR 基础
- Failover:副本提升:非 Patroni 的单副本提升路径(
pg replica) - Namespace 隔离 —— scope、stanza、容器名都带 namespace 前缀
13.1 - Patroni 高可用
Patroni 是 PostgreSQL 高可用的事实标准:它
管理每个 postmaster 的生命周期、在成员间流式复制,并在 leader 失联时执行
自动故障切换。pgcli 将 Patroni 作为独立的模式暴露 —— pg ha —— 而不是
把它揉进普通的 pg 实例管理路径。
这是独立模式,不是 addon 子命令。 拥有 PostgreSQL 进程的是 Patroni (不是 pgcli)。
pg ha成员不会出现在cfg.Instances里,不复用实例 生命周期代码,也不是pg create创建的。它唯一借鉴 addon 体系的地方,是 用 etcd 做 DCS。
仅支持 Linux,与 etcd addon 一样:Patroni 成员依赖 podman 的 host 网络。root 和 rootless podman 均可使用。macOS 上这些命令会快速失败并给出 清晰提示。
所有权边界
最需要先理解的一件事,是谁拥有什么:
| 职责 | 归属 |
|---|---|
Patroni 容器(run/start/stop/rm)、镜像、patroni.yml、端口分配、密码、DCS 接线 |
pgcli |
postmaster 生命周期、initdb、PostgreSQL 配置渲染、复制槽、故障切换 |
Patroni |
| switchover / failover / pause / edit-config | pgcli 在临时容器里包装 patronictl |
pgcli 从不直接编辑运行中 PostgreSQL 的配置,也从不跑 initdb —— 这两件事都
是 Patroni 做的。这也是为什么普通 PG 镜像的 docker-entrypoint-initdb.d
约定(admin 角色 / 默认库)在这里不适用:Patroni 自己 bootstrap 集群,
角色体系是 postgres(superuser)、replicator、rewind_user。
容器生命周期 ≠ 安全的 PG 重启
因为 Patroni 是容器里的 PID 1:
pg ha create是重装语义(stop + 重建容器)。重建 leader 会让该节点 完全离线并触发故障切换。改成员配置靠重跑create是安全的;动态参数 请优先用pg ha edit-config,它绝不碰容器。pg ha start/pg ha stop是裸的容器 start/stop。停掉 leader 容器和 该节点宕机一样具有破坏性 —— Patroni 会切到副本。计划内运维请先pg ha pause(关掉自动 failover),再停容器。- paused 的集群在 resume 之前没有自动 failover。
工作原理
pg ha 每个成员跑一个 Patroni 容器。同一 scope 的所有成员指向同一个
DCS(一个 etcd 集群),DCS 保存集群的动态配置和 leader 锁:
- 某个 scope 的第一次
pg ha create完成 bootstrap:Patroni 跑initdb,抢下 leader,成为 leader。 - 之后的每个成员自动从当前 leader
pg_basebackup并开始流复制 —— 没有 flag 区分"添加"还是"加入"。 pg ha的控制命令(switchover、pause等)在临时容器里对 DCS 跑patronictl,所以即使本机没有成员容器在跑也能工作。
DCS 是唯一真相来源:Patroni 每个周期都从 DCS 重新渲染每个成员的
postgresql.conf/pg_hba.conf,手工改盘上文件会丢 —— 请用
pg ha edit-config。
Scope 与 DCS 存储路径
pg ha create <scope> 里的 <scope> 就是 Patroni 集群名 —— 一个 HA 集群
的身份标识。所有接收 scope 的命令(status、switchover、failover、
pause、ctl 等)指的都是同一个集群;某 scope 的第一次 create 完成
bootstrap,之后同 scope 的 create 是加成员。(--member 是集群内部逐主
机的节点名 —— 每台机器一个成员。)
pg.yaml 中集群以该 scope 为 key 存放在 addons.patroni.<scope> 下。
etcd 里实际存了什么
Patroni 把一切存在固定的 etcd namespace + scope 之下:
- namespace:
/service/—— Patroni 的顶层键,pgcli 不改动。 - scope: pgcli 写入
patroni.yml的是PatroniScope(scope),即你起的名字 加上 pgcli 的 namespace 后缀。Patroni 自己没有 namespace 概念(其 etcd 前缀就是裸 scope),pgcli 把后缀烘进 scope,防止两个 pgcli namespace 共用一 套 etcd 时串群。
因此一个集群在 etcd 里的完整前缀是:
前缀以下的键属于 Patroni 自己的 DCS 布局。想查看,把 pg etcdctl 指向同机
器/同配置里一个运行中的 etcd 成员:
注意:即使建集群时用的是裸名字,这里显示的 scope 也会带着 namespace 后缀。
安装
前置条件:一个 DCS。要么复用本地 etcd addon 成员,要么指向外部 etcd。
pg ha status app 渲染 patronictl list —— 恰好一行 Leader,其余是
Replica … streaming:
不带参数时,pg ha status 汇总每个集群:容器状态、成员数、每个成员的
pg=/rest= 端口。
DCS 选型
Patroni 只需要一个可达的 etcd 集群,因此有两种布局:
-
同机部署 —— 把 etcd 成员作为 addon 跑在和 Patroni 成员相同的主机上,用
--etcd m1,m2,m3指定。最省事:一台机器同时扮演两个角色,不多占主机。适合 起步的 3 节点 HA 集群。 -
专用 DCS 主机 —— 把 etcd 集群跑在单独的(虚拟)机器上,再用
--etcd-endpoints让每个 Patroni 成员指向它。下面每一行都在不同主机上执行 (pgcli 是按主机管理的):这是可用性更高的拓扑:etcd 的 quorum 不会随某台 PG 主机一起消失,重装数据库 机器也不会顺带带走 DCS。PG 主机只是访问 DCS;
--etcd-endpoints列出全部 成员的 client URL,因此某一台 etcd 主机宕机时 Patroni 会自动顺延到下一个 endpoint(写入仍然需要 etcd 自身的 quorum —— 3 台中要活 2 台)。每台 PG 主机 上的 endpoint 列表要保持一致。只写一个 endpoint(
--etcd-endpoints 10.0.0.20:2379)也能用 —— 那一台 etcd 成员会服务整个集群,另外两台被它挡在后面。但这又把单点引回来了:E1 一 宕,Patroni 就够不到 DCS,哪怕 etcd 的 quorum 是健康的;leader 续不上锁 就会自我降级。你实际有几个成员,就把它们全部列出来。
生产环境推荐独立的奇数(3 或 5)成员 etcd 集群;同机部署用于开发和小型部署即 可。
命令
| 命令 | 作用 |
|---|---|
pg ha create <scope> --member <m> … |
登记 + (重)装一个成员 —— 重建 = 节点离线 |
pg ha list |
所有 HA 集群的紧凑表格(scope、成员数、DCS、状态) |
pg ha status [scope] |
所有集群,或单个集群的 patronictl list |
pg ha switchover <scope> |
计划内切换 leader(patronictl 会二次确认;--yes 可脚本化) |
pg ha failover <scope> |
立即提升一个副本 |
pg ha pause / resume <scope> |
关闭 / 重新开启自动 failover |
pg ha edit-config <scope> -- … |
查看或修改 DCS 里的动态配置(绝不重建容器) |
pg ha start / stop <scope> --member m | --all |
裸容器 start/stop(见生命周期警告) |
pg ha remove <scope> --member m | --scope-all [--clean-data] [--force] |
移除成员;--scope-all 同时清 DCS |
pg ha passwords <scope> [--file F] |
导出存储的密码集(--passwords-file 的格式,供其它主机使用) |
pg ha remote <scope> [--member m] [--ssh-port P] |
登记另一台主机上的成员(典型为跨主机 leader),供备份 SSH 使用 |
pg ha remote remove <scope> --member m |
撤销上述登记(不触碰远端容器) |
pg ha extension install/remove/list/apply |
安装、卸载或列出 PostgreSQL 扩展(见 HA 集群扩展) |
pg ha exec <scope> "<sql>" [--member m] [--database db] |
对 leader(或指定成员)跑一次性 SQL —— 免 dsn、免进容器 |
pg ha psql <scope> [--member m] [--database db] [-- <psql 参数>…] |
交互式 psql,目标解析方式同上 |
pg ha ctl <scope> -- <patronictl 参数…> |
透传任意 patronictl 命令 |
-- 之后的 flag 原样到达 patronictl(cobra 会剥掉 --),所以
pg ha ctl app -- show-config、pg ha ctl app -- topology、
pg ha edit-config app -- -s synchronous_mode=true --force 都能用。
edit-config --show 是 ctl … -- show-config 的便捷别名。
跨主机成员
每台主机各自跑自己的 pgcli,只管本机的成员;集群通过共享 DCS 汇合,所以
两台主机的 pg.yaml 各自持有同一个 scope 的一部分视图。
跨主机清单(以下每一项都必须在每台主机上对齐):
- 端口 —— 自动分配是按主机的,但
connect_address存在 DCS 里全集群共 享。请在每台主机上把--host-port/--restapi-port设成相同值,否则 副本之间连不通。 - 密码 —— Patroni 的复制 / rewind / REST-API 鉴权是全集群一致的。用
pg ha passwords app --file app-passwd.yml导出第一台生成的那套,对每台其 他pg ha create传--passwords-file app-passwd.yml。 --advertise-host—— 跨主机成员必传;它把监听地址翻成0.0.0.0并把 可达 IP 写进connect_address。留空则该成员只走回环。第一个(bootstrap) 成员也不例外:它就是 leader,其connect_address会写进 DCS,之后每台主 机的pg_basebackup都拨这个地址 —— 用默认值引导的集群永远无法再加跨机成 员。只要以后可能在别的主机加成员,第一次create就该传本机 LAN IPv4。- 防火墙 —— 在成员之间两两放行两个端口(PG + REST API)。
pgcli 不校验对端配置;pg ha status 会显示每个成员的 connect_address,便于
自查可达性。
备份跨主机 leader
每台主机的 pg.yaml 只记录本机成员,所以本机默认看不到另一台主机上的 leader
—— 而 pgBackRest 的 stanza-create / 全量备份恰恰要求连到 leader。
这一步现在自动完成:pg ha create 成功装好一个成员后,会把它 pgcli 私有的
端口(SSH / REST API)写进 etcd 的 /pgcli/ha/<scope>/<member> 注册表(拓扑本身
—— 成员名 + host:pgport —— Patroni 早已存在 DCS 里)。备份配置生成时用
patronictl list 拿全集群拓扑、再查注册表补齐每个远端成员的 SSH 端口,于是
pgbackrest.conf / ssh_config 自动包含所有主机的成员,pg1-host 与 SSH
HostName 直接指向远端 IP(本机成员仍走 127.0.0.1)。pg ha start/stop/remove、
autostart 与 pg ha extension 会跳过非本机成员 —— 它们归各自主机的 pgcli 管。
刷新备份配置即可:
跨机备份互信现在全自动。cluster-wide stanza 把每个成员列成一个 pg*-host,
所以从任意主机发起全量备份 / check,pgBackRest 会 SSH 探测所有主机的成员来
找 primary——这些成员的 sshd 都必须接受发起方主机的备份公钥,且其
--advertise-host 已把监听翻成 0.0.0.0(见上文),跨机 SSH 才可达。
pg backup setup 负责打通:各主机 create 时把本机备份公钥发布进同一个
/pgcli/ha/<scope>/<member> 注册表;setup 把全集群成员的公钥合并成一个每集群
一份的 authorized_keys 文件,成员容器把它作为额外的 AuthorizedKeysFile
bind-mount 进去。sshd 每次登录都重新读该文件,合并又是原地覆写,所以后来加入的
主机无需重启就被运行中的成员信任(该挂载只在首次出现时随归档配置的 pause 窗口
recreate 一次性补上)。pg ha remove 掉成员会同时删其注册表 key,下次合并就不再
信任它。
S3 仓库 CA 也走同一条路:配了 ca_file 的主机把证书发布到
/pgcli/ha/<scope>/.repo/ca,ca_file 留空的加入者自动拉取、并把本地 ca_file
指到拉下来的副本。若存储主机又是另一台机器,首台主机也能免拷文件拿到 CA——
pg backup fetch-ca <存储主机>:<端口> 直接从端点的 TLS 链里取回(见 MinIO 文档)。
注册表里只允许公开材料——SSH 公钥与自签 CA 证书;私钥、
密码、S3 secret_key 绝不写入(etcd 这条链路无认证)。
兜底:若某远端成员是那台主机升级 pgcli 之前创建的、尚未注册端口,生成器会把
它的 SSH 端口回退到本 scope 的基础端口(每台主机都从 patroni_ssh_start_port
起,通常首个成员即猜对)。猜错时,手动登记这一个成员覆盖之(不创建任何容器):
S3 备份与 WAL 归档
Patroni 集群默认不开归档——只有全量备份永远做不到时间点恢复。开启 S3 仓库
(见备份文档)即随之打开。每个集群是一个
stanza——pgcli_<scope>,与带 namespace 的 DCS scope 一致,两个 pgcli
namespace 共用一个 S3 桶也撞不上——其备份侧配置把每个成员列为一个
pg*-host,pgBackRest 自己找到 primary,failover 后也无需改配置。
pg backup setup 会把同一份 archive_command 渲染进每个成员本地的
patroni.yml(pgcli 绝不把归档 GUC 写进 DCS——那是 edit-config 的地盘;
而只要 DCS 不管理某个 key,Patroni 就会采用本地值),在 pause 窗口内按"副本
先、leader 后"重建过期的成员容器(recreate 同时就是 archive_mode 需要的
postmaster 重启),并为集群执行 stanza-create + check。此后 WAL 由持有
leader 锁的成员持续推送到 S3。
每个成员会有短暂离线、最后有一次计划内的 leader 降级——与 pg ha create 的
recreate 语义相同。
密码
某个 scope 的第一次 pg ha create 生成四份凭据 —— superuser、
replication、rewind,以及 restapi basic-auth 对(restapi_user /
restapi_password)—— 存在 pg.yaml 的对应集群下。命令行上永远不用传密码:
第一个成员自动生成,pg ha passwords 把存储的这套导出成 --passwords-file
的格式,供其它主机复用:
(不加 --file 则输出到 stdout —— 建议用 --file,免得密码落进 shell 历史和
滚屏。)
四个角色及其默认用户名(由 pgcli 固定 —— 你只设置密码,密码默认按每个角色
生成随机 16 位字符串)。长度可用 --password-length 覆盖(pg ha create /
pg create,8–64);它只对"生成"这条路径生效,对 --passwords-file 或已存储的
密码集无效:
| pg.yaml 键 | 用途 | 默认用户名 | 默认密码 |
|---|---|---|---|
superuser |
PostgreSQL 超级用户 | postgres |
随机 16 位,自动生成 |
replication |
复制 / 流复制 | replicator |
随机 16 位,自动生成 |
rewind |
pg_rewind 角色 |
rewind_user |
随机 16 位,自动生成 |
restapi_user / restapi_password |
Patroni REST API basic-auth | postgres |
随机 16 位,自动生成 |
postgres / replicator / rewind_user 这些用户名被写进渲染出的 patroni.yml
(postgresql.authentication);可生成 / 用 --passwords-file 提供的只是密码部分。
restapi_user 是唯一可通过密码文件覆盖的用户名。
需要完全自己掌控(或想手写这份文件)时,用同样格式配 --passwords-file:
pg ha create 会打印每份密码的来源(生成并存盘 vs. 来自文件路径)。渲染出的
patroni.yml 以 0600 权限写入,因为它内嵌了所有这些密码。
开机自启
和其他基础 addon 一样,Patroni 成员的容器可以在主机重启后被拉起 —— 但它只
启动已存在的容器,读取盘上已有的 patroni.yml;绝不重新渲染配置或重建
数据。
成员一次切一个(--ha --scope <scope> --name <member>)。相对 DCS 的启动顺序
无所谓:Patroni 会重试直到 etcd 应答,然后正常重选。启动服务的机制见
autostart 页面。
日志
容器名为 pgcli-patroni-<scope>-<member>(设置了 namespace 时带前缀)。
连接
pg ha exec 和 pg ha psql 是零配置路径:pgcli 自己从 DCS 解析出 leader,用存储的超管密码认证,在一次性的临时容器里跑 psql —— 不用手拼 dsn、不用进成员容器,而且远端成员同样可达(就是普通 TCP + scram,所以另一台主机上的副本也能连;用 --member 指定具体节点)。
pgcli 之外的客户端,连的是 leader 的 PostgreSQL 端口。用 pg ha status app 找 leader
(Leader 行的 Host 就是它的 connect_address),再把 pg psql 指过去。
单机默认(仅回环)成员下,就是 127.0.0.1:<host_port>。
failover 后 leader 会变,所以固定连接串应当避免 —— 除非前面有连接池或 Patroni REST API 的 leader 重定向兜着。
如果需要稳定的、能扛住故障切换的连接端点 —— 以及可选的读写分离 —— 在集群前面 放一个 HAProxy。
计划内主从切换
failover 由 Patroni 掌握,pg ha 只是包装了 patronictl 里"主动挪 leader"的
几个动词。三个命令都只吃 scope:
switchover是计划内的、优雅的那一种:当前 leader 先降级,候选再被提升, 切换瞬间没有在途事务。前提是双方健康且已追平。旧 leader 会在几秒后自动 以streaming副本身份回归(Patroni 重启它的 postmaster 时,短暂看到它stopped是正常的)。切换会开一条新的 timeline —— 这是正常的,不是脑裂。failover不做握手,直接把副本立即提升。只在 leader 已经挂了、或你有意 丢弃它时使用;对健康集群跑一次只是多一次抖动。凡是计划内的操作,都用switchover。pause在整个集群范围内关掉自动 failover。任何有计划的容器操作之前 都应该先 pause —— 例如pg ha stop --all、pg ha create重建、pg ha extension—— 否则 Patroni 会在你脚下把一个副本提升走。pg ha resume再打开。(paused 的集群没有自动 failover,直到 resume 为止。)
这些都是纯 DCS 操作 —— 在一次性容器里跑 patronictl,不碰任何成员的容器或
数据,成员分散在不同主机也照样能切。和所有 pg ha 控制命令一样,scope 会被
解析成带 namespace 后缀的 DCS 键(app → app-default),本机 leader 和
跨主机 leader 的切法完全一样。
由于客户端连的是 leader 的端口,switchover 之后连接目标会跟着变 —— 参见 连接;想要一个稳定的端点,就在前面放 HAProxy。
pg ha vs. pg replica —— 怎么选
pgcli 有两种拿到副本的方式:
pg replica + pg failover |
pg ha(Patroni) |
|
|---|---|---|
| 模型 | 手动:pg replica 建副本,pg failover 按需提升 |
自动:Patroni 保持 N 个成员同步并自愈 |
| Failover | 人工跑 pg failover;提升前主库一直宕着 |
Patroni 检测到失联,约 30s 内提升副本 |
| 所有权 | pgcli 驱动 postmaster(和普通实例一样) | Patroni 驱动 postmaster;pgcli 只拥有容器 |
| 适合 | 简单读扩展、单次计划内提升、沿用 pg 实例工具链 |
零 RTO 可用性要求、无人值守 failover |
需要靠手工控制的副本,用 pg replica。需要集群在节点宕机时无人介入也能活,
用 pg ha。
配置
每个成员渲染的 patroni.yml 完全由 pg.yaml 里 addons.patroni.<scope> 派
生。一条典型记录:
端口来自两个独立池 —— patroni_start_port(默认 35532)给 PostgreSQL,
patroni_restapi_start_port(默认 39060)给 REST API —— 所以 Patroni 成员永远
不会和普通实例或 addon 端口相撞。
DCS 的 scope 键 = scope 加命名空间后缀(Patroni 自己没有 namespace 概念,所以 把前缀烙进 scope,好让两个 pgcli namespace 共用一个 etcd 时不串台)。
默认 patroni.yml
下面是 pgcli 渲染并以只读方式挂载到每个成员容器里的配置文件。密码在 bootstrap
时自动生成;可用 --passwords-file 提供自己的密码集。
要点:
scope= Patroni 集群名。与namespace组合后形成 etcd 键前缀。etcd3.hosts= 只写host:port(不带http://前缀)。多端点逗号分隔。bootstrap.dcs里的值仅为默认值 —— 只在首次 bootstrap 时生效;之后用pg ha edit-config修改。postgresql.listen: 0.0.0.0在传了--advertise-host(跨主机)时才会设置;否则监听127.0.0.1。- 密码 在首次 bootstrap 时自动生成并存入
pg.yaml;可通过pg ha passwords导出。
动态配置
Patroni 的动态配置存在 DCS(etcd)的 /service/<scope>/config 下。每个成员
在每个循环(每 loop_wait 秒)都读取并应用这些配置 —— 所以 pg ha edit-config
是调优运行时参数的正确方式,无需重启容器。
bootstrap.dcs是一次性的。patroni.yml里的bootstrap.dcs块只在某 scope 的第一个成员执行pg ha create(即集群 bootstrap)时生效。一旦 Patroni 把配置写入 DCS,之后对 YAML 文件中bootstrap.dcs的任何修改都会被 完全忽略 —— 即使重新执行pg ha create(重装)也一样。bootstrap 之后要 改动态配置,请用pg ha edit-config。常见的误区:重新跑
pg ha create不会重新读取 YAML 里的bootstrap.dcs—— Patroni 看到 DCS 里已有config键,就直接使用它。
方式 说明 pg ha edit-config app -- -s key=value推荐,pgcli 的标准方式 pg ha ctl app -- edit-config透传到 patronictl,效果一样 Patroni REST API( PATCH /config)需要能访问到某个成员的 REST API 端口
持久化: 改动直接写入 etcd,不是容器里的文件。容器重启、pg ha start/stop,
甚至 pg ha create(重装)都不会丢失这些设置 —— 新成员会自动从 DCS 拿到最新配置。
查看当前配置
这是 pg ha ctl app -- show-config 的便捷别名。输出是存在 /service/<scope>/config
里的完整 JSON。
修改参数
改动后,所有成员在下一个循环(loop_wait 秒内)应用。无需重启。
常用可调参数
| 参数 | 默认值 | 说明 |
|---|---|---|
loop_wait |
10 |
leader 循环间隔(秒),用于锁续租、DCS 更新 |
ttl |
30 |
leader 锁的 TTL。leader 在此窗口内未续租,副本触发 failover |
retry_timeout |
10 |
DCS/PostgreSQL 操作超时。必须 < ttl - loop_wait,给 leader 至少一次重试机会 |
maximum_lag_on_failover |
1048576 |
副本可被提升的最大复制延迟(字节),默认 1 MB |
synchronous_mode |
false |
启用同步复制(零数据丢失,更高延迟) |
synchronous_node_count |
1 |
同步 standby 节点数(synchronous_mode=true 时) |
use_pg_rewind |
true |
用 pg_rewind 让失败的 leader 重新加入(比全量 pg_basebackup 快) |
use_slots |
true |
启用复制槽(副本断开时防止 WAL 丢失) |
failover_timeout |
0 |
failover 前等待时间(0 = leader 丢失时立即切换) |
PostgreSQL 运行时参数也可以在 postgresql.parameters 下设置:
这会触发 PostgreSQL reload(或重启,取决于参数的 context)。查阅 pg_hba.conf
和 postgresql.conf 参数文档,了解哪些设置需要重启。
不要直接编辑的内容
- 不要直接编辑磁盘上的
patroni.yml—— 每次pg ha create都会从pg.yaml重新生成,且 Patroni 的动态配置从 DCS 读取。 - 不要直接编辑
postgresql.conf—— Patroni 每个循环都会从 DCS 配置覆盖它。 - 不要改
scope或namespace—— 这些在 bootstrap 时烙进 DCS 键,无法修改, 只能重建集群。
完整参数参考: Patroni 动态配置 涵盖所有 DCS 可调参数,包含默认值、约束条件和示例。
扩展安装
完整参考: HA 集群扩展安装 涵盖编排顺序、跨主机工作流、
shared_preload_libraries排序规则以及纯内置扩展快速路径。
注意
- 仅 Linux(root 或 rootless)。 rootless 成员通过
--userns=keep-id以宿主用户身份运行;root 成员会将配置和数据目录 chown 给 postgres (uid 999),这样容器才能读0600的配置、写自己的数据目录。 pg_hba.conf有意保持宽松(host all all all scram-sha-256+replication一行,另加两行local ... trust)。rootless podman 的 pasta 会改写回环源地址, 而 Patroni 在自定义 bootstrap 期间生成的pg_hba只放行它自己解析出的回环 TCP 地址——两者相加会让 Patroni 连不上自己的实例、卡死pg ha restore。所以postgresql.use_unix_socket/use_unix_socket_repl被设为true,让 Patroni 改用 unix socket 连自己的 postmaster,绕过改写。收紧成固定白名单是后续待做的 改进 —— 现阶段别把这些端口暴露给不受信网络。- DCS(etcd)本身也有安全注意事项 —— 见 etcd 页面:pgcli 管理 的 etcd 不启用 TLS 或鉴权。
- 没有
init.sh/docker-entrypoint-initdb.d。 Patroni 自己 bootstrap 集群,所以普通实例的admin/默认库约定在这里不存在;连postgres(superuser)来建角色。 use_slots/use_pg_rewind已启用:Patroni 接管复制槽,并能用pg_rewind让宕过的 leader 重新加入,而不必全量 rebase。
13.2 - Patroni 动态配置
这些参数存储在 DCS(etcd)的 /service/<scope>/config 下,应用于集群的所有成员。使用 pg ha edit-config 修改:
参考:Patroni 官方文档、 Pigsty 动态配置。
bootstrap.dcs是一次性的。patroni.yml里的bootstrap.dcs块只在某 scope 的第一个成员 bootstrap 集群时生效。一旦 Patroni 把配置写入 DCS,之后 对 YAML 文件中bootstrap.dcs的任何修改都会被完全忽略 —— 即使重新执行pg ha create(重装)也一样。重新跑pg ha create不会重新读取bootstrap.dcs—— Patroni 看到 DCS 里已有config键,就直接使用它。
| 方式 | 说明 |
|---|---|
pg ha edit-config app -- -s key=value |
推荐,pgcli 的标准方式 |
pg ha ctl app -- edit-config |
透传到 patronictl,效果一样 |
Patroni REST API(PATCH /config) |
需要能访问到某个成员的 REST API 端口 |
核心时序参数
| 参数 | 默认值 | 最小值 | 说明 |
|---|---|---|---|
loop_wait |
10 |
1 | 主循环每轮休眠的秒数(锁续租、DCS 更新、状态刷新) |
ttl |
30 |
20 | leader 锁的 TTL(秒)。实际上是自动故障切换触发前的等待时间 |
retry_timeout |
10 |
3 | DCS 和 PostgreSQL 操作的重试超时。如果 DCS 或网络中断时间短于此值,Patroni 不会对主库执行降级 |
修改
loop_wait、retry_timeout或ttl时的约束:
故障切换参数
| 参数 | 默认值 | 说明 |
|---|---|---|
maximum_lag_on_failover |
1048576 |
副本有资格参与 leader 竞选的最大复制延迟(字节) |
maximum_lag_on_syncnode |
-1 |
同步 standby 被健康的异步副本替换前允许的最大延迟(字节)。≤ 0 时 Patroni 不主动替换不健康的同步 standby。设得足够高以避免高事务量期间频繁替换 |
max_timelines_history |
0 |
DCS 中保留的时间线历史条目最大数量。0 = 保留全部 |
primary_start_timeout |
300 |
leader 从故障恢复的秒数,超时则触发故障切换。0 = 检测到崩溃立即切换(异步复制下可能丢失事务)。最大切换时间 = loop_wait + primary_start_timeout + loop_wait;设为 0 时仅为 loop_wait |
primary_stop_timeout |
— | 停止 PostgreSQL 时的等待秒数(仅在 synchronous_mode 启用时生效)。如果停止操作超过此超时,Patroni 向 postmaster 发送 SIGKILL。≤ 0 或未设置 = 无效果 |
failover_timeout |
0 |
leader 丢失后触发故障切换前的等待秒数。0 = 立即 |
复制模式
| 参数 | 默认值 | 说明 |
|---|---|---|
synchronous_mode |
false |
启用同步复制(off / on / quorum)。leader 管理 synchronous_standby_names;只有最后已知的 leader 或同步 standby 可以竞选 leader。保证零数据丢失,代价是无法保证持久性时写入不可用 |
synchronous_mode_strict |
false |
没有可用的同步 standby 时,拒绝禁用同步复制——阻塞客户端对 leader 的所有写入 |
synchronous_node_count |
1 |
同步 standby 节点数。成员加入/离开时动态调整。自动限制为符合条件的节点数 |
failsafe_mode |
false |
启用 DCS 故障安全模式:DCS 不可达时,leader 保持运行而不自我降级 |
PostgreSQL 设置
| 参数 | 默认值 | 说明 |
|---|---|---|
postgresql.use_pg_rewind |
false |
使用 pg_rewind 让失败的 leader 重新加入(比全量 pg_basebackup 快)。需要数据页校验和(initdb 时 --data-checksums)或 wal_log_hints=on |
postgresql.use_slots |
true |
使用复制槽(副本断开时防止 WAL 丢失)。PostgreSQL 9.4+ 默认启用 |
postgresql.pg_hba |
— | 生成 pg_hba.conf 的规则。如果 PostgreSQL 的 hba_file 参数设为非默认值则忽略 |
postgresql.pg_ident |
— | 生成 pg_ident.conf 的规则。如果 ident_file 为非默认值则忽略 |
postgresql.parameters |
— | PostgreSQL GUC 的键值对,例如 {max_connections: 100, wal_level: "replica", wal_log_hints: "on"}。许多是复制正常工作所必需的 |
postgresql.recovery_conf |
— | standby 配置的附加 recovery.conf 条目(PG12+ 透明处理) |
通过 postgresql.parameters 键设置 PostgreSQL 参数:
备用集群
如果定义了此节,集群将作为备用集群引导,从远端主库流式复制。
| 参数 | 说明 |
|---|---|
standby_cluster.host |
远端主库地址 |
standby_cluster.port |
远端主库端口 |
standby_cluster.primary_slot_name |
复制用的槽名(可选,默认为成员名) |
standby_cluster.create_replica_methods |
从远端主库引导备用 leader 的方法有序列表 |
standby_cluster.restore_command |
WAL 恢复命令 |
standby_cluster.archive_cleanup_command |
备用 leader 的归档清理命令 |
standby_cluster.recovery_min_apply_delay |
应用 WAL 记录前的延迟 |
复制槽
| 参数 | 默认值 | 说明 |
|---|---|---|
member_slots_ttl |
30min |
副本关闭后其物理复制槽的保留时间。0 = 成员键从 DCS 过期后立即删除。仅在 PostgreSQL 11+ 生效 |
slots |
— | 永久复制槽(哈希映射)。在 switchover/failover 期间保留。PG11+ 的物理槽在所有节点上创建,每 loop_wait 秒推进一次。逻辑槽通过重启从主库复制到副本,然后每 loop_wait 秒推进一次。需要 use_slots: true |
ignore_slots |
— | 由外部管理、Patroni 不应触碰的槽(属性集列表)。任何子集匹配都会导致该槽被忽略 |
永久槽示例
节点固定的物理槽
对于固定的集群拓扑,为每个节点定义永久物理槽,防止临时中断期间槽被删除:
警告: 永久复制槽仅从 primary/standby_leader 同步到副本。应用程序应在 leader 节点上使用它们。在副本上使用永久槽会导致整个集群的
pg_wal无限增长。例外:与 Patroni 成员名匹配的物理槽(由 Patroni 创建和维护)在所有节点间同步,用于节点间复制。
查看当前配置
或直接从 etcd 读取:
13.3 - Patroni REST API
Patroni 在每个成员上暴露一个 HTTP REST API,提供健康检查端点(供负载均衡器和 Kubernetes 探针使用)、监控数据(含 Prometheus 指标)以及集群管理操作。
查找 REST API
每个成员的 REST API 端口由 pgcli 自动分配,存储在 pg.yaml 的
addons.patroni.<scope>.members.<name>.restapi_port 下。端口也写入渲染后的
patroni.yml 的 restapi.connect_address。
认证
pgcli 在 REST API 上启用了 basic-auth(用户名 postgres,自动生成密码)。
但认证是按方法区分的,而非一律要求:
| 方法 | 是否需要认证 | 端点 |
|---|---|---|
GET / HEAD / OPTIONS |
不需要 | 所有只读端点 —— /、/primary、/replica、/health、/cluster、/config、/metrics、/patroni 等 |
POST / PATCH / PUT / DELETE |
需要 | 写操作端点 —— /failover、/switchover、/config(修改)、/reload、/restart 等 |
这是有意设计:只读健康检查保持开放,让负载均衡器或 Prometheus 无需凭据即可
轮询;而改变集群状态的操作(POST /failover、PATCH /config)需要认证。凭据
的存在是为了让 patronictl 和其他 Patroni 成员能执行写操作。
所以负载均衡器健康检查不需要认证:
写操作才需要认证。密码与 patroni.yml 的 restapi.authentication 中使用的
restapi_password 相同。通过导出命令获取:
健康检查端点
所有健康检查端点响应 GET 请求。Patroni 返回描述节点状态的 JSON 文档,附带
HTTP 状态码。仅需状态码时可用 HEAD 或 OPTIONS 代替 GET(无响应体)。
仅主库端点(仅 leader 返回 200)
以下端点仅在节点为当前持有 leader 锁的 leader 时返回 HTTP 200:
| 端点 | 说明 |
|---|---|
GET / |
根路径 —— 主库健康检查 |
GET /primary |
/ 的别名 |
GET /read-write |
/ 的别名 |
GET /leader |
类似 /,但不区分 primary 和 standby_leader |
GET /master |
/leader 的传统别名 |
这些端点适用于负载均衡器健康检查,将写操作仅路由到当前主库。
仅副本端点(仅 replica 返回 200)
| 端点 | 说明 |
|---|---|
GET /replica |
副本健康检查 —— 节点运行中、角色为 replica、且未设置 noloadbalance 标签时返回 200 |
GET /replica?replication_state=streaming |
仅当副本正在流式复制(而非通过归档恢复追赶)时返回 200 |
GET /replica?lag=<max> |
仅当复制延迟低于阈值时返回 200(字节或可读格式:10MB、1GB) |
只读端点(主库和副本均返回 200)
| 端点 | 说明 |
|---|---|
GET /read-only |
任何运行中的节点(主库或副本) |
GET /synchronous / GET /sync |
仅同步 standby |
GET /asynchronous / GET /async |
仅异步 standby |
GET /read-only-sync |
主库 + 同步 standby |
GET /read-only-quorum |
主库 + quorum standby |
GET /quorum |
仅 quorum standby |
PostgreSQL 健康
| 端点 | 说明 |
|---|---|
GET /health |
PostgreSQL 运行中时返回 200(不论角色) |
Kubernetes 探针
| 端点 | 说明 |
|---|---|
GET /liveness |
Patroni 心跳循环正常运行时返回 200。主库上次心跳超过 ttl 秒、或副本超过 2*ttl 秒时返回 503。轻量级——不执行 SQL。适用于 livenessProbe。 |
GET /readiness |
节点为 leader 时返回 200;或 PostgreSQL 运行中、正在复制、且延迟在允许范围内时返回 200。接受 ?lag=<max>(默认 maximum_lag_on_failover)和 ?mode=apply|write(默认 apply)。适用于 readinessProbe。 |
监控端点
GET /patroni
以 JSON 返回详细的节点状态。Patroni 在 leader 竞选期间内部调用,也供监控系统使用:
响应字段:
| 字段 | 说明 |
|---|---|
state |
节点状态:running、stopped、starting 等 |
role |
primary 或 replica |
server_version |
PostgreSQL 版本(整数) |
xlog.location |
当前 WAL 位置(仅主库) |
xlog.received_location |
从主库接收的 WAL(仅副本) |
xlog.replayed_location |
已回放的 WAL(仅副本) |
timeline |
当前时间线编号 |
replication |
已连接的副本数组(仅主库) |
cluster_unlocked |
未持有 leader 锁时为 true |
pause |
自动故障切换暂停时为 true |
dcs_last_seen |
上次成功联系 DCS 的 epoch 时间戳 |
patroni.version |
Patroni 版本 |
patroni.scope |
集群 scope 名 |
patroni.name |
成员名 |
GET /cluster
返回完整的集群拓扑 —— 所有成员及其角色、状态和复制状态:
GET /config
返回存储在 DCS 中的当前动态配置:
等同于 pg ha edit-config <scope> --show。
GET /history
返回时间线历史。未发生时间线切换时(新集群)为空数组 []。
GET /metrics
以 Prometheus 格式返回监控数据,供 Prometheus 或兼容系统抓取:
关键指标:
| 指标 | 说明 |
|---|---|
patroni_primary |
本节点为 leader 时为 1 |
patroni_replica |
本节点为副本时为 1 |
patroni_postgres_running |
PostgreSQL 运行中时为 1 |
patroni_postgres_streaming |
PostgreSQL 正在流式复制时为 1(副本) |
patroni_xlog_location |
当前 WAL 位置(仅主库) |
patroni_xlog_received_location |
接收的 WAL(仅副本) |
patroni_xlog_replayed_location |
回放的 WAL(仅副本) |
patroni_cluster_unlocked |
未持有 leader 锁时为 1 |
patroni_is_paused |
自动故障切换禁用时为 1 |
patroni_pending_restart |
节点需要重启时为 1 |
patroni_postgres_timeline |
当前时间线 |
patroni_dcs_last_seen |
上次 DCS 联系的 epoch |
patroni_server_version |
PostgreSQL 版本 |
基于标签的过滤
健康检查端点接受查询参数,按成员 patroni.yml 的 tags 节中定义的自定义
标签过滤:
实用模式
负载均衡器路由
由于 GET 健康检查无需认证,负载均衡器可以直接轮询 REST API 端点。模式
(源自 Patroni 官方 haproxy.cfg 示例)是:后端 TCP 端口是 PostgreSQL 端口,但健康检查
走 REST API 端口,用 GET /,仅 leader 返回 200。
单一读写入口(所有流量 → 当前 leader):
check port 8008/8009—— HTTP 健康检查走 REST API 端口,而非 PG 端口。GET /仅在 leader 返回 200 → HAProxy 只把 leader 标记为UP。on-marked-down shutdown-sessions—— 故障切换时,旧 leader 的连接被切断,客户端重连到新 leader。fall 3/rise 2配合inter 3s—— 约 9 秒标记下线,约 6 秒标记上线。
读写分离 —— 再加一个用 GET /replica(仅 replica 返回 200)的 listener 承接读流量:
app_rw(:5000)——GET /→ 仅 leader 为UP→ 写操作落到 leader。app_ro(:5001)——GET /replica→ 仅 replica 为UP→ 读操作分摊到副本。
配合上文副本端点里的 GET /replica?lag=1MB,可把延迟过大的副本踢出读池。
curl 监控脚本
快速健康检查脚本:
Prometheus 抓取配置
GET /metrics 无需认证,因此抓取配置不带凭据也能工作:
13.4 - HA 集群扩展安装
在 Patroni 管理的 HA 集群中安装 PostgreSQL 扩展,与单节点实例有本质区别。
本页解释原因,并介绍 pg ha extension 命令子树。
为什么不能用 pg extension install?
单节点流程(pg extension install <instance> <ext>)的工作方式是:
- 用 Pigsty 包构建
-ext派生镜像 - 停止并用新镜像重建容器
- 编辑
postgresql.conf设置shared_preload_libraries - 在容器内执行
CREATE EXTENSION
在 Patroni 集群中,步骤 2-4 全部失效:
- Patroni 是 PID 1。 重建容器 = 该节点离线。如果是 leader,Patroni 会触发 故障切换 —— 集群在你安装到一半时重新洗牌。
- Patroni 每个循环都从 DCS 重新生成
postgresql.conf。 任何对文件的直接 编辑都会在几秒内被覆盖。shared_preload_libraries必须通过patronictl edit-config(写入 DCS)来设置。 CREATE EXTENSION必须在 leader 上运行,而 leader 可能在远程主机 —— 不在任何本地容器内。
pg ha extension 正确编排了所有这些步骤。
工作原理
安装流程遵循特定顺序,避免上述陷阱:
关键顺序是 resume 必须在 edit-config 之前:暂停状态的 Patroni 集群不会
应用 edit-config 变更。如果在暂停状态下 edit-config,shared_preload_libraries
的更新会被静默丢失。
纯内置扩展快速路径
如果所有请求的扩展都是内置的(contrib 扩展,如 hstore、uuid-ossp,随
PostgreSQL 一起发布),步骤 2-4 会完全跳过 —— 不构建镜像、不重建容器、不
暂停/恢复。流程直接走 edit-config + CREATE EXTENSION。
命令
安装
构建 -ext 镜像,暂停集群,重建本地成员容器。
- 单主机集群(所有成员在本地):自动执行 apply(resume + edit-config + CREATE EXTENSION)
- 跨主机集群:只执行 pause + recreate,集群保持暂停。需要在每台主机上分别运行
install,最后在任意主机上运行
pg ha extension apply完成安装。
扩展以逗号分隔传入:
参数:
| 参数 | 默认值 | 说明 |
|---|---|---|
--database |
postgres |
CREATE EXTENSION 的目标数据库 |
--auto-restart |
false |
跳过 edit-config 触发的滚动重启确认 |
⚠
--database影响全部扩展。 指定--database后,apply阶段会对所有已安装扩展(不仅是本次新增的)在目标数据库执行CREATE EXTENSION IF NOT EXISTS。例如,集群已安装[pg_cron, pg_stat_statements, pgvector],执行pg ha extension install app hstore --database mydb后,这四个扩展都会在mydb中创建。扩展的.so文件已在镜像中,不会重复安装,只是 SQL 对象(函数、类型等)在目标库中注册。如果只想在特定数据库中启用已有扩展,无需重新 install,直接用 psql 连接对应库执行
CREATE EXTENSION IF NOT EXISTS即可。
卸载
在 leader 上执行 DROP EXTENSION,然后通过 patronictl edit-config 更新
shared_preload_libraries(触发滚动重启)。不会重建镜像或重建容器 ——
-ext 镜像只增不减;磁盘回收很少见且需手动操作。
扩展以逗号分隔传入:
列表
显示集群扩展的三个视图:
- Config (pg.yaml): 存储在
pg.yaml中的extensions列表 - DCS (preload): 来自
patronictl show-config的shared_preload_libraries值 - Leader (installed): leader 上
pg_extension中的实际扩展
应用
手动触发安装流程的后半段:恢复集群、执行 patronictl edit-config、在
leader 上执行 CREATE EXTENSION。
用于跨主机集群 —— 在每台主机上运行 install(各自构建镜像并重建本地
成员)后,在任意主机上运行一次 apply 完成 DCS 更新和扩展创建。
⚠
--database对所有已安装扩展生效。apply会对集群中全部扩展在指定数据库执行CREATE EXTENSION IF NOT EXISTS,不仅限于本次新增的扩展。
跨主机工作流
在跨主机集群中,每台主机只管理自己的成员。扩展安装流程分为两个阶段, 全局只暂停/恢复一次,避免不必要的 leader 漂移:
第一阶段 —— 每台主机依次运行 install:
每台主机执行:
- 在本地构建
-ext镜像(合并 DCS 已有的扩展列表,确保包含所有包) - 暂停集群(幂等 —— 第二台主机不会因"已暂停"而报错)
- 用新镜像重建自己的本地成员(同主机内 replica 先、leader 后)
- 保存配置 —— 集群保持暂停状态,不 resume
推荐顺序:从 replica 所在主机开始。 如果 leader 所在主机先执行 install, leader 重建会触发故障切换(leader 漂移到其他主机)。从 replica 主机开始, leader 始终不动,直到最后才处理 leader 所在主机,最大限度减少 leader 漂移 带来的数据同步开销。
第二阶段 —— 一次,在任意主机运行 apply:
- 恢复集群(幂等 —— tolerate “not paused”)
- 通过
patronictl edit-config更新shared_preload_libraries - 等待滚动重启
- 在 leader 上执行
CREATE EXTENSION
注意:
install后集群处于暂停状态。如果忘记执行apply,集群会一直暂停 (自动故障切换被禁用)。此时运行pg ha extension apply或手动patronictl resume <scope>即可恢复。
对于单主机集群(所有成员都在本地),install 自动执行 apply 步骤 ——
不需要单独的命令。
关于 leader 漂移: 目前无法完全避免 leader 漂移。当 leader 所在主机的 容器被重建时,Patroni 进程停止,DCS 中的 leader key 在 TTL 过期后失效, 即使集群已暂停(pause),其他 replica 仍会检测到 key 失效并触发新一轮选举。
replicas-first重建顺序能确保 leader 所在主机是最后一个执行 install 的, 但无法阻止 leader 漂移本身。这是 Patroni + 容器重建的固有限制。
extensions 配置字段
扩展在 pg.yaml 的 Patroni 集群配置中以集群级别跟踪:
这个列表是 pg ha extension list 报告的内容,也是 apply 安装的依据。
install 和 remove 都会自动更新它。
shared_preload_libraries 排序
某些扩展必须出现在 shared_preload_libraries 的第 0 位(如果不在第一位,
PostgreSQL 会 FATAL)。pgcli 在扩展目录中以 PreloadFirst 属性跟踪此约束 ——
当前设置此标记的扩展:
citus—— 分布式 PostgreSQL,必须最先加载timescaledb—— 时序引擎,同样的约束
部分扩展还需要额外的 DCS 参数:
pg_cron需要cron.database_name
pg ha extension 自动处理所有情况:
- 带
PreloadFirst的扩展始终放在 CSV 开头,不管输入顺序如何 - 当存在
pg_cron时,cron.database_name设为--database的值 (默认postgres) - 当
pg_cron被移除时,cron.database_name被清除
示例
安装 pg_stat_statements 和 pg_cron
构建 -ext 镜像,重建所有成员(带 pause/resume),在 DCS 中设置
shared_preload_libraries=pg_stat_statements,pg_cron 和
cron.database_name=mydb,然后在 mydb 中创建两个扩展。
安装 Citus(必须排在 preload 第一位)
即使 citus 列在第二位,preload CSV 也会生成为
citus,pg_stat_statements —— 带 PreloadFirst 的扩展始终放在位置 0。
卸载扩展
从 leader 删除 pg_cron,从 shared_preload_libraries 中移除,并清除
cron.database_name。触发滚动重启。
查看已安装的扩展
限制
- 镜像重建是增量的。 卸载扩展不会重建
-ext镜像或缩小它。要回收磁盘, 需用podman image prune手动清理旧镜像。 - 没有按成员的扩展列表。 扩展是集群级别的 —— 所有成员共享相同的
-ext镜像和相同的shared_preload_libraries。 CREATE EXTENSION只针对一个数据库。 PostgreSQL 扩展是按数据库的。要在 多个数据库中安装,需用--database分别指定每个数据库重新运行。- 跨主机需要手动协调。 每台主机必须先运行
install,然后才能运行一次apply。pgcli 不会 SSH 到远程主机。
参考
- Patroni HA —— 集群搭建、命令和架构
- 扩展管理 —— 单节点扩展管理和 Pigsty 目录
- Patroni 动态配置 —— DCS 参数参考
13.5 - Patroni 集群备份
本页给出 Patroni HA 集群备份的完整操作流程:从 pg backup setup 打通备份
基础设施,到 pg ha snapshot 做全量/增量/差异备份,再到一个运行态坑——副本
Replay LSN 卡住导致 pg ha status 显示成员 LSN 不一致时,如何用
patronictl reinit 拉回。命令与前置机制的参考细节见
Patroni HA 与
备份;本页把它们串成一条能照着跑完的路径。
模型:集群一个 stanza,备份容器 SSH 找 primary
一个 Patroni 集群(scope)对应一个 pgBackRest stanza:pgcli_<scope>
(与带 namespace 的 DCS scope 一致,两个 pgcli namespace 共用一个 S3 桶也撞不上)。
这个 stanza 是 cluster-wide 的——备份侧配置把每个成员列成一个 pg*-host,
pgBackRest 自己 SSH 探测、定位当前的 primary,并在 failover 后自动跟随,无需改
任何配置。因此:
- 不存在"per-member 备份"。
full/incr/diff都是对整个集群这一个 stanza 操作,pgBackRest 从当时的 leader 打快照。 - 备份从共享的 backup 容器里发起,通过 SSH 连到 primary。跨主机成员也照样可达
(信任在
setup时自动打通,见下)。 pg ha snapshot命令不需要你指定 leader——传对 scope 即可,primary 由 pgBackRest 定位。
前置:pg backup setup
pg backup setup 一次跑完,把备份基础设施全部拉起并让集群具备归档能力:
setup 做的事(配了 S3 仓库时,动手前先做一次 endpoint 预检:普通 TCP 拨号,打错主机名或存储没起会立刻失败退出,而不是拖到镜像拉取 / 容器启动之后才 把真正原因埋进噪声里):
- 构建/拉取 pgbackrest 镜像、建网络与目录、生成 backup 容器视图的
pgbackrest.conf与成员本地归档视图的pgbackrest-archive.conf。 - 拉起共享 backup 容器,随后验证仓库连通性:跑一次
pgbackrest repo-ls, 验证整条链路(TLS、凭证、bucket),并把失败映射成可操作的提示——证书错误指向pg backup fetch-ca、access-denied 指向凭证、拒绝/超时指向 endpoint。 - 打通跨主机备份互信:各主机
pg ha create时把本机备份公钥发布进集群的 etcd 注册表,setup把全集群成员公钥合并成一份每集群一份的authorized_keysbind-mount 进成员容器——sshd 每次登录重读该文件,所以后加入的主机无需重启就被 信任。S3 仓库 CA 走同一条路:配了ca_file的主机把证书发布进注册表,留空的 加入者自动拉取。(详见 HA → 备份跨主机 leader。) - 开启 WAL 归档:检测到成员归档配置过期时,pause 集群、按"副本先、leader 后"
重建过期成员容器(recreate 同时就是
archive_mode所需的 postmaster 重启)、 resume,并把同一份archive_command渲染进每个成员的patroni.yml。 - 对每个集群 stanza 执行
stanza-create+check。
HTTPS 是硬性要求。 pgBackRest 拒绝明文 S3。要接收集群归档的 MinIO 必须以 TLS 提供服务——用
pg addon install minio --tls(pgcli 自签 CA,把ca.crt填进--s3-ca-file)。MinIO 在另一台机器时,用pg backup fetch-ca <存储主机>:<端口>一次 TLS 握手取回 CA,免 scp(见 MinIO → 远端主机取 CA)。
配好后确认:
backup 容器 Up 且 stanza 就绪,即可开始备份。
每个主机都要先 setup,再 create 成员。 成员的归档能力在创建时就被冻结: 容器只有在
pgbackrest-archive.conf已存在时才挂载它,渲染出的patroni.yml也只有在配置了 S3 仓库时才带归档 GUC。所以在某主机跑pg backup setup之前pg ha create出来的成员,天生不带 WAL 归档——pg ha snapshot/pg ha restore覆盖不到它,要等之后的某次setup把它标记为过期、在 pause 窗口内 重建才能补上。pg ha create会提前就此发出告警;每台主机先跑pg backup setup,创建出来即是可备份的。
快照操作:pg ha snapshot
作用在某个集群(scope)上,通过共享 backup 容器对当前 primary 执行 pgBackRest。
这是 pg snapshot(单实例)在 Patroni 集群侧的对应物。
说明:
- 每次命令前,
pg ha snapshot会自动重渲染 backup 容器视图的pgbackrest.conf(从当前拓扑),使成员增删/端口变化即时反映到 stanza——bind-mount 生效,不重建容器。 - 只删得掉非唯一的 full:pgBackRest 至少保留一个 full,
delete命中唯一 full 会 被拒(先建一个新 full 再删旧的)。 - create 期间 pgBackRest 连到当时的 leader;failover 后换 leader 再跑也成功——这正是 cluster-wide stanza 的价值。
运行态坑:副本 LSN 卡住 → patronictl reinit
症状。 pg ha status app 里某个副本的 Replay LSN 长期落后于自己的
Receive LSN(甚至与另一副本明显不一致),且不随时间收敛。注意 Patroni 的
Lag 列是相对 leader 的差值,leader LSN 一动全体数字就动;判断真卡住要看副本
自身的 recv vs replay,或隔一段时间看 Replay LSN 是否纹丝不动。
确诊。 三条证据指向同一结论——该副本的 WAL 回放断点、复制流实际已停:
record with incorrect prev-link + waiting for WAL to become available 反复出现,
就是 WAL 流在段边界出现空洞/乱序、startup 进程卡在边界等下一段。此时 Patroni 往往
仍每周期报"no action. I am … a secondary, and following a leader"——它认为在跟随、
实际 postmaster 的 walreceiver 已停摆,不会自愈。这与备份无关,是复制运行态问题。
修复:reinit 该副本。 让 Patroni 从 leader 重新 pg_basebackup 一份、重开复制。
这是对该副本数据目录的破坏性重建(不影响 leader 与其他副本),所以 Patroni 要求
--force:
注意 -f 无效:patronictl reinit 只认全写 --force(不像 switchover/failover
的 --yes)。Success: reinitialize for member node1 即已下发。
验证恢复。 reinit 会清空并重建副本数据目录,随后 basebackup + 回放。轮询确认回到
recv == replay 且 status=streaming:
reinit 完成后,恢复 pg ha snapshot create app --type full 一次全量,把快照基线对齐到
健康的拓扑。
13.6 - Patroni 集群恢复
本页是 Patroni 集群备份 的恢复侧姊妹页。集群一旦有了带 WAL
归档的 stanza(pg backup setup + pg ha snapshot),pg ha restore 就能把整个集群
回到某个时间点——它是单实例 pg restore 的 Patroni 集群对应物。本页
讲清机制(为什么集群 PITR 不是简单跑一次 pgbackrest restore)、关于 leader 所在主机
的安全性预检、端到端操作流程,以及恢复之后必须做的重新基线步骤。命令 / flag 细节同时见
恢复 → Patroni 集群;本页把它们串成一条可照着跑的流程。
前提:本机要有带 WAL 归档的 stanza
PITR 只能重放到已经归档的部分。恢复前,集群必须已经具备:
- 针对 S3 仓库预置好的集群 stanza——
pg backup setup --s3-endpoint ...(见 备份 与 HA 备份); - 至少一个全量快照,加上覆盖目标时间点的 WAL 段
(用
pg ha snapshot create <scope> --type full产生)。
目标时间点被这段历史框住:早于最新全量备份的 stop 时间、或晚于最后归档的 WAL,恢复 都落不到那里。
pg backup setup 必须在本机跑过,且 backup 容器要在运行。 对跨主机集群而言,
这条比听起来更严格:
- 承载 bootstrap 的成员从 S3 恢复,靠的是挂进它容器里的仓库配置——
pgbackrest-archive.conf挂到/etc/pgbackrest.conf,外加 S3 CA 挂到/etc/pgbackrest/ca.crt。两者都由pg backup setup生成,且容器只在文件存在时才挂载。 从没跑过 setup 的主机没有仓库配置可挂,pgbackrest restore的 bootstrap 就连不上 S3、 起不来。 - leader 本机性与 stop 时间的预检,都是在 backup 容器内跑
pgbackrest ... info。pg ha restore只会 best-effort 刷新共享的pgbackrest.conf,不会生成成员的 archive 配置,也不会启动 backup 容器——所以要先把容器拉起来(pg backup setup, 或用pg backup status确认它是Up)。backup 容器没起时,预检会被跳过,真正的失败 要拖到 bootstrap 阶段才暴露;而恢复之后的新快照本来就依赖它在运行。
一句话:在已用 pg backup setup 预置好 S3 仓库、且 backup 容器为 Up 的主机上
运行 pg ha restore。
机制:自定义 bootstrap PITR,而非裸跑 pgbackrest restore
Patroni 集群不能在存活的 postmaster 下直接 pgbackrest restore 来做 PITR——数据目录、
时间线、DCS 身份都归 Patroni 管。pg ha restore 走的是 Patroni 的自定义 bootstrap
配方:
-
先 pause 集群(
patronictl pause)。在任何东西被停止之前先冻结 failover:集群存活时停掉本机 leader,会让 Patroni 把一台本机停不掉的远端 副本 promote 成新 leader,那个远端 leader 随后会抢着在旧时间线上重新夺回 DCS——从破坏路径内部瓦解了 leader 本机性预检。pause 正是为了堵住这一点。 若上一次尝试已经把集群拆掉(DCS 已清、成员已停),重跑的 restore 没有可 pause 的活集群;那本就是我们要的"已冻结"状态,因此空 DCS 下的 pause 失败被容忍, 流程继续。 -
清除集群的 DCS 身份(
patronictl remove)。Patroni 的bootstrap.dcs只在 DCS 没有 config key 时才执行,所以移除它正是重新武装 bootstrap 路径的动作。 -
选一个本机成员,改写它的
patroni.yml,把默认的 initdb bootstrap 替换成一条指向 目标时间点的pgbackrest restoremethod:no_params: true阻止 Patroni 追加--scope/--datadir(pgbackrest会以[031] invalid option拒绝它们);keep_existing_recovery_conf: true保留pgbackrest restore自己写下的recovery.signal+restore_command+recovery_target_*。method/pgbackrest这两个键是bootstrap.dcs的兄弟,绝不嵌进它里面——恢复相关的 GUC 若泄漏进 DCS 配置,会在 promote 后的 leader 上作为运行时参数生效。 -
清空该成员的 PGDATA 并重建其容器。Patroni 面对空数据目录 + 无 DCS 配置,跑该 method,在新时间线上恢复到目标点,并借
--target-action=promote把自己 promote 成可写 leader。 -
其余成员重新加入新 leader:本机其它成员被清空并作为标准副本重启(对新 leader 做
pg_basebackup);跨主机成员经 DCS 重新加入(或被 reinit)。
leader 本机性预检
除非当前 leader 是本机的一个成员,否则 pg ha restore 拒绝执行。 动手之前它先从 DCS
读 leader,判断它是不是本机可控成员之一。
原因。 第 1 步清掉 DCS、第 3 步让一个本机成员在新时间线上重新 bootstrap。活在另一台 主机上的 leader 其 Patroni daemon 一直在跑——本机停不掉它——所以 DCS key 一旦被移除,那个 远端 leader 会抢着在旧时间线上重新夺回 leadership,与本机 bootstrap 相撞。要求 leadership 在本机(从而可被停止)就消除了这个竞态。
你会看到什么。
怎么办。 把 leadership 挪到你正站着的这台主机的某个成员上,再重试:
或者改到 leader 自己所在的主机上跑 pg ha restore。leader 为空 / 读不到时(例如最早
的 bootstrap、还没有任何成员 publish 自己)不阻断执行。
与单实例 pg restore 的差异
- 总是 promote。 没有"只读挂起、检查、再换个时间重试"的两步——bootstrap 恢复以一个
新时间线上的可写 leader 收场。执行前先用
--dry-run确认目标时间。(机制第 1 步的patronictl pause与此无关:它是在恢复期间冻结 failover,不是只读检查窗口;而且 DCS 被清除后由新 bootstrap 重新发布配置,其中没有 pause 键,集群自动处于未暂停状态。) - 没有
--promoteflag——promote 是自动的(它烧进了 bootstrap 命令里)。 - 必须在拥有成员的主机上运行,且 leader 必须是本机的(见上文预检)。
操作流程:把集群恢复到某个时间点
完整 flag 集:--time(必填,格式见恢复)、
--member、--dry-run、--tail-logs、--force。
1. 用 dry-run 确认目标安全。 什么都不碰;你能看到 stanza、选中的 bootstrap 成员、确切的
pgbackrest 命令,以及(若相关)leader 本机性告警。
2. 确保 leader 在本机(仅当 dry-run 告警时)。switchover,然后复查状态。
3. 恢复。 默认会弹确认提示,列出 scope、stanza、目标时间、bootstrap 成员,以及
“PERMANENTLY LOST” 警告。用 --tail-logs 流式输出恢复日志;用 --force 跳过提示以便
自动化。
4. 确认新集群起来。 waitForLeader 轮询 DCS 直到出现 leader(上限 15 分钟),随后
重新加入的成员作为副本启动。
恢复之后:给新时间线重新基线
恢复不是最后一步。数据目录被重建过,所以在集群重新具备备份能力之前,有两件收尾必须做:
-
建一个新的全量快照,让后续 PITR 在新时间线上有基点:
这步通常直接成功:同一 stanza 的 pgBackRest 恢复在时间线切换时保持 PostgreSQL 的 system-id 不变(实测验证),所以恢复后的首个快照不会撞上
[051] system-id ... do not match stanza。集群恢复之后不需要pg backup stanza-upgrade。(真正会改 system-id 的是往同一个 stanza 里重新 initdb——比如全新集群复用了旧 stanza 名——那才是[051]和 stanza-upgrade 的适用场景。) -
拉回任何没有自动重新加入的成员——通常是丢了旧 leader 的跨主机成员。从新 leader 重新初始化它(只破坏该副本的数据目录):
(完整细节见 HA 备份 的"卡住的副本 → reinit"一节。)
故障排查
- “no local member on this host”——你在一台不拥有该 scope 任何成员的主机上运行。
pg ha restore只能重建本机的数据目录 + 容器。到拥有成员的主机、或 leader 所在主机上运行。 - “target time is before the latest backup stop time”——最早可用点是最新全量备份的 stop
时间;报错会打印它并给出建议的
--time。若需要更晚的基点,先做一个更新的全量快照。 - 恢复超出最后归档的 WAL——不能恢复到没归档过的时间点。调早目标时间,或先确保
archive_command在正常投递(pg backup status)再重试。 - 恢复后首个快照报
[051]——按新结论不该出现:同 stanza 恢复会保持 system-id 不变(见"重新基线"一节)。万一真撞上,pg backup stanza-upgrade能非破坏性地修好。 - 对同一仓库重复做 PITR 时报
FATAL: recovery ended before configured recovery target was reached——stanza 里已经住着一条此前 promote 出来的时间线,默认的recovery_target_timeline=latest会跳到那条分支上,而它在目标时间点之前没有提交。 恢复时显式钉住较旧的时间线,即在成员patroni.yml的 bootstrappgbackrest ... restore命令上加--recovery-option=recovery_target_timeline=<旧TL>。 目标时间点不跨越已 promote 分支的首次 PITR 不会遇到这个。
13.7 - HA Exec / psql
pg ha exec 和 pg ha psql 是对 Patroni 集群直接跑 SQL 的方式。它们是单实例
pg exec / pg psql 的集群对应物:你只给一个
scope,工具负责解析目标并连上去。不用手拼 --dsn、不用 podman exec 钻进成员容器,
而且因为走的是普通 TCP,另一台主机上的 leader 或副本和本机成员一样可达。
为什么不用 pg exec --dsn 或 podman exec
在这两条命令之前,想在集群上临时跑 SQL 只有两条别扭的路:
pg exec --dsn postgres://postgres:<pass>@<leader>:<port>/db—— 结果是对的,但 dsn 要人肉拼:从pg ha status读 leader 的connect_address、从配置里抄超管密码、 再填端口。而且写死的 dsn 在 leader 一切换就失效了。podman exec -it <member> psql ...—— 只能打到本机成员,别的主机上的 leader 根本 够不着;而且你是钻进了一个本该被工具屏蔽掉的容器。
pg ha exec/psql 把这两点都抹平了:pgcli 从集群自身状态解析 leader,用存储的密码认证,
在一个一次性容器里跑 psql。你全程看不到它。
目标是怎么解析的
DCS roster(patronictl list -f json)是唯一能看到整个集群全貌的视图。每台主机的
pg.yaml 只登记自己的成员,所以本机配置连一个跑在别处、名叫什么的节点都看不到——但 DCS
知道每个成员以及它对外宣告的 connect_address。两条命令都走它:
- 默认(不带
--member) → 当前 leader。即使 leadership 自你上次查看后已经迁移, 你依然打在对的节点上——不存在过期的 dsn。 --member <名字>→ 那个指定成员,无论它在哪。指向副本可拿到只读视图 (在 standby 上SELECT,pg_is_in_recovery()返回t),或指向某个具体的远端节点。
认证用集群超管走 scram;pg_hba(host all all all scram-sha-256)接受任意来源,所以远端
成员就像复制流量本来就能跨主机到达一样,可达。
用法
pg ha exec 把 SQL 作为尾随参数接收(所以通常整体引成一个字符串);pg ha psql 开一个
交互式 shell,把 -- 之后的内容原样转给 psql。--database 在两条命令上都选择目标库
(默认 postgres)。没有 --user——超管是 pgcli 唯一持有凭证的角色,所以连上去的就是它。
交互式与脚本化
pg ha psql 在你的 stdin 是 tty 时分配一个 TTY;不是 tty 时(管道喂脚本、在 CI 里跑)它会
关掉分页器,让会话跑完就退出而不是卡在 less 里。所以下面两种用法都符合预期:
关于输出
pg ha exec 流式输出 psql 自己的格式——列头、对齐、行数、错误都直接打到终端,跟 psql -c
打印的一模一样。它不是机器可解析的转储;如果需要那种,自己接管道工具,或用
pg ha psql app -- -c "..." 加 psql 的 --csv 之类 flag。
定位
如果你要一个独立于 pgcli、能扛故障切换的稳定客户端端点,在集群前面放
HAProxy,通过它连。pg ha exec/psql 是给运维和脚本直接驱动集群用的,
不是应用前面连接池的替代品。
pg ha status / pg ha ctl 仍是查看集群状态、以及触达未被包装的 patronictl 命令的方式;
这两条命令专门负责跑 SQL。
13.8 - 示例:HA 集群 + 自签 CA 的 MinIO
这是一页实操示例,每一步都真实跑通并验证过:创建 Patroni HA 集群,架一台 对外服务自带域名证书的 MinIO 插件(这里用我们自己造的自签 CA——与公共 CA、企业 CA 证书同一种形态,只是没去买),并把集群的 pgBackRest 备份与 WAL 归档指向它。下面每条命令都是实跑过的命令;只有主机名、端口、密码和证书材料 做了泛化处理。
有两个观念让这页值得细读而非略读:
- 存储端自带 TLS 证书。 MinIO 插件不必只服务 pgcli 生成的自签证书对——
--tls-cert/--tls-key可以让它服务你已有的证书。信任签发 CA 的客户端 无需任何额外材料;私有(这里是自签)CA 则把该 CA 经--s3-ca-file交给备 份栈——与用生成证书时完全一样。见 MinIO → 使用自带证书。 - 用第二个配置文件做隔离。
backup.repo.s3是一份 pgcli 环境里全局唯 一的仓库,被所有 stanza 共享。把它改指向一个新 store,会连带把该主机上所 有既有集群的备份目的地一起悄悄改掉。在跑着生产环境的机器上试新东西的正确 姿势是独立配置文件——自己的base_dir、namespace和端口段。本示例就 是这么做的。
我们搭建的环境
| 部件 | 取值 | 说明 |
|---|---|---|
| 配置 | ~/.pgcli-app1/pg.yaml |
与生产的 ~/.pgcli/pg.yaml 分开 |
| 数据根目录 | /home/fish/bucket/pgcli-data-app1 |
完全不碰生产数据目录 |
| 命名空间 | app1 |
给所有容器名加前缀——pgcli-minio-app1-store1、pgcli-patroni-app1-app1-nodea、pgcli-backup-app1 |
| DCS | 复用生产环境运行中的 etcd m1(127.0.0.1:2379) |
早于本示例就在生产配置里创建——见前置;此处以外部端点复用,集群成员按 scope 归组,新 scope 天然隔离 |
| MinIO | store1,端口 9010/9011,监听 0.0.0.0 |
自带自签证书,SAN 为 minio1.test,127.0.0.1,<主机IP> |
| Patroni | scope app1、成员 nodea、PG <主机IP>:35632 |
单成员,即 Leader |
| 备份容器 | pgcli-backup-app1 |
与生产的 pgcli-backup-default 并存 |
前置 —— etcd DCS 是怎么来的
示例复用了这台主机上早已为生产集群服务的那套 etcd。若是从零开始,第一块要 装的也是这个插件,一次一个成员——单成员就是一个健康的单节点 etcd 集群,跑 HA 开发/测试绰绰有余:
(如果要把这个 DCS 交给其他主机的成员加入,安装时就给它一个可达地址——
--advertise-host <主机IP>——让它的 peer/client URL 播报该 IP 而非回环;同
法 --name m2/--name m3 就能扩成真正的 3 节点组。本示例都不需要:集群与
它同机,拨的是 127.0.0.1:2379。)
第 0 步 —— 一张域名证书
任何 PEM 叶证书 + 密钥都行。演示用 pgcli 自带的 pg cert 造了一张自签的
(--valid-duration 控制有效期,SAN 覆盖客户端将要拨号的名字与 IP):
pg cert 写出的是单张自签叶证书(CA:FALSE、服务端认证用途),它自己就是自
己的信任锚——形态与公共 CA/企业 CA 签发的证书一致,只是没花钱去买。若是公
共 CA(或企业 CA)证书,这一步就只是"文件你本来就有"——把 flag 指向你的
fullchain.pem(先叶、后中间证书)和私钥即可,客户端侧反而更简单:锚定在受信
CA 的链根本不需要 --s3-ca-file。
第 1 步 —— 隔离环境
--namespace 让这份配置创建的所有容器名都与众不同,于是同一台 podman 上可以
并排跑两套环境。后续每条命令都要带 -c——本页此后的每个 pg 都指
pg -c ~/.pgcli-app1/pg.yaml。
端口段。 默认端口池(etcd 2379、MinIO 9000、Patroni 35532……)与生产配 置的起始值相同,而 host 网络容器的回环端口会被 podman 发布出来——所以处处 显式给端口、两套环境互不重叠,别让它们自动分配到同一批数字。
第 2 步 —— 服务自带证书的 MinIO
安装期校验会把证书与密钥配对(不匹配、已过期、CA 当叶用、仅限非服务端用途的
证书都在此处直接失败),打印有效期;又因为是自签证书,提示客户端将把这张证书
钉为信任锚。这里用 --listen 0.0.0.0 而非回环:备份容器经 host 网络用主机 IP
访问存储,而该 IP 就在 SAN 里。快速证明它服务的是你的证书:
建仓库桶(pgBackRest 不会代建):
第 3 步 —— Patroni 集群
那条警告是设计好的操作顺序,不是问题:创建时尚无仓库,成员先不开归档上线;
下一步的 backup setup 配好仓库后会重建成员并带上归档。注意 DCS 里的
scope 是 app1-app1(命名空间前缀 + scope)——给 pg ha 命令传的都是裸
scope(app1),pgcli 自己换算限定形式;限定名也让 stanza 与同一 bucket 上
别的命名空间可能跑的 app1 互不冲突。
塞点值得备份的数据(顺带验证客户端 scram 认证链路):
第 4 步 —— 把备份栈指向自带证书的 store
--s3-ca-file 直接把我们自己的证书当信任锚——自签证书的叶里就含其根,
所以传给 --tls-cert 的那个文件就是 CA 文件。pgBackRest 把 PEM bundle 原
样交给校验器、不检查内容,因此公共 CA 证书可以完全不传这个 flag,私有 CA 把
链传在这里即可。随后这次运行会验穿整条链路并重接成员:
“repo CA published to the cluster registry” 这行是 etcd 分发通道:本 scope 的 其他每个成员(或未来新加的,比如第二台主机)都从 DCS 自动取到 CA,无需手工 scp、无需额外 flag。
第 5 步 —— 备份,并证明数据真的到了
独立证据是对象确实躺在桶里——而写入它们的是由我们的证书终结的 TLS 会话:
archive/ 是持续的 WAL 归档(重建后的成员跑的 archive-push),backup/
是全量快照。确认归档器健康:
一个来自实跑的细枝末节,知道可免追鬼:failed_count 是 1,失败对象为
00000002.history——时间线历史文件,一秒后归档成功。它撞上了
backup setup 暂停并重建成员的那一瞬窗口。setup/重建期间出现一个孤立的近期
失败会自愈;同一个 WAL 名字让计数持续增长才是真正的仓库故障。
这个示例证明了什么
- 服务自带证书的 MinIO 插件可以作为 pgBackRest 仓库端到端工作:
stanza-create、
check、全量备份与持续 WAL 归档,全部走以所给证书校验的 TLS。 - 自签不是次等路径:同一个
--s3-ca-file槽位装得下它(叶即根)、公共 CA 的 链、内部 CA 的 bundle——传输与信任管道完全相同。 backup.repo.s3每份配置文件只有一个全局仓库。 在生产环境旁边做实验, 不要去改指它——init 第二份配置(pg config init --namespace … --base-dir …),把整个实验关在里面。命名空间限定的容器名、DCS scope 与 stanza 让两套 环境彼此不可见。
拆除
示例创建的一切都在 app1 配置名下,拆除也按该配置逆序进行(数据目录显式删 除):
要用 --scope-all(而不是 --member):它会 patronictl remove 整个
scope 的 DCS 键并清空 pgcli 成员注册表;逐个 member 删除会把这些残留留在
共享 etcd 里。
etcd m1 属于生产环境——上面所有操作都碰不到它,这正是隔离的用意。
相关
- Patroni 集群备份 —— 备份机制的深入讲解
- MinIO —— 插件本体及其 自带证书一节
- 命名空间隔离 ——
--namespace如何限定名称 - Patroni HA ——
pg ha命令集
13.9 - 生成证书 —— pg cert
pg cert 签发一张自签证书——SubjectAltNames 覆盖你要求的任意域名 / IP
组合——适用于任何不想走 CA 又需要 TLS 的场合:开发或测试服务器、内部端点、
客户端归你可控的服务。它写出的就是一张普通的 TLS 服务端叶证书,哪里都能用。
本文档树里演练的用例是 pgBackRest 的 S3 路径:Patroni 集群的备份要推给一个
S3 存储(通常是 MinIO 或 silo),而它必须对外提供 TLS——pgBackRest 明确
拒绝明文 S3——pgcli 的 MinIO/silo 插件都支持自带证书(pg addon install minio --tls-cert ... --tls-key ...)。想快速拿到一张证书来走通这条路,最省事的办法就是
pg cert:
它不往 pgcli 里注册任何东西——不碰 pg.yaml、不起容器——只是把两个文件写到
你指定的路径并打印 SAN。服务侧见
MinIO → 使用自带证书,端到端跑法见
示例:HA 集群 + 自签 CA 的 MinIO。
产出什么
单张自签叶证书,不是一条链:
--cert-file里的 PEM 恰好只有一块CERTIFICATE——这张证书自我签名;CA:FALSE、扩展密钥用途serverAuth——一张规规矩矩的 TLS 服务端叶证书;- SAN 自动分流:
--host里任何能解析成 IP 的条目落进 IP SAN,其余进 DNS SAN,所以"minio.test,10.0.0.9"不需要特殊语法(*.wild.test会作为 通配符域名条目)。
因为是自签,这张叶证书就是它自己的信任锚——把同一张 .crt 交给任何允许
指定 CA 文件/证书串的 TLS 客户端即可(curl --cacert、浏览器导入、应用的
SSL_CERT_FILE……)。具体到 pgBackRest 的 S3 路径,就是把 backup.repo.s3.ca_file
/ pg backup setup --s3-ca-file minio.crt 指向它。从没见过这个文件的远端主机
也不必 scp:pg backup fetch-ca <endpoint> 能识别 TLS 握手里的自签叶证书,并
直接把它作为锚保存下来。这不是 pgBackRest 勉强容忍的旁门做法:OpenSSL 把交给
它信任库的任何证书都当作锚点,并不要求 CA:TRUE,而 pgBackRest 的 S3 路径
(curl 走 OpenSSL)用的正是这一套机制——直接实测过:
openssl verify -CAfile minio.crt minio.crt 返回 OK。
换成真正的公共 CA / 私有 CA 证书,这套自锚机制就都不需要了:锚定在受信
CA 的链根本不需要 ca_file,私有 CA 的签发证书你本来就拿在手里。
flag 一览
| flag | 默认值 | 含义 |
|---|---|---|
--host |
127.0.0.1 |
逗号分隔的域名和/或 IP,编码进 SAN(可重复传)。条目自动识别是域名还是 IP;*.wild.test 会作为通配符域名条目。 |
--cert-file |
cert.pem |
写出证书 PEM 的路径。 |
--key-file |
key.pem |
写出私钥 PEM(PKCS8,权限 0600)的路径。 |
--valid-duration |
825 天(19800h) |
证书有效期,例如 --valid-duration 8760h 是一年。 |
--ecdsa |
P-256 |
曲线:P-224/P-256/P-384/P-521。置为空字符串则不生成 ECDSA 密钥(改配 --rsa)。 |
--rsa |
(关) | RSA 密钥位数(如 2048、4096);只在明确需要 RSA 而非默认 ECDSA 密钥时才设。 |
--ca |
false |
让这张证书自成一个 CA(CA:TRUE、keyCertSign)而非服务端叶——见下节。 |
两种密钥类型都不选(--rsa 0 --ecdsa "")是错误:必须有且只有一种产出密钥。
--ca 模式不是给 --tls-cert 用的
--ca 让证书成为自己的 Certificate Authority——用于你想要一把私有根再去签
别的证书。它不是拿来喂 MinIO --tls-cert 的东西:ValidateBYOCert 会检
查文件里的第一块证书并拒绝 CA 证书——CA 的私钥是签名密钥,不是服务端密钥。
默认(不带 --ca)产出的才是你要的服务端叶证书。
独立二进制:gencert
同一套签发逻辑也编成了一个可脱离 pg 使用的独立程序:
flag 完全相同,只是单横线形式(-host、-cert-file……)。两者背后是同一个
internal/certgen 包,产出的证书类型一致——pg cert 只是让你留在 CLI 里的那
条路径。
示例:一台主机上的完整自带证书链路
第 3 步直接引用你刚造好的 .crt,因为本机已经持有它。从没见过这个文件的
远端主机则用一次握手把它取回来,无需 scp——fetch-ca 能认出 MinIO 提供的
自签叶证书并把它作为锚保存:
相关
- Silo —— Pigsty 的 MinIO 分支,自带证书形态相同
- MinIO —— 插件本体,及其 使用自带证书 与生成测试证书两节
- 示例:HA 集群 + 自签 CA 的 MinIO —— 整条链路在一个 完全隔离的环境里端到端跑通
- Patroni 集群备份 —— 消费这张证书的 S3 仓库与 WAL 归档
- Patroni HA ——
pg ha命令集
13.10 - S3 存储高可用方案
Patroni 集群能扛住一个节点失联;它的 WAL 与备份流向的那个 S3 仓库却必须同 样扛得住,否则"高可用"在备份这条路上悄无声息地就断了。store 是一个 MinIO 或 silo 插件(两者可互换——silo 是 Pigsty 的 MinIO 分支,功能面完全一致)。这里真正要紧的是两个互相正交的 故障域:
| 故障 | 由谁吸收 |
|---|---|
| 一个节点内的一块磁盘坏了 | SNMD/MNMD 下的盘级 EC,或 MinIO 数据目录底下的存储层(ZFS) |
| 一整个节点下线 | MinIO 自己的跨主机纠删码(EC) |
本页记录 pgcli 支持如何把两者组合起来,以及如何在其中取舍。
部署形态
MinIO 按节点数与每节点盘数给自己的布局分类 (SNSD / SNMD / MNSD / MNMD)。pgcli 四种全部暴 露:
| 形态 | 结构 | 适用场景 |
|---|---|---|
| SNSD(单机单盘) | 单节点、单个数据目录——默认 | 开发、测试、演示——以及配上下一节的 ZFS 后,任何需要磁盘冗余的单机部署 |
| SNMD(单机多盘) | 单节点、多块盘——每个 --drive 传一块盘 |
单机、N ≥ 4 块数据盘、要扛住坏盘又不想额外搭文件系统层 |
| MNSD(多机单盘) | ≥ 4 节点、每节点一个数据目录 | 紧凑的高可用部署 |
| MNMD(多机多盘) | ≥ 2 节点、每节点多块盘——--drive 加上完整 --endpoint 矩阵 |
不搭文件系统层就要同时扛住坏盘与坏节点 |
SNMD 原生支持:pg addon install minio --drive /mnt/disk1 --drive ...
(每盘一个 flag)会拉起一个单进程 MinIO,在盘之间做纠删码。它能换来什么(在
活的 4 盘集上实测):默认 2 片校验,可容忍 2 块盘故障;坏 1 块盘时读写都照常,
坏 2 块盘时读仍成功、写被拒(quorum 边界);可用容量约为原始总量的一半;回来
的盘由 MinIO 自己 heal。同样的 --drive 在 silo 上也可用。
MNMD 同样是原生支持:保留 --drive 传本节点的盘,再用 --endpoint 补上整
个集群的 host×drive 端点矩阵——每台、每盘各一条 URL,各自指名该节点的
/data1../dataN 槽。在活体的 4 节点 × 4 盘集上实测(minio 与 silo 皆然):
16 盘在线报单个 stripe 大小 16 的纠删码集合、EC:4;损失一整个节点
(12/16)读写照常;再损失第二个节点(8/16)写被拒、读也失败,节点重启后自
愈回 16/16。完整步骤见
插件 → MinIO → 多机多盘(MNMD)。
SNMD 与 ZFS:扛住坏盘的两条路
两者都保护单主机数据抵御坏盘,区别在冗余放在哪一层、各自代价是什么。
原生 SNMD/MNMD(--drive) |
--data-dir 底下的 ZFS 池 |
|
|---|---|---|
| quorum 单位 | 盘——MinIO 把盘计作故障成员 | 对 MinIO 不可见——一块大盘,池吸收坏盘 |
| 布局日后可否改 | install 时定死(盘集合就是 EC 集合) | 随时可换——换盘、扩容、raidz1→raidz2,--data-dir 始终不变 |
| 4 盘可用容量 | 约一半(2 数据 + 2 校验) | 看 vdev:raidz2 = 2×盘,raidz1 = 3×盘(更省) |
| 重建 | MinIO heal 回来的盘 | ZFS 本地 resilver |
| 服务哪些形态 | SNMD(单机)与 MNMD(跨主机) | SNSD 与 MNSD(同一配方垫在每个节点下) |
| 异构节点 | 每个节点必须贡献相同盘数 | 2 盘节点与 6 盘节点看起来一样(各一个 endpoint) |
坦白的取舍是:原生多盘更简单——一条命令、不用置备文件系统、零 ZFS 配置就
拿到磁盘冗余。ZFS 更灵活——底层布局随时可换、同盘下省出更多可用容量
(raidz1 4 盘留 3 份,而 SNMD 的 EC:2 只留 2 份),而且它能容纳彼此并不一致的
节点。于是:
- 单机、要磁盘冗余又不想碰 ZFS → SNMD
- 单机、要更多可用容量 / 日后还想换布局 → SNSD + ZFS
- 分布式集群、盘和节点都要扛 → 两条路现在都是一等的:原生 MNMD,或 MNSD + 每节点 ZFS——见下文"4 主机、每主机多块盘的高可用"
单机上两条路都不算错——SNMD 那 50% 容量是不用管理池子的代价,ZFS 的灵活是置 备池子的代价。下文继续讲 ZFS 这条路,以及多机场景下的 MNMD 替代方案。
ZFS:更灵活的磁盘层
两种形态(SNSD 与 MNSD)用的是同一套配方:用主机上的数据盘建一个 zpool,为
store 开一个 dataset,把 --data-dir 指到它的挂载点。MinIO 只看到"一块又大
有可靠的盘",永远不知道底下有几块物理盘。
池必须落在与根文件系统不同的设备上——这恰好也是
MinIO 的盘检查与 pgcli install 时那条提示
性警告要的东西(drive is part of root drive, will not be used)。建在自己
盘上的 ZFS 挂载点天然满足。
按盘数选布局
| 数据盘数 | SNSD(ZFS 是唯一防线) | MNSD 节点(上层 EC 兜住节点损失) |
|---|---|---|
| 1 | 无冗余——dev/test 可以,坏一块盘就等于重建 store | 每节点一块大盘就是原生 MNSD 形态,无需 ZFS |
| 2 | mirror |
mirror |
| 3 | raidz1(可用 2D) |
raidz1 |
| 4 | 机械盘用 raidz2;SSD 重建快、容量金贵时用 raidz1;写密集用 2 × mirror |
raidz1——一格校验就足以把坏盘挡在 MinIO 视野外 |
| 5–8 | raidz2(可用 (N−2)D) |
raidz1,大盘为 raidz2 |
| > 8 | 拆成两个较小的 raidz2/raidz1 vdev 条带组池——跨 12+ 块盘的 resilver 暴露窗口太长 |
同理:单个 vdev 控制在 ~8 块盘以内 |
两列的差别只在一层推理:SNSD 下 ZFS 既要扛住坏盘、又要扛住重建期间再出的 事,校验深度直接买安全;MNSD 下 ZFS 只需要阻止"坏盘升级成丢节点",一格校验 加本地快速 resilver 就是它的全部职责——再往上,宁可用更多节点换更深的本地 RAID。
4 盘主机(SNSD)——单机最常见的问题——三种形状细看:
| 布局 | 可用容量 | 可容忍 | 特点 |
|---|---|---|---|
raidz2(类 RAID6) |
2 × 盘 | 2 块盘 | 机械盘的稳妥默认:大盘 resilver 很长,raidz1 的单校验撑不住重建期间再丢一块 |
raidz1(类 RAID5) |
3 × 盘 | 1 块盘 | SSD / 小盘的容量优先之选:重建以分钟计,而非小时 |
2 × mirror(条带化) |
2 × 盘 | 每镜像 1 块盘(分属不同镜像则可容忍 2 块) | 小写性能最好——宽条带 raidz 恰恰是这方面最差的形状 |
SNSD 下 ZFS 是 store 唯一的防线,所以默认往保守了选:除非盘足够快、重建
以分钟计、且那格容量真值那点风险——否则用 raidz2。
MinIO 那句"EC 模式底下不要做 RAID"的建议,针对的是 RAID 与跨主机 EC 双重 冗余的浪费。在 SNSD 下根本没有 EC——ZFS 是数据唯一的保护——那条建议并 不适用;照字面执行(“单盘、不上 ZFS”)在 4 盘主机上意味着一块盘报废就丢掉 整个 store。
与 PostgreSQL 同机时: ZFS 缓存很激进(ARC 默认最多吃掉一半内存)。数据 库同机时请设上限——例如
/etc/modprobe.d/zfs.conf里options zfs:zfs_arc_max=8589934592——免得两个负载抢内存。
4 主机、每主机多块盘的高可用
正是上表指向的那个混合形态,而且有两条一等的路:原生 MNMD——MinIO 在每 台节点的每块盘之间做纠删码;或MNSD + 每个节点数据目录底下的 ZFS—— MinIO 每节点只见一块盘,坏盘由本地池吸收。两条路的失败方式不同,所以这是个 真实的取舍。
原生 MNMD
一条矩阵、没有文件系统层:每个节点用 --drive 传自己的盘,每个节点携带完全
相同的 host×drive 端点列表。TLS 方面,签一张共享叶子、每个节点装同一对文件
(见下文实测之后的 TLS 说明)。
在活体的 4 节点 × 4 盘集上实测(minio 与 silo 结果一致):16 盘集合报单个 stripe 大小 16 的纠删码集、EC:4——16 块盘里坏 4 块仍然健康。
| 事件 | 在线 | 效果(实测) |
|---|---|---|
| 坏 1–4 块盘 | ≥ 12/16 | 读写照常;回来的盘由 MinIO heal |
| 1 个节点下线(它的 4 块盘) | 12/16 | 读和写照常——64 MiB 往返逐字节一致 |
| 2 个节点下线 | 8/16 | 写被拒(Resource requested is unwritable)、读也失败 |
| 节点重启 | 16/16 | 自愈 |
可用容量随校验比例而定:16 盘 EC:4 保留原始字节的 12/16。代价是:布局在
install 时定死(矩阵就是 EC 集合)、每个节点必须贡献相同盘数,而 pgcli 查不
出另一台节点上写歪的矩阵——保持 N 份 pg.yaml 一致是运维的职责。跨 grid 的
TLS,服务一份共享证书:用 pg cert --host <所有节点地址,逗号分隔> 签一
张自签叶子,再用 --tls-cert/--tls-key 装到每个节点(同一对文件各处逐字节
相同,端点矩阵全部 https://)。grid 随即成环,没有任何逐节点 CA 需要对齐
——在活体的 4 节点集上验证过:环以 Network: 4/4 OK 起来、CAs/ 目录为空,
因为那张共享叶子自身就是信任锚,而每个节点本来就持有它。退路才是生成模式:
若让每个节点各跑裸 --tls,每台主机会签出自己的私有 CA,第一次跨节点握手
死在 x509: certificate signed by unknown authority,直到你手工把某一份共享
CA 种进每个节点的证书目录——而共享 pg cert 这条路把这个活儿整个消掉了。
MNSD + 每节点 ZFS
主机之间走 MNSD,每个节点的目录底下各自 ZFS。 每个节点只暴露一个
endpoint(它 ZFS 支撑的 /data):磁盘故障由本地池自愈,根本不上报到
MinIO;节点故障由 EC quorum 吸收。
4 节点集群上,两层各自能容忍什么:
| 事件 | 由谁处理 | 后果 |
|---|---|---|
| 某节点 raidz 池里坏 1 块盘 | ZFS(resilver) | MinIO 无感知,不涉及 quorum |
| 1 个节点下线 | EC(写需要 ⌈4/2⌉+1 = 4 台里的 3 台在线) | 读写照常 |
| 2 个节点下线 | 只剩 EC 读(4 台里的 ⌈4/2⌉ = 2 台) | 可读、拒写,直到有节点回归 |
| 一块盘与它所在的节点一起故障 | EC(3/4) | 依然安全——节点回归后存活池继续自愈 |
让这套方案真正灵活的,是两条性质:
- endpoint 数 = 节点数,而不是盘数。 2 盘节点与 6 盘节点在 MinIO 眼里毫
无区别。4 台主机完全可以跑不同布局——这里
raidz1×4 盘、那里 2 盘mirror、另一台raidz2×6 盘——异构不损失任何东西。 - 池容量应大致对齐。 EC 把整个集群的可用容量定在最弱成员的空闲空间上
(3 × 12T 池 + 1 × 4T 池 → 你拿到的是按 4T 条纹化,而不是 40T)。盘确实不
一致时,精简置备(稀疏
zvoldataset,或直接zfs set refquota)对这层错 配对 MinIO 保密——用 quota 把大池压到小池的尺寸,不多占一分重建的力气。
为什么不用条带化把每节点的池拿回容量。 MinIO"EC 底下别做 RAID"的建议讲
的是容量,而且这笔账是真的:4 节点 MNSD 的 EC 已经把原始总容量砍掉约一半,
所以每节点用无冗余的纯条带池(所有盘 stripe、零本地冗余)确实比 raidz1
多榨出约三分之一。但 EC 是按节点数故障成员,而条带池会把"坏 1 块盘"放大
成"整台节点 offline"——一次普通的坏盘,就烧掉了 EC 为"丢一整机"预留的预算
格,逼出跨网络重建整池,且在重建完成前把余量清零。那点容量不是白捡的,是你
悄悄把磁盘容错降级成了节点容错换来的。每节点 raidz1 正是两层不再互相掏空间
的临界点:池吸收坏盘、对 MinIO 不可见,EC 的预算留给真正的节点故障。(如果
“榨干一切"的答案确实合适,那个形态是每节点一块大盘的纯 MNSD、根本不上
ZFS——而不是一个把同样单点风险藏低一层的条带池。)
两条路怎么选
| 原生 MNMD | MNSD + 每节点 ZFS | |
|---|---|---|
| 置备 | 每节点一条矩阵命令,不用置备文件系统 | 每节点先 zpool + dataset,再每节点一个 endpoint |
| EC 数什么 | 盘——丢一台节点只是 16 个成员里少了 4 个 | 节点——坏盘根本到不了 MinIO 面前 |
| 实测故障余量 | 4 节点 × 4 盘集:到 12/16 盘(一整个节点)都正常,8/16 失效 | 4 节点:读到 2/4,写到 3/4 台节点 |
| 可用容量 | 该集合上是原始的 12/16(EC:4)——校验片数是 MinIO 为这个 stripe 选的 | 本地 raidz1 每池留 3/4,再经跨节点 EC 减半 |
| 日后可否改布局 | install 时定死——矩阵就是 EC 集合 | 可改——换盘、vdev、布局都不碰 MinIO |
| 异构节点 | 不可能:每个节点必须贡献相同盘数 | 天然支持——每节点只是一个 endpoint,本地布局随意 |
| TLS | 一张共享证书服务整个 grid:pg cert 签一张覆盖所有节点地址的叶子,--tls-cert/--tls-key 各处一致地装上(没有 CA 要对齐) |
同理——节点之间仍是 TLS grid,同一套共享叶子配法照用 |
节点彼此一致、盘的规划已经定死、只靠 MinIO 就要同时拿到盘与节点的冗余、底下 什么都不想置备——选 MNMD。各节点的盘数/盘大小不一、布局日后可能变、或者习 惯按"整台节点"而不是"逐块盘"来推理故障——选 MNSD + ZFS。
怎么选
| 场景 | 形态 |
|---|---|
| 笔记本 / 演示 / CI | SNSD,默认数据目录——无需决策 |
| 一台主机、N ≥ 4 块数据盘,备份要扛得住坏盘 | SNMD(--drive × N)——零文件系统配置;或 SNSD + ZFS(机械盘 raidz2、快 SSD raidz1)换更多可用容量与可改布局 |
| 几台主机、每台一块数据盘,要扛得住丢主机 | 纯 MNSD |
| 彼此一致的主机、每台多块数据盘,盘和主机都要扛得住 | MNMD(--drive + 完整 --endpoint 矩阵)——无文件系统层;或 MNSD + 每节点 ZFS,见上文"两条路怎么选” |
| 各主机盘数/盘大小不一致 | MNSD + 每节点 ZFS(各节点布局可不同;容量对齐)——MNMD 要求各节点盘数相同 |
store 的 TLS 故事与拓扑无关:无论选哪种形态,--tls(或 pg cert 签发的自
带证书)行为一致——见生成证书。
相关
- 插件 → MinIO —— 安装、
--tls、分布式模式的约束(本页 赖以展开的硬性规则),以及 MNMD 完整流程 - 插件 → Silo —— 可互换的分支;形态完全相同
- 集群备份 —— 仓库如何接进 Patroni(
pg backup setup) - 示例:HA 集群 + 自签 CA 的 MinIO —— 单机形态的端到 端实测流程
14 - 自签证书
为什么要自签
三个以 TLS 对外提供 S3 的插件——minio、
silo、rustfs——可以用两种方式加载证书:
- 自动生成(
--tls):pgcli 替你签一张自签 CA + leaf,托管证书目录,重启时 刷新。零配置,SAN 固定。 - 自带证书(BYO)(
--tls-cert/--tls-key):你提供这一对。公 CA 签发的证书、 你已有的通配符证书、需要自定义 SAN 的证书,都走这条。
pg cert 就是为第二条准备的签发工具:它生成一张自签证书,SAN 恰好覆盖你需要的
主机名和 IP,让你不必有真正的 CA 也能体验 BYO TLS——而且它只写出一对独立文件,
不注册进 pgcli:文件落到你指定的路径,pg.yaml 不动,也不会拉起任何容器。你得自己
用 --tls-cert/--tls-key 把它交给 store。
快速上手
pg cert 只打印它写入的 SAN,别的什么都不做——它是一个纯粹的文件生成器,不是
插件命令。
参数
| 参数 | 默认 | 含义 |
|---|---|---|
--host |
127.0.0.1 |
逗号分隔的 DNS 名和/或 IP,编进 SAN(可重复)。每项自动判定是 IP 还是域名,所以 "rustfs.test,10.0.0.9" 无需特殊写法;*.wild.test 作为通配符 DNS 项有效。 |
--cert-file |
cert.pem |
写证书 PEM 的路径。 |
--key-file |
key.pem |
写私钥 PEM(PKCS8,权限 0600)的路径。 |
--valid-duration |
825 天 |
证书有效期,例如 --valid-duration 8760h 是一年。 |
--ecdsa |
P-256 |
曲线:P-224/P-256/P-384/P-521。置为 "" 关闭 ECDSA(配 --rsa 用)。 |
--rsa |
(关) | RSA 位数(如 2048、4096);仅在你确实需要 RSA 而非默认 ECDSA 时设。 |
--ca |
false |
让证书成为自己的 CA(CA:TRUE、keyCertSign)——用于你想要一个私有根去签更多证书,不是 --tls-cert 的常见用法。 |
同一个生成器也编译成独立二进制供 pg 之外使用——make gencert → bin/gencert,
参数完全一致、只是单横线形式(-host、-cert-file …)。两者背后是同一个包,产出
的证书逐字节同类。
pg cert 产出什么
一张自签 leaf,不是链。 --cert-file 里的 PEM 恰好只有一个 CERTIFICATE 块——
证书自己签自己(CA:FALSE、serverAuth EKU、你的 SAN)。刻意没有
leaf+中间+根 的捆绑:它上面没有签发 CA,也就无链可拼。
这也是为什么 --ca 的产物不该喂给 --tls-cert:pg addon install … --tls-cert
会跑 ValidateBYOCert,它检查第一张证书并拒绝一张 CA。--ca 证书是你拿去签
别人的信任锚,不是用来对外 serve 的服务器证书。
把它当作客户端的信任锚
因为结果自签,把同一个 .crt 作为 CA 文件交给每个 TLS 客户端——被 serve 的 leaf
就是它自己的信任锚。对 pgBackRest 的 S3 仓库,这意味着把 --s3-ca-file 直接指向
pg cert 的输出:
这不是 pgBackRest 勉强容忍的变通:OpenSSL 把你交给它的任何证书都当信任锚(不要求
CA:TRUE),pgBackRest 的 S3 TLS 路径就是这套机制。直接验证过:对自签的
CA:FALSE leaf 跑 openssl verify -CAfile <cert> <cert> 返回 OK。
若要把证书放到拨号的那台机器上而不搬文件,用
pg backup fetch-ca——它直接从实时 TLS 握手里抽出
签发证书。
交给 store 加载
三个 S3 store 各自把你的这一对挂到其二进制要求的文件名下,所以你的文件名随意——
pg cert --cert-file anything.crt 对三个都成立:
pgcli 从不重新属主、也不写你的源文件;在 rustfs 的情形下这一对以只读挂载、再拷进 容器自己的证书目录(见 rustfs → 权限与属主)。
在 rustfs 上实测过
真机安装验证:rustfs serve 的 pg cert leaf 经实时 TLS 握手确认(握手实际呈现的
证书 subject == issuer,不只看 pg.yaml 记了什么),容器以 HTTPS 起来且健康,
pg mc 对它完成 alias set → mb → put → ls → get 全链路,取回的对象与上传时
逐字节一致。
续期一张 BYO 证书
单文件 bind 挂载会钉住宿主源 inode,所以就地替换文件对运行中的容器无效。要续期, 重新铸一对(或覆盖文件),再重建:
关掉 BYO(退回自动生成,或纯 HTTP):从 pg.yaml 里删掉该插件的
cert_file/key_file 行,再 --force 重建。
另见
minio·silo·rustfs——每个都有「自带证书」小节- 备份 → S3 对象存储仓库——客户端侧的
--s3-ca-file/fetch-ca
15 - 开机自启
pgcli 可以在主机重启后自动启动 PostgreSQL 实例、备份容器、PgBouncer 服务、etcd 成员以及其他受支持的 addon。此功能使用系统服务管理器:
- Linux: systemd 用户单元
- macOS: launchd LaunchAgents
工作原理
启用开机自启后,pgcli 会创建一个系统服务,在开机时(或用户登录时)运行 pg start --autostart。--autostart 标志仅启动配置中标记为 autostart: true 的实例和服务。
开机自启是配置驱动的:如果你对某个实例执行了 pg stop(或 pg stop --all),它仍然会在下次开机时自动启动。要阻止自动启动,请使用 pg autostart disable。
默认行为:备份容器在新配置中默认 autostart: true。实例、PgBouncer 和 etcd 成员默认 autostart: false,需要手动启用。
启用开机自启
单个实例
示例:
这会在实例配置中添加 autostart: true 并创建/更新开机服务。
备份容器
启用共享 pgBackRest 备份容器的开机自启。
PgBouncer
启用与特定实例关联的 PgBouncer 的开机自启:
或为远程 PgBouncer 启用:
etcd 成员
为通过 pg addon install etcd 安装的 etcd 成员启用开机自启:
省略 --name 时成员名默认为 etcd:
集群的每个成员都要分别启用一次(m1、m2、m3……)——自启是按成员配置的。
跨主机集群时,在各自主机上为该主机的成员执行此命令。
开机只做启动(start-only)。 开机时 pgcli 只把成员已存在的容器拉起
(podman start,仅当容器缺失或卡在 improper 状态时才按配置重建容器),
绝不重新注册 membership——data-dir 已初始化的 etcd 成员会从磁盘加载集群状态,
普通 start 即可重新加入,所以该成员必须事先在集群可达时安装好。成员启动后、
quorum 尚未形成前会短暂报 etcdserver: no leader,属正常现象,足够多成员
起来后即恢复。
Kafka 节点
为通过 pg addon install kafka 安装的 Kafka 实例启用开机自启:
省略 --name 时默认为 kafka。开机时 pgcli 会先按 pg.yaml 重新渲染
server.properties,再启动已有容器并等待 broker API 响应。已经格式化过的
数据目录不会被重新格式化——cluster id 与 node id 从 meta.properties 读取,
原有日志直接复用。
SQLite 数据库
为受管的 SQLite 数据库启用开机自启(仅 Linux):
--name 必填——SQLite 没有默认数据库名。复制必须已经启用,且
litestream.yml 必须存在:
开机时 pgcli 重启该数据库的 litestream replicate 进程——没有容器,数据库
文件本身也没有可启动的服务。该进程在对象存储 addon 之后启动,因此同机的
MinIO/silo/rustfs/garage/SeaweedFS 已经先开始监听。如果数据库文件丢失,该
数据库的自启会报错(其余自启目标继续执行);请恢复文件或禁用该目标。
pg sqlite replication disable 会同时清除该数据库的 autostart 标志,因为
没有复制配置时 Litestream 无法运行。
禁用开机自启
当所有开机自启目标都被禁用后,开机服务会被自动移除。
查看状态
显示:
- 哪些实例和服务已启用开机自启
- 开机服务单元名称和状态
- Linger 状态(Linux)—— rootless podman 在开机时启动服务所需
平台特定行为
Linux (systemd)
开机服务以 systemd 用户单元创建:pgcli-autostart-<hash>.service
重要:Rootless podman 需要 loginctl enable-linger 才能在开机时(用户登录前)启动容器。pgcli 会自动尝试此操作,但如果失败会打印提示。
没有 linger,服务会在用户登录时启动,而不是在系统开机时。
Systemd Unit 配置
pgcli 生成的 systemd unit 包含以下关键配置:
- Delegate=yes - 将 cgroup 控制权委托给服务进程,允许 rootless podman 正确管理容器的 cgroup 层级
- RemainAfterExit=yes - 服务执行完成后保持
active状态,而不是立即变为inactive - Type=oneshot - 一次性执行
pg start --autostart,然后退出
Cgroup 隔离与自动回退
问题:当容器由 systemd 用户单元启动时,从 SSH 会话或其他上下文访问容器时可能会遇到 cgroup 权限错误:
原因:systemd 用户单元启动的容器位于 user@1000.service/app.slice cgroup 子树,而 SSH 会话位于 session-N.scope,两者在不同的 cgroup 作用域。
解决方案:pgcli 会自动检测 cgroup 权限错误,并通过 systemd-run --user --scope 重新执行命令。这会创建一个临时 scope,使其能够加入容器的 cgroup 子树。
受影响的命令(自动回退):
pg exec- 执行 SQL 或容器命令pg psql- 交互式 psql 会话pg status- 检查容器状态pg backup- 备份操作pg extension- 扩展管理- 所有其他
podman exec操作
不受影响的命令:
pg start- 启动容器(使用 systemd-run 本身)pg stop- 停止容器pg restart- 重启容器
查看服务状态
pg autostart status 现在会显示详细的 systemctl 信息:
输出包括:
- 所有开机自启目标的启用状态
- systemd unit 名称、加载状态、活跃状态
- 服务日志和最近执行记录
- Linger 状态
示例输出:
macOS (launchd)
开机服务以 LaunchAgent 创建:com.pgcli.autostart-<hash>.plist
注意:macOS LaunchAgents 在用户登录时运行,而不是在系统开机时。这是 macOS 的限制 —— rootless podman 无法在用户登录前启动服务。
配置文件
autostart: true 标志会出现在你的 pgcli 配置文件中:
开机服务行为
开机服务运行 pg start --autostart,它会:
- 启动所有
autostart: true的实例 - 如果
backup.autostart: true,启动备份容器 - 启动
autostart: true的 PgBouncer 服务 - 启动
autostart: true的 etcd 成员(只做启动;见上文) - 启动其他
autostart: true的 addon;SQLite 数据库在对象存储 addon 之后 重启其 Litestream 复制
如果没有配置任何开机自启目标,服务会正常退出(无错误)。
每个 manager(PostgreSQL、备份、PgBouncer、etcd)在启动时都会自愈 rootless
podman 的过期状态——主机重启后 pause 进程已死,服务会在触碰容器前透明地执行
podman system migrate。因此即使只给 addon(没有 PG 实例)配置了自启,开机
也能正常工作。
故障排查
服务启动失败
查看开机服务日志:
Linux (systemd):
macOS (launchd):
Linux: “XDG_RUNTIME_DIR is not set”
此错误表示 systemd 用户单元不可用。确保你有正确的用户会话:
- SSH 会话默认支持 systemd –user
- 桌面环境中的终端会话支持 systemd –user
- Cron 任务和其他非交互式上下文不支持 systemd –user
Linux: 开机自启仅在登录后生效
运行 loginctl enable-linger <你的用户名> 以允许 rootless podman 在开机时启动服务。
macOS: 开机自启仅在登录后生效
这是预期行为。由于 rootless podman 的限制,macOS LaunchAgents 无法在用户登录前运行。
示例
为完整的生产环境启用开机自启:
下次重启后,这些服务都会自动启动。
16 - 插件 (Addons)
pgcli 支持通过插件系统扩展 PostgreSQL 功能。插件是独立的容器,为 PostgreSQL 实例提供额外能力,无需修改数据库本身。
支持的插件
目前支持以下插件:
| 插件 | 说明 |
|---|---|
pgbouncer |
连接池管理器,提供事务级连接池化 |
etcd |
分布式键值存储——独立运行、可组集群,用于 HA / DCS |
pgdog |
Postgres 代理——连接池化、负载均衡与分片 |
postgrest |
将 PostgreSQL schema 暴露为 REST API——单容器无状态,本地或远程(任意 PG 端点) |
haproxy |
Patroni 集群前的 TCP 负载均衡——读写一体或读写分离,也可用自己的 haproxy.cfg 做任意原生代理(仅 Linux) |
minio |
S3 兼容对象存储,含 Web 控制台——单机或跨主机分布式纠删码集群(Linux 与 macOS) |
silo |
S3 兼容对象存储(Pigsty 的 MinIO 分支)——功能面与 minio 插件一致,共用同一端口池;自带 mcli 客户端(Linux 与 macOS) |
rustfs |
S3 兼容对象存储(Rust 重新实现)——SNSD/SNMD/MNMD 纠删码布局,固定的容器 uid 由 pgcli 定制镜像在内部消化,共用同一端口池(仅 Linux) |
garage |
S3 兼容对象存储(Deuxfleurs 的 Rust 实现)——v1 单节点,渲染 garage.toml,两个数据目录,四个端口取自自己的池,仅明文 HTTP(TLS 需 nginx 前置)(Linux;macOS 代码完备、未实测) |
seaweedfs |
S3 兼容对象存储(Go 实现,以 volume 粒度管盘)——单节点 weed mini,或跨主机 weed server 集群(filer 元数据放共享 PostgreSQL),全集群共用同一对凭据,原生 S3 TLS;独立端口池(Linux;macOS 代码完备、未实测) |
redis |
独立键值存储,用于缓存/会话/排行榜/原子计数——大版本 7/8 可选,自动生成 requirepass,RDB 持久化,读副本,--cluster 原生集群分片,独立的端口池(Linux;macOS 代码完备、未实测) |
predixy |
Redis 协议代理——把原生集群变成一个普通的 redis:// 端点,客户端完全无需感知集群;独立端口池(仅 Linux) |
pgadmin |
pgAdmin 4——PostgreSQL 官方 Web 管理界面,单容器直接跑上游镜像;Web 登录自动生成,可选经 --dsn/--pg-name 一次性预置服务器,持久化会话存储;独立端口池(Linux;macOS 代码完备、未实测) |
nginx |
HTTP 反向代理,前置多个 Web 服务——按声明的后端做基于路径的路由,可选 TLS 终止,渲染 nginx.conf 或用自己的 --conf-file,配置变更后 pg addon nginx reload;独立端口池(Linux 与 macOS) |
grafana |
Grafana——开源可观测性面板,单容器直接跑官方上游镜像;Web 登录自动生成,Listen 默认 0.0.0.0(关闭自助注册),v1 只经环境变量(GF_*)配置、无 provisioning;独立端口池(Linux;macOS 代码完备、未实测) |
prometheus |
Prometheus——Grafana 读取的指标采集后端,单容器直接跑官方 docker.io/prom/prometheus 镜像;渲染 prometheus.yml(自带自监控 job + 每个 --scrape JOB 一个 job,该 job 声明的 target 全部并入其中;--scrape-append 增量合并;--alertmanager-url 转发已触发告警并附带对应的抓取 job;--rule-file 拷贝并加载告警规则)或用自己的 --conf-file,pg addon prometheus reload 经 SIGHUP 热加载;无 Web 登录——Listen 默认 0.0.0.0 仅靠网络边界保护(仅本机访问用 --listen 127.0.0.1);独立端口池(Linux;macOS 代码完备、未实测) |
alertmanager |
Alertmanager——Prometheus 体系的告警去重/分组/路由端,单容器直接跑官方 docker.io/prom/alertmanager 镜像;渲染 alertmanager.yml(一条最小路由指向 no-op 或 --webhook-url 的 web.hook receiver)或用自己的 --conf-file,pg addon alertmanager reload 经 SIGHUP 热加载;HA 集群 gossip 已禁用(9094 永不开);无 Web 登录——Listen 默认 0.0.0.0 仅靠网络边界保护,而可触达即意味着任何人能创建 silence(静音 on-call),仅本机访问用 --listen 127.0.0.1;与 Prometheus 的衔接在 Prometheus 侧完成(pg addon install prometheus --alertmanager-url);独立端口池(Linux;macOS 代码完备、未实测) |
victoriametrics |
VictoriaMetrics——高性能、兼容 Prometheus 的时序数据库,单容器直接跑官方 docker.io/victoriametrics/victoria-metrics 镜像;无需配置文件(一切经 CLI flag:-storageDataPath / -httpListenAddr / -retentionPeriod / --extra-arg),无热重载(变更需重建容器),镜像以 root 运行(无需预 chown);无 Web 登录——Listen 默认 0.0.0.0 仅靠网络边界保护(仅本机访问用 --listen 127.0.0.1);独立端口池(Linux;macOS 代码完备、未实测) |
nodeexporter |
node_exporter——Prometheus 官方宿主机指标导出器,单容器直接跑官方 docker.io/prom/node-exporter 镜像、无状态;它是唯一一个必须写死三条只读宿主挂载(/proc→/host/proc、/sys→/host/sys、/→/rootfs)加 --path.* 重定向的插件,否则会静默导出容器自身的数字而非宿主的;无需配置文件(一切经 CLI flag:--web.listen-address / --path.* / --collector.filesystem.mount-points-exclude / --extra-arg),无热重载(变更需重建容器),无数据目录(remove 绝不删任何东西);无鉴权、无 TLS——Listen 默认 0.0.0.0,因为抓取它的 Prometheus 通常在另一台主机,远程暴露的实例仅靠网络边界保护;用 pg addon install prometheus --scrape node-exporter=<host>:9100 从 pgcli 的 Prometheus 抓它;独立端口池,默认 9100(Linux;macOS 代码完备、未实测) |
pgexporter |
pg_exporter——Pigsty 的声明式 PostgreSQL 数据库指标导出器,单容器直接跑官方 docker.io/pgsty/pg_exporter 镜像、无状态;它是 node_exporter(描述宿主)的搭档,导出约 460 个 pg_* 指标(活动、锁、复制、膨胀、WAL、按库统计),其标志性能力是统一采集:目标要么是 pgcli 纳管的实例(--pg-name,admin DSN 每次启动现推),要么是任意 pgcli 并不托管、可达的 PostgreSQL——远程目标只用一个互斥 URL 给出:--admin-dsn(一个 admin URL,只用于建监控角色后即丢弃——只存其不含凭据的地址,导出器以监控角色连)或 --dsn(原样拨号:pgcli 绝不跑 SQL,导出器就用这个 URL 自带的凭据——适合角色已存在、或不想让 pgcli 碰库)——意义在于用一种导出器形状同时采集 pgcli 内外的数据库;默认 pgcli 在目标上创建一个低权限 pg_monitor 监控角色,故导出器从不用 admin 连库——本地 admin 凭据自动派生,远程目标用 --admin-dsn,它只用于那一批 CREATE ROLE/GRANT、绝不落盘,生成的监控密码经 pg addon password pgexporter 取回;无需配置文件(默认采集器集内建在二进制里、按版本/角色/扩展在运行时规划;可选的 --config-file 采集器 YAML 只读挂载并经 pg addon pgexporter reload SIGHUP 热重载);/up /primary /replica 健康探针可兼作 HAProxy 式路由检查;无鉴权、无 TLS,且管理端点 /reload /explain /stat 在同端口——Listen 默认 0.0.0.0 仅靠网络边界保护;用 pg addon install prometheus --scrape pg=<host>:9630 从 pgcli 的 Prometheus 抓它;独立端口池,默认 9630(Linux 宿主网络已实测;macOS bridge 够不到本地实例的回环 DSN——那里用远程 URL(--dsn 或 --admin-dsn)) |
victorialogs |
VictoriaLogs——VictoriaMetrics 家族的日志数据库,单容器直接跑官方 docker.io/victoriametrics/victoria-logs 镜像;单 HTTP 端口同时提供摄入(JSON Lines / Elasticsearch bulk / syslog / OTLP)、LogsQL 查询与内置 UI;无需配置文件(一切经 CLI flag:-storageDataPath / -httpListenAddr / -retentionPeriod / --extra-arg),--retention 接受时长(30d、3months 等),默认省略以保持二进制自身默认;无 Web 登录——Listen 默认 0.0.0.0 仅靠网络边界保护(仅本机访问用 --listen 127.0.0.1);独立端口池,默认 9428(Linux;macOS 代码完备、未实测) |
influxdb |
InfluxDB——经典 1.x 时序数据库(TSM 存储、InfluxQL),单容器直接跑 pgcli 自建的 ghcr.io/mars-base/pgcli/pgcli-influxdb 镜像(官方 1.13.1 linux/amd64 发行版二进制 + Alpine);认证始终开启(安装时生成 admin 密码,pg addon password influxdb 取回),最小化渲染的 influxdb.conf 只覆盖路径/监听/认证,其余全部沿用上游默认值,任意持久化覆盖经 --config-option 或 pg addon influxdb config set/unset;一个容器加每个实例唯一的仅回环备份/恢复 RPC 端口(上游 RPC 无认证);在线 portable backup/restore(--newdb 可并存恢复)、离线 export(influx_inspect,会先停容器,除非 --no-start 否则结束后重启)、import 自动改写 # CONTEXT-DATABASE:;顶层 pg influx 透传用一次性容器运行 influx CLI,可操作本地 addon 或任意远程 InfluxDB 1.x(--host/--dsn、TLS flags、INFLUX_USERNAME/INFLUX_PASSWORD,-import -path FILE 会挂载进容器);没有 HTTPS 监听(用 nginx 前置)、无 HA、v1 处于维护模式;独立端口池(仅 Linux/amd64) |
influx2tsdb |
influx2tsdb——在 PostgreSQL + TimescaleDB 之上提供 InfluxDB 1.x HTTP API(Line Protocol + InfluxQL)的协议适配器,单容器、无状态,镜像为 pgcli 自建的 ghcr.io/mars-base/pgcli/pgcli-influx2tsdb(上游 1.3.2 + Debian);每个 InfluxDB 数据库对应一个 PostgreSQL schema,每个 measurement 对应一个 hypertable;用 --pg-name 指向本地实例(每次启动重新推导 DSN),或用 --dsn 接任意远程 PostgreSQL/TimescaleDB;无认证、无 TLS——需用 nginx 前置或隔离端口;保留与压缩以 TimescaleDB 后台任务形式下发(add_retention_policy / add_compression_policy,每 5 分钟重新同步)——不需要 pg_cron,且代理停机期间清理照常进行;数据全在 PostgreSQL,remove 从不删数据,备份/PITR/复制/HA 都走 PostgreSQL;InfluxQL 子集、纳秒时间戳、性能低于原生 InfluxDB;独立端口池(仅 Linux/amd64) |
vlagent |
vlagent——VictoriaLogs 官方日志 agent,单容器直接跑官方 docker.io/victoriametrics/vlagent 镜像;跟踪宿主日志文件(每个 --log-path 以只读方式挂载到容器内相同绝对路径),用 vlagent 原生 glob(--include/--exclude,不是正则)选择文件,并通过一个或多个以 /insert/native 结尾的 --remote-write-url 转发给 VictoriaLogs;不给路径时仅做 HTTP 转发;无配置文件、无鉴权;独立端口池,默认 9429(Linux;macOS 代码完备、未实测) |
vmcluster |
VMCluster——VictoriaMetrics 集群版,三个组件(vminsert + vmselect + vmstorage)各跑一个容器,镜像为官方 docker.io/victoriametrics/*:*-cluster;--roles 选择本机运行哪些组件(默认全部 = 单机收敛),--storage-node 列出所有 vmstorage 地址以支持跨主机分片/副本;只有 vmstorage 有持久数据;无配置文件、无鉴权;每实例 5 个端口,基址 vmcluster_start_port(默认 8480-8484)(仅 Linux) |
vlcluster |
VLCluster——VictoriaLogs 集群版,三个组件(vlinsert + vlselect + vlstorage)共用同一个官方 docker.io/victoriametrics/victoria-logs 镜像,仅靠 flag 区分角色;--roles 选择本机运行哪些组件(默认全部 = 单机收敛),--storage-node host:storagePort 列出所有 vlstorage 地址以支持跨主机分片(无副本——任一 vlstorage 下线时查询返回 502);只有 vlstorage 有持久数据;无配置文件、无鉴权;每实例 3 个端口,基址 vlcluster_start_port(默认 9430-9432)(仅 Linux) |
gitea |
Gitea——自托管 Git 服务,单容器直接跑官方 docker.io/gitea/gitea 镜像(rootful 构建,以 uid 1000 运行);免向导安装(INSTALL_LOCK)+ 自动创建的 admin,其 Web 登录自动生成、打印一次、可用 pg addon password gitea 取回;默认内嵌 SQLite,或用 --dsn 接外置 PostgreSQL(Gitea 自建 schema,pgcli 从不建库/删库);可选经 --ssh-port 启用 ssh:// 克隆(独立第二端口池;默认关闭——只走 HTTP clone。镜像的 OpenSSH sshd 在每个实例上都无条件在跑,--ssh-port 的作用是把 parked sshd 提升为活的克隆端点);加密 secret(SECRET_KEY/INTERNAL_TOKEN)刻意永不注入——它们归持久 volume 所有,以免重装静默重设密钥锁死幸存数据库;Listen 默认 0.0.0.0(注册默认关闭,--allow-registration 打开;建号走 pg addon gitea user create),配置只经环境变量(GITEA__*)交付;独立端口池(Linux;macOS 代码完备、未实测) |
beegfs |
BeeGFS——并行文件系统,pgcli 的首个多容器插件:一条配置 = 一个单机收敛文件系统 = 三个容器(mgmtd + meta + storage,官方 ghcr.io/thinkparq/beegfs-* 镜像),按依赖顺序启动、以集群为单位管理(集群粒度的安装复用、启停、autostart、pg logs addon beegfs --service);仅 host 网络——只有 Linux、没有端口池:协议口即原生固定口(8008/8010 可用两枚 --beegfs-mgmtd-* flag 移动,meta 8005 + storage 8003 在 8.4 无任何覆盖手段),这正是 v1 每宿主仅一个文件系统(有守卫)的原因;连接认证密钥首装自动生成(pg addon password beegfs),mgmtd TLS 关闭(--tls-disable,客户端必须同样关闭);客户端是内核模块——pgcli 从不安装、从不挂载,pg addon beegfs mount-hint 输出逐字的客户端配置;license 永不注入;pg addon beegfs check 靠守护进程日志行判健康;v1 单机,v2 = 跨主机集群 |
juicefs |
JuiceFS——S3 后端的 POSIX 文件系统,pgcli 的首个宿主二进制插件(无容器):二进制从 GitHub releases 下载、sha256 校验(PATH 上已有则复用);两端全部显式、无同主机自动识别——对象存储是外部 S3 服务(--s3-endpoint http(s)://host[:port] + --access-key/--secret-key,format 前先探活),元数据引擎是一个显式 --dsn URL(postgres:///redis:///sqlite3://,pgcli 从不创建或删除该库);建桶仍是手动 pg mc 的活——pgcli 把逐字命令打印给你;挂载为普通宿主目录(不进 PG 容器);`pg addon juicefs mount |
zerofs |
ZeroFS——加密、自维护的 S3 文件系统(自有 LSM 跑在桶上,无外部元数据引擎)的宿主二进制形态:pgcli 写 config.toml 并起两个宿主进程——仅 loopback 的 9P server(9P 无认证,非 loopback --listen 直接拒绝;每实例一个端口)+ FUSE client;对象存储是外部 S3 服务、全显式给出(--s3-endpoint + --access-key/--secret-key,可选 --s3-region——无同主机 addon 自动解析);生成的加密口令包裹桶的数据密钥(pg addon password zerofs;remove 保留桶后要用原口令 --encryption-password 重接,否则 server 报 Invalid password or corrupted key);fencing 需 conditional put——--conditional-put auto(MinIO 2022-08+/真 S3)或给缺此能力的存储走 redis(必须显式 --redis-dsn),none 仅限单写者;`pg addon zerofs start |
duckdb |
DuckDB——嵌入式 OLAP 分析数据库(“SQLite for analytics”)的宿主二进制形态(无容器、无守护进程):C++ 列式引擎,向量化执行,直接查询 Parquet/CSV/JSON/Arrow;从 GitHub releases 下载、sha256 校验(PATH 上已有 duckdb 则直接复用);常用扩展(httpfs、parquet、json)自动安装;--version 指定版本(默认 1.5.6 LTS,也支持 2.0.0);`pg addon duckdb status |
clickhouse |
ClickHouse——单机列式 OLAP 数据库,直接跑官方 docker.io/clickhouse/clickhouse-server 镜像(默认固定 26.8 LTS 26.8.36.3,amd64/arm64;host 网络,数据/日志/配置/备份目录均绑定挂载);认证始终开启,安装时生成 admin 账号(pg addon password clickhouse 取回密码);默认 HTTP 8123 + 原生 TCP 9000,取自两个独立端口池(TCP 与 S3 API 池共享);Listen 默认 127.0.0.1;无 TLS 监听(用 nginx 前置),单节点、无复制/HA;`pg addon clickhouse status |
kafka |
Kafka——KRaft broker(无需 ZooKeeper),官方 docker.io/apache/kafka 镜像(固定 4.4.0,amd64/arm64;host 网络、渲染 server.properties、数据目录绑定挂载);单机 broker+controller combined 模式,或跨主机静态 controller quorum 中的一个节点(每台主机用 --cluster/--node-id/--peer 各装一次;推导 cluster id,成员不可在线变更);默认 broker 9092、controller 19093(各自端口池,controller 避开 Alertmanager 的 9093);Listen 默认 127.0.0.1(集群要求 0.0.0.0);默认 PLAINTEXT,可选 TLS 监听(--tls,SSL 端口取自 9093 池;仅加密,不认证客户端;集群客户端需拼接 CA bundle);RF=min(3, voters) / minISR=min(2, RF);pg addon kafka status|shell|topic create/list/describe/delete/retention|group list/describe|cluster status;--config-option 覆盖项持久化在 pg.yaml;仅 Linux |
rabbitmq |
RabbitMQ——消息 broker,官方 docker.io/library/rabbitmq management 镜像(固定 4.4.0,amd64/arm64;host 网络、渲染 rabbitmq.conf、数据目录绑定挂载、enabled_plugins 固定为 management,集群模式另加 etcd 发现插件);单节点,或通过 etcd 自动发现组建多机集群的一个成员(--cluster、--node-name、--etcd-endpoints 或 --etcd,共享密钥用 pg addon rabbitmq secret generate + --secrets-from-etcd,或 --erlang-cookie-file/--admin-password-file 手动分发);默认 AMQP 5672 与 management Web UI/API 15672,另有 EPMD 4369 与 Erlang distribution 25672(单机为端口池分配且仅 loopback,集群成员固定且需网络可达,一台主机一个集群实例);Listen 单机默认 127.0.0.1,集群默认 0.0.0.0;认证始终开启(自动生成管理员密码,pg addon password rabbitmq 取回,每次启动对账);可选增量 TLS(--tls:AMQPS 5671 + management HTTPS 15671,自签 CA 或自带 --tls-cert/--tls-key;仅加密,不认证客户端,distribution 流量仍不加密);pg addon rabbitmq status|shell|queue ...——declare/info/publish/get/bind/unbind/bindings 走 management API,list/purge/delete 走 rabbitmqctl;集群 declare 默认 quorum 队列(--type classic 不复制);pg addon rabbitmq cluster status|forget NODE;不启用 Prometheus 插件;仅 Linux |
rocketmq |
RocketMQ——Apache RocketMQ 5.x 消息中间件,官方 docker.io/apache/rocketmq 镜像(host 网络,渲染 namesrv.conf/broker.conf/rmq-proxy.json,broker/controller store 与日志绑定挂载);每个实例三个容器(NameServer + broker + proxy,按 namesrv → broker → proxy 启动);单机为一个无副本 async master,或跨主机集群的一个成员——每个 NameServer 内嵌 DLedger controller,broker 以 controller 模式运行并自动主备切换(--cluster/--controller-id/--advertise-host/--peer ID@HOST,每台主机传完整对端列表,一台主机一个成员,固定端口 9876/9877/10911/10912/8080/8081);NameServer 9876、controller 9877、broker 10911(HA 10912、VIP 10909)、proxy remoting 8080 与 gRPC 8081;无认证、无 TLS、无 Web UI;pg addon rocketmq status|shell|topic create/list/describe/delete|send|cluster status;--config-option 的 broker.conf 覆盖项持久化在 pg.yaml;controller/broker store 有状态,3 个以上成员才有 quorum 容错,安装后身份不可变;仅 Linux |
每个插件都有独立页面,包含命令、参数与故障排除说明。
Patroni 高可用(
pg ha)不在此列——它不通过pg addon安装,而是独立的顶层命令, 文档也已从本节拆出,见 HA 集群。本节的 etcd 与 HAProxy 仍是可被pg ha集群使用的插件。
工作原理
插件作为独立容器运行,通过 pg.yaml 管理:
pg addon install生成配置并启动插件容器- 配置与数据存放在
<base-dir>/addon/<addon-name>/ - 插件容器通过主机网络通信
- 配置更新时容器自动重启
命名空间隔离: 插件遵循配置的 namespace 设置 —— 容器名包含命名空间前缀,
因此不同配置文件可以管理互不冲突的独立插件。
常用命令
各插件的具体参数与示例见其独立页面: Pgbouncer · etcd · PgDog · PostgREST · HAProxy · MinIO · Silo · rustfs · Garage · SeaweedFS · Redis · Predixy · pgAdmin 4 · nginx · Grafana · Prometheus · Alertmanager · VictoriaMetrics · node_exporter · pg_exporter · VictoriaLogs · vlagent · InfluxDB · influx2tsdb · ClickHouse · Kafka · RabbitMQ · RocketMQ · VMCluster · VLCluster · Gitea · BeeGFS · JuiceFS · ZeroFS · DuckDB。Patroni 相关页面见 HA 集群。
16.1 - PgBouncer
PgBouncer 是轻量级的 PostgreSQL 连接池。作为 pgcli 插件,它以容器形式运行在 一个或多个 PostgreSQL 实例前面,在大量短生命周期客户端会话之间复用服务端连接 —— 默认使用事务级池化。
PgBouncer 支持两种部署模式:
- 本地模式:
pg addon install pgbouncer -i <instance>—— 存储在instances.<name>.addons - 远程模式:
pg addon install pgbouncer --dsn <dsn> --pg-name <name>—— 存储在顶层addons.pgbouncer
平台支持
PgBouncer 支持 Linux(host 网络,含跨主机连接池)与 macOS 单主机
dev/test(容器加入 pgcli-net bridge 网络并发布端口,与 podman machine 下的
PG 实例同一套机制)。macOS 上:
- 本地模式开箱即用:PgBouncer 自动按容器名访问受管实例,无需配置地址。
- 远程模式:
--dsn里的主机必须是从 Mac 可达的地址 —— 别填127.0.0.1,那是 Mac 本机,而不是 podman machine 虚拟机。 - 客户端连接仍然指向
127.0.0.1:<port>;gvproxy 会把发布端口转发到 Mac 的 回环,与 PG 实例完全一致。
工作原理
pg addon install pgbouncer生成配置文件并启动容器- 配置文件存储在
<base-dir>/addon/pgbouncer/<instance>/ - 容器通过主机网络(Linux)或
pgcli-netbridge 按容器名(macOS)访问 PostgreSQL - 配置文件更新时自动重启容器应用变更
命名空间隔离: PgBouncer 遵循配置的 namespace 设置。容器名和认证用户包含
命名空间前缀(例如 pgb_<namespace>_<instance>),因此使用不同命名空间的不同配置
文件可以为同一个 PostgreSQL 实例创建独立的连接池,而不会相互冲突。
命令
安装
参数说明:
| 参数 | 说明 | 默认值 |
|---|---|---|
--dsn |
PG 实例连接字符串(远程模式) | — |
--pg-name |
远程 PgBouncer 标识名(与 –dsn 一起使用) | — |
--max-client-conn |
最大客户端连接数 | 100 |
--default-pool-size |
默认连接池大小 | 20 |
--min-pool-size |
最小连接池大小(预热) | 0 |
--reserve-pool-size |
预留连接池大小(突发) | 0 |
--max-db-connections |
每数据库最大连接数 | 50 |
--max-user-connections |
每用户最大连接数 | 0(无限制) |
--server-idle-timeout |
空闲服务端连接超时(秒) | 600 |
--server-lifetime |
服务端连接最大生命周期(秒) | 3600 |
--server-connect-timeout |
连接 PostgreSQL 超时(秒) | 15 |
--query-timeout |
查询超时(秒) | 0(无限制) |
--query-wait-timeout |
等待连接超时(秒) | 120 |
--idle-transaction-timeout |
空闲事务超时(秒) | 0 |
--transaction-timeout |
事务超时(秒) | 0 |
--admin-users |
管理员用户列表 | (空) |
--stats-users |
只读统计用户列表 | (空) |
--log-connections |
记录连接日志 | 0 |
--log-disconnections |
记录断开日志 | 0 |
列出
PgBouncer 出现在 Local add-ons / Remote add-ons 段:
启动与停止
宿主机重启或手动停掉后,无需重跑 install 即可拉起连接池(配置与 auth 用户不动):
start 幂等 —— 已在运行则不动。stop 保留容器与配置;要彻底清理请用
pg addon remove。
卸载
工作流:
- 停止并删除插件容器
- 删除
<base-dir>/addon/pgbouncer/<instance>/目录及配置文件 - 从
pg.yaml中移除插件配置
配置文件
本地模式(存储在 instances.<name>.addons 下):
远程模式(存储在顶层 addons 下):
生成的配置文件按实例存储在 <base-dir>/addon/pgbouncer/:
认证方式
PgBouncer 使用 auth_query 方式进行动态密码查询:
- 为每个连接池在 PostgreSQL 上创建独立的认证用户,命名为
pgb_<namespace>_<instance>(例如pgb_default_mypg、pgb_test-ns_my-remote) - 安装共享的
SECURITY DEFINER函数pgbouncer_lookup()用于查询pg_authid - 当客户端连接时,PgBouncer 使用自己的认证用户执行 auth_query,获取真实用户的密码哈希
- 密码缓存在 PgBouncer 内存中,后续连接直接使用
userlist.txt 仅包含连接池的认证用户(明文密码)。其他用户通过 auth_query 动态认证——无需密码同步。
每个连接池都有独立的 PG 认证用户,因此多个连接池(本地或跨主机)指向同一 PG 实例时不会相互冲突。
修改 PostgreSQL 用户密码后,重新运行 pg addon install pgbouncer 重置认证缓存,或连接管理控制台执行 RECONNECT。
连接方式
客户端通过插件端口连接:
端口分配: PgBouncer 默认使用端口 56432。如果端口被占用,pgcli 会自动分配下一个可用端口。查看当前端口:pg addon list。
使用场景
高并发场景
短连接应用
长连接应用
只读副本
监控
PgBouncer 提供管理控制台用于监控连接池和运行状态。
连接管理控制台
使用管理员用户连接到 pgbouncer 虚拟数据库:
示例:
注意: 只有在 admin_users 中列出的用户才能访问管理控制台。
常用 SHOW 命令
| 命令 | 说明 |
|---|---|
SHOW pools |
连接池状态(活跃/等待的客户端和服务端连接数) |
SHOW clients |
所有当前客户端连接详情 |
SHOW servers |
所有当前服务端(PostgreSQL)连接详情 |
SHOW databases |
已配置的数据库及其连接参数 |
SHOW stats |
流量统计(事务数、查询数、收发字节数) |
SHOW config |
所有运行时的配置参数 |
SHOW sockets |
底层 TCP 套接字信息 |
SHOW active_sockets |
活跃的 TCP 套接字 |
SHOW mem |
内存使用统计 |
SHOW lists |
各类对象数量汇总 |
示例
其他管理命令
| 命令 | 说明 |
|---|---|
RELOAD |
重新加载配置文件 |
PAUSE |
暂停连接池(等待事务完成) |
RESUME |
恢复连接池 |
RECONNECT |
强制重新连接所有服务端连接 |
SHUTDOWN |
关闭 PgBouncer |
故障排除
连接池满
原因:达到 max_client_conn 限制。
用户认证失败
原因:认证缓存包含过期的密码哈希。
容器无法启动
常见原因:配置文件语法错误、用户列表格式不正确、端口被占用。
查询超时
原因:查询执行时间超过 query_timeout。
注意事项
- 插件端口: PgBouncer 默认使用 56432 端口,确保防火墙规则允许访问
- 用户密码: 修改 PostgreSQL 用户密码后,需重新运行
pg addon install重置认证缓存 - 配置文件: 手动编辑配置文件后,重启容器应用变更:
- 事务模式:
transaction模式不支持会话级功能(如临时表),需使用session模式
16.2 - Etcd
etcd 是一个分布式键值存储。pgcli 可以将一个或多个 etcd 成员作为独立的顶层 addon 运行 —— 它是共享基础设施,而非绑定某个实例的 sidecar。常见用途是为 PostgreSQL 高可用栈充当 DCS(Distributed Concurrent Store),或作为通用的配置 / 锁服务。
安全提示: pgcli 管理的 etcd 成员目前不提供 TLS/CA 证书,也没有 认证 / RBAC —— 任何能连到 client 端口的客户端都拥有完整读写权限。部署时请依靠 网络隔离(默认仅绑定回环;广播端口务必放在受信网络内)。CA/TLS 与认证/RBAC 支持在后续版本中规划开发。
成员通过一次次 pg addon install etcd 管理:第一个成员引导(bootstrap)集群,
后续成员动态加入同名的集群 —— 可以在同一台机器,也可以跨机器。
工作原理
- 共享基础设施: etcd 存放在
pg.yaml顶层的addons.etcdmap 中,以成员名 为 key,不隶属于任何单个实例。 - host 网络 + 可配置监听: 每个成员以
--network host运行。默认情况下 client/peer URL 绑定到127.0.0.1—— etcd 本身不带认证,单机集群仅回环暴露是 预期的安全姿态。跨机集群中每个成员通过--advertise-host广播一个对端可达的 地址,同时仍然监听回环,本机 etcdctl 与远端 peer 都能访问。 - 动态成员管理: 第一个成员以
--initial-cluster-state new启动;每个后续成员 先对一个运行中的对等成员执行etcdctl member add注册,再以--initial-cluster-state existing启动。pgcli 自动完成这些步骤 —— 同机走成员 容器,跨机走临时 etcdctl 容器。 - 集群身份:
--cluster值相同的成员加入同一个 etcd 集群(对应 etcd 的--initial-cluster-token)。默认值为pgcli-etcd。 - 内置调优: 每个成员启动时都带周期性压缩(
--auto-compaction-mode periodic、--auto-compaction-retention 24h)以及 8 GiB 后端配额 (--quota-backend-bytes 8589934592)—— 对于小型 HA 元数据存储是合理的默认值。
部署形态
pgcli 支持两种集群形态,使用的安装命令完全相同 —— 区别只在于成员是否共用一台 主机。
单机 —— 所有成员在同一台机器(测试 / 开发)
开发机或 CI 的经典布局:每个成员用不同的自动分配端口监听回环。无需
--advertise-host(默认 127.0.0.1),无需改防火墙,主机之外什么都访问不到。
三个成员同属 pgcli-etcd 集群;m1 引导,m2/m3 动态加入。这能体验真正的 3 节点
raft 语义,但没有任何主机隔离 —— 整集群随一台机器一起消失,正因如此它只是
测试形态。
跨主机 —— 每台机器一个成员(生产 HA)
生产形态:把成员分散到不同机器(最好 3 或 5 个奇数个 —— 见拓扑与 Quorum)。
每个成员广播自己主机的 LAN 地址;第一个成员 bootstrap 时必须带
--advertise-host,远端 peer 才拨得通。端口可以在各主机上重复,因为每台只绑
自己的网卡。
要求:每个成员的 --advertise-host 互相可达(同一 LAN),每台主机的防火墙为
所有成员的 client 与 peer 端口向其它成员放行(peer 是双向通信 —— 例如 4 成员
默认端口集群放行 2379-2386/tcp),且所有成员使用相同的 --cluster token。任意
一台机器宕机后其余主机照常工作;集群只需要保住机器的多数派。
| 单机 | 跨主机 | |
|---|---|---|
| 适用 | 开发、CI、功能测试 | 生产 HA |
--advertise-host |
省略(回环默认) | 必填,且每个成员都要 |
--join |
不用 | 除第一个外每个成员都要 |
| 端口 | 同主机内每个成员互不相同 | 可重复,各主机绑各自网卡 |
| 防火墙 | 无需(仅回环) | 每个成员的 client+peer 端口,双向放行 |
| 能否扛单机故障 | 否 | 能(quorum 成立时) |
命令
创建第一个成员
client 端口从 2379 起分配,peer 端口取下一个空闲端口(2380),由
etcd_start_port 自动分配。
往同一集群添加成员
在 m1 运行时,安装相同 --cluster 的其它成员即加入该集群:
pgcli 会找到一个运行中的对等成员作为协调者,通过 etcdctl member add 注册
m2/m3,再以 --initial-cluster-state existing 启动它们。端口继续无冲突地自动
分配(m2 → 2381/2382,m3 → 2383/2384)。刚扩容的集群在 leader 重选期间会短暂
失去 quorum;pgcli 会自动重试成员操作,无需在安装之间手动等待。
使用不同的 --cluster 可让成员归属另一个独立的 etcd 集群。
跨机器添加成员
不同主机上的成员也能组成同一个集群。每个跨机成员都必须广播一个对端可达的
地址,用 --advertise-host 指定。在已有成员的主机上,bootstrap 时传入本机 LAN
地址:
然后在新主机上,把 --join 指向任一现有成员的 client endpoint。pgcli 通过该
endpoint 完成注册(借助临时 etcdctl 容器 —— 本机无需已有成员容器或镜像,镜像会
按需拉取),取响应中的权威 ETCD_INITIAL_CLUSTER,再启动成员:
跨机集群注意事项:
- 每个成员都需要可达的
--advertise-host,第一个也不例外。它广播的 peer URL 会进入集群成员列表,用默认127.0.0.1启动的首个成员永远无法被其它主机 加入。计划跨机组网时,bootstrap 时就要传 LAN 地址。 --join必须搭配--cluster,且取值要与远端集群的 token 一致 —— 成员注册 由远端集群校验,而非本地配置。--join必须搭配--advertise-host;否则其它成员会注册一个拨不通的 peer URL。- 端口可以跨主机重复(两台机器都用
2379/2380),因为各主机只绑定自己的网卡。 - 成员间 client/peer 端口必须互通 —— 为每个成员的端口、双向放行防火墙。peer
之间是双向互连(都拨对方的 peer 端口),client(以及
--join/pg etcdctl)需要 能访问 client 端口。自动分配的端口每个成员不同,从安装输出或pg addon list里 读取实际端口(或用--client-port/--peer-port固定),按范围放行 —— 例如 4 成员默认端口集群放行2379-2386/tcp。漏配防火墙正是成员长时间etcdserver: no leader、或--join超时的常见原因。 - 对已注册的名字重复执行相同的
--join安装会直接报错;先用pg etcdctl member remove <hex-id>从集群注销。
用 pg etcdctl 查看
pg etcdctl 从短生命周期容器里运行 etcd 官方客户端 —— 无需钻进
成员容器(本机一个成员都没有时也能用,镜像按需拉取)。目标端点取自
ETCDCTL_ENDPOINTS,未设置时回退到配置中的第一个成员:
pg 自身参数解析器不接受的 etcdctl 参数(-w table、--hex 等)放在 -- 之后。
查看
etcd 成员出现在 Infra add-ons (etcd) 段:
启动与停止
宿主机重启或手动停掉后,无需重跑 install 即可把成员拉回来 —— 不会重新执行
member add,复用已有配置与磁盘数据:
start 可重复执行:已在运行的成员是 no-op;容器被删掉的成员会以
--initial-cluster-state existing 从其数据重建。跨机集群启动某成员前,请确认
其对端可达(quorum 成立)。
移除成员
移除时会先从集群中注销该成员(先解析其十六进制 member ID —— etcd v3.5 的
member remove 接受 ID 而非名字),再删除容器和该成员的数据目录。只要 quorum
仍成立,其余成员保持健康。
跨主机成员需要单独注销。 上面这一步的自动注销,只在本地 pg.yaml 里存在
同一集群的另一个运行中成员时才会生效 —— pgcli 看不到住在其他主机上的成员。因
此对于跨主机集群,在运行该成员的那台主机上执行移除,只会删掉容器和数据,集
群的成员列表里仍会留下它(一个"残留"成员)—— 存活的成员会一直尝试和已经删
除的这台主机建立 peer 连接。
正确的做法是分两步:
pg etcdctl 需要一个可达的 peer 来通信 —— 把 ETCDCTL_ENDPOINTS 指向该集群
中仍在运行的成员(在仍有本机运行成员的主机上执行时会自动回退到它;否则就
显式设置),再按 ID 移除(见用 pg etcdctl 查看)。
事后用 pg etcdctl member list 复核:被移除的名字应该已经不在了。
参数
| 参数 | 说明 | 默认值 |
|---|---|---|
--name |
成员名,同时作为 pg.yaml 里的配置 key |
etcd |
--cluster |
集群身份(--initial-cluster-token),取值相同即同集群 |
pgcli-etcd |
--client-port |
client 主机端口(0 = 从 etcd_start_port 自动分配) |
auto |
--peer-port |
peer 主机端口(0 = 自动分配,取下一个空闲端口) | auto |
--image |
etcd 镜像 tag | quay.io/coreos/etcd:v3.5.30 |
--data-dir |
数据目录根——绝对路径,或相对 base_dir;每个成员用 <root>/<name>/data |
<base_dir>/addon/etcd |
--advertise-host |
本成员 peer/client URL 中广播的主机(留空 = 127.0.0.1 单机;跨机填 LAN IP 或 FQDN) |
127.0.0.1 |
--join |
要跨机加入的现有成员 client endpoint,如 http://10.0.0.1:2379(隐含 --initial-cluster-state existing;需搭配 --advertise-host 与 --cluster) |
— |
容器名遵循命名空间约定:pgcli-etcd-<namespace>-<name>(未设 namespace 时省略)。
数据目录布局
数据始终按 <root>/<name>/data 布局,因此同机多成员绝不共用目录:
--data-dir 指定根目录。相对值按配置的 base_dir 解析;解析后的根会被持久化,
保证配置文件可移植:
pg addon remove 只删除该成员的 <name>/data 目录,保留共享根。
连接
用 pg etcdctl —— 它在短生命周期容器里运行 etcd 的 v3 客户端,指向某个
成员的 client URL。目标端点取自 ETCDCTL_ENDPOINTS,未设置时回退到配置中的
第一个成员。etcdctl 的原生参数放在 -- 之后:
pg etcdctl 始终使用 v3 API(ETCDCTL_API=3,etcdctl 的默认值);不支持 v2。
拓扑与 Quorum
etcd 需要成员多数派(quorum)才能接受写入:
| 成员数 | Quorum | 可容忍故障数 |
|---|---|---|
| 1 | 1 | 0 |
| 3 | 2 | 1 |
| 5 | 3 | 2 |
- 使用奇数个成员 —— 生产 HA 推荐 3 或 5。
- 移除成员可能让集群跌破 quorum(例如 3 台只剩 1 台存活),此时无法提交写入。请始终保持多数在线。
配置
安装后,pg.yaml 在顶层 addons.etcd 下记录每个成员:
端口起始值可通过顶层 etcd_start_port 配置(默认 2379)。
开机自启
容器还带有 --restart unless-stopped 策略,由每容器的 conmon 监控进程执行
(无需 podman 守护进程)——它能应对进程崩溃,但不覆盖主机重启。要让成员
在重启后自动拉起,按成员启用 autostart:
这会把成员的 autostart: true 写入 pg.yaml,并安装/刷新开机服务(见
开机自启)。开机时只做启动:拉起成员已存在的容器,
绝不重新执行 member add——已初始化的成员从磁盘加载集群状态即可重新加入。
跨主机集群时,在各自主机上为该主机的成员执行命令。pg autostart status
可查看所有成员的状态。
说明
- 单成员 vs 集群: 仅
--name m1得到单成员集群;用相同--cluster再装更多 成员即可扩容。 - 生产可用: 每个成员默认已带周期性压缩与 8 GiB 后端配额等调优参数(见上文 「工作原理」),可直接用于生产环境的 DCS 场景。
- 重复安装是幂等的: 对运行中的成员再次执行
pg addon install etcd --name <m>会以更新后的参数重建其容器;已在集群中注册的成员不会被重复 add。
16.3 - PgDog
PgDog 是一个用 Rust 编写的高性能 Postgres 代理(PgCat 的继任者)。它提供连接池化、跨副本的读写负载均衡,以及水平分片,位于一个或多 个 PostgreSQL 后端之前。pgcli 可将 PgDog 作为独立的顶层插件运行:它是共享 基础设施,而非依附某个实例的 sidecar。
安全提示: PgDog 使用
users.toml中的明文密码对客户端做认证。 pgcli 以0600权限写出该文件并只读挂载进容器,但密码仍以明文存在于磁盘和pg.yaml中。请将监听端口保持在回环地址(默认)或可信网络内,并把配置文件 当作机密对待。
PgDog 由两个 TOML 文件配置——pgdog.toml(代理本身、其后端、以及分片规则)
和 users.toml(客户端凭据)。pgcli 完全根据安装参数生成这两个文件:没有
需要手工编辑的配置文件,因此 pg addon install pgdog 就是唯一事实来源,且具
有幂等性——重新执行会根据所给参数重新渲染文件。
平台支持
PgDog 支持 Linux(host 网络)与 macOS 单主机 dev/test:macOS 下容器加
入 PG 实例在 podman machine 里使用的同一张 pgcli-net bridge 网络,并发布客户
端与 openmetrics 两个端口。macOS 上:
- 容器内的监听地址会被自动放宽为
0.0.0.0,否则发布端口转发不到只绑回环的进 程;对外连接的地址仍然是127.0.0.1:<port>。 - 后端必须是从 bridge 可达的地址。 PgDog 不会自动解析
--backend里的名字, 所以127.0.0.1指向的是 Mac 本机,而不是 podman machine 虚拟机。请给每个--backend填目标实例的容器名(用pg status -i <instance>查看Container:一行,例如app=pgcli-pg-mypg:5432:...),或 Mac 能路由到的地 址 —— 对受管实例绝对不要填127.0.0.1。
工作原理
- 共享基础设施: PgDog 存放在
pg.yaml顶层的addons.pgdogmap 中,按 代理名索引——不隶属于任何单个实例。 - 主机网络(Linux)/ bridge 网络(macOS): 代理监听其客户端端口(从
pgdog_start_port自动分配,默认 7432),并在下一个空闲端口提供 Prometheus 风格的 openmetrics 端口。Linux 上以--network host运行;macOS 上加入pgcli-net并发布两个端口。 - 生成的配置:
pgdog.toml+users.toml写在<base-dir>/addon/pgdog/<name>/下,并绑定挂载进容器的/pgdog。 - 锁定镜像: 默认
ghcr.io/pgdogdev/pgdog:v0.1.57,可用--image覆盖。
安装
最小的单后端代理(仅演示连接池化,见下方说明):
加
--sharded-table是为了让这份示例成为完整、自洽的配置(与下方"分片与副 本"示例对齐)。注意:单个--backend无从分片——不管是否声明,所有数据最 终都会落到那唯一后端,所以这份最小示例只体现连接池化。真正分片需要至少两个不 同shard编号的--backend(见分片与副本)。
--backend NAME=HOST:PORT:DBNAME[:SHARD[:ROLE]]—— 一个[[databases]]条目。NAME是客户端连接时使用的逻辑数据库名;DBNAME是后端上真实的数据 库名。SHARD默认为 0;ROLE默认为 PgDog 默认值(primary,读路由可设replica)。可重复。--user NAME:PASSWORD[:DBNAME]—— 一个[[users]]条目。DBNAME是该用户 可访问的逻辑数据库(某个--backend的名字),缺省时取第一个后端的名字。可 重复。- 至少需要一个
--backend和一个--user。
其他参数:
| 参数 | 含义 | 默认值 |
|---|---|---|
--name |
代理 / 插件键名 | pgdog |
--port |
客户端主机端口(openmetrics 取下一个空闲端口) | 自动分配 |
--host |
监听地址 | 127.0.0.1 |
--pool-mode |
transaction 或 session |
transaction |
--workers |
工作线程数 | 2 |
--default-pool-size |
每个 user/db 对的服务器连接数 | 10 |
--image |
PgDog 镜像 tag | ghcr.io/pgdogdev/pgdog:v0.1.57 |
分片与副本
对每个分片 / 角色重复 --backend,并用
--sharded-table DBNAME:TABLE:COLUMN:DATA_TYPE 声明分片键:
这会把两个分片(各含一主一副本)置于逻辑数据库 app 之后,并按 id 列路由
users 表。
准备后端
PgDog 路由到的数据库、表与角色必须已存在于后端——安装代理并不会创建它们。对于
本地 pgcli 实例,用 pg exec 创建每个分片库、其分片表,以及登录角色。以单机
default 实例为例(分片即同一端口上的不同数据库),下面的示例先准备后端(第 1–4
步)、安装代理(第 5 步),再写入数据以体现路由效果(第 6–7 步):
pg exec -i default -- psql -d <db> 在指定实例的容器内针对某个数据库执行命令。
上面的 id bigint 列与 --sharded-table "app:users:id:bigint" 的声明一致,因
此 PgDog 能按 id 路由 users 的行。第 6、7 步让这层路由可见:两个分片的查询
返回互不重叠的 id 子集(具体划分取决于 PgDog 的哈希),两者合起来正好覆盖所
有写入行——没有一行会同时落到两个分片。远程后端则直接对各主机执行等价 SQL。
后端角色不可省略。 默认情况下 PgDog 用客户端的 --user 名字及其
users.toml 密码去连后端。所以每个后端都必须存在名为 appuser(等)的登录角
色,否则连接会失败并报 password for user "..." is wrong, or the database does not exist——即使在回环 trust 配置下密码被忽略,角色仍必须存在。若走密码认证
(非回环 scram-sha-256 的默认行为),users.toml 里的密码必须与角色一致。
解耦客户端与后端用户。 PgDog 本身支持用不同于客户端的用户去连后端:一个
[[users]]条目可带server_user/server_password,一个[[databases]]条目可带user/password(后者优先级更高)。但 pgcli 的安装参数目前不会 生成这些字段,因此经pg addon install pgdog安装时,后端角色名始终等于--user的名字。若你需要一个共享的后端角色(例如统一的postgres超级用户, 或与所有客户端用户名都不同的名字),只能手工编辑生成的users.toml/pgdog.toml并重启容器——但请注意:重新执行 install 会按参数重新 生成这两个文件,覆盖掉你的手工改动。
连接
安装摘要会打印接入端点。客户端通过代理的客户端端口连接,使用逻辑数据库名和某个
--user 凭据——在 pgcli 里,就是一个指向代理的 --dsn:
Prometheus 指标在 openmetrics 端口提供:
向分片写入
写入如何落到各分片,取决于语句的形式。以下是在 PgDog v0.1.57 上的实测。
单行 INSERT —— 按分片键路由。 一条语句只带一个 VALUES 元组时,
PgDog 对 id 做哈希,把该行送到唯一一个分片:
以六条单行语句分别插入 id 1–6 时,两分片各得一部分(一次实测:shard0 收到
1,2,shard1 收到 3,4,5,6 —— 具体划分取决于 PgDog 的哈希,不应当作可依赖
的结果)。没有任何一行同时落入两个分片。
多行 INSERT —— 广播而非拆分。 一条语句带多个 VALUES 元组时,会被原
样送到每一个分片,于是每个分片都拿到全部行:
这条语句报告 INSERT 0 8 —— 每个分片各 4 行。分片内 PRIMARY KEY 仍然
有效(重复 id 分别落在不同分片,永不冲突),且没有任何错误或告警:每个分
片静默地多出一份完整副本。随后经代理读取就会看到每个 id 都成对重复出现。
正确的批量导入方式: 一条语句一行、逐行经代理插入(各行单独路由);或者
绕开代理,直接对各分片做导入(例如 pg exec -i <inst> -- psql -d <shard> 跑
COPY)。
配置
安装后,pg.yaml 在顶层 addons.pgdog 下记录每个代理:
端口基址可通过顶层 pgdog_start_port(默认 7432)配置;openmetrics 端口总是紧接
其上方分配。
查看列表
每个 PgDog 代理都会出现在 Infra add-ons (pgdog) 小节下,附带运行时状态、 端口、后端 / 用户数量、镜像和容器名:
Backends / Users 取自 pgdog.toml / users.toml 中的条目数;Status
反映容器的实时状态(已停止的代理显示为 stopped)。
启动与停止
宿主机重启或手动停掉后,无需重跑 install 即可拉起代理(配置与 users 不动):
start 幂等 —— 已在运行则不动;容器被删掉的代理会从其配置目录重建。
开机自启
容器带有 --restart unless-stopped 策略(应对崩溃,而非重启)。要在主机重启后
拉起代理:
这会设置 autostart: true 并安装 / 刷新开机服务(见
开机自启)。开机时是仅启动语义:它启动已存在的容器、读
取磁盘上已有的 pgdog.toml/users.toml,不会重新生成配置或认证。请先安装代理。
pg autostart status 会列出每个目标的状态。
日志
PgDog 以结构化 INFO 行记录连接池事件(新建服务器连接、认证、客户端连接 / 断
开)。若某个连接池不断重试并报 password for user "..." is wrong, or the database does not exist,说明上面「准备后端」的第 3 或第 4 步被跳过——后端没有
该登录角色,或其密码与 users.toml 不一致。
注意事项
- 重装幂等: 重新执行
pg addon install pgdog --name <p>会根据所给参数重新 渲染配置并重建容器。后端 / 用户 / 分片列表被本命令的参数完全替换——省略某 个--backend是移除它,而非保留。 - 明文凭据: 密码存放在
users.toml和pg.yaml中。建议为代理用户单独使 用一个低权限角色,并限制对客户端端口的网络访问。 - 写入形式差异: 单行
INSERT按分片键路由;多行INSERT VALUES会被广 播到每个分片。详见向分片写入。 - 非实例级 sidecar: 与 PgBouncer 不同,PgDog 通过
--backend里的地址指向 后端——它不必指向某个 pgcli 管理的实例,一个代理可横跨多个数据库 / 分片。
16.4 - Garage
Garage 是 Deuxfleurs
出品的 Rust、AGPLv3 S3 兼容对象存储重实现,为小规模自建和异地分布式集群设
计。pgcli 将其作为独立的顶层插件运行,CLI 表面与
minio、silo、rustfs 一致
(install、list、logs、autostart、password),但其运行时模型在四个方面与三者
都不同,这也决定了本页的写法:
- 渲染 TOML 配置文件,而不是 argv/env。 Garage 从
/etc/garage.toml读 配置。pgcli 在实例目录下渲染该文件并以只读方式 bind 挂载;minio/silo/rustfs 插件则传 argv 或 env。 - 两类目录——且自 Garage v0.9.0 起多盘是原生支持的。
metadata_dir(LMDB)与data_dir(数据块)是各自独立的 bind 挂载,且metadata_dir永远只有一个。data_dir既可以是一个路径,也可以(上游原生 地)是{ path = …, capacity = … }的列表——一块盘一项,节点因此自己就能把 数据块铺在多块盘上,而不必在底下垫主机 LVM/RAID 拼一个大文件系统。pgcli 用 可重复的--drive暴露这个列表;如今 minio、silo、rustfs、seaweedfs、 garage 全都接受每节点一串盘——见多盘数据目录与容量与尺寸 计算。 - 四个端口,来自自己的池。 S3 API、节点间 RPC、
s3_web静态站点、 admin/metrics API——每实例一起从garage_start_port(默认 3900)分配, 与共享的 minio/silo/rustfs 池是两条独立游标。 - 无原生 TLS——仅明文 HTTP。 pgBackRest 的 S3 仓库强制 HTTPS,因此一个要
接收
archive-push的 Garage 存储必须由 nginx 插件前置——见用 nginx 前置实现 TLS。
和 rustfs 一样,在开源 MinIO 仓库已归档之后,Garage 是一条很好的替代路线。pgcli
默认按单节点运行(replication_factor = 1、garage server --single-node),
再加上几个集群 flag 即可跑成多节点跨主机集群——见多节点集
群。
平台支持: 已在 Linux(host 网络)上验证。macOS 的 bridge 路径会发布全 部四个端口、代码完整,遵循与其它对象存储一致的形态,但尚未在真机上跑过。
工作原理
一个实例一个容器。pgcli 直接拉取上游 docker.io/dxflrs/garage 镜像
(仅 pull)——不像 rustfs 需要自建 wrapper 镜像,因为 Garage 在自己的容器里以
root 运行、且不带 uid-drop,所以不存在主机侧属主处理要消化。
数据存放在长于容器生命周期的 bind 挂载主机目录里:
<base-dir>/addon/garage/<name>/data→/var/lib/garage/data(数据块),<base-dir>/addon/garage/<name>/meta→/var/lib/garage/meta(LMDB),
外加渲染出的 <base-dir>/addon/garage/<name>/garage.toml →
/etc/garage.toml,以只读方式挂载(Garage 从不改写自身配置)。用
--drive(多盘)时,那个单一数据目录被"每盘一个主机目录"取代,各自挂在编号的
容器槽位 /var/lib/garage/data1..N 上——meta 目录与渲染出的配置仍然各只有一个;
见多盘数据目录。
凭据的处理与其它存储类似,但 Garage 用的是 access key / secret key 对,而不 是 root user / password:
root_user是 access key id,首次安装时生成,带 Garage 自家的GK前缀形态(GK+ 32 位十六进制,即上游 quick-start 里openssl rand -hex 16产出的形状),或用--root-user指定。root_password是 secret key,首次安装时生成(64 位十六进制),或用--root-password指定。- 第三个秘密
rpc_secret(集群 RPC 签名密钥)以同样方式生成并一并存储。单节点 下它从不出现在 CLI 表面;集群 bootstrap 会把它连同加入命令一起打印一次, 由操作员搬运到下一台主机(pgcli 从不在远端执行任何东西)。
三者都落进 pg.yaml(addons.garage.<name>.*),因此重建容器是稳定的。单节
点首次启动时容器被传入 --single-node --default-bucket 以及
GARAGE_DEFAULT_ACCESS_KEY / GARAGE_DEFAULT_SECRET_KEY /
GARAGE_DEFAULT_BUCKET env,于是 Garage 自己创建这个 access key 和一个桶(以实
例名命名、归属该 key)——无需安装后再 exec。key 与桶都存在 metadata_dir 里,
因此能挺过容器重建。集群成员不带这些 flag 启动,key 与桶改由安装尾部显式播种
(见下文)。
安装
自带凭据: 这两个 flag 只在单节点安装时生效——集群成员从不读它们, 其 key 来自 quorum 达成那次安装的
garage key create。取值原样交给 Garage,由 Garage 执行限制:access key id 短于 8 字符、或 secret key 短于 16 字符,会在容器首启时被拒(Invalid default access key: … should be at least N characters long)——又因容器是后台起的,install本身报成功、节点却 在--restart unless-stopped下后台崩溃重启。若一次自带 key 的安装起来后无 法服务,查podman logs(或干脆让pg mc alias set报错)。生成出的形状 (GK+ 32 hex / 64 hex)远远越过这两个下界;mc另外要求 access key 为[A-Za-z0-9],所以保持字母数字。(pgcli 现在也会在安装前就按同样下界拦一道, 见下。)
输出会报告端点和播种的凭据:
对存活实例重跑 install 是 no-op:容器不会被重建(处于停止的会被启动,并给出
提示),flag 会合并进已存配置,已有凭据保持不变。传 --force 会重建容器,从而让
改动过的端口、监听地址或凭据生效——但不丢数据目录。
监听地址: 默认
127.0.0.1让存储留在本地。--listen 0.0.0.0(或pg.yaml里的listen键)把它暴露到网络。由于 Garage 服务的是明文 HTTP,任何能触达端口的人都能在传输中嗅到凭据——所以只在防火墙后公开绑定, 实际做法则是放在nginx TLS 前置之后。
--tls对 garage 会被拒绝。 Garage 没有可启用的 TLS 监听器;传--tls/--tls-cert/--tls-key会直接报错并把你指向 nginx 前置,而不是默默 忽略这个 flag。
用 nginx 前置实现 TLS
由于 pgcli 不把两个插件耦合在一起,在一个 Garage 存储前面终结 TLS 是显式的两步: 先装 garage,再用 nginx 前置它。
用
--conf-file,不要用--upstream模板。 S3 请求用 SigV4 对含端口 的Host头签名。模板模式转发Host $host,而 nginx 渲染它时不带端口 ——于是 Garage 重新算出的 canonical request 不同,每个写操作都以Invalid signature失败(已实测:经模板前置pg mc mb被拒;经下面--conf-file前置 则完整往返)。自定义配置还能设client_max_body_size 0;(nginx 默认的 1 MiB 上限会拒掉任何真实对象上传)以及很长的代理超时。
--tls 让 pgcli 生成自签 CA + leaf(public.crt / private.key,挂在
/etc/nginx/certs),并起一个 HTTPS 监听器。把 S3 客户端指向
https://<host>:8081,并把前置的 CA <base-dir>/addon/nginx/gtls/tls/ca.crt
交给消费方。
前置绑
0.0.0.0,Garage 留在回环。 上面那条listen绑所有网卡,让远端 主机(如要推归档的 Patroni 节点)能访问这个 TLS 端点——TLS 是在 nginx 处 终结的,所以这样做是安全的,而直接暴露 Garage 的明文端口则不是。proxy_pass仍指向127.0.0.1:<garage-api-port>:nginx 容器跑在 host 网络上,从它那里能达 到 Garage 的回环绑定,明文那一跳从不出机器。若前置只想本机可用,改回listen 127.0.0.1:<port> ssl;。
为什么
listen与proxy_pass两行要手写死。--conf-file模式下 pgcli 原样挂载你的配置,所以里面的端口必须与两个插件被分配到的端口对上——garage 的S3 API:端口取自它的安装输出,nginx 的HTTPS:端口取自pg addon list。 ($http_host和client_max_body_size两行,正是模板前置对 S3 会搞错的地 方。)
把那个 CA 弄到远程 Patroni 主机上而无需手动 scp,正是 pg backup fetch-ca 的用
武之地——它拨通 TLS 前置,直接从握手里取出根证书:
这个拉取是 trust-on-first-use——在信任之前,把打印出的 SHA-256 与前置主机上的
sha256sum ~/.pgcli/addon/nginx/gtls/tls/ca.crt 对一遍。
前置一个集群:一台,还是本地几台
集群并不需要把所有成员都挂到前置后面。S3 的复制是数据层的事:打到任意一
个成员的请求都是完整的,该成员自己会按 replication_factor 复制。所以上面那个
单后端前置对集群同样成立——把 proxy_pass 指到一个成员的 S3 端口即可。
只有当你想把"被前置的那台成员"从接入层单点里拿掉时,才多挂几台。upstream 里只保留前置所在主机可达的成员(通常是本机房——见异地分布拓 扑),每个机房各自跑自己的前置:
相比单后端示例,这带来两个新约束:
- 成员的 S3 API 必须离开回环。 集群模式只会自动放宽 RPC bind;
--listen默认仍是127.0.0.1,远端前置够不到别的主机的成员。被前置的成员要用--listen 0.0.0.0安装(放在防火墙后——讲 TLS 的是前置),或者接受"upstream 里只有同主机成员可用"。 - 各主机端口号不同——和 RPC 端口是同一个坑:每个成员的 S3 API 端口取自它自己
的安装输出 /
pg addon list(S3 URL:),未必是 3900。
这仍然是 --conf-file 配置:--upstream 模板对 S3 的毛病不变——Host $host
不带端口会让每个 SigV4 写失败,而且它没有 client_max_body_size 0(见上面的注
意)。多 upstream 服务器时默认的轮询就够用;max_fails/fail_timeout 按需调。
(已在 3 节点集群实测:pg mc 经这样一套前置完整往返;停掉 upstream 中的一台
成员后,前置经另一台照常服务。)
把 pgBackRest 接到前置后的 Garage 存储
一旦有了 TLS 端点,一个 Garage 支撑的 S3 仓库与 MinIO 的完全一样——S3 契约相
同(path-style、repo1-s3-uri-style=path):
完整流程见备份 → S3 对象存储仓
库。pg addon password garage --name store 会
为脚本打印 secret key。
多盘数据目录
Garage 节点不必在自己底下垫一个大文件系统。从 v0.9.0 起 Garage 原生接受一组
数据目录,并自己在其中均衡数据块;pgcli 用一个可重复的 --drive 暴露它:
产出的结果(在真盘上验证过:一个 3 节点集群,每台各带两块盘):
-
每块盘 bind 挂载到自己独立的容器槽位——按
--drive顺序为/var/lib/garage/data1、data2……——渲染出的garage.toml用 Garage 的数组 形态列出它们: -
每项的
capacity是必填的——v2.4 会拒绝一个既不带 capacity 也不带read_only = true的项(invalid data_dir config: data directories in data_dir should have a capacity value or be marked read_only)。pgcli 不为它 设旋钮,而是填入每块盘自己文件系统的可用容量(GiB,向下取整、下限 1)—— 这正是"每块独立挂载的盘给一个目录"这种布局想要的,也正是让 Garage 把铺开权重 倾向更大那块盘的机制。--capacity仍是节点级的那个数,不受此影响。 -
metadata_dir从不加入这个集合——它仍是与实例数据路径同级的那一个meta目 录(LMDB 按上游设计就是一个目录)。 -
--drive与--data-dir互斥:盘列表整体取代解析出的数据目录。对一个多 盘实例不带--drive重跑 install,已存的盘会保留(和其它键一样是合并语义)。 -
落在主机根设备上的盘只警告、照样使用(与 seaweedfs 一致;MinIO/silo 直接 拒绝,rustfs 硬性要求独立设备)——多盘只有在独立挂载的盘上才买得到东西。
-
pg addon list会显示成Drives: N (multi-disk data_dir),下面每盘一行容器槽位 <- 主机目录;安装摘要打印同样的映射,形如Drive 1: … -> /var/lib/garage/data1。 -
remove --clean-data会逐个处理每一块盘目录(外加 meta 目录与渲染出的配 置),每个仍然拒绝删除处于挂载状态的点。 -
数据块确实落在两块盘上:往一个两盘节点上传一个 48 MiB 对象后,两个主机目录里 都有数据;一个 64 MiB 对象经 3 节点
rf = 3集群从三台主机分别读取 往返,sha256sum一致。stop/start 保留对象;重建的容器读到的是同样的盘。
这套做法给不了你的是:单个孤立节点上的盘级冗余——见容量与尺寸计算。 日后新增一块盘时 Garage 自己的均衡规则(已有数据块不会被重排;只读项会被机会 式地、或经显式 repair 主动地排空)属于上游 Multi-HDD support 的范畴:pgcli 负责写配置,搬运由 Garage 完成。
多节点集群
Garage 的真正价值在于跨主机集群:不同 zone 的节点、CRDT 复制的 layout、quorum
读写。pgcli 用同一条 pg addon install garage 驱动它——没有新入口——只是多
了几个集群 flag。集群是逐台主机组装的(Patroni 模式):pgcli 从不在远端主机
上执行任何东西。第一台bootstrap,打印一条现成的加入命令(内含共享的
rpc_secret 和本节点的 id@addr),你把这条命令(为下一台改好参数)自己在下一
台上执行。
角色
安装角色由你传的 flag 决定:
| 角色 | 何时 | install 做什么 |
|---|---|---|
| single | 没有 --cluster |
v1 路径,逐字节不变(--single-node --default-bucket) |
| bootstrap | 有 --cluster、无 --peer |
生成 rpc_secret,起裸 garage server,staged 本机 layout 角色,打印加入命令 |
| join | --cluster + --peer <id@addr> |
带 --rpc-secret,connect 该 peer,staged 本机角色,凑齐 quorum 的那次 apply 并提交 |
三主机走一遍
主机 1——bootstrap。 给它 --zone、--capacity 和最终的
--replication-factor。此时集群里只有一个节点,Garage 会拒绝 apply layout
(它需要 replication_factor 个有正容量的节点),pgcli 把这变成警告而不是错误
——staged 角色是集群复制的,会一直等到最后一台提交:
集群成员和其它 Garage 节点一样,也可以带 --drive 用它自己的盘(见多盘数据目
录)——已在一个 3 主机集群上验证,每台各带两块盘。
摘要会打印 secret 和一份可复制的加入模板:
主机 2——join。 复制打印出的命令,把 --advertise-host 改成本机 IP、
--zone 换一个:
现在有两个节点 assigned,仍少于 rf = 3,所以再次警告。peer 连接是可传递
的:主机 2 只需要一个 --peer(主机 1);主机 3 指向任意一台已存在的节点都行。
主机 3——凑齐 quorum 的那次安装。 第三台能看到完整的 staged 角色集(assign
是集群复制的),于是它的 layout apply 把版本 1 提交——并且就是这一次安装
完成 access key 和 bucket 的播种:
每台安装都幂等地跑播种步骤,所以最后加入的那台天然就是执行播种的——不需要
主机 1 回填。key 名就是实例名(store),桶同名,key 被授予该桶的
read/write/owner。
播种与 secret
集群里单节点的 --default-bucket / GARAGE_DEFAULT_* 快速通道不可用,所以
pgcli 用 key create + bucket create + bucket allow 显式播种。两个推论:
garage key create的 secret 只打印一次,且不幂等(重跑会铸造第二个同名 key)。pgcli 因此先查key list,缺席才创建。执行播种的那台主机把 access/secret 对写进它自己的pg.yaml;其它主机只持有公开的 key id。- 非播种主机上的
pg addon password garage无 secret 可打印,会明确告知—— secret 在完成 quorum 的那台主机的pg.yaml里。去那台取回,或自己garage key create铸一把新 key。
版本冲突与重跑
layout apply --version N 要求恰好等于 当前版本 + 1——Garage 的乐观锁。两台
主机并发安装时,慢的一台 apply 失败;pgcli 报"layout changed concurrently —
re-run the same install command"。assign 幂等、重新 stage 无害,所以重跑只是重新
读版本再 apply。对已在线成员重跑安装其它步骤都是 no-op(容器在跑→
skipping creation;已 connect→视为成功)。
zone 与 replication-factor 建议
--replication-factor必须 ≤ 你打算加入的节点数;在rf个节点拿到正容量 角色之前,集群无法播种。- 每台主机给一个互不相同的
--zone,以承受整个故障域丢失;Garage 会尽量把 副本放进不同 zone。 --capacity单位 GiB,是 Garage 划给数据块的额度;capacity 为 0 的节点被视为 不承载数据。--advertise-host对每个成员都是必需的——它成为rpc_public_addr,peer 拨号 的地址。缺了它,集群成员不可达。(--listen为回环的集群成员仍会把 RPC bind 放宽到0.0.0.0;只有 S3/web/admin 端口留在回环上。)
容量与尺寸计算
Garage 的冗余单位是节点,不是盘——一个复制对象的每一块都会被放到不同节点
上(尽量放到不同 zone),所以只要还有 replication_factor 份副本活着,单块盘
或单台节点的故障都不会丢数据。一个节点如何处置自己的盘是另一个维度的事,而
Garage 从 v0.9.0 起原生处理它:一个节点可以给定好几个数据目录,按各自声明的容
量比例把数据块均衡在这些目录里(见多盘数据目录)。MinIO 与
rustfs 是靠节点内纠删码把盘聚成一个池、以此扛单盘损坏;Garage 的多盘模式是
聚合但没有冗余——丢一块盘就丢了该节点持有的那部分数据块,对象转而从其它
节点上的副本修复回来。若你要让一个孤零零的单节点也扛得住丢盘、又没有别的节点可
用来修复,那就该在 data_dir 底下垫 LVM/RAID。MinIO/rustfs 把盘管成一个池;
Garage 管的是节点,盘只用来铺。
--capacity 就是交给这个模型的那个数:本节点可存放的 GiB 数
(layout assign -c <N>GiB)。有两个性质必须内化:
- 它是放置权重,不是配额。容量用满时 Garage 也从不拒写——真正的剩余空间是
garage status里DataAvail报的值。在一块 20 GiB 的文件系统上设--capacity 100,只会让 Garage 把盘填满(然后写坏)。把它保持在后端文件系统 可用大小以内(或更低)——多盘模式下,是各盘可用大小的总和。 - 改它是一次 layout 变更:用新的
--capacity重跑同一条pg addon install(assign 幂等,apply 会把 layout 版本加一),Garage 就把 数据块重新均衡到扩出来的空间上。先把后端文件系统扩开,才是真正买到容量。
注意一个多盘节点上有两个容量数,它们不是同一个旋钮:--capacity 是本节点在
集群 layout 里的角色(这个节点被信任存放多少,跨它所有盘共享),而每个
data_dir 项的 capacity 告诉 Garage 那块盘有多大、以便按它加权地铺。pgcli
自动填每盘的数字(见下文);只有节点级的 --capacity 归你选。
可用容量是所有节点容量之和除以副本因子——每个对象都保留 rf 份:
| 节点数 | 单节点(--capacity) |
rf | 原始总量 | 可用 |
|---|---|---|---|---|
| 3 | 15 GiB | 3 | 45 GiB | 15 GiB |
| 4 | 15 GiB | 3 | 60 GiB | 20 GiB |
| 5 | 15 GiB | 3 | 75 GiB | 25 GiB |
一般规则:可用 = Σ容量 ÷ rf。同一套数学也解释了为什么 rf = 2 把存储开销降一
半、代价是只能容忍一台节点丢失;rf = 3 是 3 倍开销,但一次写入只要有 3 份中
的 2 份 ack 就提交,所以 3 节点的 rf = 3 集群写入时可容忍 1 台宕,5 节点的可
容忍 2 台(剩余成员里任意 2 台能 ack)。因此加第 4、第 5 台同时买到容错和可用
容量,这跟单纯镜像不一样。
停一台后的 quorum 行为
停掉任意一台,其余两台照常读写(rf = 3 时的 2/3 quorum):
pg addon start 会以同一个 Node ID 重新入集群(密钥对在
metadata_dir 里,不属于容器),garage status 重新显示它健康。
start/stop/remove/autostart/logs 永远只管本机容器——Patroni 语义;没有远程编
排。
异地分布拓扑:2 本地 + 1 远端
Garage 生来为此的生产形态:本机房放两个成员(各占一个机架/zone,z1 +
z2),第三个成员放在远端机房(z3)。这个形状和写延迟的数学很合拍:
rf = 3 的写只需 2/3 quorum 提交——本地两副本 ack 即算完成,远端副本异步
跟进,WAN 链路慢、甚至临时断开都不拖慢写入。nginx TLS 前置只指向两个本地成员
(见前置一个集群);远端成员只承载数据、不进
请求路径,通常自己也起一个前置,供本地读修复/灾难时使用。
对远端成员"不是什么",有两个诚实的注脚:
- 它是副本,不是备份。 CRDT 复制同步一切,包括删除——误操作的
mc rb --force或 pgBackRestexpire都会传播到远端机房。要拿"免疫误删"的 副本得用 Garage 自己的跨集群 bucket replication(Garage CLI 功能,本文档不代 管:garage bucket replication);2+1 集群给你的是节点级与机房级的存活能力加 一份人工抢救副本,仅此而已。 - 整机房故障不会自动切换。 本地两台一起挂,只剩远台一台低于 quorum(1 <
2),集群不可写。数据在那台节点上是完好的,Garage 也有文档化的人工恢复流程
(从存活节点重新 commit 一个缩减 layout 拉起集群),但 pgcli 不编排这一步——
那是"操作员确认机房真的没了"之后手敲
garage layout的事。
所以准确的定位是:2 本地 + 1 远端 = 带机房级抢救副本的 HA,写入保持本地速 度;“备份”(免疫误删义)仍然是 pgBackRest(或跨集群 replication)的职责。
拿到集群的 AK/SK
播种的 access key 是集群级凭据(任何成员都能验证),但它的 secret 只存在于一
台主机——凑齐 quorum 的那次安装打印过一次,并写进了那台主机的 pg.yaml:
-
任何主机都能看公开 id(
pg addon list的Access key:行);非种子主机上pg addon password会报错并点名持有 secret 的主机——信息准确,secret 就是不 在那台机器上。 -
Secret 丢了,或想要一套自己的凭据?在任意成员上铸一把新 key 并授权桶—— key 是 CRDT 状态,新凭据即刻全集群可用:
这是推荐的 day-2 姿势:应用和 pgBackRest 各用各的 key(独立轮换/吊销),播种 那把留作 break-glass 管理员凭据。
pg garage是 Garage 自家控制面 CLI 的通用 透传入口——完整运维菜单见用pg garage做管理。
v2.0 不做:把节点从 layout 移除(
layout remove+ 数据迁移)、revert 已提 交的 layout、多 zone 故障切换编排。这些仍是手敲 garage CLI 的事——用pg garage跑。
使用 mc 客户端
pg mc 从一个临时容器里跑 MinIO 自家的 mc 客户端——无需本地安装、无需手动
podman run——并像对其它存储一样讲 Garage 的 S3 API。用 access key 顶替
user、secret key 顶替 password:
alias 持久化在主机上的 ~/.mc/config.json——mc 自己的默认路径,Linux 和 macOS
上一致——所以 alias set 一次,之后每次 pg mc 调用(以及 Linux 上的原生
mc)都看到同样的 alias。pg mc alias list 读回,pg mc alias remove 删
除。alias set 在写入前先对端点校验凭据,所以写错的 key 不会污染
~/.mc/config.json。底层上 pg mc 把你的 ~/.mc 挂进容器并跑
ghcr.io/mars-base/pgcli/pgcli-mc(scratch 上的静态上游二进制);此外没有任何
魔法。
任何会被 pg 自身 flag 解析器拒绝的 mc flag 都放在 -- 之后,与其它存储完全
一样——--recursive、--force、--all、--json 等等:
按平台的端点: 在 Linux 上
pg mc跑在 host 网络里,所以127.0.0.1的 alias 能直达本地插件实例。在 macOS 上容器坐在 bridge 网络里,那里的127.0.0.1是容器自己的回环——把本地 Mac 插件的 alias 指向http://GK<...>:<secret>@host.containers.internal:3900,或把远程存储指向其可 路由地址。要走 TLS,就把 alias 设成 nginx 前置的https://端点(自签 leaf 再加-- --insecure)。
常用命令
| 命令 | 用途 |
|---|---|
pg mc ls store / pg mc ls store/backups |
列桶 / 列对象 |
pg mc mb store/backups |
建桶 |
pg mc cp ./file store/backups/ |
上传(整棵树:pg mc mirror ./dir store/backups/,或给 cp 加 -- --recursive) |
pg mc cp store/backups/file ./ |
下载 |
pg mc find store -- --name '*.pglz' |
按名字搜对象 |
pg mc du store |
每桶用量 |
pg mc rm store/backups/file |
删单个对象 |
pg mc rm store/prefix -- --recursive |
批量删对象 |
pg mc rb store/bucket -- --force |
连对象一起删桶 |
同一对 access/secret key 适用于任何 S3 SDK——包括对 repo1-type=s3 仓库的
pgBackRest(经 nginx TLS 前置)。
用 pg garage 做管理
pg garage 把 Garage 自家控制面 CLI 的任意子命令转发进实例的运行中容器——手写
podman exec pgcli-garage-<ns>-<name> /garage … 的替代品。--name 选实例
(默认按名字排第一的那个);子命令词之后的一切逐字到达 /garage,所以 Garage
自家的 flag 不需要 -- 分隔。单节点实例和集群成员上用法完全一致;集群里问任意
运行中的成员即可——status、layout、key 都是 CRDT 复制状态,全集群一致。
任意成员都能应答任意
pg garage命令——读和写都一样。 每台主机都跑着 自己的 garage 节点,而 bucket、key、layout 是不属于任何单机的 CRDT 状态,所 以bucket create、key create、bucket allow、layout show——以及对应 的 delete/deny——指向哪个运行中的成员结果都一样,改动在其余成员上立即可见。 (三节点集群实测:在主机 2 上建的桶,主机 1、3 的bucket list都能马上看 到;从主机 1 发起的删除,在主机 2 的列表里立刻消失。)唯一跟成员挂钩的是 取 secret:播种 key 的 secret 只存在于铸出它那台主机的pg.yaml里,pg addon password只在那台好用、在别处会报错点名它(见拿到集群的 AK/SK)——这是 pgcli 的本地配置边界,不是 garage 的限 制,也不妨碍你从任意成员铸新 key。
健康与状态
health 是对监控最友好的一条:打印简短汇总(已知/已连/存储节点数、分区
quorum),并且会设置退出码,所以 cron 或巡检脚本可以直接用
pg garage --name store health > /dev/null。status 是宽表——盯住
DataAvail 列,那才是真实剩余空间(见容量与尺寸计算)。
key 与 bucket
先铸 key 再 allow,就是 day-2 凭据姿势:应用各用各的 key,播种那把留作 break-glass(见拿到集群的 AK/SK)。
建桶走的是同一条透传——bucket create 起名字,随后必须 bucket allow
决定谁能用(新桶在授权之前任何 key 都够不着)。安装时播种只产出以实例命名的那
一个桶;后续的桶在这里加:
桶的全局 alias 就是它的 S3 名字,全集群唯一;问任意运行中的成员即可—— 从哪个成员建/删都一样(见本节开头的"任意成员"说明)。
layout 变更(集群)
加节点走 pg addon install(assign/apply 自动完成);裸 CLI 用于审计,以及
pgcli 刻意不编排的手动边缘场景:
layout remove、layout revert 与多 zone 故障切换仍是手动 garage 操作——
v2.0 限制清单在拿到集群的 AK/SK一节末尾。
日常维护
端口
每个实例从 Garage 自己的池取四个连续端口,起点是 garage_start_port(默认
3900):先是 S3 API,然后 RPC、s3_web、admin/metrics。这个池与共享的
minio/silo/rustfs 池(那个从 minio_start_port、默认 9000 起)分开,所以一个
garage、一个 minio、一个 rustfs 实例永不冲突:
只有 S3 API 端口是旋钮(--api-port);另外三个被分配其后的空闲端口。RPC 端口
是节点间流量,对单节点实例会留在回环上;admin/metrics 端口(3903)服务 Garage
的状态 API。集群成员的 RPC 端口是 peer 拨号的目标(rpc_public_addr),所以
即便 --listen 为回环,pgcli 也会把它的 RPC bind 放宽到 0.0.0.0——这条通道由
rpc_secret 签名。注意端口号也随 --advertise-host/--peer 一起走,所以在一台
已跑着另一个 garage 实例的主机上,集群的 RPC 可能落在不同于 3901 的端口(例如
10.0.0.11:3905)。
没有 Web 控制台——用 CLI 与 admin API 管理
与 MinIO(自带跑在第二个端口上的 Web Console)不同,Garage 没有内置 Web UI。它的管理面是三样东西:
-
garageCLI,通过pg garage——检视与改动状态的实际手段。pg garage把命令跑进实例的运行中容器(它经由本机 RPC 端口通信),从此不用手敲podman exec:--name之后的内容逐字转发给/garage,所以 Garage 自家的 flag (--read --write、-z、-c)不需要--分隔——且必须放在子命令词之后, 因为 pgcli 只读它前面的自有 flag。只装了一个实例时--name可省;装了多个 时,省略则命中按名字排第一的那个(stderr 会播报)。它需要一个运行中的成 员——不同于pg mc/pg etcdctl会拉起临时客户端容器,pg garage是在节点 自己容器里 exec。完整运维菜单见用pg garage做管理。 -
admin API,在 admin 端口(
3903)——一个 JSON API。从 Garage v2 起端点形如/v2/<Operation>(如/v2/GetClusterHealth),且必须在Authorization头里 带 Bearer token;裸curl会拿到403 AccessDenied。只有少数端点无需 token: -
s3_web,在 web 端口(3902)——这不是控制台。它只为开启了网站托管的 bucket 提供静态站点内容(按 bucket 的 vhost);访问根路径返回404。
社区有第三方 Web UI(基于 admin API 构建的看板等),但它们不属于 Garage、也不由
pgcli 管理;接一个这样的界面意味着先签发一个 admin API token,再把它指向 admin
端口。pgcli 分配的 web_port(3902)是留给 bucket 网站托管的,所以第三方控制台
应该对接 3903(admin API)或用 pg garage——而不是 3902。
配置
实例位于 pg.yaml 顶层的 addons.garage 映射里:
replication_factor 默认 1。没有 cluster 却把它设 > 1 会在安装时被拒绝
——单节点凑不出多节点 quorum。上面的集群键由集群安装路径写入;也可手写后重装。
zone 与 capacity 是 layout 状态、不进 TOML,所以只出现在这里。
对 listen、端口、root_user、root_password、image_tag、data_dir、
drives 或 config_file 的修改,在下一次 pg addon install garage --name store --force 后生效。
列表
集群成员多出 Cluster / Advertise / Zone/Cap / Node ID / Nodes 几行;
Nodes: 是一次实时的 garage status 读取(尽力而为——成员不可达时省略)。多盘
实例把 Data: 行换成 Drives:,其下每盘一行缩进的"容器槽位/主机目录"(见多
盘数据目录)。集群里,未执行播种的那几台主机上 Access key 只
有公开 id(没有 secret);见播种与 secret。
pg addon list 从不打印 secret key。要刻意揭示它用
pg addon list --show-password(追加一行 Secret key:),或为脚本只打印这个秘
密:pg addon password garage --name store(secret key,与上面 Access key: 配
对;--file <path> 以 0600 写出)。
启停
install 跳过仍存在的容器(停止的会被启动);start 只启动已存在的容器(并通
过从配置重建来自愈一个处于 improper 状态的容器,同时重新渲染
garage.toml)。
开机自启
容器带 --restart unless-stopped 策略(管崩溃,不管重启)。要在主机重启后拉起实
例:
garage 自启会重新渲染 garage.toml 并启动已存在的容器。Garage 在上游镜像里以
root 运行,所以开机启动除运行 podman 本身外不需要特殊主机权限。开机是仅启
动;garage 独立于 PostgreSQL 栈,所以与其它对象存储一起被启动。
移除
这些数据目录就是对象存储——丢了它们等于丢了里面每个桶——所以 remove 默认
保留。--clean-data 删除数据目录(多盘模式下是每一块盘的目录)、meta 目录和
pgcli 渲染的 garage.toml(你自带 config_file 指向的文件从不被碰),并清理已空
的默认布局父目录。每个目录在仍是活动挂载点时都会被跳过——以一句拒绝、而不是悄悄
放过。与 rustfs 不同,Garage 以 root 写、pgcli 在受管主机上以 root 跑 podman,所以
普通删除即可——没有 podman unshare 兜底。
日志
故障排除
- install 时
pulling garage image ... : ...。 tagdocker.io/dxflrs/garage:v2.4.1不可达——检查到 Docker Hub 的网络,或先用podman pull预拉。 pg mc alias set报连接错误。 默认--listen 127.0.0.1把存储绑在回环 上;在 macOS 上用host.containers.internal而非127.0.0.1。用pg addon list确认实时 API 端口。- 经 nginx 前置的写操作报
Invalid signature。 前置把Host重写成不带端口 的形式;用proxy_set_header Host $http_host;(conf-file 配方就是这么写的)——--upstream模板模式写死$host,不适合 S3。 - 经 nginx 前置的 PUT 在 ~1 MiB 处失败。 nginx 默认
client_max_body_size为 1m;conf-file 配方把它设成0(不限)。 - pgBackRest 拒绝端点(HTTP / 证书)。 pgBackRest 对 S3 仓库强制 HTTPS。用
nginx 插件(
--tls)前置 Garage,并把backup.repo.s3.ca_file指向前置的ca.crt。 - 单节点的
replication_factor必须为 1。 没有cluster却手改replication_factor: 2会在安装时被拒——单节点凑不出多节点 quorum。要跑多节点 就设cluster(或用--cluster安装),见多节点集群。
16.5 - PostgREST
PostgREST 是一个单进程、无状态的 Web 服务器,把一个 PostgreSQL schema 直接变成
RESTful API。作为 pgcli 插件,它是贴在任意 PG 端点前面的一个容器——没有数据目录、
没有渲染出的配置文件,全部通过 PGRST_* 环境变量配置。
PostgREST 是和 PgBouncer、PgDog 同类的代理型 插件:自身不保存状态。两种部署模式与 PgBouncer 对称:
- 本地模式:
pg addon install postgrest -i <instance>——作为 sidecar 存在instances.<name>.addons.postgrest下,DSN 由该实例自动拼出。 - 远程模式:
pg addon install postgrest --dsn <dsn> --pg-name <name>——存在 顶层addons.postgrest下。
两种模式里的 --dsn 都原样透传进容器的 PGRST_DB_URI,因此可以指向任意
PG 端点:一个直连的托管实例、一个 PgBouncer 连接池,或一个 Patroni
集群前面的 HAProxy 监听口。
平台支持
PostgREST 在 Linux(host 网络,--network host)和 macOS(容器加入
pgcli-net bridge 并发布端口,与 podman machine 下的 PG 实例同形)均可用。
- macOS 上本地模式开箱可用——容器经 bridge 访问托管实例。
- macOS 上远程模式:
--dsn必须是从 Mac 本身可达的地址,而不是 podman machine VM 的地址。指向一个 Mac 能解析的真实主机 IP(或指向 VM 上服务的host.containers.internal)。 - 客户端始终访问
127.0.0.1:<port>(或--listen);gvproxy 把发布端口转发到 Mac 的 loopback。
工作原理
pg addon install postgrest拉取镜像(若缺)并启动一个完全由PGRST_*env 装配的容器。- 容器经 host 网络(Linux)或
pgcli-netbridge(macOS)访问后端 PG。 - PostgREST 启动时内省暴露的 schema,对外提供 REST 服务,并在
pgrst通道上 LISTEN 等待 schema 缓存重载信号。
PostgREST 在主机上不留任何数据——pg addon remove postgrest 只停并删容器。
容器名: pgcli-postgrest<ns>-<name>(本地模式用实例名作为 <name>;远程模式
用 --pg-name)。namespace 隔离生效:两个不同 namespace 的配置可以各跑各的 API,
互不冲突。
命令
安装
重复安装是幂等的:已存在的容器会被复用(停着的会被启动)。改过端口、监听地址、
DSN、db-pool、schema、anon-role 或 jwt-secret 后,加 --force 重建容器以生效。
参数:
| 参数 | 说明 | 默认 |
|---|---|---|
-i, --instance |
托管实例(本地模式) | default |
--dsn |
后端 PG URI(远程模式);原样进 PGRST_DB_URI |
— |
--pg-name |
标识一个远程 PostgREST 的名字(配 --dsn 必填) |
— |
--schema |
暴露的 schema(可逗号分隔多个);→ PGRST_DB_SCHEMAS |
PostgREST 默认(public) |
--db-pool |
PostgREST 面向后端的连接池大小;→ PGRST_DB_POOL |
PostgREST 默认(10) |
--anon-role |
未认证请求所切换到的角色;→ PGRST_DB_ANON_ROLE |
—(匿名访问关闭) |
--jwt-secret |
验证 Authorization: Bearer JWT 所用的密钥;→ PGRST_JWT_SECRET |
—(JWT 认证关闭) |
--port |
HTTP 主机端口 | 自动,取自 postgrest_start_port(基 3500) |
--listen |
绑定地址 | 127.0.0.1 |
--image |
容器镜像 | docker.io/postgrest/postgrest:v16.3 |
--force |
重建已存在的容器以应用改过的参数 | 关 |
--db-pool与后端的max_connections。 一套 PostgREST 打开的 PG 连接总数 约为 PostgREST 实例数 ×--db-pool。若在同一端点后跑多个副本,按此预算max_connections。
列表
PostgREST 出现在本地插件/远程插件段下,含 Status、REST URL、Backend、 Schema、DB pool、Anon role、JWT auth、Container。
启动 / 停止 / 移除 / 日志
开机自启
自启是「只启动」语义:开机时按配置里已有的 PGRST_* env 拉起容器(若容器被删则
从配置重建)。请先安装插件。
配置
PostgREST 的配置在 addons.postgrest(远程)或 instances.<name>.addons.postgrest
sidecar(本地)下:
省略 --port 时端口从 postgrest_start_port 端口池(基 3500)自动分配;该池独立
于 PgBouncer / etcd / pgdog / minio 的池。
推荐:用 HAProxy 接 Patroni 集群
对 Patroni 集群,推荐形态是 PostgREST → HAProxy 读写口 → 集群 leader,
而不是让 PostgREST 直连某个成员。把监听口作为 --dsn 传入(远程模式):
为什么用监听口、而不是成员的直连 PG 端口:成员直连口在 failover 后会失去写入 (旧 leader 不再接受写,但 DSN 仍指着它)。pgcli 在安装时会检测并给出警告, 建议改指 HAProxy 监听口。
- failover 自愈,无需重启。 leader 变更后,PostgREST 只需经 LB 重连、
自动选中新 leader——不用重启容器、也不用重跑 install。实测:一次
pg ha switchover后,正好打在被关闭旧连接上的那一个在途写请求返回503(SQLSTATE 57P01,连接被终止),紧随其后的重试就在 新 leader 上成功返回201。把 leader 切换期间那一次瞬时的503/57P01当作预期 行为并重试即可。 - HAProxy 按健康检查路由,与"谁是 leader"解耦。 读写口只放行通过
/primary检查的成员,只读口只放行通过/replica检查的副本——所以 failover 不需要重新配置 haproxy,流量自己会漂移。 - 横向扩容。 在一个负载均衡器后面跑多个 PostgREST 副本;每个副本各自贡献
--db-pool份后端连接。
库侧准备(pgcli 不代管)
pgcli 只负责安装并运行 PostgREST 容器,不碰你的数据库。API 要能读到东西,
数据库侧需要先备好未认证请求所 SET ROLE 到的角色,以及对暴露 schema 的授权
——通常归你的 migration 管,不归 pgcli。以下这段可重复执行:
角色视图的列名是
rolname,不是rolename——写错会让整批语句回滚。ALTER DEFAULT PRIVILEGES只对其之后新建的表生效;已存在的表由上面那条GRANT ... ON ALL TABLES覆盖。
PostgREST 以 DSN 用户连接、每个请求再 SET ROLE 到 web_anon,因此 API 携带的
就是这个角色的权限:web_anon 只有 SELECT 就只能读,想开写权限就对该表
GRANT INSERT/UPDATE/DELETE。连接用的登录角色本身只需是请求所扮演角色的成员,
不需要别的权限。然后用 --anon-role web_anon 安装。认证请求的两种方式——匿名
(--anon-role)与 JWT(--jwt-secret)——见下文;两者都不设时,每个请求都会被
拒绝(401)。
PostgREST 会缓存它内省到的 schema。schema 变更后需要重载缓存:
LISTEN与事务池化。 PostgREST 依赖一条持久的LISTEN pgrst会话接收重载 通知。若后端跑在事务池化模式(PgBouncer 默认)下,这条会话级 LISTEN 会被打断 ——通知送不到,经池化连接发出的NOTIFY pgrst同样送不到。实测行为:经 事务池化的 PgBouncer 发出的 reload 并不能刷新 PostgREST 的缓存(新表仍 404), 而且即便把NOTIFY直发到后端也会丢,因为 PostgREST 自己的 LISTEN 会话没有稳定 连接。要么让 PostgREST 连到会话池化或直连端点,要么重启 PostgREST 容器 (启动时会重新内省)以拾取 schema 变更。
验证 API
安装输出会打印 REST URL(http://127.0.0.1:<port>)。先在暴露的 schema 里建一张
表,再访问 API 确认服务已就绪并能返回它的行:
全新安装时几点预期:
- 根路径可能需要一点时间才应答——schema 内省在启动后才跑,最初的请求可能返回
503,直到 PostgREST 连上后端。 - 刚建的表返回
404、未认证请求返回401,说明 schema 缓存过期或未设--anon-role——修法见下方排障表。
JWT 认证
--anon-role 是最简单的模型:每个未认证请求都固定以某个角色运行。
--jwt-secret 则打开按请求划分的身份。安装时传入它(HS256 共享密钥,或用于
RS256 的 JSON Web Key);pgcli 会作为 PGRST_JWT_SECRET 传进容器。
若像上面那样内联命令替换,密钥不会进你的 shell 历史。但它会落到
pg.yaml里(pgcli 要靠它重建容器)——请把这个文件当作含密文件对待,改过 密钥后要加--force重新安装。
长度: HS256 的密钥至少 32 个字符(256-bit 密钥材料;HS384 需 48、 HS512 需 64)。更短的密钥会让 PostgREST 拒绝启动——
openssl rand -hex 32(64 字符)是稳妥的默认值。JWK(用于 RS256/ECDSA)没有这个下限,强度由 JWK 自身决定。
设了密钥后,任何带 Authorization: Bearer <token> 的请求,PostgREST 都会先验签,
再以 token 里 role claim 所指名的角色运行:
- token 必须用同一密钥签名;被篡改的一律 401。
roleclaim 必须指到一个在暴露 schema 上有授权的角色(像上面的web_anon那样建,NOINHERIT)。- DSN 里的登录角色必须是该角色的成员,因为服务请求的本质就是
SET ROLE到它。所以对web_user这类 JWT 角色,还需要GRANT web_user TO <dsn-user>;——与上面匿名角色的要求相同,DSN 用户是 superuser 时同样可省略。缺这个成员 关系,请求会失败并报permission denied to set role。 - 若你的签发方用别的字段名,可用
PGRST_JWT_ROLE_CLAIM_KEY改 claim 键——pgcli 没有暴露该 flag,需要时直接在容器上设置。
--anon-role 与 --jwt-secret 可并存:带合法 JWT 的请求按其 role claim
运行,不带的请求回落到 --anon-role。两者都不设时,每个请求都被拒绝(401)。
常见组合是:公开读用匿名,认证写用 JWT 角色。
生产环境由你应用的认证服务签发 token。手搓一个用同一 HS256 密钥的测试 token:
排障
| 现象 | 可能原因 / 处理 |
|---|---|
安装报 cannot connect to source database |
容器访问不到 DSN 的 host:port(macOS 上远程 127.0.0.1 指向 Mac 而非 VM)。用 pg exec --dsn <dsn> "SELECT 1" 验证。 |
所有请求返回 HTTP 401 Anonymous access is disabled |
未给 --anon-role 且请求没带合法 JWT——补 --anon-role,或用 --jwt-secret 安装并发一个签名 token。若已设 --jwt-secret 仍 401,说明签名或 role claim 不对。 |
写请求返回 HTTP 401,但 JSON 响应体里是 42501 / permission denied for table |
请求所扮演角色(匿名角色或 JWT 的 role claim)缺该权限——PostgREST 在 HTTP 层把权限不足报成 401,真正的 SQLSTATE 只在响应体里。给请求实际使用的角色补授权。 |
新建的表/关系在 NOTIFY pgrst 后仍 404 / PGRST205 |
reload 没送达 PostgREST(见上文事务池化)。重启容器,或改用会话/直连。 |
| Patroni failover 后写请求立刻失败 | DSN 指到了成员直连口而非 HAProxy 读写口——安装时已警告。把 DSN 改指 LB。 |
--db-pool 改动没生效 |
已存在的容器会被复用;加 --force 重装以重建。 |
相关
16.6 - SeaweedFS
SeaweedFS 是一个 Go 语言、Apache-2.0
的 S3 兼容对象存储,其标志性特征是以 volume 粒度管理磁盘:数据被切成固定大
小的 volume(.vol 文件),由 master 在各 volume 服务器之间放置,从而获得
O(1) 读取和廉价的小文件存储。pgcli 将其作为独立的顶层插件运行,CLI 表面与
garage、minio、silo、
rustfs 一致(install、list、logs、autostart、password、
pg mc),但其运行时模型在四个方面与四者都不同,这也决定了本页的写法:
- 一个进程,全部角色。 单节点跑
weed mini——master、volume 服务器、 filer、S3 网关四合一(官方认可的单机生产形态)。集群成员跑weed server -filer -s3,每台主机一个,同样是全部角色一起——绝不拆成四个 容器。 - 三个渲染出的文件,其余全走 argv。 pgcli 在实例目录下渲染
s3.json(S3 身份)以及集群场景下的filer.toml(PostgreSQL 元数据存储),以只读方 式 bind 挂载;其它配置都是weed的命令行 flag。 - 集群元数据放在共享 PostgreSQL 里。 集群每个节点都对外提供 filer + S3,
所以 filer 命名空间必须是一个数据库,而不是各节点各自的 leveldb2——pgcli 让
所有成员指向同一个
--dsnPostgreSQL。这正落在 pgcli 的主场:对象存储可以 紧挨着(甚至就在)一个 pgcli 管理的实例旁边。 - 原生 S3 TLS——
--tls(自签)或--tls-cert/--tls-key(自带证书,例如pg cert的产物)——是 MinIO 的姿态,不是 Garage 的 nginx 前置。开启 TLS 后,池化的 S3 端口只服务 HTTPS;明文监听器完全不 绑定。
平台支持: 已在 Linux(host 网络)上验证。macOS 的 bridge 路径代码完整、 遵循与其它对象存储一致的形态,但尚未在真机上跑过。
工作原理
一个实例一个容器,来自上游
docker.io/chrislusf/seaweedfs:4.48_large_disk 镜像(仅 pull)。pgcli 覆盖镜像
自带的 ENTRYPOINT,直接运行 /usr/bin/weed -logtostderr=true <subcommand> …:
官方 entrypoint 每次启动都会 chown -R /data 再降权到 uid 1000——在大目录上正
是我们想避开的"静默变慢"形态;而不带 -logtostderr 的话 weed 会把日志写进容器
内的文件,pg logs 根本看不到。
主机侧状态存放在 <base-dir>/addon/seaweedfs/<name>/ 下:
data/→/data(volume,单机时还有 filer 的 leveldb2 元数据)——多盘模式下 被每盘一个 bind 挂载(/data1…/dataN)整体取代,见多盘,s3.json→/etc/seaweedfs/s3.json:ro(S3 身份;宿主侧权限 0600),filer.toml→/etc/seaweedfs/filer.toml:ro(仅集群 /--dsn;渲染元数据 DSN 的[postgres2]段),- 开启 TLS 时,
cert.pem/key.pem挂到/etc/seaweedfs/下。
凭据是一对 access key / secret key,渲染进 s3.json(SeaweedFS 的静态身份
机制——AWS_* env 优先级最低、会被本地 store 覆盖,pgcli 从不用它):
root_user是 access key id——单节点安装时首次自动生成 (SW+ 28 位 hex),或由--root-user指定。root_password是 secret key(64 位 hex),自动生成或由--root-password指定。- 在
--cluster下两者都是必填,且每个节点必须完全一致——见多节点集 群。
身份渲染时带裸 Admin action——在 SeaweedFS 的权限模型里它无条件短路成全权
限。所有值都落进 pg.yaml(addons.seaweedfs.<name>.*),重建才稳定。安装还会
渲染出一个桶——以实例名命名(--bucket 可覆盖):单节点由 weed mini -bucket
启动时创建;集群在该节点健康门变绿后,通过 S3 API 用 mc mb --ignore-existing
播种。
安装
单节点输出的摘要报告端点和铸造的凭据:
安装以健康门收尾:pgcli 轮询 master 的 JSON /cluster/status API 最多 30 秒
才宣布成功——一个 detach + --restart unless-stopped 的容器否则会把错误的 argv
吞成一次崩溃循环、却显示安装成功。只有健康门绿了才进行桶播种。
对存活实例重跑 install 是 no-op:容器不重建(停止的会被启动并给提示),
flag 合并进存的配置,已有凭据保留。传 --force 才会重建容器,让改动的端口 /
监听地址 / 凭据生效——数据目录和元数据库都不会丢。
绑定地址: 默认
127.0.0.1保持存储本地;--listen 0.0.0.0(或pg.yaml里的listen键)把它暴露到网络。集群成员无论--listen是什么 都绑0.0.0.0——peer 必须能连到它的 gRPC 孪生口(port + 10000)做 Raft—— 而-ip仍固定注册为那一个地址供 peer 拨号。
-ip从不留给自动探测。 weed 会把它的 volume 注册到它自己猜出来的地址 下;filer/S3 的内部数据面随后去拨这个猜测地址,在一个"看起来健康"的容器上 上传就会 500。pgcli 永远显式传-ip(单机取--listen值,集群取--advertise-host)——在一台测试主机上吃过这个坑才确认的。
TLS
SeaweedFS 在 S3 口原生服务 HTTPS;没有 nginx 前置,也没有 web-console 那一路 需要代理。两种形态,同 minio/rustfs:
TLS 在底层改了什么(已实测):明文监听器关掉(-s3.port=0——该池端口直接
拒绝 TCP 连接),同一个池化 S3 端口变成 -s3.port.https。所以安装摘要里打印的
端点是 https://…:8403,不是一个新端口。pg mc 照常指向它即可——loopback
别名会自动带 --insecure;真实 CA 或生成的 CA 则要交给 S3 客户端做校验。
多盘
单节点可以用多块盘:weed 自己的 -dir 本就是一个逗号列表
(dir[,dir]…),由同一个 volume 服务器把 volume 铺在这些目录上;pgcli 把
它暴露成可重复的 --drive——每块盘一个宿主目录,各自挂到自己的槽位:
摘要和 pg addon list 会打印映射,取代单独那行 Data::
容器因此以 -dir=/data1,/data2,/data3 运行(已实测:volume 服务器的 /status
把每个宿主目录报成独立的 DiskStatuses 条目、各自记账;remove --clean-data
也会逐个遍历 drives,并拒绝删除仍是活动挂载点的那一个)。
两点语义值得知道:
- 这是放置,不是 RAID。 是一个 volume 服务器把
.vol文件分散到自己的目录 ——OS 层面既没有条带也没有镜像,各盘之间也没有纠删码(集群副本是另一件 正交的事,见多节点集群)。一块盘消失,它上面的 volume 就没了; 这是否造成数据丢失取决于 placement code,而不是本地布局。 - 设备检查是建议性的。
install会对任何与宿主根文件系统同设备的 drive 发 出警告——多盘只有配独立挂载的盘才有意义。与 MinIO(会在启动时直接拒绝这种 盘)不同,weed 照样会用它们,所以 pgcli 只警告、不阻断。请先自行格式化并挂 载每块盘;pgcli 从不做分区或格式化。
drives 是逐节点的开关,并且与集群正交组合:在每台成员的 install 命令上照常
传 --drive,每个节点就在共享集群背后跑自己的多盘 volume 服务器(已在三主机
weed server 集群上实测——每台各挂两个 drive:每台进程都显示 -dir=/data1,/data2
且各报两个独立 DiskStatuses 条目;同一个 8MB volume 的 001 副本一份落在某台的
实体盘上、另一份落在副本台的另一块 drive 上,即各节点的 volume 服务器在自己的
目录之间选择,而放置决策在节点之间进行)。各节点不需要相同的 drive 数量;每台只
看得到自己 -dir 列表里的目录。stop/start 与 --force 重建会像处理其它配置变更
一样处理 drive 集合——挂载与 -dir 都从 pg.yaml 重建。
多节点集群
集群是 MinIO-MNMD 形态,不是 Garage 的 bootstrap/join:没有"第一个节点"的
概念、没有携带集群密钥的 join 命令、也没有 layout 要 assign。你在每台主机上
跑同一条 install 命令——Raft 成员资格就是每个节点都传的完整 --peer 列表,
quorum 未齐就位的节点只是等待(其健康门报 warn 而非失败;补齐 quorum 的那次
安装完成桶播种)。
与"更大的单节点"相比有两点不同,而且这两点天然要求全部一致——每台成员的 命令都要原样携带:
- 全集群一对凭据。 每个网关只针对自己那份渲染出的
s3.json认证; SeaweedFS 没有独立 IAM server 就没有共享凭据后端(超出 pgcli v1 范围),所以 逐节点自动生成会让三个网关各自接受一份不同的身份。pgcli 因此在--cluster下拒绝自动生成:每台节点传同一对--root-user和--root-password。先跑完第一个节点的 install,从打印的摘要里复制这对值, 原样带到其余节点(pgcli 从不远程执行任何东西)。只传一半(两个里只给一 个)是错误;把一个单节点配置加--cluster重装同样是错误。 - 共享 filer 元数据库。
--dsn postgres://user:pass@host:port/db是必填, 且必须从每台主机都可拨通——所以用宿主发布端口 / LAN 地址,绝不要容器内 部的127.0.0.1:5432——数据库本身也必须已经存在:pgcli 只探测可达性,从不 创建(也从不删除)它不拥有的数据库。元数据表不需要任何准备:weed 的postgres2store 在首次使用时自动在其内建.system、filemeta和每桶一张 表(已实测)。filer.toml里渲染的是sslmode=disable,所以能连通 +pg_hba放行就够了。
三主机实操
先在一个所有节点都能到的地方准备元数据库(这里用主机 1 上一个 pgcli 管理的实 例,宿主端口 35432):
然后在每台主机上跑同一条命令,只换这台主机自己的 --advertise-host(下面的
key 是占位符——所有节点用同一对值):
想让每台成员跑自己的多盘 volume 服务器,就往同一条命令上再加 --drive
(--drive /mnt/sw-disk1 --drive /mnt/sw-disk2——见多盘);它是逐节
点的 flag,各台可以带不同的 drive 集合。已在三主机集群上实测。
摘要末尾会带一段可复制的、正好是这个形态的块,用于其余主机(凭据和 DSN 从本 节点配置里填好)。
集群还在拼装中时,先装的节点会说什么,都是正常的:
- 主机 1 起来时就能看到 leader(3 节点列表下,落单 master 立刻赢下自己的选举
——这是实测到的行为),但桶播种可能以 connection refused 失败:集群的 S3 侧
还不可服务。pgcli 打印
[!] bucket seeding incomplete … re-run this install command once the cluster has quorum,并且退出码 0。 - 主机 2(或 3)补齐 quorum;同样幂等的播种在那里成功(
Bucket created successfully)。事后重跑主机 1 的命令会安静复检,mb --ignore-existing保持 no-op。
三台都装完后,任意成员展示整个集群:
经任意节点读写
因为凭据共享、filer 命名空间是一个数据库,任意网关都服务任意对象:经主机
2 的 S3 口写入、经主机 3 用同一把 key 读回——在三主机测试集群上端到端验证,包
括故意用错 secret 时返回 The request signature we calculated does not match
(它不是每节点各自的本地信任库)。--replication 001 给每个 volume 存一份远端
副本(volume.list 显示 ReplicaPlacement:001,每个 volume 落在两个数据节点
上)。
停掉一个成员,集群仍然服务(Raft quorum 是多数;3 个 master 时 2 个继续选举和 写入):
pg addon start 让节点重新加入;它的 volume 以同一个 -ip 重新注册。成员变
更不被编排:--peer 是静态 Raft 配置——增删成员意味着重建集群(v1 局限,见
局限)。
取集群的 AK/SK
这对凭据按设计存在于每台节点的 pg.yaml 里(是你带过去的),所以不像
garage 的 CRDT key——其 secret 恰好只在一台主机上——
pg addon password seaweedfs --name store 在任意成员上都能用。如果打印出的
secret 在所有主机上都丢了,就挑一对新值,在每台节点上用 --force 重装(渲染出
的 s3.json 会被重写;数据和元数据不受影响)。
用 mc 客户端
pg mc 从一次性容器里跑 MinIO 的 mc——无需本地安装——像对其它存储一样说
SeaweedFS 的 S3 API。把 access key 当 user、secret key 当 password 用:
别名持久化在 ~/.mc/config.json;pg 的解析器会误吞的 flag 放到 -- 之后
(pg mc rm store/scratch -- --force)。macOS 上把别名指到
host.containers.internal:<s3-port> 而不是 127.0.0.1。开 TLS 就用 https://
端点(loopback 别名自动 --insecure)。
用 pg weed 运维
pg weed 在一个运行中成员的容器里跑 SeaweedFS 自己的 weed 控制工具——替代
手写 podman exec pgcli-seaweedfs-<ns>-<name> …。--name 选实例(默认:按名第
一个);pgcli 自己的 flag 必须放在子命令词之前,weed 的 flag 原样透传。两种
形态:
weed shell 菜单是 day-2 的表面(命令名已对 4.48 校验——shell 的 help 列得更
多):
weed 类 CLI 可能打印完结果还带着打开的 gRPC channel 不退出;pgcli 在其输出安
静 3 秒后(总上限 10 分钟)把这种命令切断,于是挂起暴露成一次响亮的超时——重跑
即可。
端口
每实例从 SeaweedFS 自己的池里取四个连号端口,起于 seaweedfs_start_port
(默认 8400):master、volume、filer、s3——并且每个角色还开一个
http 端口 + 10000 的 gRPC 孪生口(8400 → 18400,……),pgcli 也把这些在
池里预留了。所以第二个实例落在 8404-8407 / 18404-18407,一个 seaweedfs、一个
garage、一个 minio 实例永不冲突(各自独立池:8400 / 3900 / 9000):
--port 只设 master 锚点;其余随之。四个里只有 S3 口是客户端端点;master 8400
还服务 /cluster/status JSON API 和运维 UI 页面(admin UI 和 webdav 在 argv 里
被关掉,iceberg/lance 旁路监听器钉到 0,所以 weed 不开额外的洞)。
配置
实例位于 pg.yaml 顶层的 addons.seaweedfs 映射:
对 listen、端口、root_user、root_password、image_tag、data_dir、
drives、filer_dsn 或 TLS 材料的改动,在下一次
pg addon install seaweedfs --name store --force 后生效。集群里的这次
force-recreate 必须在每台成员上跑(每台主机各持有自己的 pg.yaml 和容器)。
列出
集群成员多出 Cluster / Peers / Leader / Filer store 四行;Leader: 是一
次实时的 /cluster/status 读(尽力而为——成员没跑就省略,集群还在拼装、尚无
leader 时打印成 (none yet — still electing / no quorum))。单节点成员这四行都
没有。多盘实例则以 Drives 块(每个 drive 一行 - /dataN <- 宿主路径)取代单
独那行 Data:——见多盘。开 TLS 时末尾追加一行 TLS: native S3 HTTPS on 8403 (cleartext listener off)。pg addon list 从不打印 secret key;要显式暴露用
pg addon list --show-password(追加一行 Secret key:),或
pg addon password seaweedfs --name store(--file <path> 以 0600 权限写入)。
Filer store 行是 host:port/db,密码永远隐藏。
启停
install 跳过还在的容器(停止的会被启动);start 只启动已存在的容器——并且
先重新渲染 s3.json/filer.toml,所以对存的 config 手工编辑会在重启后生效。
开机自启
容器带 --restart unless-stopped 策略(崩溃,而非重启)。要在主机重启后拉起实
例:
自启是只启动——渲染文件和容器都已存在。一个比 peers 先起的集群成员只是像任 何一次冷启动一样等 quorum。SeaweedFS 在上游镜像里以 root 运行,所以开机启动除了 运行 podman 本身不需要额外主机特权。
移除
数据目录就是对象存储——丢了它等于丢了里面所有 volume——所以 remove 默认
保留它。--clean-data 删除 data 目录、s3.json,以及存在时的 filer.toml /
TLS 目录(每个都要先 stat——缺失的可选项什么都不打印),并拒绝碰任何还是活动
挂载点的东西。filer 元数据库永不删除,在任何主机上、任何时候都是:移除时会
打印
于是你清楚真要清干净时该 DROP DATABASE 什么(每台成员的 remove 都打同样的提
示;数据库比它们所有容器都活得久)。
日志
局限
v1 有意不做的事:
- 集群成员变更(增/删/替换节点)不被编排——
--peer是静态全列表;改它意味 着重建集群。 - EC 纠删码、分层(tiering)、cloud-sync(向其它 S3 复制)、FUSE 挂载,以及
独立 IAM server(动态
s3.accesskey.*用户)都不被管理——全部仍是手工pg weed的领地,尤其 IAM 是 SeaweedFS 本身动态共享凭据的唯一途径,这里超出 范围。 - 凭据轮换是一次带
--force在每台成员上的重装;pgcli 从不就地编辑一个活的s3.json。 - 只播种一个桶(实例名);后续桶就是普通
pg mc mb的事——它们自动落进同 一个 filer 数据库。 filer.toml里的 DSN 渲染sslmode=disable;如果你的 PostgreSQL 在这一 条上强制 TLS,pgcli 的渲染不适用(自带 TLS 的--dsn不是 v1 表面)。
故障排查
--cluster安装报requires explicit shared credentials。 这是设计上预 期的——用显式的--root-user/--root-password对跑第一个节点,并把同样的值 带到每个其余主机(安装摘要会打印填好它们的现成命令)。单节点安装可以省略它 们、拿到自动生成的 key。requires a shared filer metadata database: pass --dsn …。 没有--dsn的集群无法在各网关间维持一个命名空间。预建好数据库,传一个主机可达的 URI(发布端口 / LAN 地址——容器内的127.0.0.1:5432不行)。[!] no leader yet — normal while the cluster is still assembling。 健康 门的 quorum 警告,不是失败:把其余成员的 install 跑完(或等它们起来后重跑这条), 它就绿了。[!] bucket seeding incomplete … re-run this install command。 晚一步的同 一种姿态:这个节点拿到了 leader 但它的网关还不可服务。最后一台加入后重跑同一 条 install;mb --ignore-existing让它在桶已落地时是一个干净的 no-op。- 单机健康外观下上传 500。 历史上就是缺
-ip的签名(volume 注册了一个自动 猜的地址,数据面拨不通);pgcli 永远传-ip,若你还看到,检查有没有被手工改 过的 argv 或一个很旧的二进制。 mc: … you already own it。 对一个已存在的桶跑了裸mc mb;pgcli 自己的 播种用--ignore-existing,所以只会从手工mc里看到这个——加上那个 flag。- 错 secret 的困惑:
The request signature we calculated does not match。 这 对值不匹配该节点渲染的s3.json——在集群里,这意味着各节点装的时候用了不同 的凭据;用同一对值 +--force在每台重跑 install。 - install 时
pulling seaweedfs image …失败。docker.io/chrislusf/seaweedfs:4.48_large_disk不可达——检查到 Docker Hub 的 网络,或先podman pull一把。
16.7 - RustFS
rustfs 是 Rust 重新实现的 S3 兼容对象存
储:同样的 S3 API、Web 控制台、纠删码多盘/多节点布局——但运行时模型与
MinIO/silo 完全不同。pgcli 将其作为独立的顶层插件运行,CLI 表面与
minio、silo 一致(install、TLS、BYO 证书、
drives、logs、autostart),共用同一个端口池;但有三点决定了本页的写法:
- 固定的容器用户。 上游 rustfs 在镜像里写死
User=rustfs(uid/gid 10001)。pgcli 把这件事完全放在自家镜像内部处理——见下节 权限与属主——所以 rustfs 存储不需要任何主机侧属主处理。 - 只有三种拓扑,没有多节点单盘。 rustfs 只支持 SNSD / SNMD / MNMD—— 见部署模式。
- 自己的 TLS 文件名。 rustfs 从
RUSTFS_TLS_PATH读取rustfs_cert.pem/rustfs_key.pem,而不是 MinIO 的public.crt/private.key。
想要一个精简、Rust 原生的 S3 端点就选 rustfs;它可与 minio、silo 在同一台 主机并存(三者共用一个端口池)。
平台支持: rustfs 插件实际上是仅 Linux。其运行时模型(固定容器 uid、独立块设备上的盘、host 网络)是 Linux 容器路径,也只在 Linux 上做过验 证;macOS 的 bridge 路径代码完整、wrapper 镜像也是双架构,但尚未在真机上跑 过。
工作原理
一个实例一个容器。数据存放在 bind 挂载的主机目录里(默认
<base-dir>/addon/rustfs/<name>/data,或 --drive 的每个盘各自一个目录),
生命周期长于容器。与 minio/silo 不同,pgcli 不直接驱动 rustfs 二进制
——它运行镜像自带的 /entrypoint.sh(经下面说的 wrapper 转发),因为正是这
个 entrypoint 会展开 RUSTFS_VOLUMES 里的多盘花括号范围、创建各盘目录、并
组装服务端 argv。
pgcli 拉取的镜像不是纯上游镜像——是
ghcr.io/mars-base/pgcli/pgcli-rustfs:1.0.0,一个建立在
docker.io/rustfs/rustfs:1.0.0(首个 GA 版本)之上的精简 pgcli wrapper。
pg addon install rustfs 只负责 pull;pgcli 从不在运行时构建它。tag 跟随被
pin 的上游版本。
凭据处理方式与 minio/silo 相同:
root_user默认admin(rustfs 对 access key 无最小长度要求,任意非空值 都可以;admin还顺带避开了对rustfsadmin的告警);root_password首次 install 时生成(或用--root-password显式提 供),存于pg.yaml(addons.rustfs.<name>.root_password),并在 install 摘要里只打印一次。
它们是 rustfs 的 root access key / secret key,通过 RUSTFS_ACCESS_KEY /
RUSTFS_SECRET_KEY 环境变量传入——即每个 S3 客户端(含 pg mc alias set)
口中的 access key 与 secret key。
权限与属主
这是 rustfs 与 minio/silo 真正不同的地方,而 pgcli 已把它收进镜像、在实际 使用中变得不可见。
上游 rustfs 以一个**固定的、不可配置的 uid/gid(10001)**运行。在 rootless
podman 下,主机用户对这个 uid 没有任何权限主张,所以让一个 bind 挂载的数据
目录对该进程可写的「朴素做法」是在主机侧做 chown 腾挪
(podman unshare chown 10001:10001 …)——脆弱,而且和操作者是否为 root 与
其说相关。pgcli 完全绕开了这一切:
- wrapper 镜像的 entrypoint 以容器内 root 启动,把它自己的 bind 挂载数据
目录
chown成 10001,然后su降到rustfs用户,再exec未做任何 改动的上游/entrypoint.sh。pgcli 不传--userflag,也完全不做主机 侧的属主操作。 - 两种 daemon 模式下的效果一致;只有主机可见的数字 uid 不同,因为那是
podman 用户命名空间的属性,与 pgcli 无关:
- rootful podman → 目录落在真实的主机 uid
10001; - rootless podman → 落在附属(subordinate)主机 uid
(
subuid_start + 10001,例如110001),10001只在容器内可见。
- rootful podman → 目录落在真实的主机 uid
两种情形都不需要操作者以 root 运行、也不需要手工 chown。唯一还值得知道的
约定是:当你自己挂载盘(--drive)时,挂载点应归运行 pg 的那个用户所有
——rootless podman 下 root 拥有的挂载点落在命名空间 uid 范围之外,容器内的
chown 无法认领它,跟任何 bind mount 一样。
TLS 是拷贝,不重新属主。 rustfs 必须以 10001 身份读它的 key+cert,但
pgcli 生成的证书目录(0700、pgcli 拥有)必须继续由 pgcli 拥有,pg cert、
CA 刷新、pg backup fetch-ca 都要在它上面工作。所以 wrapper 不 chown
那个目录:它以只读方式挂载该目录,然后以容器内 root 身份把那两个必需文件
拷贝到一个新建的、由 10001 拥有的容器本地目录——RUSTFS_TLS_PATH 指向
那里。任何主机文件都不会被改动;BYO 路径走同一套拷贝机制,BYO 的 key/cert
同样只读挂载、按所需文件名被拷贝、也从不被 chown。
安装
输出会报告端点和 root 凭据:
在 Console: URL 用打印出的 root 用户/密码登录控制台。把 S3 客户端(含
pgBackRest)指向 S3 API: URL,或用 pg mc 直
接在终端操作。
对一个在跑的实例重跑 install 是 no-op:容器不会被重建(停着的会被启动并
有提示),flag 会合并进已存配置,已有的 root 密码保留。传 --force 才重建
容器,从而让改动的端口、监听地址、端点列表或凭据生效——数据目录不受影响。
绑定地址: 默认
127.0.0.1让存储只在本机可达。--listen 0.0.0.0(或pg.yaml里的listen键)会把它暴露到网络上。能访问该端口的任何人 都可尝试 root 凭据,所以要么放在防火墙后、要么开 TLS(--tls)。
TLS(--tls)
--tls 让 rustfs 提供 HTTPS。pgcli 用标准库生成一个自签 CA 与一张叶子证书
——SAN 覆盖 loopback 名称、localhost 以及主机的每张网卡 IP——写入
<base_dir>/tls/rustfs/<name>/:rustfs_cert.pem / rustfs_key.pem(rustfs
要求的名字,同时另存一份 public.crt / private.key 便于 CA 工具复用)与用
于分发的 ca.crt。证书目录以只读挂载、并如权限与属主所述被
拷进容器;端点 URL 变成 https://。
为什么需要它:pgBackRest 对 S3 仓库强制 HTTPS,所以一个要接收 Patroni
archive-push 的 rustfs 必须支持 TLS。backup.repo.s3.ca_file 指向
ca.crt,pgBackRest 就会带完整证书校验连接——见备份 → S3 对象存储仓
库。该页关于 MinIO 仓库的一切同样适用于
rustfs:S3 契约完全一致(path-style、repo1-s3-uri-style=path)。
把 CA 送到远端主机——无需 scp。 服务端证书以 leaf+CA 链的形式被提供, 所以另一台机器上的消费方可以直接从一次 TLS 握手里抽出根证书:
fetch 是 trust-on-first-use——信任前先把打印出的 SHA-256 与存储主机上的
sha256sum ~/.pgcli/tls/rustfs/<name>/ca.crt 比对一下。
自带证书(--tls-cert / --tls-key)
--tls 只会服务 pgcli 自家的自签证书对。若要服务你已经持有的证书,传
--tls-cert <leaf(+chain).pem> --tls-key <key.pem>。配合 --tls(隐含)这
会取代生成的证书:两个文件以只读挂载,并按 rustfs 要求的名字被拷进容器
(/opt/rustfs/certs/rustfs_cert.pem、…/rustfs_key.pem);pgcli 既不重新
属主、也不写它们的源目录。
还没有证书?pg cert 能按你给定的 SAN 铸一张自签 leaf,rustfs 原样 serve
它(真机验证过:rustfs 实际呈现的证书与 pg cert 输出在实时握手上吻合,
pg mc 对它完成完整的 put/get 往返)。签发的参数、客户端信任锚、续期,见
自签证书。
BYO 模式的其余一切——为什么续期需要 --force(单文件挂载会钉住宿主源
inode)、客户端如何挑选信任锚、pg cert 作为测试证书铸造——与 MinIO 插件完
全一致,见 MinIO → 自带证书。关掉 BYO: 从 pg.yaml 里删
掉这个插件下的 cert_file/key_file,再 pg addon install rustfs --name store --force 重建。
部署模式
rustfs 有三种布局——没有多节点单盘模式:
| 模式 | 形态 | 适用场景 |
|---|---|---|
| SNSD(单节点单盘) | 一个节点、一个数据目录——不给 --endpoint/--drive 时的默认 |
dev、test、demo |
| SNMD(单节点多盘) | 一个节点、多块盘——每块一个 --drive,见下文 SNMD |
单主机上抵抗磁盘故障 |
| MNMD(多节点多盘) | 多节点、每节点多盘——本节点的 --drive + --endpoint 列表 |
同时抵抗磁盘故障与节点故障 |
rustfs 有意没有 MNSD(多节点单盘)。只给 --endpoint 不给 --drive 会在
install 期被拒:rustfs 自己派生每盘 volume 范围,所以一个分布式节点必须声明
自己有几块盘。
rustfs 严格要求物理盘互斥。 在 SNMD/MNMD 下,每个
--drive必须位于自 己的块设备上。若两块盘共享一个设备(同一st_dev),rustfs 进程启动即[FATAL]退出。pgcli 以分离方式启动容器,所以pg addon install仍返回 0,故障表现为一个永远变不健康的容器——若多盘 install 起不来,查pg logs addon rustfs --name store。(这比 MinIO/silo 更严,后者只告警。)
单节点多盘(SNMD)
一个 rustfs 进程、若干主机目录、在它们之间纠删码。用 --drive 一次一块盘替
代 --data-dir:
每个 --drive 是位于独立设备上的一个主机目录;第 N 块盘(0-indexed)被
bind 挂到容器路径 /data/rustfsN,RUSTFS_VOLUMES 被设为花括号范围
/data/rustfs{0...3},由镜像的 /entrypoint.sh 展开成各个独立目录。
--drive 与 --data-dir 互斥;把 --drive 和 --endpoint 组合就是 MNMD。
pg addon remove rustfs --name store --clean-data 会删除每个 drive 目录
——但会拒删仍然处于挂载状态的盘,这样一次手滑的 --clean-data 绝不会穿过
活挂载点 rm -rf 到下面的真实磁盘。若确实要清数据,先卸载。
分布式 / 集群模式(MNMD)
每个节点跑各自的 pgcli 与各自的 pg.yaml;每份 pg.yaml 带同一份完整
的 endpoint 列表与同一套 root 凭据。对 rustfs 而言,--endpoint 列表是
每节点一条 scheme://host:port(不带 path——rustfs 从 --drive 自己派生
/data/rustfsN volume 范围),每节点再各传各的盘:
除了一份份逐字节相同的配置,还有两点关键:--listen 必须是 0.0.0.0(或本节点
自己的可达地址)而不是默认的 loopback——podman host 网络下端点广播的是各节点真实
IP,只监听 127.0.0.1 的节点永远进不了集群。而且所有节点的 endpoint 列表与 root
凭据必须完全一致:rustfs 据此派生出同一个纠删码集合,一旦对不上就退化成彼此独立
的单实例。
--endpoint 传的是不带 path 的 scheme://host:port,每节点重复一条(写成逗号串
--endpoint a,b,c 也等效——它是 string-slice flag);pgcli 会给每条拼上本节点的
/data/rustfs{0...N-1} 盘范围,再用空格把四条 URL 连成单个 RUSTFS_VOLUMES。
(RUSTFS_VOLUMES 本身必须是空格连接、不能是逗号连接:rustfs 二进制会把逗号串错误
切分,把 http:// 塌缩成 http:/,节点因此解析不出自己的盘——列表中第一个节点以
VolumeNotFound 退出,其余节点卡在 waiting for storage_quorum,始终凑不出可写集
群。空格分隔的字面 URL 与官方文档的紧凑花括号形式
http://node{1...4}:9000/data/rustfs{0...3} 解析结果完全一致,却能直接用各节点的
普通 IP,不需要 /etc/hosts 或 DNS。)
跨主机 MNMD 已在真机 4 节点 × 4 盘集群上验证(Linux,rootful podman):四台全部
/health 返回 200,且写入一次的对象从每一台读回都逐字节一致——纠删码分片确实
散布在全部 16 块盘上。
使用 mc 客户端
pg mc 在一次性容器里跑 MinIO 的 mc 客户端,对 rustfs 的 S3 API 与其它
存储一样可用:
S3 数据面(PUT/GET)经 pgBackRest 与一次基础 mc 往返对 rustfs 已端到端证
实,其中包括一段完整的 PITR 流程——全量 base backup、WAL 的 archive-push、
以及一次 --time 恢复:从 rustfs 取回归档 WAL 回放并精确停在目标时间
(Linux,rootless podman 实测)。如实标注:管理面 的 mc 命令(alias set
校验、bucket policy、admin 系列)尚未对 rustfs 逐一验证。把它当作纯对象
put/get 用,理解某些 MinIO 专属的管理命令未必在 rustfs 上有对应。
端口
每个实例从 minio_start_port(默认 9000)起的同一个池里取两个连续端
口:先 S3 API、后 console。这个池在 minio、silo、rustfs 之间共享——三
者由同一个游标分配,故在同一主机并存不冲突(先按名字排 minio,再 silo,再
rustfs):
配置
实例位于 pg.yaml 顶层的 addons.rustfs 映射里:
改动 listen、端口、root_user、root_password、image_tag、data_dir、
tls/cert_file/key_file 或 endpoints,需在下一次
pg addon install rustfs --name store --force 后生效。
列表
pg addon list 从不打印密码。要有意揭示它,加 --show-password(追加一行
Root password:),或只把密钥原样打到 stdout 供脚本使用:pg addon password rustfs --name store(即 access-key 密钥,与上面的 Root user: 配对;
--file <path> 以 0600 权限写入文件)。健康端点是 GET /health
(返回 200),不同于 MinIO 的 /minio/health/live。
启停
install 会跳过仍在的容器(停着的则启动);start 只启动已存在的容器(并
在状态异常时从配置重建以自愈)。在 TLS 生成模式下 start 还会重新校验并按
需重签叶子证书。
开机自启
容器带 --restart unless-stopped 策略(崩溃重启,而非开机自启)。要在主机
重启后拉起实例:
rustfs 的固定容器 uid 在 pgcli 的 wrapper 镜像内部处理,所以开机时的启动除 了运行 podman 本身不需要任何特殊主机权限。开机是只启动;rustfs 独立于 PostgreSQL 栈,故最后启动。
移除
数据目录就是对象存储——丢了它就丢了里面所有 bucket——所以 remove 默
认保留它。--clean-data 才删除(并清理默认布局下已空的父目录)。在
rootless podman 下,对象归一个附属 uid 所有、普通 rm 无法 unlink,因此
--clean-data 会透明地回退到 podman unshare rm 来回收它们。
日志
故障排除
- install 时
pulling rustfs image ... : ...。 wrapper tagghcr.io/mars-base/pgcli/pgcli-rustfs:1.0.0不可达——检查到 ghcr.io 的网络 访问,或用podman pull预拉。 - 多盘 install 起不来(永不健康)。 两块盘共享一个物理设备时 rustfs
[FATAL]。查pg logs addon rustfs --name store看磁盘检查是否触发,并确 保每个--drive在独立设备上(独立磁盘或各自的 loop 挂载)。 - rootless podman 写不进某个盘。 该盘的挂载点归 root 所有、落在命名空间
uid 范围之外,容器无法认领。把挂载点
chown给运行pg的用户。 - 手工
--api-port撞端口。 池在 minio/silo/rustfs 之间共享,一个显式端 口必须被三者的自动分配器都视为已占用。每个存储需要一对连续端口。 --clean-data落了个目录没删。 rootless podman 下的附属 uid 目录树经podman unshare rm回退删除;若该路径不可用,自己动手podman unshare rm -rf <dir>。
16.8 - HAProxy
HAProxy 是 Patroni 官方文档推荐放在集群前面的负载
均衡器:客户端只连一个稳定地址,HAProxy 对各成员的 REST API 做健康检查,把写
请求路由到当前 leader(读写分离模式下,读请求路由到 replica)。pgcli 将其作为
独立的顶层插件运行——共享的路由基础设施,而非实例级 sidecar——镜像固定为
官方 haproxy:3.2.23-alpine。
平台支持: HAProxy 插件目前仅支持 Linux。它通过主机网络访问 Patroni 成员,而 macOS 的
podman machine不会把主机网络暴露给容器。macOS 上NewHAProxyManager会快速失败并给出明确提示;pg addon list仍可显示已安装 实例,但没有实时状态。
工作原理
路由方式遵循 Patroni 官方 haproxy.cfg 的模式。每个后端就是一个 Patroni 成员,每个成员有两个关键端口:
- PostgreSQL 端口 —— HAProxy 实际转发连接的目标端口;
- REST API 端口 —— HAProxy 发送
httpchk健康检查的端口。
Patroni 的 REST API 对下列 GET 请求的应答(这些 GET 按设计无需认证):
| 端点 | 返回 200 的节点 |
|---|---|
GET / |
仅 leader |
GET /replica(可加 ?lag=<上限>) |
仅 replica,且延迟在限制之内 |
于是写监听器里唯一 UP 的服务器就是 leader,读监听器里唯一 UP 的就是各
replica——健康检查状态一变,故障切换自动生效。pgcli 把上述配置渲染到
<base-dir>/addon/haproxy/<name>/haproxy.cfg,并以只读方式挂载进容器。
两种路由模式
用 --mode 选择:
unified(默认)——单个监听器把所有流量发给当前 leader。简单, 每条连接都可读可写。leader 切换时,写监听器用on-marked-down shutdown-sessions掐断旧连接,让客户端重连到新 leader。split—— 读写分离:_<name>_rw监听器把写请求发给 leader,另一个_<name>_ro监听器把读请求轮询分发到各 replica(可按 lag 过滤)。客户端 端口从 1 个变成 2 个。
两种配置模式
在上面两种路由模式之上,还有一个谁来写 haproxy.cfg 的选择:
- 渲染模式(默认)——pgcli 根据
--node/--ha加--mode、端口和--max-lag生成配置,每次安装都会重写。就是上面说的 Patroni 读写代理。 - 自定义模式(
--conf-file <path>)——你提供完整的haproxy.cfg,pgcli 原样运行它,用于任意原生 HAProxy 代理场景(普通 TCP 中继、HTTP frontend、任意数量的frontend/backend/listen段——HAProxy 能做的都 行),不再限于 Patroni 集群。pgcli 把文件复制到自己的配置目录、以只读方式 挂载该副本,(重)启容器前先用haproxy -c校验,且永不改写、不删除你的 原文件。改完文件后,pg addon haproxy reload会重新校验并热加载运行中的 实例、不断流(仅 conf-file 实例——见热加载)。所有监听器都来自 文件本身;--mode、--rw-port/--ro-port/--stats-port、--max-lag、--listen与--node/--ha在这里没有意义,会被拒绝或忽略——见 自定义配置文件。
统计页
每个实例还会得到一个 HTTP stats 监听器,可在浏览器里观察服务器
up/down 状态:http://<listen>:<stats-port>/。
安装
后端成员用两种方式之一提供(互斥),或者用 --conf-file 完全绕过这套生成逻辑
(见自定义配置文件):
--node NAME=HOST:PGPORT:RESTPORT(可重复)——显式列出;--ha <scope>—— 自动从pg ha管理的 Patroni scope 中推导所有本机 成员,直接使用各成员的 PostgreSQL 与 REST API 端口。
--node 各字段的含义,以 node1=10.0.0.11:35532:8008 为例:
| 字段 | 含义 |
|---|---|
node1 |
成员名——成为 haproxy.cfg 中的 server 名,也是 stats 页里的标签 |
10.0.0.11 |
本机可访问到该成员的地址(成员的 advertise-host;同机可写 127.0.0.1) |
35532 |
成员的 PostgreSQL 端口——HAProxy 实际转发连接的目标 |
8008 |
成员的 Patroni REST API 端口——健康检查打这个端口 |
用 --ha 时这四个值都来自 pg.yaml:Patroni 成员名、其 advertise-host
(默认 127.0.0.1)、以及 host_port / restapi_port。
安装输出会报告分配到的端口和现成的连接串:
之后增删成员
后端列表跟随 pg.yaml 中该 scope 的本机成员,因此任何拓扑变化——增员或
移除——都通过重跑同一条 install 命令同步:它重新推导完整后端列表并重建
容器。
用 pg ha create <scope> --member <m> 扩容后,新成员还不在运行中的 HAProxy
配置里:
对称地,pg ha remove <scope> --member <m> 之后,被移除的成员会作为过期后端
残留在配置里——重跑 install 即可将其剔除:
显式 --node 安装时没有自动推导:直接改 --node 列表本身(增加或删除某个
spec)再重跑——目标列表每次整体替换,命令里写的就是最终全集。
跨主机集群
--ha 只从本机的 pg.yaml 推导后端,而它只记录本机 pg ha create 管理的
成员。位于其他主机的成员从来就不是本机的后端——在那边增删成员,本机
无需重新同步(也不会有残留)。若想用一个 HAProxy 前置整个集群,请改用显式
的 --node 列表,逐个填上各成员的 advertise 主机地址:
注意:在单台主机上用 --ha 安装的实例,写流量只会路由到本机的 leader——
一旦故障切换把 leader 提到了远程成员,rw 监听器就没有 UP 的服务器了,直到
leader 迁回来。对于 leader 可能漂移的跨主机集群,优先用显式的、列出全部成员的
--node 列表(或每台主机各跑一个 HAProxy,再用上层的 VIP / DNS 前置它)。
自定义配置文件
上面的渲染模式是 pgcli 对 Patroni 路由的一种固定写法。用 --conf-file 就完全
跳过生成,直接跑你自己的 haproxy.cfg——任意原生 HAProxy:普通 TCP 中继、
HTTP frontend、任意数量的 frontend/backend/listen 段,upstream 软件支持
什么就能做什么。pgcli 只保留容器生命周期(拉取、创建、启停、remove、autostart)
和配置校验门:
--conf-file 与 --node/--ha 互斥(同时给出会以报错拒绝安装),文件必须存在
且非空。一个把原始 TCP 中继和不相干 HTTP 监听器混在一起的典型文件:
安装摘要随后报告自定义模式,而不再报告端口(没有端口可报——它们在你的文件里):
这个模式的具体行为,逐条说明(以下每一条都是真实主机上安装实测的结果):
- 你的文件是唯一事实来源;跑起来的是 pgcli 的副本。 install 把它复制到
<base-dir>/addon/haproxy/<name>/haproxy.cfg,只读挂载这份副本。你的原文件 永不重写,pg addon remove也只删 pgcli 这份副本——你的文件留在原地。 - 改配置靠
reload(无损)或重跑 install(有损)。 改完文件后,要么pg addon haproxy reload把它热应用到运行中的容器、不断流,要么重跑同一条 install 重建容器。两者都会先把文件重新复制一份并用haproxy -c校验。渲染 模式的旋钮在这里改了没用——--mode、--rw-port/--ro-port/--stats-port、--max-lag、--listen描述的是生成器,不是你的文件。见热加载。 - 动容器之前全部经过校验。 每次 install 都先把挂载的副本扔进一次性容器跑
haproxy -c(仅检查)。坏文件会以 HAProxy 自己的诊断信息让 install 失败、 退出码非零,并回滚到上一份好配置——运行中的容器和磁盘上的配置原样不动, 之后的重启或pg addon start不可能复活一份被拒绝的配置。(warning——比如 缺 timeout——放行;只有真正的 error 才拒。) - 端口由你自己选。 端口池跳过 conf-file 实例:既不分配,也不把你文件里的
端口登记为已用。监听端口请自己规划,避开渲染模式实例自动分配的端口和主机上
其他服务——撞了的话 HAProxy 启动时以
cannot bind socket失败。 - 容器仍走 host 网络,与渲染模式一致。绑
127.0.0.1的 frontend 只有主机 本机能访问;要让别的容器/主机进来,绑主机的地址(上面pg_relay就是)。 - 代理的健康状况由你的配置负责。
pg logs <name>显示 HAProxy 自己的 stdout(前提是文件里写了 log stdout,如示例);stats 页只可能是你自己写的stats段——pgcli 不会替你加一个。 - 切回渲染模式只需一条不带
--conf-file的 install:重新给--node/--ha, 实例就回到渲染模式(磁盘上的 conf-file 副本会被生成的配置覆盖)。
示例:带健康检查的 HTTP 负载均衡
conf-file 实例就是一个完整的 HAProxy,所以 Patroni 那套并非它唯一能做的事——
下面是一个对多台 app 服务器做 HTTP 轮询、并带主动健康检查的配置(就是这份配置
实跑过,pg logs 里观察到了 DOWN/UP 行为):
每部分的作用,以及实测观察到的现象:
- 多条
server行——HAProxy 在新连接间轮询分发到各UP服务器(每台加一 行;列表顺序即初始轮转顺序)。 option httpchk GET /+http-check expect status 200——健康探测是 HTTPGET /,只有 200 算 UP。方法和服务实际应答的要一致:对HEAD返405、对GET返200的服务,得用GET形式,而不是默认的HEAD /。default-server inter 3s fall 3 rise 2——每 3s 探测一次,连续 3 次失败标 DOWN,连续 2 次成功恢复。- 每台 server 上的
check为该 server 开启探测。
某后端不可达时,其探测失败,fall 3 后 HAProxy 把它从轮转中摘掉并记日志——
pg logs <name> 里看到:
而所有前端流量仍稳定返回 200——只流向存活的 app1。等 app2 的端口重新应答,
过了 rise 2 它就自动加回轮转,无需 reload。要增删 server 行,改完
文件跑 pg addon haproxy reload(见热加载):它先用 haproxy -c
校验,再热换 worker、不断开在途连接。后端成员的增减仍以走上面的健康检查那条
最干净(你停掉的 server 无需任何 reload 就被摘出轮转);reload 是给你改动
server 列表本身用的。
并发调优(线程数)
HAProxy 的模型是单进程、多线程——没有 nginx 那种 worker_processes。在
conf-file 模式下,你在自己文件的 global 段里调它(渲染模式不设这些):
nbthread N——worker 线程数。对镜像验证过:haproxy -c接受nbthread 4。- 不设置时的默认——在多处理器主机上,HAProxy 会自动按 CPU 数铺开线程(其
原话:“Threads will automatically be used on multi-processor machines if
available”),所以只有要钉死具体数量时才需要
nbthread。 cpu-map auto:1-N 0-M——可选,把线程 1…N 绑定到 CPU 核 0…M(已验证)。nbproc已不存在。 自 HAProxy 2.5 起它被直接拒绝(haproxy -c报 “nbproc is not supported any more since HAProxy 2.5”)。用线程,不用多进程。
改它意味着编辑文件后跑 pg addon haproxy reload——master 重新读配置、按新的
线程数起一个新 worker(实测:nbthread 2 → 4 热应用,在途连接不断),因此
不像重跑 install 那样掐流量。这是你文件里的配置项,不是 pgcli 的 flag。
热加载
--conf-file 实例支持无损热加载配置——HAProxy 版的 pg addon nginx reload:
官方镜像本就以 master-worker 模式(-W)运行 HAProxy:pid 1 是 master,
子进程是 worker。reload 给 master 发 SIGUSR2,于是 master 用磁盘上的配置
起一个新 worker、把监听套接字交接过去,再让老 worker 处理完在途连接后才退出
——不断流,不同于 install 那次有损的容器重建。(这是实测过的行为,不是
纸上推演:一个运行中的实例的 worker 被整段换掉,期间始终返回 200。)
在发任何信号之前,reload 做的正是 install 那道校验门——它不是一个盲目
的信号:
- 把你的
--conf-file重新(原样)复制到挂载的haproxy.cfg; - 把副本扔进一次性容器跑
haproxy -c(仅检查); - 只有校验通过才发
SIGUSR2。校验不过的文件会带着 HAProxy 自己的诊断 信息中止 reload(退出码非零)、把上一份好配置写回磁盘、运行中的容器 原样不动——所以一份被拒的编辑,之后的重启也绝不会把它捡起来。
前提与范围:
- 仅
--conf-file实例。 渲染实例的配置每次 install 都从--node/--ha重建、不接受手改,因此它的更新路径仍是pg addon install haproxy。对它跑reload会报这条提示并拒绝。 - 容器必须在运行。 reload 打的是活的 master;实例停着的话先
pg addon start haproxy --name custom(一次全新的 install 也会把改好的文件 带起来)。 reload是给就地改运行中配置用的。仅仅是维护时临时下线某后端仍完全不需 reload——健康检查会把它摘出轮转;reload 是改server列表 /global旋钮 本身时用的。
同样的热加载也可以通过 install 的 --reload 参数触发:对一个正在运行的
conf-file 实例,install 会校验并热加载而非重建容器,一条命令既(重)配置又应用
、不断流:
不加 --reload 时,install 保持默认的容器重建行为(有损)。对已停止或渲染模式
的实例加 --reload 只打印一条提示、按正常 install 走。
读写分离使用示例
用 --mode split 安装后,会暴露两个客户端端口:
| 端口 | 流量 | 后端 |
|---|---|---|
write_port(默认 5000) |
读写 | 仅 leader |
read_port(默认 5001) |
只读 | 各 replica(轮询,lag ≤ max-lag) |
stats_port(默认 5002) |
HTTP 统计页 | — |
故障切换后 HAProxy 通过健康检查自动识别新 leader —— 无需改连接串。浏览器打开
http://127.0.0.1:5002/ 可实时查看后端状态。
端口
端口从主机级端口池自动分配:每个实例连续取 3 个空闲端口(rw,split 模式下再
加 ro,最后 stats),起始值为 haproxy_start_port(默认 5000)。也可以显式
指定:
端口池会探测占用并记录所有 HAProxy 实例已分配的端口,因此同一台机器上可以跑
多个实例而不会冲突。split 实例占 3 个端口(rw / ro / stats),unified
实例占 2 个(rw / stats):
conf-file 实例(见自定义配置文件)不消耗端口池: 池既不给它分配,也不登记它文件里的端口。
绑定地址默认 127.0.0.1;如需暴露到网络,用 --listen 0.0.0.0(或在
pg.yaml 里设 listen)。除非后端确有需要,请保持 loopback——这里 PostgreSQL
前面没有任何认证。
配置
全部配置位于 pg.yaml 顶层 addons.haproxy 映射中,以实例名为 key:
targets 由 install 渲染并重新推导;mode、端口、replica_max_lag、
image_tag 可在这里手调,然后 pg addon install haproxy --name lb ... 生效。
渲染出的 haproxy.cfg 不含任何密钥——只有主机、端口和健康检查 URI。
查看列表
conf-file 实例显示的是来源文件,而不是端口和后端:
启动与停止
宿主机重启后,无需重新渲染配置即可拉起实例:
install 总是重建容器以保证配置生效;start 只启动已有容器(状态不当时会
用磁盘上的 haproxy.cfg 自愈重建)。对 --conf-file 实例,
pg addon haproxy reload 能把改好的文件热应用到运行中的容器、免去这次重建
——见热加载。
开机自启
容器带有 --restart unless-stopped 策略(应对崩溃,而非重启)。要在宿主机
重启后拉起实例:
这会设置 autostart: true 并安装 / 刷新开机服务(见开机自启)。
开机时是仅启动语义:它启动已存在的容器、读取磁盘上已有的 haproxy.cfg,
不会重新渲染配置——请先安装实例。开机服务会在 Patroni 成员之后启动
HAProxy,因此其后端也已在拉起中。pg autostart status 会列出每个目标的状态。
移除
停止并删除容器,同时删除其配置目录。
日志
容器日志走 stdout(log stdout format raw local0 info),健康检查状态变化与
连接事件都会出现在这里。
故障排除
- stats 页所有后端都 DOWN。 健康检查打的是各成员的 REST API 端口。先确认
其可达:
curl http://<host>:<rest_port>/在 leader 上返回 200、其余 503;/replica正相反。返回000说明 REST API 挂了或端口写错。 --ha找不到成员。--ha只推导pg ha在pg.yaml中管理的本机 成员。远程成员或 pgcli 不管理的 scope,请用--node显式列出。- 手动
--rw-port撞端口。 请选端口池(haproxy_start_port起)之外的 端口,否则会被自动分配器视为已占用。 - conf-file 安装没过
haproxy -c,但旧配置还在跑。 这是设计行为:被拒的 副本会回滚,容器不动。按 HAProxy 报错指出的行修文件,再重装。若容器确实起来 后又死了(cannot bind socket、配置引用的文件缺失),看pg logs <name>。 - reload 报"容器未在运行"。 reload 打的是活的 master,实例必须先起来——
pg addon start haproxy --name <name>(或一次全新的 install,也会把改好的 文件带起来)。 - reload 因
haproxy -c报错而拒绝文件。 与 install 同一道门:reload 中止、 上一份好配置写回、什么都没重载——按 HAProxy 指出的行修好再 reload。 - macOS。 暂不支持——HAProxy 需要主机网络访问 Patroni 成员,podman machine 虚拟机不提供该能力。
16.9 - Silo
silo 是 Pigsty 社区维护的 MinIO 分支:S3 兼容对象
存储、内置 Web 控制台,并完整保留了 MinIO 的对外契约——S3 API、MINIO_*
环境变量、server /data --address :9000 --console-address :9001 命令行、
--certs-dir 目录布局、以及纠删码集群模式。pgcli 将其作为独立的顶层插件
运行,功能面与 minio 插件 完全一致——install、TLS、BYO 证书、
分布式模式、logs、autostart 一一对应。如果你跟随 Pigsty 的发布节奏就选 silo;
两者可以在同一台主机并存(共用一个端口池,统一游标分配,不会冲突)。
平台支持: silo 插件两个平台都支持。Linux 上通过主机网络提供服务; macOS 上加入
pgcli-netbridge 网络并发布两个端口,Mac 用127.0.0.1:<port>即可访问 API 与控制台。公开镜像为双架构(amd64 + arm64)。两个客户端——pg mcli(silo 自带)与pg mc(MinIO 的)——也都双平台可用,且都能 直连 silo 存储。
工作原理
一个容器、一个目录:silo server /data --address <listen>:<api-port> --console-address <listen>:<console-port>,其中 /data 是实例主机数据目录
(默认 <base-dir>/addon/silo/<name>/data)的 bind mount。silo 存储的一切
都在这个目录里,因此它的生命周期长于容器。
镜像是公开的官方 tag docker.io/pgsty/silo:RELEASE.2026-09-16T00-00-00Z——
双架构、控制台内置、并且连 mcli 客户端一起打包——所以 pg addon install silo 只负责拉取;pgcli 运行时从不构建镜像(pgcli-minio 是自建 tag,因为
MinIO 官方镜像移除了控制台,silo 没有这个问题)。pgcli 通过显式
--entrypoint silo 直接驱动 silo 二进制,绕开镜像自带的 entrypoint 包装
脚本,与驱动 MinIO 的方式一致。
凭据的处理方式与 Patroni 相同:
root_user默认admin;root_password首次 install 时自动生成(也可以用--root-password显式指定),存入pg.yaml(addons.silo.<name>.root_password),并在安装摘要中打印一次方便记录。
二者就是 silo root 的 access key / secret key——经由 silo 从 MinIO 继承的
MINIO_ROOT_USER / MINIO_ROOT_PASSWORD 环境变量传入——所有 S3 客户端(包括
pg mcli alias set <名称> <URL> <root_user> <root_password>)所说的 access
key 与 secret key 就是它们。
容器按 MinIO 官方部署建议(契约原样沿用)带上 --ulimit nofile=1048576:1048576 和 --stop-timeout 60;macOS 上 podman machine
虚拟机的 RLIMIT_NOFILE 上限更低,因此该平台改用 65536——单机 dev/test
足够。root 凭据通过 -e 传入会出现在 podman inspect 里——与手工运行容器的
暴露程度相同;对 rootless 单机部署(本插件的定位)可以接受。
安装
输出会报告端点与 root 凭据:
用打印出的 root 用户和密码登录 Console: URL 的控制台。S3 客户端(包括
pgBackRest)指向 S3 API: URL,或在终端里用下文的
pg mcli 操作。
对存活实例重复执行 install 是 no-op:容器不会被重建(已停止的会直接启动
并给出提示),命令行参数会合并进已存储的配置,root 密码保持不变。想改端口、
监听地址、endpoint 列表或凭据,用 --force 重建容器——数据目录不会丢。
绑定地址: 默认
127.0.0.1让存储保持本地可见。--listen 0.0.0.0(或pg.yaml里的listen键)会把它暴露到网络。届时任何能连到端口的 人都可以尝试 root 凭据,所以只在防火墙后面、或配合 TLS(下述--tls) 使用。
TLS(--tls)
--tls 让 silo 以 HTTPS 提供服务。pgcli 用标准库生成自签 CA 与叶子证书——
SAN 覆盖回环名、localhost 和主机的所有网卡 IP——写入
<base_dir>/tls/silo/<name>/:public.crt / private.key 供 silo 的
--certs-dir 使用,ca.crt 用于分发。证书目录以只读方式挂载,端点 URL
变为 https://。
为什么需要它:pgBackRest 对 S3 仓库强制 HTTPS(明文是上游明确拒绝的
选项),所以用于接收 Patroni archive-push 的 silo 必须讲 TLS。把
backup.repo.s3.ca_file 指向 ca.crt,pgBackRest 就会做完整证书验证——见
备份 → S3 对象存储仓库。该页
关于 MinIO 仓库的一切对 silo 同样逐字适用:客户端契约完全相同。
关于存储与集群配对的一点:backup.repo.s3 每个配置文件只有一个全局仓库,
所以第一个接入的 TLS 存储(无论 minio 还是 silo)会接收该环境下所有
stanza。确实需要两个目的地时,请再维护一份配置(见
备份 → 共享备份容器,以及完整示例
HA 集群 + 自签 CA MinIO)。
pg mcli 在命令通过回环 alias 访问 TLS 存储时自动加 --insecure(mcli
不会按 alias 持久化 CA 信任;同机回环下这样可接受)。指向局域网 IP 的 alias
不算回环——自己补 -- --insecure,或正规配置 CA。外部 https:// 端点保持
完整验证。对已有实例切换 --tls 需要 --force 才生效。
有效期与其他主机的访问。 CA 与叶子证书都是长生命周期;pgcli 在
--force 时、或主机地址变化时重新签发叶子(silo 监视证书文件并热加载重新
签名的一对,部署因此无需重建即可拿到新链),所以把 ca.crt 交给客户端是
每个存储一次性的动作。TLS 客户端用自己拨出的地址校验叶子证书的 SAN——
客户端自身的地址无关紧要。远程主机因此把 backup.repo.s3.endpoint 指向存储
主机的某个 IP(回环名与所有网卡 IP、含虚拟网桥,都在 SAN 里;裸主机名不在
——除非设置 MINIO_SERVER_URL,其 host 会被加入)。
把 CA 拿到远程主机上——不需要 scp。 服务端证书以叶子+CA 链的形式提供, 另一台机器上的消费方可以直接从 TLS 握手中取回根证书:
此取回是 trust-on-first-use——先与存储主机上 sha256sum ~/.pgcli/tls/silo/<name>/ca.crt 比对指纹再信任。随后一次 setup 会把 CA
重新发布进 Patroni 集群的 etcd 注册表,其他集群主机零手工步骤即可获得
(见备份 → S3 对象存储仓库)。
自带证书(--tls-cert / --tls-key)
--tls 永远只服务 pgcli 自签的一对。要服务你已有的证书——公共 CA 为真实
域名签发的、或你的私有 CA 签发的——改用 --tls-cert <leaf(+chain).pem> --tls-key <key.pem>。它隐含 --tls(无需重复传),取代生成的证书:两个文件
以只读方式直接挂到 silo --certs-dir 要求的文件名上
(/opt/silo/certs/public.crt、/opt/silo/certs/private.key),pgcli 从不
复制或重签——私钥始终只在你放置的那一个位置。
BYO 模式的其余一切——install 校验什么、为什么续期必须 --force(单文件
挂载锁定源 inode)、客户端如何选取信任锚、以及用 pg cert 生成测试证书——
与 MinIO 插件完全相同,见
MinIO → 使用自带证书。pg cert 示例
改写如下:
关闭 BYO。 没有 off 开关——跨重跑的 config 合并是单向的,与 --tls
本身一致。要回到生成模式,删除 pg.yaml 里该插件下的
cert_file/key_file,再 pg addon install silo --name store --force 重建。
部署形态
silo/MinIO 对自身的部署布局有明确分类;本插件四种全部支持:
| 形态 | 结构 | 适用场景 |
|---|---|---|
| SNSD(单机单盘) | 单节点、单个数据目录——不给 --endpoint 也不给 --drive 时的默认 |
开发、测试、演示 |
| SNMD(单机多盘) | 单节点、多块盘——每个 --drive 传一块盘,见下文 SNMD |
单机部署要扛住坏盘,又不想额外搭文件系统层 |
| MNSD(多机单盘) | 多节点、每节点一块数据盘——即下文的分布式模式 | 紧凑的高可用部署 |
| MNMD(多机多盘) | 多节点、每节点多块盘——--drive 传本节点的盘,--endpoint 传全集群矩阵 |
既要扛坏盘、又要扛坏机,且不额外搭文件系统层 |
pg addon install silo 开箱即是 SNSD。要得到 MNSD,传入集群的
endpoint 列表(至少四节点)——见下文分布式 / 集群模式。
MNMD 把两个 flag 组合起来:--drive 传本节点的盘(与 SNMD 完全一致),
--endpoint 传整个集群的 host×drive 端点矩阵(每台每个盘各一条 URL)——
见下文多机多盘(MNMD)。SNSD/MNSD 下的磁盘冗余另一条路仍然
可用——把 ZFS 池垫在 --data-dir 底下——它能让底层布局随时可换,空间利用上
也往往更省。S3 存储高可用方案 对比了原生
MNMD 与 ZFS 两条路,也覆盖"4 主机、每主机多块盘"这个既能扛坏盘又能扛坏机的
混合形态。
单机多盘(SNMD)
一个 silo 进程、多个宿主目录、盘之间做纠删码。每个 --drive 传一块盘,替代
--data-dir:
每个 --drive 是一块独立设备上的宿主目录;第 N 块盘挂到容器路径
/dataN,服务进程以 silo server /data1 /data2 ... /dataN 启动。--drive
与 --data-dir 互斥——多盘模式的数据位置只由 --drive 决定;--drive 再加
上 --endpoint 就是 MNMD,即多盘模式的多机版本,见下文多机多盘
(MNMD)。和 silo/MinIO 所有盘一样,与宿主根设备共享的盘会
在启动时被拒绝;pgcli 会列出越界的盘、在 install 时给出警告。
EC 换来什么(在活的 4 盘 silo 集上实测):4 盘集默认 2 片校验——可容忍 2 块盘故障。坏 1 块盘时读写都照常;坏 2 块盘时读仍成功、写被拒——这就是 quorum 边界,和 MNSD 用的是同一套算术,只不过成员是盘而不是节点。可用容量约 为原始总量的一半。回来的盘由 silo 自己 heal,pgcli 无需介入。校验片默认值随 盘数变化——本文只实测了 4 盘这一种形状。
pg addon remove silo --name store --clean-data 会删除每一块盘的目录——但
拒删仍处于挂载状态的盘,所以一次误操作的 --clean-data 绝不会穿透挂载点把
底下的盘 rm -rf 掉。真要丢弃下面的数据,先 umount。
分布式 / 集群模式
silo 的纠删码(EC)集群模式与 MinIO 完全一致——命令形状相同,规则也相同。
它要求至少四个互不相同的 host:port endpoint,且与其他插件不同,没有
中心协调者:每个节点各自运行 pgcli 和各自的 pg.yaml,每一份 pg.yaml
携带相同的完整 endpoint 列表与相同的 root 凭据。pgcli 只为本节点的容器
执行启动——组环的握手由 silo 自己跨主机完成。
三条硬性规则(endpoint 必须可路由且互不相同、数据目录必须位于与根文件系统
分离的磁盘——否则 pgcli 在 install 时告警、集群模式下不设置逐节点的
MINIO_SERVER_URL)以及 quorum 计算,与
MinIO → 分布式 / 集群模式相同;silo 因为继承
了这些检查,执行得同样严格。
多机多盘(MNMD)
MNMD 与 MinIO 下的做法完全一致:用 --drive 传本节点的盘,用 --endpoint
传整个集群的 host×drive 端点矩阵——每台、每个盘各一条 URL。每个端点必须指
名该节点的一个 /data1../dataN 盘槽,矩阵长度必须是每节点盘数的整数倍且至少
包含一台远端节点,--tls 节点的端点必须全部是 https://——这四项 pgcli 都会
在启动容器前检查。完整步骤见
MinIO → 多机多盘(MNMD)。
在活体的 4 节点 × 4 盘 silo 集(16 × 2 GiB)上实测到的形状与 MinIO 一致:
16 块盘在线,EC:4,位于单个 stripe 大小为 16 的纠删码集合里;损失一整台
节点(12/16 在线)时读和写都照常工作,64 MiB 往返逐字节一致;再损失第二台
节点(8/16 在线)时写被拒(Resource requested is unwritable)、读也失败,
节点重启后集合自愈回 16/16。16 盘 EC:4 保留原始字节的 12/16。
TLS 集群的推荐与 MinIO 下一条完全一致——一张 pg cert 叶子、SAN 覆盖所有节
点地址、每个节点 --tls-cert/--tls-key 同一对文件——并在 silo 上按与 minio
相同的方式验证过:grid 直接成环,没有逐节点 CA 需要对齐。配法见
MinIO → 多机多盘(MNMD)。
使用 mcli 客户端
pg mcli 从 pgsty/silo 镜像(经 --entrypoint mcli 选择——镜像默认
entrypoint 起的是服务端)在一次性容器里运行 silo 自带的 mcli 客户端——本机
零安装:
mcli 与 mc 的 alias 契约相同,因此互通——对 silo 存储、MinIO 存储或任何 S3
端点都可用。pgcli 有意把两个客户端指向同一个文件:alias 持久化在主机
~/.mc/config.json(mc 的原生默认路径),所以 pg mc 注册的 alias 对
pg mcli 同样可见,反之亦然——只设一次,不必每个客户端各设一次(无状态的
MC_HOST_<name> 形式两边也都识别)。原生 mcli 若按自身默认运行则读
~/.mcli/config.json,除非把 MC_CONFIG_DIR 指过去,否则看不到这个共享文件。
alias set 在写入前会对端点校验凭据,密码错误时配置文件保持不变。cp /
mirror / diff 的本地路径参数会按真实绝对路径解析并挂载,与 pg mc 行为
一致(macOS 上请保持在 home 目录内)。会被 pg 自身解析器拒绝的 mcli
参数放到 -- 之后:
两个客户端共用 MinIO 页面记录的命令表——
常用命令——把 pg mc 换成 pg mcli 即可。
端口
每个实例从一个池中取两个连续端口,起点为 minio_start_port(默认
9000):先 S3 API,后控制台。该池与 minio 插件共享——minio 与 silo
实例在同一游标上分配,同机并存不冲突;先 minio 表(按名称),后 silo 表:
配置
实例位于 pg.yaml 顶层 addons.silo 映射,以实例名为键:
对 listen、端口、root_user、root_password、image_tag、data_dir、
tls/cert_file/key_file 或 endpoints 的修改,在下一次
pg addon install silo --name store --force 后生效——普通 install 会跳过仍
存在的容器,--force 重建它(数据目录永不触碰)。
查看列表
pg addon list 永不打印密码。要有意揭示它,加 --show-password(追加一行
Root password:),或只把密钥原样打到 stdout 供脚本使用:
输出的值即 access-key 密钥,与上面的 Root user: 配对使用。--file <path>
以 0600 权限写入文件而非终端,避免落入 shell 历史与回滚缓冲。
启停
主机重启后,不重新应用配置地拉回实例:
install 跳过仍存在的容器(已停止的则启动);start 只启动已存在的容器
(状态异常时依配置重建自愈)。TLS 生成模式下 start 还会重新校验、必要时
重签叶子证书(silo 热加载挂载的证书)。
开机自启
容器带有 --restart unless-stopped 策略(管崩溃,不管重启)。要在主机重启
后拉起实例:
这会设置 autostart: true 并安装/刷新 boot service(见
开机自启)。boot 是只启动的。silo 独立于 PostgreSQL
栈,因此排在最后启动;无顺序约束。pg autostart status 列出所有目标的状态。
删除
数据目录就是对象存储——丢了它等于丢掉其中所有 bucket——所以 remove
默认保留并打印其位置。--clean-data 删除它(并清理默认为空的默认布局父
目录;data_dir 覆盖路径及其父目录永不触碰)。
日志
silo 输出到 stdout:启动行(API:/Console: 地址、Documentation:)与请求
错误。API: http://... 块确认监听已就绪。
故障排查
- install 报
pulling silo image ... : ...。 公开 tagdocker.io/pgsty/silo:RELEASE.2026-09-16T00-00-00Z不可达——检查到 docker.io 的网络/registry 访问,或用podman pull预先拉取。 - 手工
--api-port撞端口。 选择自动池(minio_start_port及以上) 之外的端口,否则自动分配器会视其为已占用;该池与 minio 插件共享,所以 silo 实例也要避开 minio 实例的显式端口,反之亦然。每个存储都需要一对 连续端口。 - 主机重启后
pg addon start无效 / 失败。 看pg logs addon silo --name store -f——多半是数据目录被删(--clean-data或手工),silo 拒绝在曾格式化过的空目录上启动;或绑定端口变了。 - 控制台可访问但 S3 客户端超时。
MINIO_SERVER_URL由listen+ API 端口构成;绑127.0.0.1却从其他主机访问时,客户端会被重定向到回环 URL。把listen设成客户端真正可达的地址。(集群模式完全不设MINIO_SERVER_URL。) - 用
pg mc设的 alias 在pg mcli里看不到(或反之)。 两者按设计共 享~/.mc/config.json,出现这种情况说明 alias 确实没写进去——pg mc alias list(同一文件)核对一下,或用环境变量传MC_HOST_<name>。原生 mcli 读 的是~/.mcli,不指MC_CONFIG_DIR就看不到这个共享文件。 - macOS。 受支持:插件在
pgcli-netbridge 上服务并发布两个端口,Mac 的127.0.0.1:<port>即可访问(容器内部绑0.0.0.0,MINIO_SERVER_URL声明 Mac 使用的回环地址)。改端口/凭据后--force重建容器。pg mcli的容器不能用127.0.0.1alias——见 MinIO mc 页的 端点说明。
16.10 - MinIO
MinIO 是 S3 兼容的对象存储。pgcli 将其作为独立的顶层插件 运行——共享基础设施,而非实例级 sidecar——带 Web 控制台。默认采用单机 (single-node)模式,也支持跨主机的分布式集群模式(见下文 分布式 / 集群模式)。两种模式下它都是通用对象存储。 silo——Pigsty 的 MinIO 分支,保持线路兼容——作为同级插件提供,功能面 完全一致;两者共用一个端口池,可在同一主机并存。
平台支持: MinIO 插件两个平台都支持。Linux 上通过主机网络提供服务; macOS 上加入
pgcli-netbridge 网络并发布两个端口,Mac 用127.0.0.1:<port>即可访问 API 与控制台,与其他插件一致。公开镜像为双架构 (amd64 + arm64),任意主机架构均可运行。pg mc客户端同样两个平台可用。
工作原理
一个容器、一个目录:minio server /data --address <listen>:<api-port> --console-address <listen>:<console-port>,其中 /data 是实例主机数据目录
(默认 <base-dir>/addon/minio/<name>/data)的 bind mount。MinIO 存储的一切
都在这个目录里,因此它的生命周期长于容器。
镜像是公开的预构建 tag ghcr.io/mars-base/pgcli/pgcli-minio:20250422221226——
上游静态二进制(双架构 amd64 + arm64,来自 minio/minio 的 GitHub release)跑在
Alpine 上,因为 MinIO 官方镜像移除了内置的 Web 控制台。pg addon install
只负责拉取;pgcli 运行时从不构建镜像。
凭据的处理方式与 Patroni 相同:
root_user默认admin;root_password首次 install 时自动生成(也可以用--root-password显式指定),存入pg.yaml(addons.minio.<name>.root_password),并在安装摘要中打印一次方便记录。
二者就是 MinIO root 的 access key / secret key——所有 S3 客户端(包括
pg mc alias set <名称> <URL> <root_user> <root_password>)所说的 access key
与 secret key 就是它们。
容器按 MinIO 官方部署建议带上 --ulimit nofile=1048576:1048576 和
--stop-timeout 60;macOS 上 podman machine 虚拟机的 RLIMIT_NOFILE 上限更
低,因此该平台改用 65536——单机 dev/test 足够。注意 root 凭据通过 -e 传
入,会出现在 podman inspect 里——与手工运行容器的暴露程度相同;对 rootless
单机部署(本插件的定位)可以接受。
安装
输出会报告端点与 root 凭据:
用打印出的 root 用户与密码登录 Console: 地址的控制台。S3 客户端
(包括 pgBackRest)指向 S3 API: 地址即可,也可以在终端用下文的
pg mc直接操作。
对已存在的实例重复执行 install 是无损的:不会重建容器(处于停止状态的会
直接启动并给出提示),命令行参数会合并进已存配置,已有的 root 密码保持
不变。要让改过的端口、监听地址或凭据生效,加 --force 重建容器(数据目录不受
影响)。
绑定地址: 默认
127.0.0.1,存储仅本机可见。--listen 0.0.0.0(或pg.yaml里的listen键)会把它暴露到网络上——能访问该端口的任何人都可尝试 root 凭据,因此只应在防火墙后或 TLS 终结代理之后这样暴露。
TLS(--tls)
--tls 让 MinIO 以 HTTPS 提供服务:pgcli 用标准库生成一把自签 CA 和一张叶证
书(SAN 覆盖回环名、localhost 与本机所有网卡 IP),落在
<base_dir>/tls/minio/<name>/(public.crt / private.key 供 MinIO
--certs-dir 用,ca.crt 供分发),随后把证书目录只读挂进容器。端点 URL 随
之变为 https://。
之所以需要它:pgBackRest 对 S3 仓库强制 HTTPS(明文被上游明确拒绝),所以
一个要接收 Patroni archive-push 的 MinIO 必须说 TLS。把 ca.crt 路径填进
backup.repo.s3.ca_file,pgBackRest 就会以完整证书校验连接它——见
备份 → S3 对象存储仓库。
关于"store 与集群怎么配对"的一点说明:backup.repo.s3 是一份配置文件里唯一
的全局仓库,所以最先接上的那台 TLS MinIO 会收到该环境里的所有 stanza——
再装一台 MinIO 并不会让集群们多出一个可按集群选择的 store。确需两个目的地
时,跑第二份配置(见备份 → 共享备份容器,以及完
整示例示例:HA 集群 + 自签 CA 的 MinIO)。
pg mc 在命令通过回环别名访问 TLS 存储时会自动附加 --insecure(mc 无法
按别名持久化 CA 信任;同机 127.0.0.1 回环可接受)。指向局域网 IP 的别名不算
回环——需自己加 -- --insecure,或正确配置 CA 信任。外部 https:// 端点仍
做完整校验。给已存在的实例开关 --tls 需要 --force 重建容器才生效。
有效期与跨主机访问。 CA 与叶证书均有效期 100 年(公共 CA 需遵守的 825 天
上限是浏览器策略,Go 校验器对私有根 CA 并不强制)。pgcli 会在 --force 时、
或主机地址变化时重签叶证书——因此对每个存储,分发 ca.crt 只需做一次。TLS
客户端校验的是它所拨号的目标地址是否匹配叶证书的 SAN,客户端自身地址从不
出现在任何证书里。所以远端主机(虚拟机里的 Patroni
成员、另一台机器上的备份容器)把 backup.repo.s3.endpoint 指向存储主机的
某个 IP 即可(回环名与本机所有网卡 IP——含虚拟网桥——都在 SAN 里;裸主机名
不在,除非设置了 MINIO_SERVER_URL,其 host 会被追加进 SAN)。
远端主机取 CA——无需 scp。 服务端证书以"叶 + CA"链形式下发,所以另一台机器 上的消费方可以直接从 TLS 握手里把根证书拉回来:
这次拉取本质是"首次使用即信任"(TOFU)——在你拿到 CA 之前,没有任何东西能
验证它——所以信任前请把打印出的 SHA-256 与存储主机上
sha256sum ~/.pgcli/tls/minio/<name>/ca.crt 的结果对拍(如同核对 SSH 主机指
纹)。之后一次 setup 会把 CA 发布进 Patroni 集群的 etcd 注册表,集群里其余
主机自动拉取、零手工步骤(见备份 → S3 对象存储仓库)。手工拷
贝 ca.crt 仍是可用的兜底路径,也是存储主机跑的是"链下发"之前的旧 pgcli 时的
唯一选择——fetch-ca 的报错会明确指出这一点,在存储主机上重跑
pg addon install minio --tls 即可升级(证书热重载,无需重启)。pgBackRest
stanza 从不要求 MinIO 与它同机。
使用自带证书(--tls-cert / --tls-key)
--tls 只会下发 pgcli 自签的那对证书。如果你想对外提供自己已有的证书——某
个公共 CA 为真实域名签发的,或你的私有 CA 签发的——改用
--tls-cert <leaf(+链).pem> --tls-key <key.pem>。它与 --tls(会被隐式打开,
无需重复传)配合,取代生成的证书:两个文件被只读挂载到 MinIO --certs-dir
要求的文件名上(/opt/minio/certs/public.crt、/opt/minio/certs/private.key),
pgcli 从不复制或重签它们——私钥只留在你放置的那一个地方。
安装时校验什么、不校验什么。 --tls-cert/--tls-key 会在任何容器启动前,
通过 Go 自身的 TLS 加载器配对校验,所以密钥与证书不匹配、证书其实只是一张
CA、证书已过期、或证书被限定用于服务端以外的用途——都会在安装时直接失败,而
不会延后成一个反复崩溃重启的容器。证书 SAN 是否覆盖所配置的 --listen 地址
也会被检查,但只作告警——域证书常常是通过 DNS 或负载均衡器背后的名字被访问
的,与它运行在哪台主机无关,所以这里的不匹配是提示、不是致命错误。
续期。 替换证书/密钥文件后重建容器:
重建是必需、而非可选的:文件以只读方式 bind 进容器后,运行中的 MinIO 不会
感知被替换的证书——既不会感知"写新文件再覆盖旧文件"(那会换掉单文件挂载所钉
住的 inode),甚至连原地重写同一个文件也不感知。已实测:改写宿主文件后,容器
仍继续下发旧的证书对,直到 --force 重建为止。pgcli 从不重签自带证书——证书
生命周期由运维方掌握——所以没有可依赖的自动刷新。
客户端。 用受公共 CA 签发的证书时,S3 客户端(mc 系、aws CLI、
pgBackRest 经 repo*-s3-ca-file)完全不需要额外信任材料——信任链本就锚定在
它们已信任的 CA 里。其余证书则把信任锚交给客户端的
backup.repo.s3.ca_file / --s3-ca-file:私有 CA 证书交签发 CA(或完整的
叶+中间证书链);自签证书既然自身即锚,pg cert 造的那张就直接交被服务的
.crt 本身。从没见过这个文件的远端主机,可以不用 scp,直接从 TLS 握手里自动
把它拉回来——fetch-ca 会识别自签叶证书并替你保存:
请把打印的 SHA-256 与存储主机上对你交给 --tls-cert 的那个 .crt 跑
sha256sum 的结果对拍(首次使用即信任,与上面取回生成 CA 的用法同理)。公共
CA 证书则仍然用不上这条命令——没有需要去取回、且尚未被信任的东西。
关闭自带证书模式。 没有 --tls-cert=/关闭 这类 flag——跨重跑的配置合并是
单向的,与 --tls 本身的行为一致。要回到生成证书模式,从 pg.yaml 里该实例
下删掉 cert_file/key_file,再用 pg addon install minio --name store --force
重建即可。
生成测试证书:pg cert
尝试自带证书模式不必先有一套真的 CA——pg cert 签发一张自签证书,SAN 覆盖你
要求的任意域名 / IP 组合:
它产出的任何东西都不碰 pg.yaml、也不碰容器——只是往你指定的路径写两个 PEM
文件并打印 SAN。flag 一览:
| flag | 默认值 | 含义 |
|---|---|---|
--host |
127.0.0.1 |
逗号分隔的域名和/或 IP,编码进 SAN(可重复传)。条目会自动识别是域名还是 IP,"minio.test,10.0.0.9" 不需要特殊语法;*.wild.test 会作为通配符域名条目。 |
--cert-file |
cert.pem |
写出证书 PEM 的路径。 |
--key-file |
key.pem |
写出私钥 PEM(PKCS8)的路径。 |
--valid-duration |
825 天(19800h) |
证书有效期,例如 --valid-duration 8760h 是一年。 |
--ecdsa |
P-256 |
曲线:P-224/P-256/P-384/P-521。置为空字符串则不生成 ECDSA 密钥(配合 --rsa 用)。 |
--rsa |
(关) | RSA 密钥位数(如 2048、4096);只在明确需要 RSA 而非默认 ECDSA 密钥时才设。 |
--ca |
false |
让这张证书自成一个 CA(CA:TRUE、keyCertSign)——用于你想拿它当私有根再去签别的证书,而非常规的 --tls-cert 用法。 |
同一套签发逻辑也编成了一个可脱离 pg 使用的独立二进制:make gencert →
bin/gencert,flag 完全相同、只是用单横线形式(-host、-cert-file……)。两
者背后是同一个 internal/certgen 包,产出的证书类型逐字节一致。
pg cert 写出的是单张自签叶证书,不是一条链。 --cert-file 里的 PEM 恰好
只有一块 CERTIFICATE——这张证书自我签名(IsCA: false、serverAuth EKU、
你要求的 SAN)。它有意不是"叶+中间+根"的打包链:它上面没有签发 CA,所以没有
东西可以拼进链;ValidateBYOCert 只看文件里的第一块证书,并拒绝 CA 证书
(leaf.IsCA)——这也正是 --ca 产出的那张证书不能拿去喂 --tls-cert
的原因:它是信任锚本身,不是服务端证书。
既然产出是自签证书,在客户端侧就把生成的 minio.crt 当作 pgcli 自己为
--tls 生成的那张 ca.crt 同样使用——被下发的那张叶证书本身就是自己的
信任锚,所以 backup.repo.s3.ca_file / pg backup setup --s3-ca-file 直接
指向同一个文件即可(见
备份 → S3 对象存储仓库)。这不是 pgBackRest
勉强容忍的旁门做法:OpenSSL 的信任库把通过 -CAfile/SSL_CTX 交给它的任何
证书都当作锚点,并不要求 CA:TRUE,而 pgBackRest 的 S3 TLS 路径(curl 走
OpenSSL)用的正是这一套机制——直接实测过:对 pg cert 产出的自签、CA:FALSE
叶证书跑 openssl verify -CAfile <证书> <证书>,返回 OK。
部署形态
MinIO/silo 对自身的部署布局有明确分类;本插件四种全部支持:
| 形态 | 结构 | 适用场景 |
|---|---|---|
| SNSD(单机单盘) | 单节点、单个数据目录——不给 --endpoint 也不给 --drive 时的默认 |
开发、测试、演示 |
| SNMD(单机多盘) | 单节点、多块盘——每个 --drive 传一块盘,见下文 SNMD |
单机部署要扛住坏盘,又不想额外搭文件系统层 |
| MNSD(多机单盘) | 多节点、每节点一块数据盘——即下文的分布式模式 | 紧凑的高可用部署 |
| MNMD(多机多盘) | 多节点、每节点多块盘——--drive 传本节点的盘,--endpoint 传全集群矩阵 |
既要扛坏盘、又要扛坏机,且不额外搭文件系统层 |
pg addon install minio 开箱即是 SNSD。要得到 MNSD,传入集群的
endpoint 列表(至少四节点)——见下文分布式 / 集群模式。
MNMD 把两个 flag 组合起来:--drive 传本节点的盘(与 SNMD 完全一致),
--endpoint 传整个集群的 host×drive 端点矩阵(每台每个盘各一条 URL)——
见下文多机多盘(MNMD)。SNSD/MNSD 下的磁盘冗余另一条路仍然
可用——把 ZFS 池垫在 --data-dir 底下——它能让底层布局随时可换,空间利用上
也往往更省。S3 存储高可用方案 对比了原生
MNMD 与 ZFS 两条路,也覆盖"4 主机、每主机多块盘"这个既能扛坏盘又能扛坏机的
混合形态。
单机多盘(SNMD)
一个 MinIO 进程、多个宿主目录、盘之间做纠删码。每个 --drive 传一块盘,替
代 --data-dir:
每个 --drive 是一块独立设备上的宿主目录;第 N 块盘挂到容器路径
/dataN,服务进程以 minio server /data1 /data2 ... /dataN 启动。--drive
与 --data-dir 互斥——多盘模式的数据位置只由 --drive 决定;--drive 再加
上 --endpoint 就是 MNMD,即多盘模式的多机版本,见下文多机多盘
(MNMD)。和 MinIO 所有盘一样,与宿主根设备共享的盘会被
MinIO 在启动时拒绝;pgcli 会列出越界的盘、在 install 时给出警告。
EC 换来什么(在活的 4 盘集上实测):MinIO 把每个对象切成数据片 + 校验
片,4 盘集默认 2 片校验——可容忍 2 块盘故障。坏 1 块盘时读写都照常;坏 2 块
盘时读仍成功、写被拒——这就是 quorum 边界,和 MNSD 用的是同一套算术,只不过
成员是盘而不是节点。可用容量约为原始总量的一半(4 × 2 GiB 盘 →
mc admin info 报 3.6 GiB 可用、EC:2)。回来的盘由 MinIO 自己 heal,pgcli
无需介入。校验片默认值随盘数变化——本文只实测了 4 盘这一种形状。
pg addon remove minio --name store --clean-data 会删除每一块盘的目录——但
拒删仍处于挂载状态的盘,所以一次误操作的 --clean-data 绝不会穿透挂载点把
底下的盘 rm -rf 掉。真要丢弃下面的数据,先 umount。
分布式 / 集群模式
也支持 MinIO 的纠删码(EC)集群模式。它要求至少 4 个互不相同的
host:port 端点,并且与其他插件不同,没有中心协调者:每台主机各自跑
一份 pgcli、各自一份 pg.yaml,而每一份 pg.yaml 都完整写入同一份端点
列表和同一套 root 凭据。pgcli 只负责拉起本机对应的那一个容器——跨主机的
组环握手由 MinIO 自己完成。
端点列表就是模式开关:为空即单机(行为不变),非空则以
minio server <ep1> <ep2> ... 进入分布式模式。
每个端点的形式是 http://<host>:<port><路径>。host:port 是节点之间互相
访问、完成组环握手的地址。尾部那段 <路径> 不是 HTTP 路由——客户端永远
看不到它——它是 export path(导出路径),即该节点把自己那一份纠删码分片
数据存放在容器内的哪个目录。MNSD 下(不给 --drive)pgcli 把 --data-dir
挂载到容器的 /data,所以这段路径必须从 /data 开始——最简单就直接写
/data。四个端点里这段路径字符串要保持一致。(MNMD 下节点也会传 --drive,
此时每个端点改为指名该节点的一个 /data1../dataN 盘槽——见下文
MNMD。)
注意端点里的路径是容器内路径,与宿主目录结构无关:如果 /data 是一块
共享盘、还想在上面放别的东西,把 --data-dir 指到它的子目录即可——
--data-dir /data/minio 让本集群的数据落在宿主的 /data/minio,而端点仍然
写 http://<host>:9000/data(端点无法指名这个子目录,它看到的是挂载根)。
只有当你有意往卷内部再扩展一层(比如 /data/mystore)时,数据才会在宿主上
多落一层到 <data-dir>/mystore——避免把 --data-dir /data/minio 和端点
.../data/minio 搭配使用,那会嵌套成 /data/minio/minio。
--root-password 是可选的:不传的话首次 install 会自动生成一个(打印一次、
存入 pg.yaml)。集群模式下每台传同一个值,就能保证全集群凭据一致,不需要
任何手工复制。
安装摘要会列出成员并提醒一致性要求:
集群模式有三条硬性约束,都是实测踩出来的:
- 端点必须是可路由、互不相同的主机。 同一主机会折叠成"单机多盘"并被
拒绝(
use path style endpoint for single node setup);127.0.0.0/8回环段直接被拒(resolves to localhost)。请用各节点真实的局域网地址。 - 数据目录必须位于与根文件系统不同的磁盘上。 MinIO 拒绝与系统盘同设备
的驱动器(
drive is part of root drive, will not be used)。pgcli 会用 数据目录与/的stat做检测,在--data-dir落在根设备时于 install 时给出警告——警告是提示性的、不阻断;请把--data-dir指向独立挂载的 数据盘。 - 集群模式下不下发
MINIO_SERVER_URL。 它在各节点本就不同(各自宣告 自己的 IP),而 MinIO 要求所有MINIO_*环境变量逐节点字节一致,否则每台 都会卡在Waiting for at least 1 remote servers with valid configuration。 地址由端点列表决定。单机模式仍按listen设置MINIO_SERVER_URL。
Quorum(EC): 写需要 ⌈N/2⌉+1 个节点在线,读需要 ⌈N/2⌉。因此 4 节点
集群在挂掉 2 台时仍可读、但拒绝写;重启下线的节点后集群自愈。MNMD 下被计数的
成员是盘而不是节点——活体 4×4 集群上实测的盘级边界见下文
MNMD。
仅跨主机。 这是真正的分布式部署——每台主机由你自己跑 pgcli。pgcli 不会 在节点间 SSH、也不做成员注册;保持 N 份
pg.yaml一致是运维的职责。
多机多盘(MNMD)
MNMD 就是每个节点贡献多块盘的 MNSD:MinIO 在整个 host×drive 矩阵上做纠删
码,因此这套集合不需要任何文件系统层就能同时扛住单块盘故障和整台节点故障。
用 --drive 传本节点的盘(与单机多盘(SNMD)完全一致),用
--endpoint 传整个集群的端点列表——每台、每个盘各一条 URL,不只是本机。
与 MNSD 唯一的区别是导出路径。每个节点把自己的盘挂到 /data1../dataN,所以
每个端点必须指名其中一个槽:http://<host>:<port>/data<k>。pgcli 不解析
URL——它把整个矩阵原样交给 minio server,与 MinIO 官方文档的写法一致——因
此每个节点都要携带同一份完整列表。列表长度必须是每节点盘数的整数倍(MinIO 要
求每个节点贡献相同数量的盘),且必须至少包含一台远端节点;会折叠到单台主机的
矩阵会在容器启动前就被拒绝,--tls 节点搭配明文 http:// 端点同样会被拒。
用 TLS 服务整个 grid——一份证书,而不是每节点一张 CA。 让每个节点各自跑
裸 --tls,每台主机就会签出自己的私有 CA,于是第一次跨节点握手死在 x509: certificate signed by unknown authority,剩下的活儿只能靠手工把某一张 CA 种进
每个节点的证书目录。干净的做法是整个 grid 共用一份证书:pg cert 写出一张
SAN 覆盖所有节点地址的自签叶子,把这一份 .crt/.key 逐字节相同地复制到所有
节点,用 --tls-cert / --tls-key 服务它(根本没有逐节点 CA 需要对齐)。这
就是推荐的 TLS 配法——反正 pgBackRest 对 S3 仓库强制 HTTPS,一个备份 store 本
就是一个 TLS grid。端点矩阵届时必须全部是 https://,且节点拨出的每个地址都
要在叶子的 SAN 里。(同一份共享叶子服务 MNSD 集群同样合适——握手的还是那几台
服务器。)
--listen 0.0.0.0 是有意为之:每个节点都要在其他 15 条端点所指名的地址上应答。
--root-password 必须处处一致,与 MNSD 的要求相同。
在活体的 4 节点 × 4 盘集群(16 × 2 GiB)上——正是用上面这套共享 pg cert 的
TLS 配法成环,Network: 4/4 OK,没有逐节点 CA 需要对齐——用 mc admin info
实测到的形状:
- 16 块盘在线,EC:4,位于单个 stripe 大小为 16 的纠删码集合里——该集合报 4 片校验,因此 16 块盘里坏 4 块仍然健康。
- 损失一整台节点(4 块盘 → 12/16 在线)时读和写都照常工作;64 MiB 上传 /下载往返逐字节一致。
- 再损失第二台节点(8/16 在线):写被拒(
Resource requested is unwritable),读也失败——该集合已不再安全可服务。重启节点后自愈回 16/16。 - 可用容量随校验比例而定:16 盘 EC:4 保留原始字节的 12/16。
--clean-data会逐盘删除目录,但拒删仍在挂载的盘;回来的盘由 MinIO 自己 heal。
所有节点的 pg.yaml 都携带同一份 16 行矩阵和一个共享的 root 密码;保持它们
一致,与 MNSD 一样,是运维的职责。
使用 mc 客户端
pg mc 在一次性容器里运行 MinIO 自家的 mc 客户端——不用本地安装,也不用
手敲 podman run:
别名持久化保存在宿主的 ~/.mc/config.json——这是 mc 自己的默认路径,
Linux 和 macOS 都一样——alias set 一次,之后每次 pg mc(以及 Linux 上
原生安装的 mc)都能看到同一批别名。pg mc alias list 读回列表,
pg mc alias remove 删除单个别名。alias set 会先拿凭据向端点校验,通过才
写文件——密码打错不会在配置里留下一个用不了的别名。pg mc 底层做的事只是把
你的 ~/.mc 挂载进容器,然后运行 ghcr.io/mars-base/pgcli/pgcli-mc(静态上
游二进制 + scratch,首次使用时自动拉取);没有别的魔法。
cp / mirror / diff 的本地文件参数同样可用:pg mc 会把每个本地路径按
realpath 解析,并把该绝对路径原样挂载进容器,因此
所见即所得。下载到尚不存在的路径(新文件名、或还没建的 ./newdir/)也没问
题——挂载的是它最近的已存在父目录,mc 写出的文件会落回宿主。macOS 上路径必须
位于家目录之下——那是 podman machine 虚拟机唯一共享的目录树;家目录之外的
路径 pg mc 会明确提示并跳过。
任何 pg 自己解析器会拒绝的 mc 标志都要放到 -- 之后,与 pg etcdctl 的
约定完全一致。mc 的长标志(--all、--json、--recursive、--force、
--newer-than 等)pg 一个都不认识,因此全部放 -- 之后:
放在 -- 之前会在 pg 这一层就被拦下,报错形如 unknown flag: --recursive
——看到它就说明分隔符漏了。
原生 mc 有个与本地挂载相关的注意点值得知道:在文件类命令(cp / mirror /
diff)里,首段不是已知别名的参数一律按本地路径处理。所以别名拼错时不会报
错,而是安静地上传/下载到当前目录下一个以拼错名字命名的本地目录——pg mc
只是忠实挂载了 mc 判定要读的路径,这是原生行为而非 pg mc 的怪癖。执行文件
操作前先用 pg mc alias list 确认别名存在。
MC_HOST_<name> 环境变量形式的别名——mc 的无状态用法,不碰配置文件——同样
适用于 pg mc,因为该变量会被转发进容器:
各平台的端点: Linux 上
pg mc走主机网络,127.0.0.1别名可直达本机 addon 实例。macOS 上容器位于 bridge 网络,127.0.0.1是容器自己的回环—— 指向 Mac 本机 addon 的别名请用http://admin:<密码>@host.containers.internal:9000,远端存储则用其可路由地 址。pg mc本身在两个平台上行为一致。Mac 浏览器访问控制台走的是宿主机侧的
127.0.0.1:<console-port>(插件已发布 该端口),这与pg mc容器能不能用127.0.0.1无关——那只是宿主机自己的回环。
常用命令
| 命令 | 用途 |
|---|---|
pg mc ls store / pg mc ls store/backups |
列出桶 / 对象 |
pg mc mb store/backups |
创建桶 |
pg mc cp ./file store/backups/ |
上传(目录树用 pg mc mirror ./dir store/backups/,或给 cp 加 -- --recursive) |
pg mc cp store/backups/file ./ |
下载 |
pg mc find store -- --name '*.pglz' |
按名字搜索对象 |
pg mc du store |
统计各桶大小 |
pg mc rm store/backups/file |
删除单个对象 |
pg mc rm store/prefix -- --recursive |
批量删除对象 |
pg mc rb store/bucket -- --force |
连对象一起删桶 |
同一套 root 凭据适用于任何 S3 SDK——包括 pgBackRest 的 repo1-type=s3
仓库(见备份、恢复)。
端口
每个实例从同一个端口池取两个连续端口,基址为 minio_start_port(默认
9000):先 S3 API、后控制台。与其他自动分配池一样,会跳过已占用端口与同级
实例的显式端口:
配置
实例存放在 pg.yaml 顶层 addons.minio 映射中,以实例名为键:
修改 listen、端口、root_user、root_password、image_tag、data_dir、
tls/cert_file/key_file 或 endpoints 后,执行
pg addon install minio --name store --force 即生效——普通 install 会跳过已存在
的容器,--force 会重建它(数据目录永不受影响)。
查看列表
pg addon list 从不打印密码。要有意揭示它,加 --show-password(追加一行
Root password:),或只把密钥原样打到 stdout 供脚本使用:
输出的值即 access-key 密钥,与上面的 Root user: 配对使用。--file <path>
以 0600 权限写入文件而非终端,避免落入 shell 历史与回滚缓冲。
启动与停止
主机重启后,不重新下发配置即可拉起实例:
install 会跳过已存在的容器(处于停止状态的直接启动);start 只启动已有
容器(状态异常时依据配置自动重建)。
开机自启
容器带 --restart unless-stopped 策略(管崩溃、不管重启)。主机重启后自动拉起
实例:
这会置 autostart: true 并安装/刷新 boot service(见
开机自启)。开机路径是只启动语义。MinIO 与 PostgreSQL
栈相互独立,因此排在最后启动,无顺序依赖。pg autostart status 列出所有目标
的状态。
移除
数据目录就是对象存储本身——丢了它等于丢掉里面所有 bucket——因此 remove
默认保留它并打印其位置。--clean-data 会删除它(并清理默认布局下已清空的父
目录;显式 data_dir 及其父目录永不受影响)。
日志
MinIO 日志走 stdout:启动行(API:/Console: 地址、Documentation:)与请求
错误。看到 API: http://... 块即确认监听已就绪。
故障排除
- install 报
pulling minio image ... : ...。 公开 tagghcr.io/mars-base/pgcli/pgcli-minio:20250422221226拉不到——检查网络/registry 可达性,或先podman pull手动拉取。 - 手动
--api-port撞端口。 请选端口池(minio_start_port起)之外的端口, 否则会被自动分配器视为已占用;另注意 MinIO 需要的是一对连续端口。 - 主机重启后
pg addon start无效果/失败。 看pg logs addon minio --name store -f——多半是数据目录被删了(--clean-data或人为),MinIO 拒绝在曾格式化过的空目录上启动;或者绑定端口变了。 - 控制台能打开,但 S3 客户端超时。 单机模式下
MINIO_SERVER_URL由listen+ API 端口拼成;若绑在127.0.0.1却从其他主机访问,客户端会被 重定向到回环地址。把listen设为客户端真正可达的地址。(集群模式完全 不下发MINIO_SERVER_URL——见分布式 / 集群模式。) - 集群卡在
Waiting for at least 1 remote servers with valid configuration。 节点之间配置不一致。逐台用podman inspect pgcli-minio-<ns>-<name> --format '{{json .Config.Env}}'核对每份pg.yaml的endpoints列表与root_password是否字节级一致,并确认 没有残留的每节点MINIO_SERVER_URL。 - macOS。 已支持:插件加入
pgcli-netbridge 并发布两个端口,Mac 用127.0.0.1:<port>即可访问(容器内部绑0.0.0.0,MINIO_SERVER_URL宣告 Mac 使用的回环地址)。改过端口或凭据后用--force重建容器。用pg mc时 注意容器内127.0.0.1别名不可用——见上文端点说明。
16.11 - Redis
Redis 是应用需要缓存、会话后端、排行榜(有序集合)或
原子计数时最常选的内存键值存储。pgcli 将其作为独立的顶层插件运行,CLI
表面与对象存储一致(install / start / stop / logs / autostart / remove),
全部通过 pg.yaml 管理。
它是整个插件族里刻意做得最简单的一个:
- 一个容器、一个端口、一个密码。
requirepass在首次安装时自动生成并 存入pg.yaml——不存在"未认证的 Redis"这种可达状态。 - 版本选择。 首个支持版本选择的插件:
--version 7|8通过内置映射表解析 到固定的docker.io/library/redis标签(7 →7.4.11,8 →8.10.2,默认 8)。--image仍可绕过映射表直接指定任意标签。 - 可调的持久化。 默认数据集是落在 bind 挂载主机目录里的 RDB 快照——重启
后仍在,
pg addon remove(不带--clean-data)后也仍在,重装同名实例即可 复活数据。--aof在其上叠加写前日志,用于崩溃安全、不驱逐的存储;--save no可彻底关闭快照(见参数设置)。
在 PostgreSQL 实例旁边做缓存/会话/排行榜/计数就选 Redis;它与 PG 栈无关,可 与任何插件并存,同一主机也能跑多个实例(甚至跨大版本)。
范围之外: Sentinel(自动故障转移)不由 pgcli 管理。Redis 的原生集 群是支持的——
--cluster把实例变成 cluster-enabled 成员,组建由你自己跑 一次pg redis-cli -- --cluster create(默认纯主分片,用--cluster-replicas N可给每个主加从属,见原生集 群)。读副本也支持——--replica-of把一个实例变成对另一个 主节点的只读跟随(见读副本与已知限制)。
参数设置
pgcli 刻意把 Redis 的参数面做得很小:一个可调项对应一个 flag,最终都落到
redis-server 的原生选项上——经镜像自带 entrypoint 用 argv 覆盖,不生成
redis.conf;一个实例一个容器,直接用纯上游镜像(pgcli 不为 Redis 构建
wrapper,这点与 rustfs 不同)。下表列出每个可调项及其默认值:
Flag / pg.yaml 键 |
默认值 | Redis 选项 | 含义 |
|---|---|---|---|
--version / version |
8 |
—(决定镜像 tag) | 安装的大版本(见版本选择) |
--image / image_tag |
由版本映射表解析 | — | 直接指定任意 tag |
--port / port |
从 redis_start_port 自动分配(基 6379) |
--port |
宿主端口 |
--listen / listen |
0.0.0.0 |
--bind |
监听地址(127.0.0.1 收紧) |
--password / password |
自动生成 20 字符 | --requirepass |
认证;始终必须存在(副本的必须与主节点相同) |
--maxmemory / maxmemory |
不设(无上限) | --maxmemory |
数据集上限,开启驱逐 |
--maxmemory-policy / maxmemory_policy |
allkeys-lru |
--maxmemory-policy |
驱逐策略,仅在设了上限时有意义 |
--aof / aof |
关 | --appendonly yes |
在快照之上叠加写前日志 |
--appendfsync / appendfsync |
everysec(Redis 自身默认) |
--appendfsync |
AOF 刷盘强度,仅在开 --aof 时有意义 |
--save / save |
Redis 默认计划 | --save |
RDB 快照计划;no 关闭快照 |
--replica-of / replica_host+replica_port |
不设(主节点) | --replicaof + --masterauth |
成为本机主实例的只读副本(host/port/密码都从配置里取) |
--replica-of-host / replica_host |
— | --replicaof |
跨机副本的主节点地址(与 --replica-of-port + --password 搭配) |
--replica-of-port / replica_port |
— | --replicaof |
主节点端口,与 --replica-of-host 搭配 |
--cluster / cluster |
不设(独立实例) | --cluster-enabled yes + --masterauth |
原生集群成员:非空值即组名——同名归同一集群,且同组成员共用首成员的密码(--cluster create 用一个密码认证全部操作数节点,与"副本密码必须等于主节点"同理)。每个成员还会带上 --masterauth,以便 Redis 在组建时被提升为从属的节点能向主节点认证。与 --replica-of/--replica-of-host 互斥——一个实例要么是集群成员,要么是读副本。pgcli 只装节点,组建由你自己做——见原生集群 |
--cluster-replicas / cluster_replicas |
0(纯主分片) |
—(仅回显进建议的 --cluster create) |
组建步骤里每个主的从属数:摘要按 3*(1+N) 个节点计数,打印的命令以 --cluster-replicas N 结尾。哪些成员成为从属是 Redis 在 --cluster create 时的决定,而非安装时。它是与密码同类的组属性——首成员设定,后续成员继承,显式不一致会被拒绝 |
--advertise-host / advertise_host |
不设(127.0.0.1,单机) |
--cluster-announce-ip/-port/-bus-port |
本成员向对等节点宣告的地址——填局域网 IP,跨机集群必备(Redis 集群总线不支持 NAT/端口重映射) |
--data-dir / data_dir |
<base-dir>/addon/redis/<name>/data |
--dir /data(主机目录 bind 挂载到 /data) |
数据落盘位置 |
| — | autostart: false |
— | 开机自启(pg autostart enable --redis) |
各旋钮的组合方式:
requirepass始终开启——即使手工往pg.yaml里留空password,也不 存在"未认证的 Redis"这种可达状态。- 持久化默认只有 RDB 快照——沿用 Redis 自身的
save计划,外加停机时补 一份最终快照(--stop-timeout 30给足时间)与启动时从dump.rdb回放。 --aof在快照之上叠加写前日志(--appendonly yes)——耐久、不驱逐的 存储形态。--appendfsync调节刷盘强度:always(耐久)/everysec(默 认)/no(吞吐)。AOF 文件在数据目录的appendonlydir/下,启动时先于 快照回放,两者同开时以 AOF 为准。--save no关闭快照——与--aof搭配即 AOF-only(安装摘要显示aof only);单独使用则是纯内存实例,重启丢数据(install 会警告)。--maxmemory隐含驱逐,默认allkeys-lru——上限的语义是"缓存,超了 就驱逐"。传--maxmemory-policy noeviction变成硬顶(超上限写失败而非驱逐 键),或干脆不设--maxmemory表示无上限。policy 依赖--maxmemory、--appendfsync依赖--aof——CLI 在拉镜像/建容器之前就拒绝这类组合。--replica-of造只读副本——见读副本。持久化/驱逐旋钮与角色 正交:副本也能设--maxmemory或开--aof,跟主节点一模一样。--cluster造集群成员,与--replica-of/--replica-of-host互斥—— 一个实例只有一个角色。同--cluster组成员自动共用首成员的密码(后续成员 带不同的--password会在拉镜像/建容器之前被拒绝);每主从属数由--cluster-replicas N同样按组继承。见原生集群。
Redis/Valkey 的选项空间远比这张表大,pgcli 有意不包装多节点控制面
(Sentinel、自动故障转移),集群编排也一样:pgcli 装 cluster-enabled 节
点,但从不替你跑 --cluster create/add-node/resharding/缩容——见原生集
群与已知限制。读副本是支持的(读副本),
pgcli 留下的只是编排那一层:主节点挂了不自动提升副本。引擎本身倒是可以用
--image 变通:pg addon install redis --image docker.io/valkey/valkey:8 跑的
是 Valkey(Redis 的分支),大版本反解析为 8。
版本选择
--version 选大版本;映射表把每个大版本钉到一个固定的补丁标签:
| 大版本 | 默认镜像标签 | 选择方式 |
|---|---|---|
| 7 | docker.io/library/redis:7.4.11 |
pg addon install redis --name cache --version 7 |
| 8 | docker.io/library/redis:8.10.2 |
pg addon install redis --name cache(默认 8) |
解析规则:
--version 7(或8)→ 表中 tag 存入image_tag,大版本存入version;--image <tag>→ 以你的 tag 为准;大版本从 tag 反解析出来仅供展示 (redis:7.2.4→7),解析不出则留空(展示时原样显示 tag);- 两者都不给 → 大版本
8; - 未知的
--version→ 报错并列出可用大版本。
两个大版本可在同一主机并存——tag 不同、端口各自从同一池分配。表中 tag 跟随
各大版本的最新补丁,与 docs/images.md 的条目一起升级。
安装
安装摘要打印客户端所需的一切:
安全模型。
listen默认0.0.0.0——Linux(host 网络)下端口发布在 所有网卡上,requirepass是唯一的闸门。这对"应用跑在另一台机器"很方便,但 也意味着密码是分量的:用--listen 127.0.0.1可把实例收紧为仅回环。pg addon list与日志从不打印密码;需要时显式揭示——pg addon password redis --name cache或pg addon list --show-password(也可直接读pg.yaml)。
重复执行 install 是幂等的:运行中的容器原样不动,停止的容器会被拉起。要让
改动的端口/监听/密码/镜像生效,加 --force(容器按配置重建;数据目录不受影
响)。持久化类旋钮(--aof/--appendfsync/--save/--maxmemory-policy)同
样靠 --force 生效。
使用 Redis
pg redis-cli
pg redis-cli 用该实例镜像起一个一次性容器跑 Redis 官方客户端——宿主机不
用装任何东西,也不用输密码:
本地目标是 --name <实例>,省略时取按名字排序的第一个 redis 实例(装了多个时,
实际选中的目标会提示在 stderr)。pgcli 自己的 flag 只在命令词之前解析;命令
词之后一切原样转发给 redis-cli——负数索引、经 -- 的选项 flag 都靠这个。
连接远程端点 —— --host/--port 覆盖客户端连去哪里,无需把远程注册成
插件:
要连远程用这两个,而不是 redis-cli 自己的 -h/-p:后者落在命令词之后,会
被当成命令参数(ping -h 10.0.0.7 报错),而连接用的 -h/-p 由 pgcli 负
责注入。REDISCLI_AUTH(redis-cli 原生环境变量)若已设置,任何模式下都覆盖
存储的密码:
应用连接
任何 Redis 客户端都可用;安装时打印的 DSN 形如
redis://:<password>@<host>:<port>/0。需要 TLS 则要在前面架 stunnel 之类
——见已知限制。
读副本
--replica-of 把新实例变成已有主实例的只读副本:它持续拉取主节点的写入
并提供读,对它的写会被 READONLY 拒绝。这是读扩展与数据冗余,不是高可
用——pgcli 不跑 Sentinel,主节点挂掉不会自动提升副本(见[已知限制](#已知限
制))。
本机的 --replica-of 一切从配置解析:副本采用主节点的 version(跨大版本复制
不受支持——不一致会立刻报错)、它的密码,以及端点 127.0.0.1:<主节点端口>。
这一个密码随后同时服务 requirepass(副本自己的客户端用它认证)与
--masterauth(副本向主节点认证),所以 --password/--maxmemory/--aof
等与主节点完全一致。存储下来的 replica_host/replica_port 意味着即使主节点
已从配置中删除,pg addon start 也能重建出同样的 --replicaof argv。
跨机主节点用显式形式——pgcli 查不到远程主节点的密码,得由你给(必须与主 节点相同):
pg addon list 与安装摘要都会打印 Role: 行——master,或
replica of <host>:<port> (read-only);pg redis-cli --name cache-ro info replication 在(携带 RDB 的)首轮同步完成后显示 role:replica 且
master_link_status:up。读流量指向副本、写流量指向主节点,例如用各自的 DSN:
要在运行时把一个副本提升为独立主节点(这类手工故障转移正是 pgcli 有意留给你的 部分),先解除复制,再——若希望重启后依然成立——把角色从配置里去掉:
原生集群
--cluster 把实例变成 Redis 原生集群 的成员——按 16384 个哈希槽分片,而
非对某个主节点做只读复制。这是 pgcli 唯一支持的多节点拓扑;[Sentinel](#已知
限制) 不支持。
pgcli 在这件事上的角色刻意很窄:装 cluster-enabled 的节点,但从不替你组
建。这本来就是 Redis 自己的两步流程,不是 pgcli 发明的花活——集群 = 若干
cluster-enabled yes 的服务器 + 一条对它们整体跑一次的 redis-cli --cluster create。把这两步拆开,pgcli 就不必去猜"这是不是我等着的最后一个成员、现在
该不该把集群拉起来"(那种 etcd 式的自动 bootstrap 状态机);组建永远是你
的决定,跑一次,跟你对着裸 redis-server 做的事一模一样。
同组成员自动共用首成员的密码:--cluster create 用一个 -a(这里是
pg redis-cli 注入的那一个 REDISCLI_AUTH)认证操作数里的每个节点,所以
必须一个密码打通全组——和"读副本的密码必须等于主节点"是同一条
规则。后续成员带不同的 --password 会在拉镜像/建容器之前被拒绝;不带
--password 则自动继承组内的值。每个成员还会带上 --masterauth(与
requirepass 相同的共享密码):--cluster create 时 Redis 可以把任意节点提升
为从属,从属需要 --masterauth 才能向主节点认证——没有它节点就会陷入永久
的重连风暴(master_link_status:down,CPU 满载)。--cluster 与
--replica-of/--replica-of-host 在一次安装里互斥——一个实例要么是集群成员,
要么是读副本,绝不兼得。
三个 addr:port 操作数就是每个成员的 PeerAddr:port——单机集群里是
127.0.0.1:<端口>,跨机集群里是每个成员的 --advertise-host:<端口>(见下
文)。安装摘要已经会打印出这条命令,按当下配置好的 --cluster app 成员现
场拼装(比如装到 n3 时看到 pg redis-cli --name n3 -- --cluster create 127.0.0.1:6379 127.0.0.1:6380 127.0.0.1:6381 --cluster-replicas 0),复制
粘贴即可用,不需要手工拼:
(安装摘要/列表的输出与英文文档格式一致——pg CLI 的输出本身是英文,两种
语言下字节相同。)
--cluster-replicas 0(默认)就是"纯主分片"——每个成员各管一段槽,且都没有
从属。要让每台主带从属(这样某台主挂掉时它的从属能自动顶上,槽区间不至于随
之失联),在第一个安装时传 --cluster-replicas N 并装够成员数:每台主需要它
自己加它的 N 个从属,所以总数是 3*(1+N)——N=1 时是 6 个成员:
哪些成员被 Redis 选为主、哪些选为从,是它在 --cluster create 时的决定(能
跨主机均衡时它会尽量均衡),所以 pgcli 的实例命名刻意是角色中立的;从属重启
后会从自己的 nodes.conf 重新接上主——角色与 masterauth 配对都能跨重启保留,
无需重新 create。上面的安装摘要在引入从属后会从"3 masters configured"切换为
按节点计数(“6 nodes configured”),并把组内的 --cluster-replicas N 回显
进建议命令;它绝不会把从属所在组的节点叫成 master。
组建之后
连到 cluster 成员的 pg redis-cli 自动是集群感知客户端:pgcli 会注入
redis-cli 自己的 -c,get/set 落到"错的那个节点"时会跟着 MOVED 重定
向跑到真正持槽的那台——不用你记 -c,随便指哪个 --name 都一样:
最后一行是例外,不是规则:命令是 redis-cli 自己的 --cluster <子命令> 时
(这类子命令会按操作数自己拨到目标节点),pgcli 会抑制 -c 注入——它是
组建/管理面,不是数据命令。
上面的自动 -c 依赖 pgcli 从 pg.yaml 读到一个本地集群成员(即 --name
路径)。当你从一台没配该 addon 的机器用 --host/--port 远程连集群时,
pgcli 无从知道目标是不是集群节点,于是什么都不注入——一个未跟随重定向的
get/set 落到错误的节点,只会返回 MOVED ...。这时你自己透传 redis-cli 的
-c(它是 redis-cli 的 flag,所以放在 -- 透传之后、命令字之前):
-c 对写和读一样重要:不带它,指向非持槽节点的 set 会返回 MOVED 且不会
生效,那个 key 根本不存在。写的时候也带上 -c,任意单个节点即可服务整个
键空间。
也可以不用环境变量、改用 redis-cli 自己的 -a 传密码——它同样是连接选项,
所以放在同一个透传位置、命令字之前:
与 REDISCLI_AUTH 相比有两点区别:redis-cli 每次都会打印一条安全警告(-a/
-u 写在命令行上,同机用户可以用 ps 看到),而把 flag 放到命令字之后
(get foo -a …)会报 “wrong number of arguments”——它会被当成命令参数。除
非是敲一条就跑,否则优先用环境变量;两者在认证上完全等价。
如果你的客户端完全无法做成集群感知的,就在集群前放一个 Predixy 代理:
它自己跟随重定向,对外只暴露一个普通的 redis:// 端点。
集群总线是每成员的第二个端口:永远是 client + 10000(Redis 的固定
规则,比如 6379→16379)——不从 redis_start_port 池分配、不写入
pg.yaml;pg addon list 会在组名旁把它一并显示出来:
拓扑会自愈:每个节点的 --cluster-config-file nodes.conf 落在自己
--dir /data 的 bind 挂载里,所以 pg addon stop/start(或主机重启)后
整个集群原样回来,不需要再 create——与本仓库 autostart 的"只启动"模型一致。
两条命令可以直接检查集群健康,都走透传(是的,-c 那条是客户端侧 flag,所以放在 -- 之后):
跨机成员:--advertise-host
单机集群默认用 127.0.0.1 拨对端,不需要额外 flag。Redis 集群总线不支
持 NAT 或端口重映射,所以跨机成员必须显式宣告"对端实际能到达"的地址:
--advertise-host 会往节点 argv 里加 --cluster-announce-ip/-port/-bus-port,
并且把 PeerAddr(就是上面拼 --cluster create 操作数用的地址)从
127.0.0.1 换成这个地址。你自己还得做对两件事:主机间防火墙放行每个成员
的总线端口(client+10000——默认 6379 时即 16379)——不只是客户端端口
——并且组内每个成员都要带这个 flag,不能只给新加入的补。
后续每台主机上的 --password "$PW" 不是摆设:共享密码的继承只在同一份
pg.yaml 内生效——单机上,后续成员自动采用组内密码;跨机上每台主机有自己
独立的配置文件,各自生成各自的秘密。因此跨机集群必须在每台主机上显式钉同
一个密码,密码从首成员用 pg addon password 读出来。
(--cluster create 用这一个密码认证操作数里的每一个节点,不一致会以逐节点
认证错误的形式暴露出来。)
状态: 跨机集群已在三台主机上端到端验证(
--advertise-host→--cluster-announce-*、跨机--cluster create、跨机 MOVED 重定向,以及各 自主机 stop/start 后的自愈)。单机流程另有自动化 e2e (test/addon/test_redis_cluster.sh),在其中覆盖两种形态:纯三主组,以及 3 主 × 1 从组(缺失--masterauth的重连风暴正是这样被抓出来的)。多机场 景目前还是手工流程,尚未进测试套件。
端口
每个实例从 Redis 自己的池 redis_start_port(默认 6379)取一个端口
——与对象存储共用的 minio_start_port 池是独立游标,所以 redis、minio、
rustfs 实例互不冲突:
--port 可钉死某个端口;自动分配会跳过显式占用的与主机上已监听的。原生集
群成员也一样——集群总线端口永远是 Redis 自己的 client+10000
(由客户端端口推导出来),不占这个池。
配置
实例位于 pg.yaml 顶层 addons.redis 映射下:
改 listen、port、password、maxmemory/maxmemory_policy、
aof/appendfsync/save、replica_host/replica_port、
cluster/cluster_replicas/advertise_host、image_tag/version 或 data_dir 后,
下一次 pg addon install redis --name cache --force(或用对应 flag)生效。副本角色
与集群角色通常分别由 --replica-of* / --cluster(含 --cluster-replicas)/
--advertise-host 这组 flag 设定,而非手改——见读副本与原生集群。
列表
pg addon list 从不打印密码。要有意揭示它,加 --show-password(会给每个 Redis
实例追加 Password: 与一条可直接粘贴的 Raw DSN: 行——对象存储则追加
Root password: 行),或只把某一个实例的值原样打到 stdout 供脚本使用:
pg addon password 与其它子命令一样接受 --name(缺省即插件自身名字),并支持
--file——把值以 0600 权限写入文件而非终端,避免落入 shell 历史与回滚缓冲。
启停
stop 发优雅 SIGTERM——配合 --stop-timeout 30,Redis 退出前会写完最后一
份 RDB。start 只拉起已有容器(状态不当时会按配置自愈重建)。
开机自启
容器带 --restart unless-stopped 策略(管崩溃,不管重启)。主机重启后拉起
实例:
Redis 自启是只启动(先 install),并从数据目录回放 RDB 快照,数据集跨重
启保留。Redis 与 PostgreSQL 栈无关,开机 pg start --autostart 把它放在最
后启动。
移除
不带 --clean-data 时主机数据目录(含 dump.rdb)原地保留——重装同名实例
数据集复活。--clean-data 删除它,并在 rootless podman 的从属 uid 让 Redis
写的文件对宿主用户不可删时,经 podman unshare 兜底。清空的每实例父目录会
顺手剪掉;自定义 data_dir 的父目录绝不动。
日志
故障排查
- 裸客户端报
NOAUTH Authentication required——预期行为:每个实例都有requirepass。用pg addon password redis --name cache取,或直接用会注入密 码的pg redis-cli。 maxmemory驱逐超出预期 —— 设了--maxmemory上限后默认策略是allkeys-lru(到上限就驱逐任何 key:是缓存,不是硬存储)。要把上限变成 硬天花板就传--maxmemory-policy noeviction(超限后写入报错而非驱逐),或 干脆不设--maxmemory(无上限)。- AOF 未回放最新写入 ——
--appendfsync everysec(默认)在硬崩溃时可能 丢最后一秒的写入;承受不起就用--appendfsync always。优雅pg stop总会 刷盘,所以只有非正常 kill 才会踩到。 - 安装时报镜像不存在 —— 两个大版本都是按需拉取的纯
docker.io/library/redis镜像;隔离网络的主机先podman loadtar 包 (目录与导出步骤见仓库内docs/images.md),之后会跳过拉取。 - 手工改过、不在表中的
version——install报错并提示用--image, 不会静默改写你的值。 - 端口已被监听 —— 自动分配会扫描主机监听并跳过;要稳定端口号就用
--port钉死。 - 副本拒绝写入(
READONLY) —— 这是角色本身,不是故障:写请发往主节点。 要让它成为独立主节点,见读副本。 - 刚建好的副本短暂显示
master_link_status:down—— 首轮(携带 RDB 的) 全量同步期间正常;redis 8 经 rdbchannel 传快照时还会再重连一次链路。看info replication,最终会稳定为up且role:replica。
已知限制
- 无自动故障转移 —— 读副本是支持的(见读副本),原生集群分片
也支持(见原生集群),但 HA 控制面不支持:Sentinel 不在
范围内,集群编排也一样——pgcli 装 cluster 成员,但从不替你组建、扩容、
再平衡。副本给你的是读扩展与一份冗余数据,不是一对能自愈的节点——主节点挂
掉时由你自己提升副本(
replicaof no one,见上)。没有副本的单实例是单点: 可以用--aof让它在磁盘上耐久,但无法故障转移到其它节点。 - 原生集群的组建与再整形仍为手工 ——
--cluster装 cluster-enabled 节点,--cluster-replicas N(回显进建议命令)可让每台主带从属,某台主挂掉时它的从 属能自动顶上。pgcli 不替你做的事是:跑--cluster create/add-node/ resharding/del-node;pg addon remove删成员就是纯删除,不会搬槽——要缩 容或改拓扑请先在 Redis 层把它的槽挪空、把节点忘记,再删。另注意:纯主分片 (--cluster-replicas 0)没有可提升的从属——一台主挂掉,它负责的那段槽就随 之不可用,直到你介入。 - 跨机集群(
--advertise-host)为手工流程、未进 e2e 套件 —— flag、它产生 的--cluster-announce-*argv、防火墙/总线端口规则都已实现、单元测过,并 且已在三台主机上手工验证(组建、跨机 MOVED 重定向、自愈均通过)。单机 流程(纯主分片与 3 主 × 1 从)有自动 e2e;跨机带从属尚无自动 e2e,但它依赖 的正是让单机从属连上的那同一份逐成员--masterauth。见[原生集群](#原生集 群)里的状态说明。 - 无 TLS —— Redis 7 的部分构建支持原生 TLS,但这里未接入;按可信网络对
待链路并依赖
requirepass,或干脆--listen 127.0.0.1仅回环。跨机集群意 味着成员间的总线流量同样是明文。 - macOS 代码完备、未实测 —— bridge 路径(发布端口、
pg redis-cli走host.containers.internal)与对象存储同构;尚未在 Mac 上跑过。 - 默认持久化是 RDB 粒度 —— 崩溃会丢失上次快照之后的写入。这个缺口要紧时
启用
--aof(everysec下最多仍丢一秒;always可封死);缓存/会话数据 接受仅快照即可,耐久数据请放 PostgreSQL。
16.12 - Predixy
Predixy 是一个高性能 Redis 协议代理。
pgcli 把它做成一个独立的顶层插件:它接住一个 Redis 原生集群
(3 个以上节点、16384 个 slot、MOVED 重定向),再把它交回应用侧——形式是
一个普通的 redis:// 端点。客户端不需要懂集群:不加 -c、不处理重定向,
连上就能读写。
平台支持: 与 HAProxy、Redis 一样,Predixy 插件需要 Linux host 网络 才能拨通后端节点;macOS 不支持。
工作原理
Predixy 在客户端端口上说 Redis 协议,并到 --backend 列出的每个集群节点保持
持久连接。请求按 slot 路由:如果落地的节点并不持有该 key 的 slot,Predixy 自己
跟随 MOVED、在正确的节点上重发命令——对客户端完全透明,客户端永远看不到重定向。
pgcli 为每个实例渲染一个配置文件
<base-dir>/addon/predixy/<name>/predixy.conf,并以文件级只读 bind mount
盖到镜像内置的 /usr/local/predixy/conf/predixy.conf 上。渲染文件里的
Include license.conf 相对该文件所在目录解析,因此镜像里那份签名过的 sibling
仍然可见——pgcli 从不分发也不管理 license(见已知限制)。
一把密码,两侧复用
代理夹在两方已认证者之间——它自己的客户端,以及它后面的集群——pgcli 用 同一把密码覆盖两侧:
Authority { Auth "<pw>" { Mode admin } }——客户端必须用这把密码AUTH才能用代理;ClusterServerPool { Password "<pw>" }——Predixy 用它向集群节点认证。
之所以成立,是因为这把密码就是被代理集群自己的 requirepass:客户端本来就
认它,节点本来就接受它。直接从 redis 插件里取出来:
于是一把集群成员密码同时也守住代理端点——不必再同步第二个密钥。(想要一把独立 的客户端密码,那是另一种 Authority 形态,pgcli 不渲染;见 已知限制。)
安装
--backend 与 --password 都必填。--backend 是集群的完整节点列表——
pgcli 从不从本机 redis 插件推导它,因为被代理的集群常常在别的机器上;拓扑只有
你知道,所以由你来列。可重复传该 flag,也可一次逗号分隔;重复节点会被拒绝。
摘要会打印客户端应当指向的那一个端点:
所有校验都在拉取或创建任何东西之前完成:缺 --backend、条目格式不对(无端口、
端口非数字或越界、空条目)、节点重复、缺 --password、密码里含 " 或换行
(Predixy 的引号字符串语法无法转义它们)、--workers < 1,都会快速失败并给出清晰提示。
重跑 install 与别处一样是幂等的:运行中的容器原样保留(只重写配置),停着的容器
被启动,--force 才会重建容器,从而让改动的 port/listen/password/backend 生效。
后端列表每次整体替换,所以集群扩容(再装 redis 成员、--cluster add-node …)
之后,用更新后的完整列表重跑同一条 install 即可。
Workers
--workers N 设置 Predixy 的 WorkerThreads(默认 1)。每个 worker 是一个
忙轮询事件线程;在专用于代理的多核机器上,N 约等于你愿意投入的核心数,吞吐大致线性扩展。
使用代理
任何 Redis 客户端连到代理端口、带上集群密码,表现就如同在跟一个单节点 Redis 说话—— 整片 keyspace 都通过这一个端点服务:
(DSN 形态:redis://:<password>@<listen>:<port>/0。)
用 pg redis-cli
pg redis-cli 经 --host/--port 连到代理——没有指向 Predixy 实例的 --name
路径(--name 只解析 redis 插件):
本机若配了 redis 插件,则复用它的镜像与密码(见 redis.md
里的 pg redis-cli)。该插件恰好是集群成员时,pg redis-cli 照旧注入 -c——穿过
代理也无害,因为每一个 MOVED 都被代理自己吸收,客户端根本看不到。这正是代理的
意义所在:同样这些普通客户端命令,无论 -c 在不在,都能工作。
跨机
已端到端验证:代理装在一台机器上,前端是三主机集群
(--backend 10.10.0.15x:6379,…),而另一台机器上的普通无集群感知客户端穿过它连接——
slot 属于远端节点的 SET 被吸收并落盘,之后能从那个节点自己的端口读回。唯一的条件是可达:
代理所在主机必须能拨通每个 --backend 节点的客户端端口(集群总线端口是成员之间的,不是代理的)。
端口
每个实例从 Predixy 自己的端口池取一个端口,predixy_start_port(默认 7617,
即镜像 EXPOSE 的端口)——与 Redis 的 redis_start_port 池分开,所以代理和它前端的
集群永不冲突。--port 可固定实例端口;自动分配会跳过已被占用的。
bind 地址默认 0.0.0.0(AUTH 密码是唯一的门);--listen 127.0.0.1 收紧为仅回环。
配置
实例位于 pg.yaml 顶层的 addons.predixy 映射下:
渲染出的 predixy.conf 用 Predixy 自己的语法承载同样信息;手改它会被下次 install 抹掉——
要调整请改 pg.yaml(或对应 flag)再重装。
列表
pg addon list 会隐去密码;--show-password 才显示,而
pg addon password predixy --name proxy 单独裸打印这一个值以供脚本使用。
启停
start 只启动已存在的容器(状态不当时从磁盘上已渲染的配置重建以自愈);若该文件缺失它会
报错——重跑 install 生成。
开机自启
开机是仅启动(先 install),且 boot service 把 Predixy 排在 Redis 之后, 于是代理拨号时被代理成员已经在起来的路上。见开机自启。
移除
停止并移除容器、删除渲染出的配置目录、去掉 pg.yaml 条目。被代理的集群不受影响——
代理只是它的一个消费者。
日志
启动横幅会报告实际生效的构建信息(Workers:2 等)——这是确认 --workers 是否生效的地方。
已知限制
- 单一 admin Authority 用户——渲染器只发一条
Auth "<pw>" { Mode admin }:代理的所有客户端共用一把密码、拥有全部权限。 按用户认证、只读用户、keyspace 限制都不在渲染范围内(Predixy 本身支持更多; pgcli 的能力面到此为止)。 - 仅集群池——
ClusterServerPool是唯一的后端类型;不配置 Sentinel 或单机池。 - license 烤在镜像里——
ghcr.io/mars-base/pgcli/predixy:7.0.1-alpine内置的 免费版 license 把客户端数上限设为ClientLimit 128,并于 2026-12-31 到期; pgcli 从不分发或轮转它——由镜像构建负责。到期后重新 build 镜像即可(Makefile 目标 已经会换上当前 license)。 - 客户端侧不做 TLS(仅密码门;与 Redis 本身一样,把
listen收回回环或放在可信网络)。 - 仅 Linux——拨后端需要 host 网络;macOS 不支持(快速失败)。
故障排除
- 客户端收到
ERR invalid password——客户端必须AUTH那把密码;用pg addon password predixy --name proxy核对(它应当等于集群的密码,pg addon password redis --name n1)。 - 容器立即退出——Predixy 会当场拒绝它的配置:单独一行的
{,或含"/换行而破坏 引号的密码。pgcli 在写文件前已校验这两项,出现这种情形说明有人手改过predixy.conf——重装即可。 - 命令挂起或每个 key 都报错——某个后端节点从代理所在主机不可达(地址写错、被防火墙拦)。
pg logs addon predixy会显示拨号失败;--backend必须列本主机能拨通的节点。 - 集群变更后后端列表过期——install 整体替换列表;增删成员后,用当前完整节点集重跑它。
16.13 - pgAdmin 4
pgAdmin 4 是 PostgreSQL 的官方 Web 管理界面——在
浏览器里浏览 schema、执行 SQL、观察服务器状态。pgcli 将其作为独立的顶层插
件运行,CLI 表面与其他 web/infra 插件一致(install / start / stop / logs /
autostart / remove),全部通过 pg.yaml 管理。
它是插件族里唯一的 UI 插件,这一点决定了它的设计:
- 它是 Web 应用,不是数据库前置。 pgAdmin 连接哪些服务器由你自己在其 UI
里注册决定;它不像 PostgREST 或 PgBouncer
那样专职前置某一个实例。这就是它做成顶层-only 插件
(
addons.pgadmin.<name>)、没有实例 sidecar 的原因。 - 它有自己的登录。 Web UI 需要邮箱 + 密码登录。这不是 PostgreSQL 密码
——是 pgAdmin 自己的账号,首次安装时自动生成并存入
pg.yaml。之后用pg addon password pgadmin取回。 - 持久化的会话存储。 pgAdmin 把配置、已保存的服务器、浏览历史存在 bind
挂载数据目录里的 sqlite 库中。它跨
pg addon remove(不带--clean-data) 存活,重装同名实例即复活。 - 可选的一次性预置。
--dsn或--pg-name <实例名>在 install 时向 UI 的servers.json预注册一个服务器,让浏览器打开时列表里已有它。这只是一个便利 ——不是运行期耦合:pgAdmin 不会因它阻塞,之后可在 UI 里增删。
建议:每个数据库一个 pgAdmin,随开随用。 pgcli 管理的 pgAdmin 是单个轻 量容器,秒级启动、内存占用极小——完全可以当一次性工具用。与其跑一个共享的 pgAdmin 把所有服务器都注册进去,不如为当前正在操作的数据库创建专用实例,用完 即删:
每个实例有独立的端口(自动分配)、独立的登录、独立的 pgpass——数据库之间无 交叉污染,共享会话存储里不会残留陈旧连接。下次需要时,秒级重装。
不在范围内: 这里 pgAdmin 只提供明文 HTTP——没有接入 TLS 终止(需要 HTTPS 就在前面架反向代理)。
servers.json本身不能携带目标服务器的密码 (pgAdmin 拒绝导入),但当预置 DSN 带了密码时,pgcli 通过 pgAdmin 的 pgpass 支持预配置它,预置服务器连接时无需再输入——见预置服务器。
参数
单容器直接跑上游 docker.io/dpage/pgadmin4 镜像(pull-only——pgcli 从不构建
它),全部经由 entrypoint 读取的 -e 环境变量配置。下表列出每个旋钮及其默认
值:
参数 / pg.yaml 键 |
默认值 | 环境变量 | 含义 |
|---|---|---|---|
--image / image_tag |
docker.io/dpage/pgadmin4:9.18 |
— | 直接指定镜像标签 |
--port / host_port |
自动,取自 pgadmin_start_port(基址 5050) |
PGADMIN_LISTEN_PORT |
Web 宿主端口 |
--listen / listen |
127.0.0.1 |
PGADMIN_LISTEN_ADDRESS |
绑定地址(0.0.0.0 对外开放,见监听与暴露) |
--email / email |
admin@pgcli.lan |
PGADMIN_DEFAULT_EMAIL |
Web 登录账号(会经过镜像校验,见排障) |
--password / password |
自动生成,20 字符 | PGADMIN_DEFAULT_PASSWORD |
Web 登录密码——不是 PG 密码 |
--dsn / dsn |
不设 | —(渲染 servers.json + pgpass) |
以 URI 预注册一个服务器——带密码时同时预配置(host 是从容器内拨号的,见预置的 host 与 listen 地址) |
--pg-name |
不设 | —(渲染 servers.json + pgpass) |
改为从本机已管理实例解析该服务器 |
--data-dir / data_dir |
<base-dir>/addon/pgadmin/<name>/data |
(挂载到 /var/lib/pgadmin) |
会话存储位置 |
| — | autostart: false |
— | 开机自启(pg autostart enable --pgadmin) |
旋钮间的配合关系:
email+password只在空数据目录上生效。 pgAdmin 只在初始化pgadmin4.db期间读取PGADMIN_DEFAULT_*;该文件一旦存在,库里的账号就是 权威,环境变量被忽略。因此复活既有目录的重装会打印一个对它无效的密码(install 会明说)——要真正重置登录,先--clean-data移除;复活时也可以把--password钉成原值。--dsn与--pg-name互斥——二者都表示"预置这个服务器",一个给 URI, 一个从本实例解析端点。--name是插件自己的键,与--pg-name无关。预置里的 host 是 pgAdmin 的连接出发地,不是你浏览器的位置——见预置的 host 与 listen 地址。listen默认 loopback——Web UI 有登录门,所以除非传--listen 0.0.0.0从别的主机访问(summary 会警告登录成了唯一防线),它保持127.0.0.1。完整 情形见监听与暴露。email必须过得了镜像的校验器——entrypoint 用email_validator检查它, 直接拒绝保留 TLD(.local、.invalid、.test),命中就崩溃重启。默认值admin@pgcli.lan正是为通过校验而选;自己起名时同理(.lan、.internal这类由你掌控的名字都行——不要求是真实可投递的地址,投递检查是关掉的)。
安装
install summary 打印 URL 与(自动生成的)Web 登录:
Seeded: / Seed auth: 行只在传了 --dsn/--pg-name 时出现。当 DSN 携带密
码时 Seed auth: 显示 password pre-configured (pgpass);不带密码时显示
no password in the DSN — first connect will prompt。重复 install 是幂等的:
运行中的容器原样保留,已停的被启动。要让改动的端口/listen/email/预置生效,加
--force(容器按配置重建;数据目录不动)。
用 --force 重建
不带 --force 的 pg addon install pgadmin 对运行中的容器是空操作——它不动
现有配置,只确认容器在跑。当你正要修改某些东西(端口、listen、email、镜像、
预置 DSN、--pg-name 目标、data_dir)时,这恰好是错误的行为:运行中的容器
钉在创建它时的配置上,编辑看似被接受,但直到下次重建前什么都不会改变。
--force 就是应用这些改动的旋钮。它停止运行中的容器、移除它,并按当前配置创建
一个新的;数据目录不动,已存服务器和浏览历史在新容器里回来:
两个值得说清的坑:
email/password仍然只对空数据目录重新生效。 pgAdmin 只在初始化pgadmin4.db期间读取PGADMIN_DEFAULT_*;该文件一旦存在,库里的账号就是 权威,环境变量被忽略。单--force不会重置 Web 登录——要真正重置账号,得配合--clean-data(先把存储清掉)。- 运行中的容器会被重建,不是原样保留。 如果浏览器正开着 UI,
--force会杀 掉会话;下一次页面加载会重新认证新容器。
使用 Web UI
把浏览器指向 summary 里的 URL(在跑 podman 的那台主机上),用登录邮箱 + 密码
签名进入,pgAdmin 自己的服务器树就出现了。在 UI 里添加连接,或在安装时用
–dsn / –pg-name 预注册一个。
如果主机不在你的浏览器旁边:设 --listen 0.0.0.0(登录成了唯一防线):
监听与暴露
--listen(配置键 listen)是 pgAdmin HTTP 服务绑定的地址,默认 loopback。
三种姿态如下——都要配 --force,因为容器需重建才会采纳改动:
- Linux 上容器共享宿主网络,所以
--listen 0.0.0.0会在<宿主IP>:<端口>应答,--listen 127.0.0.1只在127.0.0.1:<端口>。放宽到0.0.0.0意味着 pgAdmin 之外只剩邮箱/密码登录这一道门——install 会警告这一 点——因此除非网络可信,保持 loopback。 - macOS 上情况相反:bridge 网络在 podman machine 内部总是绑
0.0.0.0, pgcli 发布端口,所以listen在那里实际上是恒被放宽的,你从 Mac 经 gvproxy 用127.0.0.1:<端口>访问 UI。见平台支持。
取回 Web 登录
pg addon list 显示 URL 和登录邮箱,但不加 --show-password 绝不打印密码。
预置服务器
--dsn 与 --pg-name 都渲染一份一次性的 servers.json,pgAdmin 在首次启动
时导入,浏览器打开时那个服务器已在树里。--pg-name 从本机已管理实例解析端点
(其 host/port/database/user 及密码,读自 pg.yaml);--dsn 以 URI 给出:
预置携带什么、不携带什么:
- 目标服务器的密码在 DSN 携带时被预配置。
servers.json本身不能持有密码 (pgAdmin 拒绝导入),所以 pgcli 在旁边写一个 libpqpgpass文件,并通过ConnectionParameters.passfile把预置条目指向它——这是 pgAdmin 9.18 导入/导 出服务器文档中正式记录的绝对路径机制。浏览器里的连接完全跳过密码输入。不带 密码的 DSN(如postgres://app@host/db)只渲染servers.json,pgAdmin 在首 次连接时照常提示输入。 - pgpass 文件以明文在磁盘上持有密码。 它位于
<base-dir>/addon/pgadmin/<name>/pgpass,mode 0600,只有宿主用户和容器内的 uid 5050 可读——但它确实在磁盘上,不像servers.json从不持有机密。权衡与 所有使用.pgpass的工具相同:免密连接以磁盘上的一个文件为代价。移除 pgAdmin 实例(无论是否带--clean-data)会把 pgpass 与servers.json一并删除——明文 机密不会比实例活得更久。 - 它是一次性便利,不是耦合。 安装后服务器活在 pgAdmin 自己的
pgadmin4.db里;在 UI 里增、删、改名都行。无论预置目标是否可达,插件照常运行。 PGADMIN_REPLACE_SERVERS_ON_STARTUP=True在有预置时被设置,所以改了--dsn/--pg-name的--force重装会声明式地重新应用预置,而不是留下过期 条目。
要去掉预置并停止声明式重载,用 --force 重装且不带 --dsn/--pg-name。
预置的 host 与 listen 地址
这两个旋钮指向不同的机器,却常被混为一谈。--listen 决定谁能访问 pgAdmin
的 Web UI;预置里的 host 决定pgAdmin 从哪里拨数据库。拨号是服务端动作——
由 pgAdmin 容器发起,不是你的浏览器——所以用 --pg-name 预置本机管理的实例
时,记录 127.0.0.1 是正确的:pgAdmin 与那个 PostgreSQL 共享 podman 宿主
的回环。把 UI 暴露到网络并不要求预置的 host 对浏览器可路由:
反过来的坑:用 --dsn 预置一个远程 host,再从别处用浏览器访问那个
pgAdmin——连接依然源自 pgAdmin 容器内部,所以那个 host 必须从容器的网络
可达,而不是从你的笔记本可达。
端口
每个实例从 pgAdmin 自己的端口池 pgadmin_start_port(默认 5050)取一个
端口——与 Redis、Predixy、对象存储的池各自独立游标,所以一个 pgadmin、一个
redis、一个 minio 实例永不撞车:
--port 可把实例钉在特定端口;自动分配会跳过任何已显式指定或宿主上已占用的
端口。
配置
实例位于 pg.yaml 的顶层 addons.pgadmin map:
对 listen、host_port、email、password、image_tag、dsn/
server_name、data_dir 的修改,在下一次
pg addon install pgadmin --name console --force(或对应 flags)后生效。记住:
email/password 只对空数据目录重新生效,所以对既有存储改它们需要先
--clean-data(或接受生效账号仍是原账号)。
列出
pg addon list 从不打印密码。要刻意揭示,加 --show-password(每实例追加一行
Login password:),或用 pg addon password pgadmin --name console 裸打印——
并且优先用 --file,让机密留在文件里而不是 shell 历史与回滚缓冲。
启停
start 只启动已存在的容器(并以按配置重建来自愈不当状态)。数据目录跨
stop/start 保留,pgAdmin 的已存服务器与浏览历史随之回来。
开机自启
容器带 --restart unless-stopped 策略(管崩溃,不管重启)。要在宿主重启后拉起
实例:
pgAdmin 自启是只启动(先 install)。开机时 entrypoint 重新 chown
/var/lib/pgadmin,并从数据目录重放 pgadmin4.db,已存服务器随之复活。pgAdmin
独立于 PostgreSQL 栈——它是自己拨号的浏览器 UI——所以没有排序约束,开机
pg start --autostart 把它与其他非数据库插件一起启动。
移除
不带 --clean-data 时宿主数据目录(连同 pgadmin4.db)原地保留——重装同名实
例即复活已存服务器与会话存储(复活后可用的登录是原密码,不是新生成的)。
--clean-data 删除它,当 rootless podman 的映射 uid 使 pgAdmin 的文件对宿主用
户不可删时,经 podman unshare rm 兜底。空的实例父目录被修剪;自定义
data_dir 的父目录从不动。servers.json(若预置过)宿主所有,两种移除都随实
例目录一并删掉。
日志
排障
- 用打印的密码登录被拒 —— 你在保留的数据目录上重装了。能用的登录是
pgAdmin 在首次初始化时写进
pgadmin4.db的那个;新生成的密码无效(install 会警告 “reviving an existing pgAdmin data dir”)。用--clean-data从零开 始,或用pg addon password pgadmin确认存的是什么,或重装时把--password钉成原值。 - “pgAdmin could not start” / 容器秒退(或崩溃循环) —— 空目录上 entrypoint
需要
PGADMIN_DEFAULT_EMAIL和PGADMIN_DEFAULT_PASSWORD都在;手改过pg.yaml、密码为空且数据目录被清过,就两个都没有。让 install 生成,或传--password。它还会校验邮箱并拒绝保留 TLD(.local、.invalid、.test),报 “does not appear to be a valid email address”——容器随即退出, 在重启策略下反复自旋。这正是默认值取admin@pgcli.lan而非.local的原因。 - 从别的机器打不开 UI ——
listen默认127.0.0.1。--listen 0.0.0.0 --force重装并接受 Web 登录成为唯一防线。 - 预置的服务器显示但连不上 —— 若预置 DSN 不带密码,pgAdmin 在首次连接时
提示输入(预置服务器)。若 DSN 带了密码却仍然提示,说明
pgpass 文件失效了——最常见原因是容器启动时 entrypoint 的
chown没跑(例如 直接传了--user 5050而不是--user 0,导致文件属主是宿主用户,libpq 静默 丢弃它)。确认 host/port 从 pgAdmin 所在处可达。 - install 时找不到镜像 ——
docker.io/dpage/pgadmin4按需拉取;离线宿主先podman load对应 tar(目录与导出步骤在仓库docs/images.md),pull 会被跳 过。 - 首次启动响应慢 —— pgAdmin 在 gunicorn 绑定之前先跑 sqlite 迁移;端口要
等它完成才回 302(跳
/login)。负载高的宿主上这可能要几十秒。
已知限制
- 无 TLS —— 这里 pgAdmin 提供明文 HTTP。要 HTTPS 就在前面架反向代理(配合
PGADMIN_URL_SCHEME/PGADMIN_DISABLE_STATIC_FILE_SERVER调参);pgcli 不接 这条线。loopback 默认让常见场景留在127.0.0.1。 - 不适合大数据量导入导出 —— pgAdmin 的数据流经 HTTP(导出:查询 → 容器 →
浏览器;导入:上传 → 容器 → 数据库),大表容易超时或内存溢出。应改用
pg export/pg import(pgcli 实例),或pg exec --dsn "..." -- pg_dump(任意数据库)。详见 数据导入导出。 - 预置密码以明文存于磁盘 ——
servers.json旁边的 pgpass 文件是 mode 0600 但没有加密;移除实例时一并清理。没有办法预配置密码而不落盘文件(pgAdmin 没有 KMS/keyring 集成)。 - 登录只对新存储生效 —— pgAdmin 只在初始化
pgadmin4.db期间读取PGADMIN_DEFAULT_*;该文件一旦存在,库里的账号就是权威。没有原地改密码的路, 只有--clean-data+ 重装。 - macOS 代码完备、未实测 —— bridge 路径(发布端口、bridge 下
proxyBindHost把 loopback 放宽到0.0.0.0)与 redis 同构;尚未在 Mac 上演 练过。
16.14 - Nginx
nginx 是 HTTP 反向代理,用于前置多个 Web 服务——仪表盘、API、
管理面板——通过基于路径的路由和可选 TLS 终止。pgcli 将其
作为独立的顶层插件运行,CLI 表面与其他 web/infra 插件一致(install / start
/ stop / logs / autostart / remove),全部通过 pg.yaml 管理。
它是插件族里的 HTTP 网关,这一点决定了它的设计:
- 反向代理,不是负载均衡器。 与 HAProxy——前置 Patroni 的 TCP
负载均衡器——不同,nginx 是 HTTP 反向代理,按 URL 路径路由。每个
location块 转发到一个 upstream(Web 应用、API 服务等),客户端通过单一端口访问多个服务。 - 文件驱动配置。 pgcli 根据你声明的 backends 渲染
nginx.conf(或通过--conf-file接受自定义文件),以只读方式挂载到官方nginx:1.27-alpine镜像中。TLS 可选:未提供 BYO 证书时,pgcli 通过tlsca生成自签名叶子证书。 - 独立日志挂载。 每个 backend 有自己的 access 和 error 日志文件
(
access_<name>.log、error_<name>.log),位于挂载的/var/log/nginx/目录下,因此无需解析共享日志即可观察每个服务的流量。 - nginx 专属子命令。 除了通用插件生命周期(install/start/stop/remove),
nginx 还暴露
test(通过临时容器运行nginx -t验证配置语法)、reload(通过nginx -s reload热加载)和exec(在容器内运行任意命令)。
平台支持: nginx 在 Linux(主机网络)和 macOS(pgcli-net 桥接网络
- 端口发布)上均可工作。上游镜像为多架构(amd64 + arm64),任何主机架构均可。
工作原理
路由基于路径:每个 location 块转发到一个 upstream。配置根据你声明的 backends
渲染(或从自定义文件复制),以只读方式挂载,nginx 在前台运行
nginx -g 'daemon off;'。
两种配置模式
通过 --conf-file(自定义)或 --upstream(模板)选择:
- 模板模式(默认)——pgcli 根据
--upstream规格渲染nginx.conf。每个 upstream 成为upstream块和location块;TLS 在 HTTP 服务器块旁添加 HTTPS 服务器块。模板 覆盖常见场景:基于路径的路由到若干 Web 服务。 - 自定义模式(
--conf-file)——你提供完整nginx.conf路径;pgcli 复制到 插件目录,仅管理容器生命周期。当生产配置超出模板能力时使用(limit_req_zone、resolver+ 变量proxy_pass、WebSocket、SNI、每个 backend 多个 location、 upstream keepalive 等)。--upstream和--conf-file互斥。
按 backend 独立日志
每个 backend 在挂载的 /var/log/nginx/ 目录下有自己的日志文件:
access_<name>.log—— 该 backend 的location块的 access 日志error_<name>.log—— 该 backend 的location块的 error 日志
全局 access.log 和 error.log 捕获服务器级事件。日志目录在主机上创建于
<base-dir>/addon/nginx/<name>/log/,跨容器重启持久化。
TLS 终止
TLS 可选。未提供 --tls-cert/--tls-key 时,pgcli 通过 tlsca.Generate 在
<base-dir>/addon/nginx/<name>/tls/ 下生成自签名叶子证书。HTTPS 监听器与 HTTP
监听器并行运行,现有明文客户端继续工作。提供 BYO 证书时,改为挂载指定文件。
BYO 证书常常就是 pg cert 签发的叶子证书——该命令签一张自签名证书,SAN 可以是
任意域名 + IP 组合,正是"客户端可控、能把 CA 文件交出去"的 Git/DB 端点所需形
态:
代理 Git(及任何大 POST):必须抬高 client_max_body_size。 模板模式不设
请求体上限,于是 nginx 用其内建默认 1 MiB——超过它的 git push(或任何上
传)会返回 HTTP 413。模板没有对应 flag,所以给 Git 服务前置时要用自定义模
式(--conf-file);完整可用样例见下文「常见自定义配置模式」里的「Git HTTP
推送与 client_max_body_size」。
安装
backends 通过两种方式之一提供(互斥):
--upstream name=<n>,path=<p>,backend=<h:p>(可重复)——显式 upstream 目标;--conf-file <path>—— 自定义nginx.conf文件(完全跳过模板渲染)。
每个 --upstream 字段的含义——以
--upstream name=web,path=/app,backend=127.0.0.1:8000 为例:
| 字段 | 含义 |
|---|---|
name |
nginx.conf 中的 upstream 名称(省略时默认为清理后的 path) |
path |
location 路径前缀(例如 /app → Web 应用、/api → API 服务;默认为 /) |
backend |
要代理到的 host:port(例如 127.0.0.1:8000);可重复指定多个 server 做负载均衡 |
backends |
多个 server 的简写形式:backends=h1:p1,h2:p2,...(与 backend 互斥,二选一) |
安装摘要报告分配的端口和配置路径:
使用 --conf-file 时,摘要显示 Config: <path> (from <confFile>) 并跳过
Backends: 部分(pgcli 不解析用户的配置)。
用 --force 重建
不带 --force 的 pg addon install nginx 在容器运行中时现在会自动 reload
配置改动:写入新 nginx.conf,用 nginx -t 验证,验证通过后发 nginx -s reload。
验证失败时回滚旧配置,跳过 reload。这意味着大部分改动(backends、--conf-file、
worker_connections、TLS)无需重建容器即可生效。
--force 仍用于需要重建容器的场景(镜像标签、listen 地址、端口、网络模式):
它停止运行中的容器、移除它,并按当前配置创建一个新的:
nginx 子命令
除了通用插件生命周期(install/start/stop/remove),nginx 还暴露三个日常运维子 命令:
test —— 验证配置语法
在临时容器(podman run --rm)内运行 nginx -t,挂载与真实容器相同的卷。
不需要 nginx 容器在运行中——它启动一次性容器、验证配置、退出。这是在 reload
前测试配置改动的安全方式:
--conf-file 在安装之前验证独立的配置文件——适合 CI 流水线或部署前检查:
临时容器挂载相同的 nginx.conf(以及启用 TLS 时的证书目录),因此验证与真实
容器启动时看到的完全一致。
reload —— 热加载配置
向运行中的容器发送 nginx -s reload——优雅重启,采纳配置改动而不丢弃进行中的
连接。需要容器在运行中(若已停止,先用 pg addon start nginx --name proxy)。
exec —— 在容器内运行命令
在 nginx 容器内运行任意命令,stdio 透传。需要容器在运行中。-- 分隔符是必需的
——之后的所有内容原样传给容器。
参数
单容器直接跑上游 docker.io/library/nginx 镜像(pull-only——pgcli 从不构建它),
全部通过渲染的(或自定义的)nginx.conf 和 podman run 标志配置。下表列出每个
旋钮及其默认值:
参数 / pg.yaml 键 |
默认值 | 含义 |
|---|---|---|
--image / image_tag |
docker.io/library/nginx:1.27-alpine |
直接指定镜像标签 |
--http-port / http_port |
自动,取自 nginx_start_port(基址 8080) |
HTTP 监听器宿主端口 |
--https-port / https_port |
自动(HTTP 之后下一个空闲端口) | HTTPS 监听器宿主端口(仅 TLS) |
--listen / listen |
127.0.0.1 |
绑定地址(0.0.0.0 对外开放) |
--worker-connections / worker_connections |
1024 |
events 块中的 worker_connections(每个 worker 的最大并发连接数) |
--tls / tls |
false |
在 HTTP 监听器旁启用 HTTPS 监听器 |
--tls-cert / tls_cert |
不设 | BYO 证书路径(PEM 叶子 + 链);不提供时 pgcli 生成自签名证书 |
--tls-key / tls_key |
不设 | BYO 私钥路径(PEM) |
--upstream |
不设(可重复) | name=<n>,path=<p>,backend=<h:p>[,backend=<h:p>...] —— 一个 upstream 目标,可包含多个 backend |
--conf-file / conf_file |
不设 | 自定义 nginx.conf 路径(与 --upstream 互斥) |
| — | autostart: false |
开机自启(pg autostart enable --nginx) |
旋钮间的配合关系:
--conf-file与--upstream互斥。 二者都表示"这是配置"——一个给路径, 一个声明 backends。安装拒绝组合。--tls不提供--tls-cert/--tls-key时生成自签名证书。 证书位于<base-dir>/addon/nginx/<name>/tls/,挂载到/etc/nginx/certs。HTTPS 监听器在下一个空闲端口上与 HTTP 并行运行。--tls-cert/--tls-key不提供--tls时被忽略。 这些标志仅在启用 TLS 时生效。--worker-connections在--conf-file模式下会注入。 两者同时设置时, pgcli 会修补用户的events块(或新增一个)来设置指定值。用户原有的worker_connections指令(如有)会被替换,而非合并。--listen默认 loopback —— 代理保持127.0.0.1,除非传--listen 0.0.0.0从别的主机访问。Linux 上容器共享主机网络,所以0.0.0.0在<host-ip>:<port>应答;macOS 上桥接发布端口,proxyBindHost把 loopback 放宽到0.0.0.0。
端口
每个实例从 nginx 自己的端口池 nginx_start_port(默认 8080)取一到两个
端口——与 Redis、Predixy、对象存储的池各自独立游标,所以一个 nginx、
一个 redis、一个 minio 实例永不撞车:
--http-port 和 --https-port 可把实例钉在特定端口;自动分配会跳过任何已显式
指定或宿主上已占用的端口。TLS 实例消耗两个端口(HTTP + HTTPS);普通实例消耗
一个。
配置
实例位于 pg.yaml 的顶层 addons.nginx map:
对 listen、http_port、https_port、worker_connections、tls、
tls_cert/tls_key、conf_file、backends 的修改,在下一次
pg addon install nginx --name proxy 时生效——纯配置改动自动 reload(无需
--force)。需要重建容器的改动(镜像标签、listen 地址、端口)才需要 --force。
常见自定义配置模式
模板模式无法满足时,使用 --conf-file 提供完整 nginx.conf。以下模式
提取自实际 70+ server 的生产配置,覆盖最常见的场景。
完整 http 块骨架 —— 下面的模式展示的是单独的 server 块;用如下
http 块包裹它们:
动态 DNS 解析:resolver + 变量 proxy_pass —— 避免 nginx 在启动时解析
主机名并永久缓存。变量强制每次请求重新查询:
按 URL 限速 —— 在 http 块声明 limit_req_zone,按 location 应用。
不同速率使用不同 zone:
WebSocket 代理 —— 需要 Upgrade、Connection 头以及 HTTP/1.1:
带 keepalive 的 upstream —— 减少高吞吐 backend 的连接开销:
基于 Host 的路由:proxy_set_header Host —— 按 IP 代理但设置正确的
虚拟主机:
SNI 透传:proxy_ssl_server_name —— 当 upstream 在 HTTPS 后面且依赖 SNI
选择证书时必须开启。不开启的话,nginx 在 TLS 握手时不发送 Host 头,upstream
可能拒绝连接:
同一 server 多 location 各自限速 —— 不同路径需要不同速率限制时,声明
独立 zone 并在每个 location 应用。server 级别的 limit_req 做兜底,
特定路径可覆盖为更紧或更松的限制:
自定义日志格式 —— 替换默认 combined 格式,捕获 request body、响应耗时
和 upstream 延迟。log_format 在 http 块声明一次,每个 server 各自设置
access_log 路径。日志目录 /var/log/nginx/ 由 pgcli 挂载,自定义日志路径
统一用这个目录:
Git HTTP 推送与 client_max_body_size —— 用一份完整的自定义 nginx.conf
前置 Git 服务(Gitea、GitLab、裸 git http-backend 等),抬掉 1 MiB 请求体上限,
并让长 git-receive-pack 请求不断流:
--conf-file 与 --upstream 互斥,但 --tls / --tls-cert / --tls-key 在
自定义模式下依然生效:pgcli 仍把证书对挂在 /etc/nginx/certs/public.crt 与
/etc/nginx/certs/private.key——上面这份配置正是引用它们;自定义文件只是接管了
listen/ssl_certificate 这些行的书写。
静态文件服务 + 缓存 —— 从宿主挂载目录提供文件,带浏览器缓存头、 sendfile 优化和访问控制:
列出
启停
start 只启动已存在的容器(并以磁盘上已有的 nginx.conf 重建来自愈不当状态)。
配置和日志目录跨 stop/start 保留。
开机自启
容器带 --restart unless-stopped 策略(管崩溃,不管重启)。要在宿主重启后拉起
实例:
nginx 自启是只启动(先 install)。开机时读取磁盘上已有的 nginx.conf,所以
代理以最后已知配置启动。nginx 独立于 PostgreSQL 栈——它是自己拨号的反向代理
——所以没有排序约束,开机 pg start --autostart 把它与其他非数据库插件一起启动。
移除
停止并删除容器,同时删除其配置目录(包括 TLS 证书和日志)。空的实例父目录被 修剪。
日志
容器日志走 stdout,所以 access 和 error 事件出现在这里。按 backend 查看日志, 看挂载的日志目录:
排障
- 启动时配置语法错误。 容器退出,重启策略反复自旋。用
pg addon nginx test --name proxy检查(容器停了也能用)——它在临时容器里跑nginx -t,报告确切行号。 --conf-file与--upstream一起用。 安装拒绝组合——选一种模式。- 从别的机器打不开代理。
listen默认127.0.0.1。--listen 0.0.0.0 --force重装并接受代理成为 backends 前的唯一防线。 - install 时找不到镜像。
docker.io/library/nginx按需拉取;离线宿主先podman load对应 tar(目录与导出步骤在仓库docs/images.md),pull 会被跳过。 - Reload 失败"container not running"。
pg addon nginx reload需要容器在 运行中。先用pg addon start nginx --name proxy启动。
已知限制
- 无负载均衡或健康检查。 这里的 nginx 是反向代理(基于路径的路由,每个 location 单个 upstream),不是 TCP 负载均衡器。要在 Patroni 前做负载均衡,用 HAProxy。
- 模板刻意保持简单。 渲染出的
nginx.conf覆盖基于路径的路由到若干 backends,可选 TLS。生产配置需要limit_req_zone、resolver+ 变量proxy_pass、WebSocket、SNI、每个 backend 多个 location 或 upstream keepalive 时,应改用--conf-file。 - macOS 代码完备、轻度测试。 桥接路径(发布端口、
proxyBindHost把 loopback 放宽到0.0.0.0)与其他桥接插件同构;已演练但未在负载下测试。
16.15 - Grafana
pg addon install grafana 以受管容器方式运行 Grafana——开源可观测性面板——直接跑官方上游镜像。与 pgAdmin 一样,它是顶层插件:不依附于任何单个 PostgreSQL 实例,也不会自动前置它们——数据源和仪表盘都在 Web UI 里自己添加。v1 是纯 Web UI:配置只经环境变量(GF_*)交付,不挂载任何 datasource/dashboard 的 provisioning。
Web 登录密码在首次安装时自动生成、打印一次、存入 pg.yaml,之后用 pg addon password grafana 取回。与 pgAdmin 不同,Listen 默认 0.0.0.0:监控面板天然要给别的机器看,而容器跑在宿主网络上。暴露面由自动生成的强密码与关闭自助注册兜底——只想本机访问就传 --listen 127.0.0.1。
平台:Linux 是已实测的平台。设计上也支持 macOS(镜像多架构;容器在
pgcli-netbridge 上以发布端口的方式跑在 podman machine 里),但尚未在 Mac 上验证过。
参数
| 参数 | 默认值 | 说明 |
|---|---|---|
--name |
grafana |
实例名(map 键) |
--image |
docker.io/grafana/grafana:13.2.4 |
覆盖镜像 |
--port |
自动(基址 3000) | Web 宿主端口;自 grafana_start_port 自动分配 |
--listen |
0.0.0.0 |
绑定地址——默认开放全部网卡;127.0.0.1 收紧为仅回环 |
--admin-user |
admin |
Web 登录账号(GF_SECURITY_ADMIN_USER) |
--password |
自动生成 | Web 登录密码(GF_SECURITY_ADMIN_PASSWORD;缺省时生成,只在首次启动时生效,见下) |
--data-dir |
自动 | 覆盖数据目录(默认在 base 目录下) |
--force |
false | 即使正在运行也重建——用于应用改动的端口、凭据或监听地址 |
数据目录存放 Grafana 自己的 sqlite 元数据库(grafana.db):仪表盘、数据源、用户与设置都在里面。它跨 pg addon remove 存活(用 --clean-data 才删除),重装同名实例即复活一切。镜像的 entrypoint 不做属主修复(它以固化的 uid 472、root 组运行,目录不可写时只会告警),所以 pgcli 在创建容器前先在宿主上把数据目录 chown 过去:rootless podman 下经 podman unshare chown 472:0,rootful 宿主且 pgcli 以 root 运行时直接递归 chown。
安装
命令会按需拉取镜像、创建容器,并打印 URL 与登录密码:
用浏览器打开 URL,以打印的凭据登录。在 UI 里添加 PostgreSQL 数据源(Connect data → PostgreSQL;每个受管实例的宿主端口在其 pg.yaml 条目里,或由 pg status <instance> 显示),再基于它搭建仪表盘。
GF_SECURITY_ADMIN_USER / GF_SECURITY_ADMIN_PASSWORD 只在 grafana.db 不存在时生效——它们初始化登录,不覆盖登录。在保留的数据目录上重装,Web 登录仍是原来的;此次打印的密码无效(install 会明说)。要修改既有存储的登录,进容器跑 grafana-cli admin reset-admin-password <new>(pg exec 风格:podman exec -it <container> ...),或 --clean-data 移除后重装。
重复 install 会复用运行中的容器、启动已停的容器——配置改动只有 remove + 重装或 --force 之后才生效。
监听与暴露
Grafana 的 Listen 默认 0.0.0.0:容器共享宿主网络命名空间,所以面板在宿主每个网卡的分配端口上可达。这是有意为之——监控面板就是给别的机器看的——并由自动生成的强 admin 密码加 GF_USERS_ALLOW_SIGN_UP=false(关闭自助注册)保护。想要仅回环的部署,传 --listen 127.0.0.1(用 --force 重建);pg addon list 会显示 http://127.0.0.1:<port>/。--listen <addr> 也接受任意指定网卡的地址。
取回 Web 登录
这个登录是 Grafana 的 Web 签名,与任何 PostgreSQL 角色无关。随时可重读密码:
它也存于 pg.yaml(addons.grafana.<name>.password)。
端口
每个 Grafana 实例占有一个端口:Web UI。它取自以 grafana_start_port(默认 3000——Grafana 惯例端口;从 3000 起步是因为书签和文档都默认它)为基址的端口池,每多一个实例递增,并跳过任何已被受管容器或 used_ports 列表占用的端口。分配结果写入 pg.yaml,所以重装绝不会把别的插件在用的端口再发一遍:
Grafana 的端口池基址与其他任何插件都不重号;显式 --port 仍然优先。
配置
配置只经环境变量(GF_*)交付:镜像内的 grafana.ini 从宿主上不可编辑。pgcli 设置的变量有:登录对、监听器(端口 + 绑定地址)、关闭上报(GF_ANALYTICS_REPORTING_ENABLED / GF_ANALYTICS_CHECK_FOR_UPDATES)、GF_USERS_ALLOW_SIGN_UP=false。更多配置不在 v1 范围内;需要不同构建就用 --image 覆盖镜像。
列出
默认不显示密码以保持列表可安全粘贴;pg addon list --show-password 揭示明文,pg addon password grafana 则为脚本裸打印单个密码。
启停
容器以 podman 的 unless-stopped 重启策略运行,宿主重启后随 podman 服务自动回来。start/stop 显式管理它们:
开机自启
要让 Grafana 实例在宿主开机时自动启动,开启它:
这会在 pg.yaml 里给实例打标记,并安装/刷新一个开机服务(Linux 上是 systemd --user unit,macOS 上是 LaunchAgent)来执行 pg start --autostart。开机启动只负责把容器拉起来——数据目录在创建时已 chown 过,除 podman 本身外不需要任何宿主特权。完整机制、状态与关闭方式见 开机自启。
移除
rootless podman 下,--clean-data 经 podman unshare rm 删除数据目录——Grafana 以容器内 uid(472)写文件,它映射到宿主的 subordinate uid,宿主用户无法直接 rm。
日志
排障
install失败于 “chowning Grafana data dir … to 472:0” —— 镜像以 uid 472 运行且从不自己修复属主,所以 pgcli 在创建容器前先把宿主数据目录 chown 过去:rootless podman 下经podman unshare chown(映射后的 subordinate uid 才是正确目标),rootful 宿主且 pgcli 自身以 root 运行时(sudo podman,或以 root 跑pg)直接递归chown。pgcli 是直接调用podmanCLI,所以只在这两种情形之外才会失败——例如非 root 的pg被显式指向别人的 rootful daemon。遇到时以 root 跑 install,或改用 rootless podman;单纯sudo chown -R 472:0 <data-dir>不能消除该错误,因为每次 install 都会重新校验属主。- 登录密码不对 ——
GF_SECURITY_ADMIN_PASSWORD只在grafana.db不存在时初始化登录。对既有存储用容器内grafana-cli admin reset-admin-password <new>,或pg addon remove grafana --clean-data+ 重装(会清掉仪表盘)。 - 从别的机器打不开面板 ——
Listen默认0.0.0.0,端口在宿主所有网卡上是开的;打不开就是宿主防火墙的问题,不是绑定地址。用pg addon list确认端口,然后放行。 - 端口已被分配 —— 已分配的端口记在
pg.yaml里并为后续实例跳过;先pg addon remove释放,或用--port N另选一个。 - 数据加载失败 / 容器以权限告警退出 —— 数据目录必须保持 uid 472 所有;在宿主上手动
chown过、或用错误属主恢复过备份都会破坏它。重跑 install(--force)重新应用属主修复。
已知限制
- v1 是裸 Web UI:没有 datasource/dashboard provisioning,不支持反代子路径(没有
--root-urlflag——GF_SERVER_ROOT_URL不设置,所以要在根路径上用 nginx 前置),且是明文 HTTP(TLS 终止是 nginx 插件的职责)。 - 宿主侧的属主 chown 在 rootless podman(
podman unshare chown)下自动完成,pgcli 以 root 跑在 rootful 宿主上也自动完成(直接chown)。非 root 的 pgcli 连接一个显式配置的远程 rootful daemon 时没有自动路径。 - macOS 上容器跑在 podman machine 里;目前只在 Linux 上验证过。
16.16 - Prometheus
pg addon install prometheus 以受管容器方式运行 Prometheus——指标采集与告警后端——直接跑官方 docker.io/prom/prometheus 镜像。与 Grafana 一样,它是顶层插件:不前置某个具体实例(它抓很多端点),也不依附于任何一个。二者是天然搭档——Prometheus 负责采集与存储、Grafana 负责可视化——且生命周期形状完全一致(单容器、Linux 上宿主网络、各自端口池里的一个 web 端口、持久数据目录、开机自启、日志)。
Prometheus 与 Grafana 的分歧在配置方式。Grafana 只经环境变量(GF_*);Prometheus 读一份配置文件(prometheus.yml),且把运行时调优作为镜像名之后的 CLI flag 传入(--config.file / --web.listen-address / --storage.tsdb.path / --storage.tsdb.retention.time)。所以 pgcli 渲染 prometheus.yml、只读 bind mount 进容器,并让 --config.file 指向容器内该路径。
没有 Web 登录/密码。 与 Grafana(其
0.0.0.0默认由自动生成的强密码兜底) 不同,Prometheus 完全不带鉴权。它的 HTTP API 可执行任意 PromQL,昂贵查询也 能把自身打满,所以远程暴露的实例只靠网络边界保护——防火墙,或--listen 127.0.0.1收紧为仅回环。请把它当作一个无鉴权的读/写控制台来对待,而 非一个有密码保护的面板。
平台:Linux 是已实测的平台。设计上也支持 macOS(镜像多架构;容器在
pgcli-netbridge 上以发布端口的方式跑在 podman machine 里),但尚未在 Mac 上验证过。
参数
| 参数 | 默认值 | 说明 |
|---|---|---|
--name |
prometheus |
实例名(map 键) |
--image |
docker.io/prom/prometheus:v3.15.0 |
覆盖镜像 |
--port |
自动(基址 9090) | Web 宿主端口;自 prometheus_start_port 自动分配 |
--listen |
0.0.0.0 |
绑定地址——默认开放全部网卡;127.0.0.1 收紧为仅回环(见上方暴露警告) |
--scrape |
—(自带自监控 job) | 抓取声明 JOB=HOST:PORT[,HOST:PORT...] [KEY=VALUE...](可重复)——job 名必填,逗号分隔的多个 target 全部并入这一个 job,在渲染的 prometheus.yml 里成一个 static_configs job;target 列表之后的裸 KEY=VALUE token 成为逐 target 标签,作用于该 spec 的所有 target(带标签的 spec 含空格,整个值要用引号包成一个 shell 参数)。同一 job 名跨 spec 折叠为一个 job;同一 node 被两个 spec 提到时标签合并、只抓一次,而标签集不同的 node 拆成同 job 下多个 static_configs 块(即 nodename=api1 这类按节点打标签的原生形状)。声明的是完整 job 集合(整体替换已存列表)。目标会被宿主侧 GET /metrics 探测,探测不通仅告警 |
--scrape-append |
— | 同样的声明格式(标签同样支持),但把追加的 target 连同其标签合并进已存的 --scrape 列表(同名 job 并入、新名字排到末尾)而非替换。每个被追加的目标都会探测,任一拉不到数据即中止 install——什么都不写。与 --scrape、--conf-file 互斥 |
--scrape-interval |
15s |
渲染配置里的全局 scrape_interval;与 --conf-file 互斥 |
--retention |
15d |
TSDB 保留期(--storage.tsdb.retention.time);时长如 15d、6mo、2y。是 CLI flag,故与 --conf-file 可并存 |
--conf-file |
— | 你自己的 prometheus.yml 路径——完全跳过渲染(与 --scrape/--scrape-append/--scrape-interval/--alertmanager-url/--remote-write-url/--remote-write-queue 互斥);见下 |
--alertmanager-url |
— | 本 Prometheus 转发已触发告警到的 Alertmanager 实例(可重复):裸 HOST[:PORT](不写端口则补 9093)或 http(s)://HOST[:PORT][/path]。渲染的配置会多出一段 alerting: 指向它,并且多出一个对应的 alertmanager 抓取 job(顺带盯住 Alertmanager 自己的 /metrics——不是另一个开关,二者一体)。全部条目先校验后落盘,且必须同一种 scheme(http/https 混用即中止);不探测连通性。与 --conf-file 互斥;见下 |
--remote-write-url |
— | 把每个抓取到的样本推送一份到的远端存储 URL(可重复;完整 http(s)://host:port/path URL)。渲染的配置多出一段顶级 remote_write:。本地 TSDB 仍按自己的 --retention 保留并可查——远端是一份 WAL 兜底的副本,远端挂掉只是队列积压,恢复后自动重放。先校验(必须是带 host 的 http(s) URL);不探测连通性。与 --conf-file 互斥;见"把样本推给 VictoriaMetrics"一节 |
--remote-write-queue |
— | 在每个 --remote-write-url 条目下渲染一段 queue_config:(可重复 KEY=VALUE):正整数 capacity、max_shards、min_shards、max_samples_per_send,以及时长 batch_send_deadline、min_backoff、max_backoff(如 5s、1m30s、2y)。VictoriaMetrics 建议高压下加大批量(max_samples_per_send=10000 capacity=20000 max_shards=30)。必须搭配 --remote-write-url;键与值先校验后落盘(未知键、非正整数、无单位时长都会中止)。与 --remote-write-url 同为渲染模式;差异化到单个远端的调优仍需 --conf-file |
--rule-file |
— | rule 文件路径(*.rules.yml,顶层是 groups:;可重复)——pgcli 把每个文件拷进托管 rules 目录(只取 basename;两个文件撞名即中止),把该目录只读挂到 /etc/prometheus/rules,渲染的 prometheus.yml 随即用 rule_files 加载这些拷贝。与 --conf-file 可并存:拷贝和挂载无论如何都会发生,你自己的文件决定要不要引用它们。文件不存在或为空即中止——什么都不写;见下 |
--data-dir |
自动 | 覆盖数据目录(默认在 base 目录下) |
--force |
false | 即使正在运行也重建——用于不热加载改动的配置,或应用改动的端口/监听/保留期(--rule-file 从无到有一样会触发重建,见下) |
数据目录是 Prometheus 的 TSDB。它跨 pg addon remove 存活(用 --clean-data 才删除),重装同名实例即复活已采集的指标。镜像以固化的 nobody(uid/gid 65534)运行且不自建可写目录,所以 pgcli 在创建容器前先在宿主上把数据目录 chown 过去:rootless podman 下经 podman unshare chown 65534:65534,rootful 宿主且 pgcli 以 root 运行时直接递归 chown(与 Grafana 完全同构,只是 id 不同)。
安装
命令会按需拉取镜像、渲染一份 prometheus.yml、创建容器,并打印 URL、配置路径与保留期:
渲染的配置始终含一个抓取本服务自身 /metrics 的自监控 job,所以绝不会空转;--scrape 添加更多 job。在 Grafana 里把 http://<host>:9090/ 加成 Prometheus 数据源,即可立刻查询。
给 target 打标签(Ansible 渲染配置里那种 nodename: api1)就是在 target 列表后面
追加裸 KEY=VALUE token——每个带标签的 node 各一条 --scrape:
分组以 target 为中心,所以同一个 node 被提两次绝不会抓两遍:两条 spec 都提到
10.0.0.5:9100 时,它合并两边的标签集、只成为一个 target
(node=a:1 nodename=api1 + node=a:1 env=prod → a:1 同时带两个标签)。
唯一被拒绝的是矛盾——同一 node 的同一个标签键给了两个不同值,直接中止而不是
悄悄丢掉一个。标签值不能含空白(device="eth0, eth1" 请改用 --conf-file),
标签键必须满足 [A-Za-z_][A-Za-z0-9_]*;值以引号渲染,所以 yes、2
不会在 YAML 里改变类型。
带 --scrape 重复 install 会整体替换已存的 job 集合。想增量追加就用 --scrape-append——格式与 --scrape 完全一致(标签同理),它把每个被追加 target(连同其标签)并入同名已存 job、新 job 名排到末尾,然后热加载:
两个 flag 的探测策略刻意不同:被追加的目标必须以 200 应答 GET /metrics,否则命令在写入任何东西之前就中止(pg.yaml 与运行中的容器分毫未动);而 --scrape 声明完整集合时,探测不通只告警、install 照常——Prometheus 自己会把它标成 DOWN,且全量声明时常包含尚未就绪的端点。探测从宿主拨号——Linux(宿主网络)上与容器抓取处于同一命名空间,判定即容器所见;macOS bridge 下两者不同,故直接跳过探测。
抓取目标是从 Prometheus 容器内拨号的。Linux(宿主网络)上 127.0.0.1:<port> 直达宿主回环;macOS bridge 下它到的是容器自身命名空间,所以远程或 VM 内的目标要给一个可路由地址。
对运行中的实例重复 install:当渲染/拷贝的配置确实变了时热加载(SIGHUP),没变则原样不动;--force 则重建容器——用于应用改动的 --port / --listen / --retention(它们是容器参数,而非配置文件内容)。
把告警转发给 Alertmanager(--alertmanager-url)
让一个 Prometheus 指向一个或多个 pgcli 托管的 Alertmanager,把已触发的告警推给它们:
渲染出的 prometheus.yml 会因这一个 flag 多出两处——一段推送已触发告警的 alerting:,以及一个与之对应的 alertmanager 抓取 job(盯住每个 Alertmanager 自己的 /metrics):
接受的形式是裸 host[:port](不写端口则取 Alertmanager 惯用的 9093)或完整的 http(s)://host[:port][/path] URL;抓取目标那侧会剥掉 scheme 与 path,https 则在两段都带成 scheme: https(CA 文件等 TLS 旋钮超出渲染范围——那些要用 --conf-file)。所有条目先于任何写入完成校验,且必须共用一种 scheme(http 与 https 混用即中止),但不做连通性探测:Alertmanager 死没死由 Prometheus 自己跟踪,pg addon prometheus reload 也修不了网络问题。重跑 install 会整体替换已存的 URL 列表(它不像 --scrape 有追加形式)。
实机验证接线:
把样本推给 VictoriaMetrics(--remote-write-url)
Prometheus 内置的 TSDB 是编译进二进制的,换不掉——把 VictoriaMetrics(或任何远端存储)垫在它后面的方式是 remote_write:每个抓取周期把每个样本的一份副本推给远端,而本地 TSDB 照常保留自己的 --retention 窗口、照常提供查询。pgcli 两端都已经能跑,所以这对组合就是两次 install:
渲染出的 prometheus.yml 多出一段顶级 remote_write:——给了 --remote-write-queue 时,每个 URL 下再多一段 queue_config::
重跑 install 会整体替换已存的 URL 列表(与 --alertmanager-url 一样没有追加形式),URL 变化靠热加载(SIGHUP)生效——不重建容器。条目必须是完整的 http(s)://host[:port]/path URL(与 Alertmanager 目标不同,没有补端口/补 scheme 的约定——path 本身就是写入端点);先校验但不探测连通性。--remote-write-queue 走同一条路:重复传入即整体替换已存列表、先校验(只收白名单键——整数容量/分片与时长超时),且同一组 KEY=VALUE 会渲染进每个远端条目下,要按远端差异化调优仍需 --conf-file。没有 --remote-write-url 却单独传它会被中止——无处可调。
验证链路活着且在投递:
把 Grafana 从 Prometheus 改指到 VictoriaMetrics(http://<host>:8428/,集群版则是 http://<host>:8481/select/0/prometheus/)之后,Prometheus 实例就退化成薄薄一层采集+转发,VictoriaMetrics 成为可查询的存储。已在 Prometheus TSDB 里的历史数据不会自动回填——一次性迁移用 vmctl prometheus 工具。
加载告警规则(--rule-file)
Prometheus 从 rule_files: 评估告警/记录规则。把 rule 文件交给 pgcli,它就变成托管拷贝:
pgcli 把每个文件拷进 <base>/addon/prometheus/<name>/rules/,把该目录只读挂到 /etc/prometheus/rules,渲染的配置按文件名引用拷贝:
目录是扁平的,所以起作用的是 basename:两个源文件撞同一个 basename 会中止 install(拷贝会悄悄覆盖掉一个)。
改你的原 rule 文件,一次普通的 reload 即可生效:pg addon prometheus reload 在发信号之前会把每个 --rule-file 重拷进托管目录,所以 Prometheus 原地重读新规则——不用 reinstall、不重启容器:
坏 rule 文件的遭遇与坏配置一样:Prometheus 沿用上一份好规则集并把解析错误打进日志,所以手改规则后 reload 完记得查日志(或 rules API)。这些拷贝是托管产物,与渲染出的 prometheus.yml 一样,pg addon remove 无论有没有 --clean-data 都会删掉它们。你的原文件绝不被碰。
--rule-file 与其他渲染旋钮的不同点在于:它与 --conf-file 可并存——pgcli 无论如何都会拷规则、加挂载,是否引用 /etc/prometheus/rules 由你自己的文件决定(在里面写 rule_files: ["/etc/prometheus/rules/*.rules.yml"] 就能匹配到你传入的一切)。唯一一个新增后无法热加载生效的 flag 也是它:从无到有一样会改变挂载集合,而 /etc/prometheus/rules 这个挂载只在有 rule 文件时存在,SIGHUP 加不了 volume,所以这一跃变(及其反向)会重建容器。
自定义配置文件(--conf-file)
把你自己的 prometheus.yml 交给 pgcli,它就完全跳过渲染:
pgcli 把你的文件拷贝进托管路径(<base>/addon/prometheus/<name>/prometheus.yml),并只读挂载这份拷贝——绝不挂你的原文件。这是刻意为之:单文件 bind mount 锁死源 inode,原地改你的原文件不会传播进运行中的容器。正因它是拷贝,改完你的原文件后跑 pg addon prometheus reload 会重拷并通知 Prometheus 重读。pgcli 不校验、不改写文件内容(与 nginx/haproxy 的 --conf-file 同一约定)。--conf-file 与渲染旋钮 --scrape/--scrape-append/--scrape-interval/--alertmanager-url/--remote-write-url/--remote-write-queue 互斥(operator 文件自己掌管其 alerting: 与 remote_write: 段);--retention、--listen 是 CLI flag,--rule-file 是文件拷贝而非渲染旋钮,三者与它并存仍有效。重装时省略 --conf-file 即回到渲染模式。
完整样例
一份接好 pgcli Alertmanager、并加载 pgcli 托管 rule 文件的真实运维配置:
上面这种带标签的 nodes 形状,正是如今渲染模式会产出的——三条
--scrape node=…:9100 nodename=… 折叠成一个 nodes job、各带一个有标签的
static_configs 块;渲染器没有暴露的按 job 附加项(抓取超时、relabel 等)仍归手写文件管。
localhost:9090、localhost:9093 能通,是因为 Linux 上容器跑在宿主网络里——Prometheus 容器内的 localhost 就是宿主,两个 web 端口都在上面监听。这份配置里能看到 pgcli 只参与了一处:--rule-file 会把你的 rule 文件拷进 <base>/addon/prometheus/<name>/rules/ 并把该目录挂到 /etc/prometheus/rules,所以上面的 rule_files: glob 才解析得到;而 alerting: 段完全由你自己写——--conf-file 模式下 pgcli 不往文件里加任何东西,也不派生 alertmanager 抓取 job,且 --alertmanager-url 作为渲染旋钮会被拒绝。手写文件别等 reload 后才从日志里发现错误,先自查一遍:
检查的是 pgcli 挂载进容器的那份拷贝,所以在 install(或 reload)之后跑,而不是对着你的原文件路径跑。
热加载(配置热更新)
它重写托管的 prometheus.yml(渲染模式重渲、--conf-file 模式重拷你的文件,两种模式下都会重拷 --rule-file 的拷贝),并向运行中的容器发 SIGHUP,让 Prometheus 原地重读——不重建容器、不中断抓取。它刻意用信号而非 HTTP POST /-/reload 端点,于是那条无鉴权重载路由永远不需要开启(不设 --web.enable-lifecycle)。若新配置有错,Prometheus 会沿用上一份好配置并打日志,所以对手写文件 reload 后请查 pg logs addon prometheus。实例须已在运行(pg addon start prometheus)。有一类变化是信号救不了的:--rule-file 从无到有一样会增删 /etc/prometheus/rules 挂载,所以这一跃变要靠普通 install 重建容器来应用——非空集合内部的改动,reload 就够了。
监听与暴露
Prometheus 的 Listen 默认 0.0.0.0:容器共享宿主网络命名空间,所以 UI 与 API 在宿主每个网卡的分配端口上可达。这与 Grafana 的默认一致(别的机器上的 Grafana 要能连它)——但背后没有密码。任何能连到端口的人都能执行任意 PromQL、读你采集的指标。想要仅回环的部署,传 --listen 127.0.0.1(用 --force 重建);pg addon list 会显示 http://127.0.0.1:<port>/。否则请在防火墙层面限制访问。
端口
每个 Prometheus 实例占有一个端口:Web UI/API。它取自以 prometheus_start_port(默认 9090——Prometheus 惯例端口)为基址的端口池,每多一个实例递增,并跳过任何已被受管容器或 used_ports 列表占用的端口。分配结果写入 pg.yaml,所以重装绝不会把别的插件在用的端口再发一遍。9090 与 Grafana(3000)、pgAdmin(5050)、nginx(8080)都不重号;显式 --port 仍优先。
配置
渲染的 prometheus.yml 承载全局 scrape_interval、始终存在的自监控 job、以及每个 scrape JOB 名一个抓取 job——该 job 各条 spec 列出的所有 target 折叠进去,再按合并后的标签集分桶,于是每一组不同的 KEY=VALUE 组合成为自己单独的 - targets:/labels: 块;--alertmanager-url 会加上 alerting: 段与随之而来的 alertmanager 抓取 job,--remote-write-url 会加上一段顶级 remote_write:(--remote-write-queue 在每个条目下填充 queue_config: 块),--rule-file 会加上 rule_files: 段。retention 与监听绑定不在那个文件里——它们作为 CLI flag 传给 prometheus 二进制(--storage.tsdb.retention.time、--web.listen-address),这正是它们能与 --conf-file 并存的原因。
列出
没有凭据可揭示——Prometheus 本就没有——所以 --show-password 对它不会多显示任何东西。
启停
容器以 podman 的 unless-stopped 重启策略运行,宿主重启后随 podman 服务自动回来。start/stop 显式管理它们:
开机自启
要让 Prometheus 实例在宿主开机时自动启动,开启它:
这会在 pg.yaml 里给实例打标记,并安装/刷新一个开机服务(Linux 上是 systemd --user unit,macOS 上是 LaunchAgent)来执行 pg start --autostart。开机启动只负责把容器拉起来——读磁盘上已存在的托管 prometheus.yml,且数据目录在创建时已 chown 过,除 podman 本身外不需要任何宿主特权。启动顺序上 Prometheus 排在 Grafana 之后、nginx 之前。完整机制、状态与关闭方式见 开机自启。
移除
rootless podman 下,--clean-data 经 podman unshare rm 删除数据目录——Prometheus 以容器内 uid(65534)写文件,它映射到宿主的 subordinate uid,宿主用户无法直接 rm。--conf-file 模式只删 pgcli 的拷贝;你的原文件在何处都不被触碰。--rule-file 的原文件同样幸存——被删的只有托管的 rules/ 拷贝,且它们在每次 remove 时都消失(无论有无 --clean-data,与渲染出的 prometheus.yml 一样,没有值得复活的状态)。
日志
排障
install失败于 “chowning Prometheus data dir … to 65534:65534” —— 镜像以nobody(uid/gid 65534)运行且从不自己修复属主,所以 pgcli 在创建容器前先把宿主数据目录 chown 过去:rootless podman 下经podman unshare chown(映射后的 subordinate uid 才是正确目标),rootful 宿主且 pgcli 自身以 root 运行时直接递归chown。pgcli 是直接调用podmanCLI,所以只在这两种情形之外才会失败——例如非 root 的pg被显式指向别人的 rootful daemon。遇到时以 root 跑 install,或改用 rootless podman;单纯sudo chown -R 65534:65534 <data-dir>不能消除该错误,因为每次 install 都会重新校验属主。- 某个抓取目标显示
DOWN—— 确认 Prometheus 能连到它(它从容器内拨号;Linux 上127.0.0.1:<port>是宿主回环,但跨主机/VM 目标需要可路由地址,macOS bridge 上尤甚)。确认 exporter 在跑、路径是/metrics。pg logs addon prometheus会显示抓取失败。 --scrape-append中止于 “returned no data” —— 这是设计行为:被追加的目标必须以 200 应答宿主的GET /metrics,否则任何东西都不写入(已存实例与pg.yaml分毫未动)。要么先把 exporter 拉起来,要么改用--scrape(仅告警)声明完整 job 集合。macOS 上跳过该探测。- 配置 reload 没生效 ——
pg addon prometheus reload需要容器在运行;若新的prometheus.yml有格式错误,Prometheus 会沿用上一份好配置并打日志。查pg logs addon prometheus,改好文件,再 reload。 - 端口已被分配 —— 已分配的端口记在
pg.yaml里并为后续实例跳过;先pg addon remove释放,或用--port N另选一个。 - 远程 API 打不开 / 意外可访问 —— 默认
0.0.0.0下打不开是宿主防火墙问题,不是绑定地址;反之若你不想被访问,用--listen 127.0.0.1 --force。
已知限制
- v1 是裸采集后端:不自带 PostgreSQL exporter(自己装好,再
--scrape postgres-exporter=127.0.0.1:9187指向它)。但长期存储与告警都是接好的:--remote-write-url把抓取到的样本推给 VictoriaMetrics 插件(单机版或vmcluster),--alertmanager-url把已触发告警转发给 pgcli 的 Alertmanager,--rule-file加载告警规则(三者都是渲染模式;其余场景用--conf-file手写)。它是你让 Grafana 连的采集/存储/查询层。 - 无鉴权。 Prometheus 的 API 执行任意 PromQL;
0.0.0.0绑定的唯一保护是网络边界。这比 Grafana 有密码兜底的0.0.0.0是实质更弱的态势——不要把它暴露到不可信网络。 - pgcli 渲染或拷贝
prometheus.yml,但不校验它(install 阶段没有promtool check config闸门)。渲染出的配置不可能有错,但有错的--conf-file只会在 reload 时从容器日志里暴露。 - 宿主侧的属主 chown 在 rootless podman(
podman unshare chown)下自动完成,pgcli 以 root 跑在 rootful 宿主上也自动完成(直接chown)。非 root 的 pgcli 连接一个显式配置的远程 rootful daemon 时没有自动路径。 - macOS 上容器跑在 podman machine 里;目前只在 Linux 上验证过。
16.17 - Alertmanager
pg addon install alertmanager 以受管容器方式运行 Alertmanager——位于 Prometheus 下游的告警去重、分组、路由与通知层——直接跑官方 docker.io/prom/alertmanager 镜像。与 Prometheus、Grafana 一样,它是顶层插件:不前置某个具体实例,也不依附于任何一个。Prometheus 与 Alertmanager 是天然一对——Prometheus 触发告警,Alertmanager 决定通知谁、怎么通知——而本插件在形状上是 Prometheus 的近乎完全同构体(单容器、Linux 上宿主网络、各自端口池里的一个 web 端口、持久数据目录、开机自启、日志、渲染配置文件 + 镜像名之后的 CLI flag、SIGHUP 热加载、无 Web 登录)。
衔接的工作在 Prometheus 那一侧。
pg addon install alertmanager不碰任何prometheus.yml——也不需要碰:由 Prometheus 自己指向本实例,命令是pg addon install prometheus --alertmanager-url <本实例的 host:port>,它会注入alerting:段(外加一个抓取本实例/metrics的 job)。若 Prometheus 用的是自己的 配置文件(--conf-file),就自己在里面写alerting:段。两种方式只要指过来了,告警 就会流进来。
没有 Web 登录/密码——而且比 Prometheus 的赌注更大。 Alertmanager 完全不 带鉴权,且它的无鉴权 API 的可写性比 Prometheus 那个以读为主的 API 更危险:任何能 连到端口的人都能创建 silence——即悄悄压掉 oncall 期待的每一条通知,且不会在 Prometheus 自己的指标里留下任何痕迹。它的
0.0.0.0默认因此只靠网络边界保护, 而这条边界在这里比插件家族里任何其他地方都更重要。请把它当作把一套告警体系的静音 开关交出去来对待,而非一个有密码保护的面板。
平台:Linux 是已实测的平台。设计上也支持 macOS(镜像多架构;容器在
pgcli-netbridge 上以发布端口的方式跑在 podman machine 里),但尚未在 Mac 上验证 过。
参数
| 参数 | 默认值 | 说明 |
|---|---|---|
--name |
alertmanager |
实例名(map 键) |
--image |
docker.io/prom/alertmanager:v0.34.1 |
覆盖镜像 |
--port |
自动(基址 9093) | Web 宿主端口;自 alertmanager_start_port 自动分配 |
--listen |
0.0.0.0 |
绑定地址——默认开放全部网卡;127.0.0.1 收紧为仅回环(见上方暴露警告) |
--webhook-url |
— | 要通知的 webhook URL(可重复)。每个 URL 都被渲染成同一个 web.hook receiver 下的一条 webhook_configs——多个 URL 会扇出通知到每一个。写入任何东西之前先用 net/url 校验(可解析、http/https、Host 非空);格式错误的 URL 会让 install 直接中止,不留任何痕迹。不探测连通性(见下)。与 --conf-file 互斥 |
--conf-file |
— | 你自己的 alertmanager.yml 路径——完全跳过渲染(与 --webhook-url 互斥);见下 |
--data-dir |
自动 | 覆盖数据目录(默认在 base 目录下) |
--force |
false | 即使正在运行也重建——用于不热加载改动的配置,或应用改动的端口/监听 |
数据目录承载 Alertmanager 的状态:silence 与通知状态。它跨 pg addon remove 存活
(用 --clean-data 才删除),重装同名实例即复活它们。镜像以固化的 nobody(uid/gid
65534)运行且不自建可写目录,所以 pgcli 在创建容器前先在宿主上把数据目录 chown 过
去:rootless podman 下经 podman unshare chown 65534:65534,rootful 宿主且 pgcli 以
root 运行时直接递归 chown(与 Prometheus、Grafana 完全同构,只是 id 不同)。
--webhook-url 不做连通性探测,这是刻意为之:不像 Prometheus 的抓取(GET /metrics,install 时即可达并可应答),webhook 只在告警真正路由过去时才发的 POST
——install 时拨它一下也告诉不了你什么可靠信息。唯一的提前闸门是 URL 的形状;端点
是否存在、是否可达、能否接受这个 payload,只能在告警实际触发时才知道(通知没到就查
pg logs addon alertmanager)。
安装
命令会按需拉取镜像、渲染一份 alertmanager.yml、创建容器,并打印 URL、配置路径以及
路由渲染成什么:
没有 --webhook-url 时,渲染的配置是一条指向仅有名字、no-op receiver 的单路由:一
份合法、功能完整的配置——Web UI 与 API 在线、silence 可用、HA 集群端口已禁用——只是
什么都不发。这是"先把端点拉起来,路由之后再定"的默认形态。要真正发出通知,加一个
webhook:
对运行中的实例重复 install:当渲染/拷贝的配置确实变了时热加载(SIGHUP),没变
则原样不动;--force 则重建容器——用于应用改动的 --port / --listen(它们是容器参
数,而非配置文件内容)。
带不同的 --webhook-url 集合重复 install 会整体替换已存列表(一个都不传则清空回
no-op);正是这个 diff 决定要不要 reload。
自定义配置文件(--conf-file)
把你自己的 alertmanager.yml 交给 pgcli,它就完全跳过渲染:
pgcli 把你的文件拷贝进托管路径(<base>/addon/alertmanager/<name>/alertmanager.yml),
并只读挂载这份拷贝——绝不挂你的原文件。这是刻意为之:单文件 bind mount 锁死源
inode,原地改你的原文件不会传播进运行中的容器。正因它是拷贝,改完你的原文件后跑
pg addon alertmanager reload 会重拷并通知 Alertmanager 重读。pgcli 不校验、不改写文
件内容(与 nginx/haproxy/prometheus 的 --conf-file 同一约定)。--conf-file 与
--webhook-url(塑造渲染文件的那个 flag)互斥——真正的路由树(email/Slack/PagerDuty、
子路由、inhibition)活在你自己的文件里。重装时省略 --conf-file 即回到渲染模式。
这份文件就是普通的 Alertmanager YAML——pgcli 不往里加东西,除了"能解析"之外对它也没 任何要求。下面是一份完整示例,含子路由树、inhibition,以及三种不同类型的 receiver:
pgcli 本身不校验这份文件(install 阶段没有 amtool check-config 闸门——见已知限
制),但镜像自带 amtool,所以你可以在信任一次 reload 之前,进正在运行的容器里检查
托管的那份拷贝(容器名以 pg addon list 打印的为准,带命名空间前缀——上面那段实录里
是 pgcli-alertmanager-default-am1):
热加载(配置热更新)
它重写托管的 alertmanager.yml(渲染模式重渲、--conf-file 模式重拷你的文件),并向
运行中的容器发 SIGHUP,让 Alertmanager 原地重读——不重建容器、不中断通知。
与 Prometheus 不同,Alertmanager 的 POST /-/reload 端点是存在的,且无法关闭(没
有类似 --web.enable-lifecycle 的开关)。pgcli 仍然用 SIGHUP 重载——不是因为那条路由
被关掉了,而是为了永远不需要依赖一条按镜像设计就一直敞开的无鉴权 HTTP 端点。任何能打
到端口的人本就能直接打这条路由;pgcli 只是不往这个面上再加东西。若你 reload 一份被拒
绝的手写 --conf-file,Alertmanager 会沿用上一份好配置并打日志,所以之后请查
pg logs addon alertmanager。实例须已在运行(pg addon start alertmanager)。
监听与暴露
Alertmanager 的 Listen 默认 0.0.0.0:容器共享宿主网络命名空间,所以 UI 与 API 在
宿主每个网卡的分配端口上可达。背后没有密码,而且——与 Prometheus 不同——这个 API
允许调用方创建 SILENCE,即压掉 oncall 期待的每一条通知,还能重载自身配置。仅回环
部署才是安全的默认:传 --listen 127.0.0.1(用 --force 重建);pg addon list 会
显示 http://127.0.0.1:<port>/。否则请在防火墙层面限制访问,并把敞开的 0.0.0.0 当
作一个可被触达的静音开关来对待。
端口
每个 Alertmanager 实例占有一个端口:Web UI/API。它取自以 alertmanager_start_port
(默认 9093——Alertmanager 惯例端口)为基址的端口池,每多一个实例递增,并跳过任何
已被受管容器或 used_ports 列表占用的端口。分配结果写入 pg.yaml,所以重装绝不会把
别的插件在用的端口再发一遍。9093 与 Grafana(3000)、pgAdmin(5050)、
Prometheus(9090)、nginx(8080)都不重号;显式 --port 仍优先。
9094——HA gossip 端口——永不被开启,因为它被一个 flag 关掉了。 Alertmanager 的高可用集群默认在 TCP+UDP
9094上监听,且对一个单实例而言会不停尝 试引导永远不会到来的 peer。pgcli 传--cluster.listen-address=(空串)彻底关掉它 ——这是单实例约定,每次创建都强制执行,于是集群端口永远不开、也没有 UDP 监听器残 留。(这只关乎9094的集群用途;这个数字并未被预留,所以 web 端口池照样可以 把9094作为第二个实例的 web 端口分配出去——见下面的示例。)把多个 pgcli 托管的 Alertmanager 集群成一个 gossip 组并未接通(见已知限制)。
配置
渲染的 alertmanager.yml 承载一个 global.resolve_timeout、一条根路由
(group_by: ['alertname'],默认 group_wait/interval/repeat_interval)指向单个
receiver,以及这个 receiver 的 webhook_configs——web.hook 下一个 webhook URL 一
条,列表为空时则是仅有名字的 no-op receiver。监听绑定、存储路径、被禁用的集群地址
不在那个文件里——它们作为 CLI flag 传给 alertmanager 二进制
(--web.listen-address、--storage.path、--cluster.listen-address=),这正是
--listen 能与 --conf-file 并存的原因。
列出
没有凭据可揭示——Alertmanager 本就没有——所以 --show-password 对它不会多显示任何东
西,pg addon password alertmanager 也解析不出任何东西。
启停
容器以 podman 的 unless-stopped 重启策略运行,宿主重启后随 podman 服务自动回来。
start/stop 显式管理它们:
开机自启
要让 Alertmanager 实例在宿主开机时自动启动,开启它:
这会在 pg.yaml 里给实例打标记,并安装/刷新一个开机服务(Linux 上是 systemd --user
unit,macOS 上是 LaunchAgent)来执行 pg start --autostart。开机启动只负责把容器拉起
来——读磁盘上已存在的托管 alertmanager.yml,且数据目录在创建时已 chown 过,除 podman
本身外不需要任何宿主特权。启动顺序上 Alertmanager 排在 Prometheus 之后、nginx 之前——
是上面那个采集器天然的搭档,衔接也恰好在采集器那一侧完成(pg addon install prometheus --alertmanager-url …)。完整机制、状态与关闭方式见 开机
自启。
移除
rootless podman 下,--clean-data 经 podman unshare rm 删除数据目录——Alertmanager
以容器内 uid(65534)写文件,它映射到宿主的 subordinate uid,宿主用户无法直接 rm。
--conf-file 模式只删 pgcli 的拷贝;你的原文件在何处都不被触碰。
日志
排障
install失败于 “chowning Alertmanager data dir … to 65534:65534” —— 镜像以nobody(uid/gid 65534)运行且从不自己修复属主,所以 pgcli 在创建容器前先把宿主数 据目录 chown 过去:rootless podman 下经podman unshare chown(映射后的 subordinate uid 才是正确目标),rootful 宿主且 pgcli 自身以 root 运行时直接递归chown。pgcli 是直接调用podmanCLI,所以只在这两种情形之外才会失败——例如非 root 的pg被显式指向别人的 rootful daemon。遇到时以 root 跑 install,或改用 rootless podman;单纯sudo chown -R 65534:65534 <data-dir>不能消除该错误,因为每 次 install 都会重新校验属主。- 收不到任何通知 —— 最常见的原因是
no-opreceiver:一次不带--webhook-url的 install 渲染的配置永远什么都不发。查pg addon list里的Config:行(它写的是no-op receiver还是rendered (N webhook))。若确实配了真实 webhook,它在 install 时不做连通性探测——可达性只有告警真正触发时才知道,所以要查pg logs addon alertmanager里的 POST 失败,并确认端点能接受 Alertmanager 的 webhook payload。 --webhook-url中止于解析错误 —— 这是设计行为:每个 URL 都在 pg.yaml 或任何文 件被写入之前完成校验(net/url解析、http/https scheme、Host 非空),所以格式错 误的 URL 不会动到运行中的实例与配置。改对 URL 再重跑。- 配置 reload 没生效 ——
pg addon alertmanager reload需要容器在运行;若新的alertmanager.yml被拒绝(路由引用了未定义的 receiver、YAML 格式错误), Alertmanager 会沿用上一份好配置并打日志。查pg logs addon alertmanager,改好文件, 再 reload。 - 端口已被分配 —— 已分配的端口记在
pg.yaml里并为后续实例跳过;先pg addon remove释放,或用--port N另选一个。 - 远程 API 打不开 / 意外可访问 —— 默认
0.0.0.0下打不开是宿主防火墙问题,不是绑 定地址;反之若你不想被访问(记住:可触达即意味着任何人能创建 silence),用--listen 127.0.0.1 --force。
已知限制
- 单向衔接。 本插件既不往
prometheus.yml注入alerting:段,也不从 Prometheus 读取任何信息——它只接收被推进其 API 的东西。接通的动作在 Prometheus 那一侧完成(pg addon install prometheus --alertmanager-url …,渲染模式;--conf-file模式就自己写alerting:段)。反过来,本插件这边也永远不会去检视任何prometheus.yml。 - 无鉴权——且其后果比 Prometheus 更尖锐。API 能创建 silence(静音 oncall)、能重载
配置,且
POST /-/reload在镜像里无法关闭(pgcli 只是不去用它)。0.0.0.0绑定的 唯一保护是网络边界;不要把它暴露到不可信网络。 - 单实例 / 集群已禁用。
--cluster.listen-address=始终以空串传入,所以 HA gossip (TCP+UDP 9094)永不打开。把多个 pgcli 托管的 Alertmanager 集群起来在 v1 里不支持。 - Webhook 在 install 时不做连通性探测(只校验 URL 形状)。可达性与 payload 是否 被接受,在告警触发时才浮现于日志。
- pgcli 渲染或拷贝
alertmanager.yml,但不校验它(install 阶段没有amtool check-config闸门)。渲染出的配置不可能有错,但有错的--conf-file只会在 reload 时 从容器日志里暴露。 - 宿主侧的属主 chown 在 rootless podman(
podman unshare chown)下自动完成,pgcli 以 root 跑在 rootful 宿主上也自动完成(直接chown)。非 root 的 pgcli 连接一个显式配 置的远程 rootful daemon 时没有自动路径。 - macOS 上容器跑在 podman machine 里;目前只在 Linux 上验证过。
16.18 - Gitea
pg addon install gitea 以受管容器方式运行 Gitea——轻量级自托管 Git 服务——直接跑官方上游镜像(docker.io/gitea/gitea,rootful 构建)。与 Grafana 一样,它是顶层插件:不依附于任何单个 PostgreSQL 实例,也不自动前置它们——但可以把某个 PG 实例用作自己的元数据库(见下文 --dsn)。单个容器同时提供 Web UI、Git HTTP 流量,以及 git-over-ssh(跑在镜像自带的 OpenSSH sshd 上——见下文「Git SSH」小节;无论 --ssh-port 与否这个 sshd 都在跑,该 flag 只决定它是否是活的克隆端点)。
安装全程非交互:INSTALL_LOCK=true 跳过 Gitea 的 Web 初始化向导,admin 账号随后由容器内 gitea admin user create 命令行创建。Web 登录密码在首次安装时自动生成、打印一次、存入 pg.yaml,之后用 pg addon password gitea 取回。Listen 默认 0.0.0.0——Git 服务天然要给别的机器克隆——暴露面由自动生成的强密码与关闭自助注册兜底;只想本机访问就传 --listen 127.0.0.1。
平台:Linux 是已实测的平台。设计上也支持 macOS(镜像多架构;容器在
pgcli-netbridge 上跑在 podman machine 里,发布 Web 端口、以及启用时的 SSH 端口),但尚未在 Mac 上验证过。
参数
| 参数 | 默认值 | 说明 |
|---|---|---|
--name |
gitea |
实例名(map 键) |
--image |
docker.io/gitea/gitea:28.1.0 |
覆盖镜像(仅限 rootful 构建,见已知限制) |
--port |
自动(基址 3080) | Web 宿主端口;自 gitea_start_port 自动分配 |
--ssh-port |
未指定 | 让那个一直在跑的 sshd 变成活的 git-over-ssh 克隆端点(见下文「Git SSH」小节)。pgcli 给每一个实例都从 gitea_ssh_start_port(基址 2222)分配一个端口用来 park sshd 离 22,SSH 开或关都一样;--ssh-port 0 关掉克隆语义、端口占用不释放 |
--listen |
0.0.0.0 |
绑定地址——默认开放全部网卡;127.0.0.1 收紧为仅回环。同时也是 Gitea 渲染进 clone URL 的主机名 |
--admin-user |
gitea |
Web 登录账号(故意不用 git——那是容器内的 OS 用户,ssh:// 克隆认证的就是它) |
--password |
自动生成 | Web 登录密码(仅在 admin 账号尚不存在时生效,见下) |
--dsn |
空 | 用 postgres://user:pass@host:port/db 把元数据放到外置 PostgreSQL,替代内嵌 SQLite;目标库必须已存在 |
--allow-registration |
false | 打开自助注册(Web UI 出现注册入口)。默认关闭——见「监听与暴露」一节 |
--data-dir |
自动 | 覆盖数据目录(默认在 base 目录下) |
--force |
false | 即使正在运行也重建——用于应用改动的端口、凭据、dsn、监听地址或注册开关 |
安装
命令会按需拉取镜像、创建并启动容器,等 Gitea 首启迁移跑完,创建 admin 账号,然后打印:
用浏览器打开 URL,以打印的凭据登录,在 Web UI 里建第一个 org/repo。HTTP 克隆用同一个登录:
数据目录与 /data
Gitea 的所有资产都在这一个 bind 挂载目录里:仓库、渲染出的 /data/gitea/conf/app.ini、SQLite 元数据库(/data/gitea/gitea.db——用 --dsn 时不存在)、SSH host key、日志。它跨 pg addon remove 存活(用 --clean-data 才删除),重装同名实例即复活一切。
镜像的 rootful 构建以 git 用户运行——uid/gid 1000,经 USER_UID/USER_GID 钉死,宿主的预 chown 与容器进程从此不可能漂移——而其 entrypoint 只 chown 它自己创建的文件,所以 pgcli 在创建容器前先在宿主上把数据目录 chown 过去:rootless podman 下经 podman unshare chown 1000:1000,rootful 宿主且 pgcli 以 root 运行时直接递归 chown。
pgcli 绝不注入 SECRET_KEY 或 INTERNAL_TOKEN。 Gitea 每次启动都会把所有 GITEA__<section>__<KEY> 环境变量折叠进持久化的 app.ini;若 pgcli 每次安装重新生成并注入加密 secret,那么在保留数据目录的重装上,新的 SECRET_KEY 会覆盖持久 app.ini,而幸存的数据库里 2FA 种子与 OAuth client secret 仍是按旧密钥加密的——一次无报错、无日志的静默锁死。所以这些密钥归 volume 所有:Gitea 首启自生成,pgcli 只拥有 admin 登录密码。
admin 密码只在数据库里不存在该 admin 账号时生效——账号是 gitea admin user create 建出来的,pgcli 从不重置。在保留的数据目录(或保留的 --dsn 数据库)上重装,Web 登录仍是原来的;此次打印的密码无效(install 会明说)。要修改既有存储的登录,进容器跑 gitea admin user change-password(pg exec 风格:podman exec -it <container> gitea admin user change-password ...),或 --clean-data 移除后重装(SQLite 模式——dsn 模式的外置库还得自己 drop)。
重复 install 会复用运行中的容器、启动已停的容器——配置改动只有 remove + 重装或 --force 之后才生效。
数据库:SQLite 或 --dsn
默认 Gitea 用 /data 里的内嵌 SQLite——零依赖、单文件、随数据目录一起复活。要给共享或长命的部署,把元数据指到 PostgreSQL:
pgcli 在写入任何东西之前校验 DSN,并把它渲染成 GITEA__database__DB_TYPE=postgres 加 HOST/NAME/USER/PASSWD。安装摘要与 pg addon list 回显时凭据是隐藏的(只留 host:port/db)。与 PostgREST 的 --dsn 同一约定:目标库必须已存在(在受管实例上 pg exec <instance> -c 'CREATE DATABASE gitea ...',或任意其他方式建好);Gitea 首启自建自己的 schema。pg addon remove gitea——即使带 --clean-data——永远不碰外置数据库;里面只有 Gitea 的表。
Git SSH(可选)
每个 Gitea 实例都会跑一个 OpenSSH sshd——官方镜像的 s6 openssh
服务是无条件启动的(它的 run 脚本就是 exec sshd,没有任何
GITEA__server__* env 能关掉)。--ssh-port 切换的其实不是"有没有 sshd",
而是"这个 sshd 是不是活的 git-over-ssh 克隆端点"。pgcli 给每一个实例都从
gitea_ssh_start_port(基址 2222)分配一个端口——不管 SSH 开还是关——就是为了
把这个永远在跑的 sshd 挪开 22:在宿主网络上,留在 22 就会跟宿主自己的 sshd
死磕,日志会永远刷 Bind to port 22 … Address in use。pgcli 用的是镜像的
裸 env SSH_LISTEN_PORT / SSH_PORT——sshd_config 里那条 Port 行只
看这两个裸变量(只设 GITEA__server__SSH_LISTEN_PORT 仅改 Gitea 自己认为的
端口,sshd 绑的还是 22)。
- SSH 关(默认,不传
--ssh-port):DISABLE_SSH=true,Gitea 不写 git 用户的authorized_keys、也不显示ssh://clone URL。s6 的sshd照样在跑、parked 在池端口上,只是没有任何客户端会去连它。池里的端口占用 不释放——它本来就还要 park sshd。 - SSH 开(
--ssh-port N,N>0):git-over-ssh 就走这个 s6 sshd—— 即镜像的原生路线。DISABLE_SSH=false让 Gitea 给 git 用户写authorized_keys(sshd 通过AuthorizedKeysFile指向/data/gitea/ssh/读取);Gitea 自己的内建 Go SSH 监听器用START_SSH_LISTENER=false显式关掉,避免同一个 netns 里两个进程抢同一个端口;SSH_DOMAIN/SSH_PORT渲染 clone URL 为ssh://git@<host>:N/<owner>/<repo>.git。
认证走 Web UI 里给 git 账号添加的 SSH key(OS 用户是 git,Web admin 是
gitea)。host key 落在 <data>/ssh/,跨重装稳定——若用 --clean-data
移除过,客户端下次会看到新 host key 告警。
macOS bridge 下端口会额外发布,但只有 SSH 开启时才发布——parked 的
sshd 是有意不可达的,所以不给 -p。后续 install 不传 --ssh-port 会保留
已存的开关状态;--ssh-port 0 关掉克隆端点语义(端口占用不释放——
只有 DISABLE_SSH 翻转和 clone URL 变化会随 --force 重建生效)。
监听与暴露
Gitea 的 Listen 默认 0.0.0.0:容器共享宿主网络命名空间,所以 Web UI 与 Git HTTP 在宿主每个网卡的分配端口上可达。这是有意为之——Git 服务就是给别的机器克隆的——并由自动生成的强 admin 密码加 DISABLE_REGISTRATION=true(关闭自助注册;建号请用 pg addon gitea user create)保护。想要仅回环的部署,传 --listen 127.0.0.1(用 --force 重建)。
--allow-registration 翻开的就是后一道闸:pg addon install gitea --name <n> --allow-registration --force 会渲染 DISABLE_REGISTRATION=false,Web UI 上随之出现注册入口;重新关闭要用 --allow-registration=false --force——开关是存进 pg.yaml 的配置值、只在显式传该 flag 时改写,所以普通重装(或不带 flag 的重装)会原样保留存储状态,改动也只有 --force 才推入运行中的容器。放开带来的是建号垃圾而非夺权:实测注册表单建出的账号 IsAdmin=false——Gitea 没有任何办法让注册产生管理员;提升为站点管理员只有 pg addon gitea user create --admin 或 admin 面板两条路,与账号如何产生无关。若确要在公开监听下开放注册,Gitea 同属 service 段的配套收紧项(REGISTER_EMAIL_CONFIRM、验证码、REQUIRE_SIGNIN_VIEW)不在 pgcli 渲染的 env 集合里,仍属 Gitea 侧配置。
--listen 同时也是展示用 clone URL 的主机名:通配绑定不是可连地址,所以 0.0.0.0 会显示成 localhost;显式主机/IP 则原样进入 ROOT_URL 与 ssh:// URL。当 Gitea 躲在转发了 X-Forwarded-Proto 与 Host 的反代后面时——pgcli 的 nginx 模板默认就转这两个头——Gitea 会按进来的请求重写页面显示的 clone URL,所以仓库页的克隆按钮读作 https://<代理主机>/<owner>/<repo>.git,完全正确(scheme 取自 X-Forwarded-Proto、host 取自 Host、标准端口省略)。pgcli 渲染的 ROOT_URL 仍是 http://<host>:<web端口>/(没有配置项能宣告 https/代理端口),但那只管 Gitea 服务端按 ROOT_URL 直接生成的 URL;交互式 clone URL 跟着请求走。要上 TLS,见下文「经 nginx 提供 HTTPS」一节。
经 nginx 提供 HTTPS
Gitea 自己只走明文 HTTP;要用 https:// 访问,就用 nginx 插件前置它——由 nginx 终止 TLS 再反代到 Gitea 的 Web 端口。两点关键:自带证书(或让 pgcli 签一张自签证书),以及用 nginx 的自定义 --conf-file 模式——模板默认的 client_max_body_size(1 MiB)会把稍大的 git push 用 HTTP 413 拒掉。
把代理配置写成 /srv/gitea-nginx.conf——对 Git 真正要紧的是
client_max_body_size 10m(nginx 内建默认只有 1 MiB,不抬高的话稍大的 push 还没
到 Gitea 就以 HTTP 413 死掉)和放长的 proxy_*_timeout(流式 git push 会超过
60s 默认值)。同一份文件作为通用模板样例也在 nginx 文档里(「Git HTTP 推送与
client_max_body_size」):
nginx 代理现在在 https://10.0.0.9/ 上服务,并转发到 Gitea 的 127.0.0.1:3080。用代理地址克隆与 push(把 .crt 作为 CA 交给客户端,自签证书才能通过校验):
注意:
- Gitea 的 Web 端口(3080)仍然直接开着,代理并不会把它藏起来。若只想让 nginx 能访问源站,给 Gitea 用
--listen 127.0.0.1重装让它只绑回环,并把代理 backend 指向127.0.0.1:3080(同一台宿主)——因为两者都跑在宿主网络上,这样是通的。 pg addon nginx reload不会吃到对外部挂载nginx.conf的手工改动(bind mount 钉住了原始 inode,sed -i/编辑器整体重写后容器看不到,直到重启)。reload 只应用 pgcli 自己写入的改动。手改配置后,用pg addon stop nginx --name proxy && pg addon start nginx --name proxy重启,或--force重装。- 真实 CA 证书(Let’s Encrypt / ZeroSSL / 企业 CA)才是线上环境的正解——见下文「线上域名 + 真实 CA」;它彻底免去
--cacert交接,浏览器与 git 默认可信。 - git-over-ssh(
--ssh-port)与此无关;这里的 TLS 只前置 Web 与 Git-over-HTTP。
线上域名 + 真实 CA(Let’s Encrypt / 企业 CA)
面向公网的部署不要发 pg cert 的自签叶子——给主机指一个真域名(如
git.example.com → 203.0.113.7),为它拿一张 CA 签发的证书。如何取得这张证书
不在 pgcli 的范畴内——任意 CA、任意工具都行;只要最后手里有一张 PEM 证书(含中
间链)和它的私钥即可。
然后照上文那样创建 nginx 代理,只把证书路径换成你的域名证书——同一份
--conf-file、端口、--tls 都不变:
Gitea 安装也带上 --listen git.example.com(配 --force),让 ROOT_URL 与任
何 ssh:// 提示都带上真主机名。起来后用户直接
https://git.example.com/<owner>/<repo>.git 克隆,无需任何 CA 交接——浏览器和
git 默认可信公共 CA。
记得在证书过期前更换。pgcli 在装的时候会把自带的证书对拷进插件的 TLS 目录
(<base>/addon/nginx/<name>/tls/ 下的 public.crt / private.key),而这个目
录是 bind-mount 进容器的——所以续期就是:覆盖这两个文件,然后
(已实测:reload 后所服务的证书立即更换。这行得通是因为 TLS 是目录挂载;不
像 nginx.conf 那个单文件挂载,新内容运行中的容器直接可见。)
取回 Web 登录
这个登录是 Gitea 的 Web/Git-HTTP 凭据,与任何 PostgreSQL 角色无关(即使带 --dsn)。随时可重读密码:
它也存于 pg.yaml(addons.gitea.<name>.password)。
账号管理(成员与管理员)
自助注册是关闭的,所以除安装 admin 外的每个账号都由 admin 创建。
pg addon gitea user 封装了容器内的 gitea admin user … CLI(自动以 git uid 运行),
让你在宿主上直接管账号,不必手写 podman exec。实例必须在运行。
flag(create) |
默认 | 说明 |
|---|---|---|
--username |
(必填) | 账号名 |
--email |
<username>@localhost |
需要收通知/重置密码时传真实地址 |
--password |
自动生成 | 指定登录密码,或让 pgcli 生成强密码并一次性打印 |
--admin |
false | 建站点管理员而非普通成员 |
--must-change-password |
false | Gitea 自身对"除第一个外的账号"默认 true:会强制首次登录改密,而改密前 HTTP clone/push 与 API basic-auth 一律 403。pgcli 显式发 =false,让成员建号即可克隆——只有要发一次性密码时才设 true |
这些是 Gitea 自己的账号、非 pgcli 托管,所以自动生成的密码只打印一次、
不写入 pg.yaml——建号时当场记下。(只有安装 admin 的登录会被存,那个用
pg addon password gitea 取回。)要改既有账号密码,进容器跑
gitea admin user change-password。
delete 拒删安装 admin(那是 pgcli 存的登录)。普通删除只对"不拥有任何东西"的
账号成功;若该账号还有仓库,Gitea 会以 user still has ownership of repositories
拒绝——加 --purge 才能连账号及其拥有的一切(仓库、组织、评论)一起删掉。
端口
每个 Gitea 实例占一个 Web 端口,取自以 gitea_start_port(默认 3080——Gitea 惯例的 3000 在 pgcli 里是 Grafana 池的基址,所以 Gitea 往后挪了一个池;仍然一眼认得出"Gitea 的端口")为基址的端口池,每多一个实例递增。每一个实例——SSH 开或关都一样——还从第二个池(gitea_ssh_start_port,默认 2222——Gitea 惯例的 SSH 映射)再拿一个端口用来 park 那个永远在跑的 s6 sshd 离 22;SSH 是否启用只决定那个端口是活的克隆端点还是一个没人连的空闲监听。两个池都跳过任何已被受管容器或 used_ports 列表占用的端口,分配结果写入 pg.yaml:
两个池的基址(3080 / 2222)与其他任何插件的池都不重号。
配置
配置只经环境变量(GITEA__<section>__<KEY>,外加两个裸 SSH_LISTEN_PORT /
SSH_PORT 变量——sshd 只读裸的这两个,GITEA__server__* 那份只驱动 Gitea
自己的 clone-URL 渲染)交付:镜像每次启动把它们折叠进 app.ini,/data
里那份就是被这些变量重写出来的,所以手改 app.ini 不是受支持的配置路径——
pgcli 没设的 Gitea 项虽在 app.ini 里能存活,但请自担风险。pgcli 设置的变量
有:跳过向导(INSTALL_LOCK)、监听器(HTTP_PORT/HTTP_ADDR)、展示 URL
(ROOT_URL)、注册闸门(DISABLE_REGISTRATION,两态都显式渲染——默认
true,--allow-registration 时 false;因为 Gitea 自己的默认是开,
少发一行就覆盖不掉持久 app.ini 里的旧值)、总是发出的裸
SSH_LISTEN_PORT/SSH_PORT 对(把 s6 sshd park 离 22)、DISABLE_SSH=true
(SSH 关:Gitea 不写 authorized_keys、不显示 ssh:// URL)或——SSH 开
时——DISABLE_SSH=false 加 GITEA__server__ 下渲染 URL 用的
SSH_LISTEN_PORT/SSH_PORT/SSH_DOMAIN 三件套,并显式
START_SSH_LISTENER=false 让 Gitea 自己的 Go 监听器不与 s6 sshd 在同 netns
里双绑同一端口、可选的 database 块,以及进程 uid 钉死(USER_UID/USER_GID)。
列出
默认不显示密码以保持列表可安全粘贴;pg addon list --show-password 揭示明文,pg addon password gitea 则为脚本裸打印单个密码。
启停
容器以 podman 的 unless-stopped 重启策略运行,宿主重启后随 podman 服务自动回来。start/stop 显式管理它们:
开机自启
要让 Gitea 实例在宿主开机时自动启动,开启它:
这会在 pg.yaml 里给实例打标记,并安装/刷新一个开机服务(Linux 上是 systemd --user unit,macOS 上是 LaunchAgent)来执行 pg start --autostart。开机启动只负责把容器拉起来——数据目录在创建时已 chown 给 uid 1000,除 podman 本身外不需要任何宿主特权。带 --dsn 时外置 PostgreSQL 也必须在跑:有自启标记的受管实例在同一趟里排得更靠前;其他端点只需届时可达即可。完整机制、状态与关闭方式见 开机自启。
移除
rootless podman 下,--clean-data 经 podman unshare rm 删除数据目录——Gitea 以容器内 git uid(1000)写文件,它映射到宿主的 subordinate uid,宿主用户无法直接 rm。带 --dsn 时,两种方式都绝不碰外置数据库——真要删数据得自己去 drop。
日志
排障
install失败并报 chown 诊断 —— 镜像以 uid 1000 运行且从不修复既有挂载的属主,所以 pgcli 在创建容器前先把宿主数据目录 chown 过去:rootless podman 下经podman unshare chown,rootful 宿主且 pgcli 自身以 root 运行时(sudo podman,或以 root 跑pg)直接递归chown。pgcli 是直接调用podmanCLI,所以只在这两种情形之外才会失败——例如非 root 的pg被显式指向别人的 rootful daemon。遇到时以 root 跑 install,或改用 rootless podman;单纯sudo chown -R 1000:1000 <data-dir>不能消除该错误,因为每次 install 都会重新校验属主。- 重装后登录密码不对 —— admin 账号只被创建,从不被重置。对既有存储(保留的
/data或--dsn数据库)用容器内gitea admin user change-password,或推倒重来(--clean-data;dsn 模式的外置库还要手动 drop)。 - 明明部署在服务器上,clone URL 却显示
localhost—— 通配的--listen 0.0.0.0在展示 URL 里坍缩成localhost,因为0.0.0.0不可连接。传--listen <host-or-ip>(配--force)让 clone URL 变真实。 ssh://克隆报 connection refused / 端口不对 —— 没带--ssh-port装的话克隆端点就是关的(s6 sshd 一直在跑,但只是 parked、不是 clone 端点)。而且 pgcli 让SSH_PORT(URL 里显示的)、GITEA__server__SSH_LISTEN_PORT(Gitea 自己认为的端口)和裸SSH_LISTEN_PORT(s6 sshd 实际绑的)三者保持一致——手工配出不同的值正是坏的形态。- 日志刷
Bind to port 22 … Address in use—— sshd 的 park 端口被改回 22 了(例如手改的 app.ini 或剥掉了裸 env)。一次干净的 install 不会走到这一步:pgcli 给每个实例都发出裸SSH_LISTEN_PORT=<池端口>,SSH 开或关都一样。 - HTTP
git push要密码且拒收 Web 密码 —— Gitea 要的是账号凭据;若该账号开了 2FA,HTTP push 必须改用 personal access token 或 SSH key,而不是密码。 - 端口已被分配 —— 已分配的端口记在
pg.yaml里并为后续实例跳过;先pg addon remove释放,或用--port/--ssh-port另选。
已知限制
- 镜像必须钉在版本 tag 上,升级=换
--image(Gitea 在新镜像首启时向前迁移数据库——没有回退路径)。rootful 与 rootless 构建的数据布局互不兼容:pgcli 钉的是 rootful 构建,换成-rootlesstag 读不了现有/data(也读不了别的 rootless 部署写的数据)。 - 明文 HTTP:TLS 终止与真域名是 nginx 插件的职责——用它的自定义
--conf-file模式前置 Gitea(模板默认的 1 MiBclient_max_body_size会打断稍大的git push)。Gitea 依X-Forwarded-Proto+Host重写页面 clone URL,所以在 nginx 默认转发这些头时,克隆按钮读作https://<代理>/…是对的。pgcli 把ROOT_URL渲染成http://<host>:<web端口>/,v1 没有配置项能改——目前可见的影响只是:凡 Gitea 直接依ROOT_URL(而非依请求)生成的 URL,仍用内部 http 源。 - v1 不配置 OAuth、SMTP、webhook 调优或仓库存储(MinFS)选项——上表之外的一切都是 Gitea 侧配置,pgcli 刻意不碰。自助注册是关的;建号请用
pg addon gitea user create。 - macOS 上容器跑在 podman machine 里;目前只在 Linux 上验证过。
16.19 - VictoriaMetrics
pg addon install victoriametrics 以受管容器方式运行 VictoriaMetrics——高性能、兼容 Prometheus 的时序数据库——直接跑官方 docker.io/victoriametrics/victoria-metrics 镜像。与 Prometheus 和 Grafana 一样,它是顶层插件:不前置某个具体实例(它从多个来源摄入指标),也不依附于任何一个。三者是天然搭档——Prometheus 负责采集、VictoriaMetrics 以优异的压缩率做长期存储、Grafana 负责可视化——且生命周期形状完全一致(单容器、Linux 上宿主网络、各自端口池里的一个 web 端口、持久数据目录、开机自启、日志)。
VictoriaMetrics 与 Prometheus 的分歧在配置方式。Prometheus 读一份配置文件(prometheus.yml)加 CLI flags;VictoriaMetrics 完全不需要配置文件——一切都作为镜像名之后的 CLI flag 传入(-storageDataPath / -httpListenAddr / -retentionPeriod / 自定义 flags 经 --extra-arg)。所以 pgcli 在创建容器时直接传这些 flags;无需渲染配置、无需 SIGHUP reload、无需 chown(镜像以 root 运行)。
没有 Web 登录/密码。 与 Grafana(其
0.0.0.0默认由自动生成的强密码兜底) 不同,VictoriaMetrics 完全不带鉴权。它的 HTTP API 可执行任意 MetricsQL (PromQL 的超集),昂贵查询也能把自身打满,所以远程暴露的实例只靠网络边界 保护——防火墙,或--listen 127.0.0.1收紧为仅回环。请把它当作一个无鉴权的读/写 控制台来对待,而非一个有密码保护的面板。
平台:Linux 是已实测的平台。设计上也支持 macOS(镜像多架构;容器在
pgcli-netbridge 上以发布端口的方式跑在 podman machine 里),但尚未在 Mac 上验证过。
参数
| 参数 | 默认值 | 说明 |
|---|---|---|
--name |
vm |
实例名(map 键) |
--image |
docker.io/victoriametrics/victoria-metrics:v1.154.1 |
覆盖镜像 |
--port |
自动(基址 8428) | Web 宿主端口;自 victoriametrics_start_port 自动分配 |
--listen |
0.0.0.0 |
绑定地址——默认开放全部网卡;127.0.0.1 收紧为仅回环(见上方暴露警告) |
--retention-months |
1 |
数据保留月数(-retentionPeriod);如 3 = 3 个月、12 = 1 年 |
--extra-arg |
— | 传给二进制的额外 CLI flag(可重复);最后追加,故 last-wins 覆盖早期 flags。如 --extra-arg -search.maxUniqueTimeseries=1000000 |
--data-dir |
自动 | 覆盖数据目录(默认在 base 目录下) |
--force |
false | 即使正在运行也重建——用于应用改动的端口/监听/保留期/额外参数(config 变化时 install 也会自行重建) |
数据目录是 VictoriaMetrics 的 TSDB。它跨 pg addon remove 存活(用 --clean-data 才删除),重装同名实例即复活已存储的时序数据。镜像以 root 运行(与 Prometheus 的 uid 65534 不同),故无需预先 chown 数据目录。
安装
命令会按需拉取镜像、创建容器,并打印 URL、保留期与任何额外参数:
VictoriaMetrics 立即可摄入和查询。在 Grafana 里把 http://<host>:8428/ 加成 Prometheus 数据源(VictoriaMetrics 兼容 Prometheus),即可用 PromQL 或 MetricsQL 查询。
对运行中的实例重复 install:当配置确实变了(端口、监听、保留期、额外参数)时重建,没变则原样不动;--force 即使配置未变也重建容器。
VictoriaMetrics 不支持热配置重载(SIGHUP),所以所有变更都需要重建容器。这比 Prometheus 简单(无配置文件要渲染、无 reload 信号),但意味着改配置时会有短暂中断。
自定义参数(--extra-arg)
VictoriaMetrics 接受许多 CLI flags 来调优缓存大小、查询限制、摄入速率等。经 --extra-arg 传入(可重复):
额外参数最后追加到容器的命令行,故它们经 last-wins 语义覆盖早期 flags。这让你在 pgcli 的一等旋钮之外调优 VictoriaMetrics,无需编辑任何配置文件。
验证 flags 已生效:
监听与暴露
VictoriaMetrics 的 Listen 默认 0.0.0.0:容器共享宿主网络命名空间,所以 UI 与 API 在宿主每个网卡的分配端口上可达。这与 Prometheus 的默认一致(别的机器上的 Grafana 要能连它)——但背后没有密码。任何能连到端口的人都能执行任意 MetricsQL、读你存储的时序数据。想要仅回环的部署,传 --listen 127.0.0.1(用 --force 重建);pg addon list 会显示 http://127.0.0.1:<port>/。否则请在防火墙层面限制访问。
端口
每个 VictoriaMetrics 实例占有一个端口:Web UI/API。它取自以 victoriametrics_start_port(默认 8428——VictoriaMetrics 惯例 HTTP 端口)为基址的端口池,每多一个实例递增,并跳过任何已被受管容器或 used_ports 列表占用的端口。分配结果写入 pg.yaml,所以重装绝不会把别的插件在用的端口再发一遍。8428 与 Prometheus(9090)、Grafana(3000)、pgAdmin(5050)、nginx(8080)都不重号;显式 --port 仍优先。
配置
没有配置文件——一切都作为 CLI flags 传给 victoria-metrics 二进制(-storageDataPath、-httpListenAddr、-retentionPeriod,加上任何 extra_args)。retention 存为字符串(月数),extra_args 是最后追加的 CLI flags 列表。
列出
没有凭据可揭示——VictoriaMetrics 本就没有——所以 --show-password 对它不会多显示任何东西。
启停
容器以 podman 的 unless-stopped 重启策略运行,宿主重启后随 podman 服务自动回来。start/stop 显式管理它们:
开机自启
要让 VictoriaMetrics 实例在宿主开机时自动启动,开启它:
这会在 pg.yaml 里给实例打标记,并安装/刷新一个开机服务(Linux 上是 systemd --user unit,macOS 上是 LaunchAgent)来执行 pg start --autostart。开机启动只负责把容器拉起来——数据目录在 install 时已创建,除 podman 本身外不需要任何宿主特权。启动顺序上 VictoriaMetrics 排在 Prometheus 之后。完整机制、状态与关闭方式见 开机自启。
移除
rootless podman 下,--clean-data 经 podman unshare rm 删除数据目录——VictoriaMetrics 以 root(容器内 uid 0)写文件,它映射到宿主的 subordinate uid,宿主用户无法直接 rm。
日志
排障
- 查询返回空数据 —— 确认 VictoriaMetrics 在运行(
pg addon list显示running)、健康端点有响应(curl -s http://<host>:<port>/-/healthy输出VictoriaMetrics is Healthy.)、且你查的是正确的时间范围(用query_range配显式start/end/step)。查pg logs addon victoriametrics看摄入错误。 - Install 失败于 “container already exists” —— 容器名由
--name和命名空间派生;用--force重建,或先pg addon remove清理一次不完整的 install。 - 额外参数没生效 ——
--extra-arg在容器创建时应用,而非运行时。用--forcereinstall 以新 flags 重建容器。查podman logs <container>确认 flags 出现在启动日志里。 - 端口已被分配 —— 已分配的端口记在
pg.yaml里并为后续实例跳过;先pg addon remove释放,或用--port N另选一个。 - 远程 API 打不开 / 意外可访问 —— 默认
0.0.0.0下打不开是宿主防火墙问题,不是绑定地址;反之若你不想被访问,用--listen 127.0.0.1 --force。
已知限制
- v1 是裸存储后端:不自带抓取能力(用
pg addon install prometheus --remote-write-url http://<host>:8428/api/v1/write把 pgcli 的 Prometheus 实例指向它,或单独跑vmagent)、不做告警。它是你让 Prometheus 和 Grafana 连的长期存储层。 - 无鉴权。 VictoriaMetrics 的 API 执行任意 MetricsQL;
0.0.0.0绑定的唯一保护是网络边界。这与 Prometheus 同一态势——不要把它暴露到不可信网络。 - pgcli 不校验
--extra-arg值(install 阶段没有 pre-flight 检查)。有错的 flag 只会在启动时从容器日志里暴露。 - macOS 上容器跑在 podman machine 里;目前只在 Linux 上验证过。
16.20 - BeeGFS
BeeGFS 是一套走「易用」路线的并行文件系统:2004 年生于德国弗劳恩霍夫工业数学研究所(ITWM)——当年一个课题组要用 Lustre,嫌它 部署运维太重,干脆自己写了一个,目标从一开始就定死:不求规模最大,但求部署和 维护最省人力。2014 年核心成员从研究所出来成立商业公司 ThinkParQ,产品改名 BeeGFS,一路从研究所内部工具长成欧洲高校与区域超算里常见的共享存储。
它解决的是「多台机器共享一份高吞吐数据」的问题:本地文件系统只活在一台机器 上,NFS 这类共享存储在多客户端并发读写大文件时很快见顶。BeeGFS 和 Lustre 同 属并行文件系统——元数据与数据分离、文件切块摊到多台存储服务器、客户端拿到位置 信息后直连数据节点读写,聚合带宽随服务器数线性增长——但在每一个可以简化的地方 都选了简单:服务角色可以混布在同一台机器上、守护进程启动即自动注册组网(不需要 手工分发配置)、扩容就是加机器、普通以太网就能跑而不强制 InfiniBand。代价是不 追求极限规模与最细的调优自由度,换来的是没有专职存储工程师的课题组、部门级集 群也能一两天用起来。
典型场景:几台到两三百台机器的 HPC 集群、AI 训练的数据供给层(7.3 起支持 GPUDirect Storage,存储与 GPU 显存直通,大规模训练/推理直接读共享数据集)、 影视行业后期编辑的素材数据(多人并发剪辑大码流素材不抢带宽)、海量小文件 或大文件顺序读写的部门级共享盘。一句话选型:国家级超算、上千节点、有专职存储 团队——Lustre;「当天用上、不想养存储团队」——BeeGFS。
平台:仅 Linux。BeeGFS 守护进程跑 host 网络(BeeGFS 说的是自家 TCP/UDP 协议而非 HTTP,不存在可分配的容器端口映射)。macOS 的 podman 没有 host 网络,
pg addon install beegfs在 Mac 上会带明确报错直接拒绝。
客户端刻意不在管辖范围内。 挂载 BeeGFS 需要
beegfs-client内核模块 (DKMS)。内核模块无法容器化,所以 pgcli 既不安装也不挂载它——只管服务端 服务(收敛宿主上是三个容器,跨主机成员上是本机承担的角色子集)。pg addon beegfs mount-hint会打印装了beegfs-client+beegfs-utils包的主机所需的逐字配置。见「挂载客户端」一节。
参数
| 参数 | 默认值 | 说明 |
|---|---|---|
--name |
beegfs |
文件系统名(map 键);容器名为 <前缀>-<角色>,按本机承担的角色各建一个 |
--image |
8.4.0 |
版本后缀——manager 据此拼出 ghcr.io/thinkparq/beegfs-<角色>:<version>(各角色共用同一 tag) |
--data-dir |
<base>/addon/beegfs/<name> |
一个目录管整个文件系统;三个服务用 <dir>/{mgmtd,meta,storage} |
--listen |
0.0.0.0 |
客户端自引用 mgmtd 的地址(sysMgmtdHost);host 网络下守护进程实际绑所有网卡 |
--password |
自动生成(仅含 mgmtd 的宿主) | 连接认证密钥(ConnAuth),全部守护进程与所有客户端共享;首装生成仅限本机跑 mgmtd——join 宿主必须显式传集群既有值 |
--roles |
全部三角色 | v2:本机承担哪些服务——mgmtd,meta,storage 的逗号列表;不传即收敛单机文件系统 |
--mgmtd-host |
— | v2:本机 meta/storage 注册指向的 mgmtd 地址(--roles 不含 mgmtd 时必填) |
--node-id |
1 |
v2:本机 meta/storage 的 BeeGFS 节点号(1..655);storage target ID 由它派生(2 → 201、202…)。跨主机须按服务类型唯一 |
--storage-target |
单目录 | v2:一个宿主目录一个 storage target——多块盘就重复传(每盘一个 target) |
--license-file |
— | ThinkParQ license .pem 的路径——搬运进 mgmtd 容器(内容存 pg.yaml,注入为 BEEGFS_LICENSE_FILE_DATA)。仅限 mgmtd 宿主——pgcli 从不生成 license,之后用 pg addon beegfs license 查看/热换 |
--beegfs-mgmtd-port |
8008 |
移动 mgmtd 的 BeeMsg 口(meta/storage 注册指向的唯一可移动口);0 = 用默认 |
--beegfs-mgmtd-grpc-port |
8010 |
移动 mgmtd 的 gRPC 口(beegfs CLI 拨的口);0 = 用默认 |
--force |
— | 即使本机容器已在跑也重建 |
meta/storage 刻意没有 --port:见「端口」一节。
安装
全程非交互。先一次性拉齐本机角色所需的镜像(避免网络故障把集群卡在"半注册"
状态),然后按依赖序拉起:先 mgmtd,再 meta,再 storage——join 宿主(如
--roles storage)只跑自己那一步。meta/storage 会阻塞等它们打出
Registration and management info download complete——这一行同时证明
“mgmtd 可达"与"该服务已入集群”。每个容器首启执行一步
beegfs-setup-<service>(由镜像 entrypoint 消费的 beegfs_setup_N 环境变量
驱动)初始化自己的 target 目录;幂等 marker 保证对幸存数据目录重建容器时直接
复用既有 target。
重复执行 install 是安全的,且以集群为粒度(按本机角色计):
- 本机角色全在跑 → 跳过(要重建用
--force,要看健康用pg addon beegfs check); - 本机角色全停且存在 → 直接启动存好的集群(不 pull、不重建);
- 其余情况(partial、或全新安装)→
EnsureCluster按序重建整套。
安装摘要打印本机角色与对应容器名、镜像、mgmtd 地址与两个口(join 宿主标
remote)、节点号与数据目录、conn-auth 状态,以及指向
pg addon beegfs mount-hint 的一行;跑 mgmtd 的宿主还会提示
pg addon beegfs join-hint。--listen 是通配 0.0.0.0 时附
暴露 NOTE(关掉 TLS 的并行文件系统,对任何握着 conn-auth 密钥的机器都是一扇
门)。
跨主机集群(v2)
BeeGFS 的扩展轴是加机器,不是手工同步配置:每个服务守护进程启动时只需
sysMgmtdHost=<mgmtd地址> 加共享 conn-auth 密钥,就会自行注册进集群——
mgmtd 本身就是注册中心。pgcli 完全照搬这个模型:没有中心化部署命令,pgcli
从不 SSH。每台宿主各自跑一条 pg addon install beegfs --roles <子集>,用自己
本地的 pg.yaml 描述本机承担的角色,剩下的组网全是 BeeGFS 自己完成——与
Patroni 跨主机成员同一套声明式逐机模型(那边聚合拓扑靠 etcd,这边靠 mgmtd 的
SQLite 注册表)。
三台机器一个文件系统:
客户端照旧从 vm01 的地址挂载——跨主机对内核模块客户端完全透明。
规则与边界,如实说明:
--roles不含 mgmtd 时--mgmtd-host必填(没有 localhost 可回落), 且 join 必须带集群既有的 conn-auth——pgcli 绝不替既有集群的成员生成密 钥。密码传错几秒内报错并指路pg addon password beegfs,而不是等注册超时。--node-id在跨主机间必须按服务类型唯一(node ID 是按服务类型分的命名 空间:meta_1与storage_1共存)。它派生 storage target ID:节点 2 的 target 是 201、202…(node_id*100 + 序号,上限 node_id 655)。- 每台宿主的
pg.yaml条目只存自己承担的角色;check、start/stop、list、autostart 全是本宿视角。集群完整成员表在 mgmtd 里 (beegfs ... node list)。 - mgmtd 仍是单点:它挂掉后已挂载客户端的 IO 照常(客户端直连 meta/storage),
但注册与管理操作停摆。管理节点双活是企业 license 功能,不归 pgcli 接线。
license 同样只装在 mgmtd 宿主——join 宿主既不存也不需要 PEM
(
join-hint会说明这一点),证书由 mgmtd 为整个文件系统校验。 - 仍然一宿主一文件系统——扩展轴是跨主机,不是同宿主第二个文件系统 (固定口 8005/8003 必撞)。
数据目录与持久化
收敛宿主拥有三个宿主目录,各自 bind 挂载进自己容器的 /data/<role>:
--data-dir <dir> 一个 flag 排出全部三个目录(<dir>/{mgmtd,meta,storage})
——使用者从不按角色分别寻址。跨主机成员只用自己角色的那个目录。多 storage
target(重复 --storage-target)时,每个宿主目录挂进同一个 storage 容器的
/data/storage/<targetID>;单 target 保持朴素的 /data/storage 路径,v1 的
存量 store 因此原样复活(target 目录内部记录自身路径,复活必须挂同一路径)。
三个镜像以 root 运行并自属数据目录,所以与
Gitea(uid 1000)、Grafana(uid 472)不同,没有预 chown 环节——宿主目录
直接可写。
数据全部跨容器删除存活:pg addon remove beegfs 不带 --clean-data 保留目录,
同名重装即复活文件系统(检测到既有 mgmtd.sqlite 时安装会打印
NOTE)。复活沿用的是存储里原有的注册表与连接认证——新传的 --password 只作用
于全新初始化的守护进程,改不动幸存的 SQLite 注册表。
故障处理:跨节点条带做性能,ZFS 做可靠性
BeeGFS 的跨节点条带是性能机制,不是冗余机制:默认 RAID0 布局下,丢任意一 个 storage target 就丢它承载的 chunk——条带化的文件即随之损坏。节点级数据冗 余是 buddy mirroring,而那是企业 license 功能。所以社区版把两件事干净地分层, 而存储侧需要一个本地故障的底层方案:
-
跨节点:条带做吞吐。 每个文件的 chunk 摊到多台机器的多个 target 上,聚 合带宽随服务器数线性增长——
--num-targets/--chunk-size调的就是这一层。 它买的是速度,不是安全。 -
每台 storage 服务器内部:target 底下垫一个有弹性的卷。 推荐底座是 ZFS——因为 BeeGFS 的 storage target 就是"一个目录",ZFS 免费把这个目录 变成自愈存储:checksum 防静默损坏、copy-on-write、快照、压缩。守护进程对此 毫无感知,所以这一层完全不需要 pgcli 或 BeeGFS 参与:建池、每个 target 一个 dataset、把
--storage-target指过去即可。(mdadm RAID1/5/6 也能用,但 ZFS 的 checksum 能抓住静默位翻转——否则会被 原样喂给客户端。)一个 target 一个 dataset,可让磁盘故障根本不进 BeeGFS 的 target 路径。
-
整机故障仍然救不回来(社区版):一台服务器倒下,它的 storage target 随 之消失——本地 ZFS/RAID 扛得住坏盘,扛不住烧掉的机器。无 license 的缓解 手段:把
meta/mgmtd服务在多台上做重复(免费——这是服务冗余,文件 系统仍能挂载、管理操作仍可运转)、对元数据做快照、并在负载允许时把 storage 数据当作"可重灌"处理(训练集可以重新生成——这是社区版诚实的取舍)。想要真 正的跨节点镜像,只能 temporary 全功能试用证(免费、限期、一系统一张)或付 费 enterprise license。 -
元数据同样值得照顾:meta target 丢失则整棵树读不出来(与数据是否冗余无 关)——它的目录也应放本地镜像(体量很小)。pgcli 不做 BeeGFS 备份:保护 mgmtd 注册表与 meta target 靠的是宿主级快照——对承载角色目录的卷做 ZFS 快 照,或在守护进程停住时做一次静默文件拷贝。
端口
BeeGFS 的四个监听口是协议原生固定口,不是端口池:
实机核验:BeeGFS 8.4 的 meta 与 storage 守护进程不接受任何监听口覆盖
(connPortsList/connTcpPort 被拒为非法参数,connMetaPort 收下但注册就
断)。只有 mgmtd 的两个口能动;移动 mgmtd 的 BeeMsg 口后,meta/storage 的注册
会自动重定向(manager 给两者注入 connMgmtdPort=<port>)。直接推论:每台宿
主只能有一个 BeeGFS 文件系统——第二个无论如何绑不了 8005/8003,与 pgcli 分
配什么无关,所以 EnsureCluster 显式挡掉第二个,CLI 也会带真实理由拒绝
pg.yaml 里手改的非默认 meta_port/storage_port。跨主机则各绑各的、互不冲
突——这正是 v2 用 --roles 扩角色的原因(见「跨主机集群(v2)」)。因此没有
beegfs_start_port 端口池——与其他插件不同,BeeGFS 的端口是固定的,不是分
配的。
连接认证与 TLS
文件系统里的每个守护进程与所有客户端共享一个连接认证密钥(ConnAuth,经
CONN_AUTH_FILE_DATA 环境变量交给镜像 init 写成 /etc/beegfs/conn.auth)。
首装自动生成(20 位随机)仅限跑 mgmtd 的宿主、打印一次、存入 pg.yaml,
之后用 pg addon password beegfs 取回;跨主机 join 宿主必须把这个既有值经
--password 带过去(见「跨主机集群(v2)」),pgcli 绝不替既有集群的成员生成
密钥。与其他 pgcli secret 一样,ApplyDefaults
绝不把它默认成字面量。
mgmtd 的 TLS 是关闭的(--tls-disable true,v1 的明确决策)。这意味着只
有同样带 --tls-disable 的客户端能入集群,且这是真实的暴露考量:通配
--listen 0.0.0.0 + TLS 关闭 = 网络上任何握着 conn-auth 密钥的机器都能触达
8008/8010/8005/8003。非私有网络请绑回环(--listen 127.0.0.1)或对这四个口
做防火墙。
License:自 BeeGFS 8 起每个部署都应有 license。pgcli 从不生成
license(与 Gitea 加密 secret 同一原则),但可搬运你申领的那张:PEM
内容存入 pg.yaml(随配置备份一起走),注入 mgmtd 容器为
BEEGFS_LICENSE_FILE_DATA——官方镜像的 init 把它写成
/etc/beegfs/license.pem,mgmtd 的 start.sh 再转成 --license-cert-file。
只有 mgmtd 宿主能带 license:它的镜像是唯一自带 license 库的,证书由它的
守护进程为整个文件系统校验(join 宿主传 --license-file 会被安装直接拒绝)。
license 三档(全部走 ThinkParQ 自助门户,社区版一分钟可领):
| 档位 | 价格 | 作用 |
|---|---|---|
| community | 免费 | 解除无 license 的限制(8.4 的五个客户端挂载封顶、quota 告警);有容量上限,不解锁企业功能 |
| temporary | 免费试用 | 限期全功能;8.4 起一个系统限持一张 |
| enterprise | 付费 | storage pools、quota、mirroring(伙伴镜像)、管理节点 HA 双活 |
重点:mirroring(伙伴镜像)是 ENTERPRISE 功能——必须付费 license。 无 license 时 mgmtd 拒绝一切镜像请求(
beegfs mirror create→ “no license loaded”,实机核验)。免费的 community license 不解锁它, 只有付费 enterprise(或限期全功能的 temporary 试用证)才行。社区版的 数据冗余止步于多跑几个 meta/mgmtd daemon——那是服务冗余,不是数据镜像。
申领在体系外完成:beegfs license --get 打印系统绑定的申领 URL → 浏览器
打开 → 下载 .pem。然后交给 pgcli——安装时 --license-file license.pem,
或事后 pg addon beegfs license --set(见「License 状态与热换」一节)。
无 license 跑 8.4 的现实是:mgmtd 打 quota 告警、每条 beegfs 命令都
带 warning、客户端挂载上限五个(第六个被拒)、企业功能直接拒绝(如
beegfs mirror create 报 “no license loaded”)。装上任意有效 license——包
括免费的社区版——即解除告警与挂载上限。
挂载客户端
客户端是宿主内核模块——pgcli 不装。要在 Debian/Ubuntu 主机上挂载文件系统, 需自行添加 ThinkParQ apt 仓库并安装客户端包。8.4 + Debian 12(bookworm)的完整 命令(已在测试机实测):
RHEL 系主机上同一仓库以 .repo 文件发布于
https://www.beegfs.io/release/beegfs_8.4/dists/ 下(rpm --import …/GPG-KEY-beegfs + wget …/beegfs-rhel9.repo -O /etc/yum.repos.d/beegfs.repo)。URL 一律以 ThinkParQ 下载
页 上你的 BeeGFS 版本 + 发行版对应的为准——
服务端 pin 在 8.4,客户端就应挂对应 8.4 的仓库。
beegfs-client 依赖 build-essential、推荐内核头文件——DKMS 在安装时对着
linux-headers-$(uname -r) 编译 beegfs 模块,所以运行中内核的头文件必须就位,
且每次内核升级都会重新编译。
然后照抄 pg addon beegfs mount-hint 的输出——它逐字渲染三个文件:
conn.auth 的坑: 客户端的连接认证文件必须以换行符结尾。
echo <secret> > /etc/beegfs/conn.auth可以;printf <secret> > …(无行尾换 行)会被拒为tried to authenticate stream with wrong secret——尾部那个字节 是两侧比对内容的一部分。
挂载与验证:
由于集群关着 TLS,每个 beegfs CLI 调用都要带关 TLS 的形态、指向 gRPC 口;
node list 会列出全部四类节点(management、meta、storage,以及挂载成功后的本客
户端):
常用客户端操作(全部经 8.4 实测——beegfs CLI 是 8.x 的重写版,7.x 那套
beegfs-*-ctl 用法不适用):
mount-hint 是纯本地渲染——从不碰 podman,客户端本就不归 pgcli 管。mgmtd 地
址按配置条目渲染:跨主机成员上是存好的 --mgmtd-host;本机 --listen 为通配
时打印占位符 <mgmtd-host-ip>(0.0.0.0 不可连),而不是客户端连不上的值。
健康检查
报告本机角色的聚合状态(running / stopped / partial (mgmtd,meta)),
并按服务给出容器是否在跑、是否打出就绪日志行——mgmtd 是
Waiting for shutdown signal(在服务),meta/storage 是
Registration and management info download complete(已入集群)。镜像都不
含 beegfs-ctl/check-servers,所以守护进程自己的日志就是权威的容器内信
号;集群完整成员表在 mgmtd 里——看全局用上面的 beegfs gRPC CLI
(node list)。check 对停着的集群同样可用(它报状态,不要求运行中)。
取回连接认证密钥
pg addon list 只显示 presence 标记(Auth: none / set (connection-auth)),
只有加 --show-password 才打印密钥本身——随手 list 绝不泄密。
License 状态与热换
在 mgmtd 宿主上跑(license 只活在那台——见「连接认证与 TLS」)。不带参数
时报告两侧:pg.yaml 里有没有 PEM,以及 mgmtd 加载它时自己打了什么——有效
证书是 Successfully loaded license certificate,坏/过期证书则是 mgmtd 的
校验错误行。pgcli 只做 PEM 形状检查(BEGIN/END 块),真正的校验永远是
mgmtd 的事。
--set <pem> 读文件、把内容写进 pg.yaml,然后重建 mgmtd 容器让新 env
落地(podman 的 env 只在创建时存在)。meta/storage 和全部客户端会自行重注册
——换证只需几秒且不碰数据。--clear 抹掉 PEM 同样重启,回到无 license 态。
--set 与 --clear 互斥;安装时带 --license-file 是同一件事的首次装载
(容器本来就在那时创建,无需重建)。
List
BeeGFS 区块(“Infra add-ons (beegfs)")每个文件系统一条:按本机角色计的聚合
状态、角色列表、mgmtd 地址与两个口(join 宿主标注 remote)、固定的 meta/storage
口、conn-auth presence、镜像、本机角色对应的数据目录(每个 storage target 一
行)、容器名。podman 不可用时仍然列出配置条目(状态显示 n/a)。
启停
容器带 podman 的 unless-stopped 重启策略,宿主重启后随 podman 服务自动回
来。start/stop 按依赖顺序显式管理本机承担的角色:
跨主机成员只动自己那一个子集——--roles storage 的宿主 start/stop 只有
storage。
开机自启
BeeGFS 是唯一的集群粒度 autostart:pg start --autostart 拉起本宿主所跑的
文件系统部分(按 mgmtd → meta → storage 顺序,或配置的角色子集),而不是单个
容器。因为镜像以 root 运行并
自属目录,autostart 除 podman 外不需要任何宿主权限(没有 Gitea 那样的 chown
环节)。每台挂载主机上的客户端经由自己的 beegfs-mounts.conf 自行重挂——
pgcli 从不管理那一步。完整机制、状态与关闭方式见
Autostart。
移除
移除按反序拆本机角色(storage → meta → mgmtd)。--clean-data 拒删仍是
挂载点的目录。
指向本宿主的客户端挂载点无论如何都得先卸载——pgcli 够不着客户端(那不归
它管),请先去客户端主机上卸载。
日志
一个文件系统最多横跨三个容器,所以 pg logs addon beegfs 用 --service 选一
个(默认 mgmtd):
--service 只对 beegfs 有效(配其他插件类型时报错)。
排障
install beegfs --name fs2报 “a BeeGFS filesystem already exists on this host”——这是一宿主一文件系统规则,不是 bug:8.4 里 meta(8005)与 storage(8003)的口动不了,同宿主第二个文件系统永远绑不上。扩容请在另一台 宿主上加角色(pg addon beegfs join-hint渲染逐字命令),或先pg addon remove掉第一个。check显示 meta/storage 一直 not registered——mgmtd 必须先在服务; mgmtd 容器若退出,两者会阻塞到beegfsSetupTimeout(60 秒)让安装失败而非 挂死。看pg logs addon beegfs --service mgmtd找 mgmtd 退出原因(宿主上 端口被占、或数据目录不可写)。install在meta_port/storage_port配置校验处拒绝——pg.yaml被手 改出了非默认值。守护进程根本绑不了那些口,删掉钉死的值回落 8005/8003。- 客户端挂不上 /
beegfsCLI 报 TLS 错误——集群跑的是 mgmtd TLS 关闭;所 有客户端与beegfsCLI 调用都必须同样带--tls-disable,且/etc/beegfs/conn.auth必须与pg addon password beegfs的值逐字相同。 - join 宿主秒报 “wrong secret”——它的 install 传入的
--password不是集群 既有的连接认证值(BeeGFS 日志打tried to authenticate stream with wrong secret)。安装几秒内中止而不是等注册超时;到 mgmtd 宿主用pg addon password beegfs取回正确值后重装。 pg addon beegfs license打出 mgmtd 的校验错误——存进去的 PEM 对 这台 系统无效(证书是系统绑定的——申领要在 mgmtd 宿主跑beegfs license --get)或已过期。容器照常跑、社区功能不受影响,mgmtd 只是表现为无 license 态;--clear(或用对的 PEM--set)修复配置侧。- 别的机器对着
localhost挂载失败——通配--listen 0.0.0.0时mount-hint把sysMgmtdHost渲染成占位符;用--listen <可达IP>(配--force)重装,客户端才能拿到真实 mgmtd 地址。
已知限制
- 仅 Linux。 host 网络是硬性要求,podman 只在 Linux 提供;macOS 上安装被 拒。不存在 bridge 兜底——BeeGFS 不是 HTTP。
- 一宿主一文件系统、端口固定。 pgcli 钉死原生口
(8008/8010/8005/8003);8.4 里 meta/storage 的监听口不可移动,单宿主只容得
下一个文件系统。向外扩展是跨主机(
--roles),不是同宿主第二个文件系统。 - pgcli 从不安装、从不挂载客户端。 客户端是内核模块(DKMS),无法容器
化;pgcli 只管服务端容器,挂载靠
mount-hint输出文档。 - TLS 关闭;license 可搬运、不代发。 mgmtd 以
--tls-disable true运行,beegfsgRPC CLI 与客户端必须同样带--tls-disable。pgcli 从不生成 license、也不管续费——它只把你线下申领(beegfs license --get打印系统 绑定 URL)的 ThinkParQ PEM 搬进 mgmtd 容器,pg addon beegfs license查看与热换。mirroring、pools、quota 仍是需要购买的 enterprise license 功能;无 license 的 8.4 客户端挂载上限为五个。 - 跨主机部署是逐机手动的(v2 设计如此)。 每台宿主各自跑自己的
pg addon install beegfs --roles ...;pgcli 从不 SSH,也没有"从一台机器部 署整个集群"的命令。扩容 = 在新宿主上跑一条命令(conn-auth 密钥靠运维手工 带过去——见join-hint),每台宿主的pg.yaml只描述自己那一片。 - mgmtd 是单点。 管理守护进程就是集群的注册表;它停摆后,新注册与管理操作 停摆(已挂载的客户端照常对 meta/storage 做 IO)。管理节点双活是 BeeGFS 企业 license 功能,不在 pgcli 的 v1/v2 接线范围内。
16.21 - VMCluster
pg addon install vmcluster 以受管容器方式运行 VictoriaMetrics 集群版——3 个组件各跑一个容器,使用官方 docker.io/victoriametrics/{vminsert,vmselect,vmstorage}:v1.154.1-cluster 镜像。与单机版 victoriametrics addon(1 容器 1 端口)不同,它管理最多 3 个容器、3 个镜像、5 个端口,支持跨主机部署和水平扩展。
架构:
| 组件 | 职责 | HTTP 端口 | 内部 TCP |
|---|---|---|---|
| vminsert | 摄入端,按 metric hash 分片写入所有 vmstorage | 8480 | 拨 vmstorage:InternalInsertPort |
| vmselect | 查询端,从所有 vmstorage 合并结果 | 8481 | 拨 vmstorage:InternalSelectPort |
| vmstorage | 数据存储(可多节点分片+副本) | 8482 | 监听 InternalInsertPort + InternalSelectPort |
没有 Web 登录/密码。 与单机版 VictoriaMetrics 一样,集群版完全不带鉴权。 它的 HTTP API 可执行任意 MetricsQL(PromQL 的超集),昂贵查询也能把自身打满, 所以远程暴露的实例只靠网络边界保护——防火墙,或
--listen 127.0.0.1收紧 为仅回环。请把它当作一个无鉴权的读/写控制台来对待。
平台:仅 Linux。VMCluster 需要 host networking 来承载组件间的内部 TCP 端口。 macOS 上安装会被拒绝并给出明确错误提示。
部署形态
形态 1 — 单机收敛(默认)
3 个组件全在一台主机上,类似 BeeGFS v1:
形态 2 — 跨主机分布
形态 3 — 水平扩展 + 副本
参数
| 参数 | 默认值 | 说明 |
|---|---|---|
--name |
vmc |
实例名(map 键) |
--roles |
insert,select,storage |
本主机运行的组件子集(逗号分隔,跨主机时选子集) |
--storage-node |
自动推导 | vmstorage 地址(可重复;格式 host:insertPort:selectPort),列出集群中所有 vmstorage 节点。单机收敛时省略即可 |
--insert-port |
自动(基址 8480) | vminsert HTTP 端口 |
--select-port |
自动(基址 8481) | vmselect HTTP 端口 |
--storage-port |
自动(基址 8482) | vmstorage HTTP 端口 |
--internal-insert-port |
自动(基址 8483) | vmstorage TCP 端口(vminsert 协议) |
--internal-select-port |
自动(基址 8484) | vmstorage TCP 端口(vmselect 协议) |
--listen |
0.0.0.0 |
绑定地址——默认开放全部网卡;127.0.0.1 收紧为仅回环 |
--retention-months |
1 |
数据保留月数(仅 vmstorage 角色) |
--replication-factor |
1 |
副本因子(每条数据写几个 vmstorage 节点) |
--insert-image |
docker.io/victoriametrics/vminsert:v1.154.1-cluster |
vminsert 镜像覆盖 |
--select-image |
docker.io/victoriametrics/vmselect:v1.154.1-cluster |
vmselect 镜像覆盖 |
--storage-image |
docker.io/victoriametrics/vmstorage:v1.154.1-cluster |
vmstorage 镜像覆盖 |
--insert-extra-arg |
— | vminsert 的额外 CLI flag(可重复) |
--select-extra-arg |
— | vmselect 的额外 CLI flag(可重复) |
--storage-extra-arg |
— | vmstorage 的额外 CLI flag(可重复) |
--data-dir |
自动 | vmstorage 数据目录覆盖 |
--force |
false | 即使正在运行也重建 |
安装
命令会按需拉取 3 个镜像、按依赖序创建容器(storage → insert → select),并打印摘要:
VMCluster 立即可摄入和查询。
集群版 URL 格式
重要:集群版的 URL 路径与单机版 VictoriaMetrics 不同。所有 API 端点都需要在路径中包含 <accountID>(使用 0 表示默认账户):
通过 vminsert 摄入数据:
通过 vmselect 查询数据:
Grafana 数据源:指向 vmselect,URL 为 http://<host>:8481/select/0/prometheus/
Prometheus remote_write:配置 remote_write 到 http://<host>:8480/insert/0/prometheus/api/v1/write
端口
每个 VMCluster 实例占有 5 个连续端口,取自 vmcluster_start_port 端口池(默认 8480):
+0InsertPort(vminsert HTTP — 摄入端点)+1SelectPort(vmselect HTTP — 查询端点)+2StoragePort(vmstorage HTTP — 管理/指标端点)+3InternalInsertPort(vmstorage TCP — vminsert 拨此端口)+4InternalSelectPort(vmstorage TCP — vmselect 拨此端口)
连续分配是因为 --storage-node 显式引用内部 TCP 端口,整块必须保持连续。8480 与 VictoriaMetrics(8428)、Prometheus(9090)、Grafana(3000)等都不重号。
配置
没有配置文件——一切都作为 CLI flags 传给 3 个二进制。storage_nodes 是跨主机拓扑;空即自动推导单节点。
动态添加存储节点
VictoriaMetrics 集群版没有动态发现机制——-storageNode 列表在 vminsert/vmselect 容器启动时写入。添加 vmstorage 节点需要一个短暂的维护窗口来重建接入层容器(不是热加载)。现有 vmstorage 里的历史数据不受影响。
维护影响:重建 vminsert/vmselect 会导致几秒写入中断。如果写入连续性很重要,请在低峰期操作。vmstorage 里的历史数据在整个过程中始终可用——只有摄入会短暂中断。
步骤
- 在新主机上创建 vmstorage:
- 在接入层主机上重建 vminsert + vmselect,追加新的
--storage-node:
--force 会删掉并重建 vminsert/vmselect 容器,中间有几秒写入中断。已有的 vmstorage 数据完全不受影响(只有接入层容器被替换)。
添加节点后的数据分布
- 旧数据保留在原来的 vmstorage 节点上(不会自动 rebalance)。
- 新写入按 metric hash 在所有节点(旧 + 新)之间重新分片。
- vmselect 从所有节点合并结果,查询端无缝看到新旧数据。
VictoriaMetrics 集群版不会把已有数据迁移到新加入的节点。对大多数监控场景来说这不是问题——时间序列是持续写入的,几小时后新节点上就有足够的数据让查询均衡。如果需要完全均匀分布,只能导出再重新导入(/api/v1/export → /api/v1/import)。
移除存储节点
反向操作:先重建 vminsert/vmselect 时去掉要下线节点的 --storage-node,再在目标主机上执行 pg addon remove vmcluster。被移除的 vmstorage 上的数据将不再对查询可见——如果需要保留,请提前导出。
启停
容器以 podman 的 unless-stopped 重启策略运行,宿主重启后随 podman 服务自动回来。
开机自启
开机启动以集群为单位:每台主机只拉自己的角色容器,按依赖序(storage → insert → select),已有容器直接启动、仅缺失/异常的重建,不会重建正在运行的容器。跨主机部署时要在每台主机上分别执行。完整机制见 开机自启。
移除
只有 vmstorage 有持久数据;vminsert 和 vmselect 是 stateless,删除后不留任何东西。
日志
已知限制
- 无鉴权。 集群版的 API 执行任意 MetricsQL;
0.0.0.0绑定的唯一保护是网络边界。 - 仅 Linux。 组件间的内部 TCP 端口需要 host networking。
- 无自动负载均衡。 多 vminsert/vmselect 实例需在前面手动放 HAProxy 或 nginx。
- 无 TLS。 需要 TLS 终止请用 nginx addon 前置。
- pgcli 不校验
--extra-arg值。有错的 flag 只会在启动时从容器日志里暴露。
16.22 - JuiceFS
JuiceFS 把真正的 POSIX 文件系统铺在对象存储之上:文件
数据切块后存进 S3 桶,另由一个元数据引擎(SQLite、Redis、PostgreSQL……)
保存命名空间、文件属性与块映射。你拿到的是一个可以 ls、cat、grep 的普通
目录——每次写入最终都化作桶里不可变的对象。「想要文件系统、但字节应该住在
S3」就是它解决的问题:快照近乎免费(对象不可变)、容量就是桶的容量、任何知道
元数据 URL 的主机都能把同一个桶再挂起来。
作为 pgcli 插件,JuiceFS 是第一个宿主二进制形态的 addon:没有容器。
pg addon install juicefs 从 GitHub releases 下载官方二进制(sha256 校验;
PATH 上已有 juicefs 则直接复用),面向一个外部 S3 服务 format 卷,再在
宿主上完成挂载。
平台:仅 Linux(与 BeeGFS 一样明确拒绝 macOS)。挂载点是一个普通宿主 目录——只对宿主进程可见,不会传播进 PG 实例容器(v1 不做 rshared 挂载传播; addon 管理的是本机的挂载,不是集群的)。
一切全显式
卷的两端都在命令行上点名——pgcli 不做同主机 addon 的自动识别或凭据继承:
- 对象存储:一个外部 S3 服务(云,或另一台机器上的 MinIO),用
--s3-endpoint http(s)://host[:port]加--access-key/--secret-key给出。 pgcli 在 format 前先探一次端点,不可达的主机会快速失败并提示 “not answering”,而不是卡死在juicefs format里。端点会在 format 时被烤进卷 元数据——之后挂载只需要元数据 URL。 - 元数据引擎:一个显式 URL——
--dsn(别名--meta-dsn),接受postgres://、redis://或sqlite3://。pgcli 从不创建或删除该数据库; PostgreSQL 时它必须已经存在。 - 桶:从不由 pgcli 创建。用
pg mc这个包装命令自己供给一次——安装时会 原样打印出那两条命令:
换引擎也只是换 URL:
挂载点完全由你指定——默认落在 pgcli 的 addon 目录下,用 --mount-point 指到
任意位置。目录要你亲手建好(mkdir -p),且运行 pg 的用户对其有读写权限——
pgcli 从不创建挂载点:
三种引擎都可以就用 pgcli 自己的服务——先用 pgcli 建好,再作为普通的"外部 URL"显式接线(安装代码从不自动查找它们;换成托管数据库或云桶也一模一样):
以上示例中 pg 与存储同机,loopback(默认 127.0.0.1)即可。JuiceFS 客户
端在别的机器上时,把存储发布到网络、--s3-endpoint 指向它的地址——端点在
format 时被烤进卷元数据,所以从一开始就发布好:
(redis 示例同理:redis 默认就绑 0.0.0.0——口令即门禁。)
凭据和端点已经住在元数据里,所以同一个卷之后在任何有元数据 URL 的主机都能挂。
参数
| 参数 | 默认 | 说明 |
|---|---|---|
--name |
juicefs |
卷名(配置 map 的键;format 时的 JuiceFS 卷名也是它) |
--dsn / --meta-dsn |
— | 必填。元数据引擎 URL:postgres://、redis:// 或 sqlite3:// |
--s3-endpoint |
— | 必填。外部 S3 服务,形如 http(s)://host[:port](不带桶路径、不内嵌凭据) |
--access-key / --secret-key |
— | 必填且必须成对。外部 S3 的凭据 |
--bucket |
juicefs-<name> |
桶名——pgcli 不创建(见上) |
--mount-point |
<base>/addon/juicefs/<name>/mnt |
挂载的宿主目录——pgcli 从不创建:先 mkdir -p 建好,且运行 pg 的用户可写 |
--cache-dir |
<base>/addon/juicefs/<name>/cache |
本地数据缓存目录 |
--install-dir |
/usr/local/bin |
宿主二进制落点(不可写时退到 ~/.local/bin——注意保持其在 PATH 上) |
--version |
内置 pin(1.4.1) |
要下载的版本(其 sha256 已 pin 在 pgcli 源码里) |
--no-mount |
关 | 一切就绪但不挂载,之后手动 pg addon juicefs mount |
--force |
关 | 按命令行参数重建配置条目(元数据引擎里卷已存在时 format 依然跳过) |
FUSE 本身(/dev/fuse + fusermount3)只探测、不安装——缺 fuse3 包时安装
会失败并给出该执行的 apt 命令。
日常管理
remove 从不删 S3 数据、也从不碰元数据存储——弄丢 --dsn 指向的目标就等于
弄丢整个文件系统。挂载属主就是运行 pg 的用户。
换挂载路径
挂载点记在配置条目上;mount 接受 --mount-point 覆盖,把卷迁到新路径——
新路径会持久化进 pg.yaml,之后的普通 mount(含 autostart)都用它。旧的
挂载必须先卸掉,新目录同样要预先建好且可写(与安装契约一致):
仍在挂载时尝试迁移会被拒绝并提示先 umount;指到不存在的目录则报出
mkdir -p 补救命令。挂载点与 pgcli 自己的簿记(pid/日志/缓存都在
<base>/addon/juicefs/<name>/)完全解耦,迁移从不影响卷本身——换的只是同一
个桶加元数据在另一处的挂法。
开机自启
JuiceFS 是宿主二进制 addon,所以 pg start --autostart 在开机时做的是
重新挂载而不是起容器。后端是外部服务——只有 S3 端点重新可达时开机才能成
功。绝不重新 format。挂载以 boot 服务所运行的用户身份完成。
16.23 - VictoriaLogs
pg addon install victorialogs 以受管容器方式运行 VictoriaLogs——VictoriaMetrics 家族的高性能、低成本日志数据库——直接跑官方 docker.io/victoriametrics/victoria-logs 镜像。与 VictoriaMetrics 和 Prometheus 一样,它是顶层插件:不前置某个具体实例(它从多个来源摄入日志),也不依附于任何一个。VictoriaMetrics 存指标、VictoriaLogs 存日志、Grafana 同时可视化两者——三者生命周期形状完全一致(单容器、Linux 上宿主网络、各自端口池里的一个 web 端口、持久数据目录、重启策略)。
VictoriaLogs 在配置方式上是 VictoriaMetrics 插件的精确镜像:完全不需要配置文件——一切都作为镜像名之后的 CLI flag 传入(-storageDataPath / -httpListenAddr / -retentionPeriod / 自定义 flags 经 --extra-arg)。所以 pgcli 在创建容器时直接传这些 flags;无需渲染配置、无需 SIGHUP reload、无需 chown(镜像以 root 运行)。单容器在单个 HTTP 端口上提供一切:摄入(JSON Lines、Elasticsearch 兼容 bulk、syslog、OTLP)、LogsQL 查询与内置 UI。
没有 Web 登录/密码。 VictoriaLogs 完全不带鉴权。它的 HTTP API 可摄入 并查询任意日志,昂贵查询也能把自身打满,所以远程暴露的实例只靠网络边界保护 ——防火墙,或
--listen 127.0.0.1收紧为仅回环。请把它当作一个无鉴权的读/写 控制台来对待,而非一个有密码保护的面板。
平台:Linux 是已实测的平台。设计上也支持 macOS(镜像多架构;容器在
pgcli-netbridge 上以发布端口的方式跑在 podman machine 里),但尚未在 Mac 上验证过。
参数
| 参数 | 默认值 | 说明 |
|---|---|---|
--name |
vl |
实例名(map 键) |
--image |
docker.io/victoriametrics/victoria-logs:v1.53.0 |
覆盖镜像 |
--port |
自动(基址 9428) | Web 宿主端口;自 victorialogs_start_port 自动分配 |
--listen |
0.0.0.0 |
绑定地址——默认开放全部网卡;127.0.0.1 收紧为仅回环(见上方暴露警告) |
--retention |
二进制默认 | 数据保留时长(30d、3months、1y 等),原样作为 -retentionPeriod 传入。省略则保持 victoria-logs 自身默认 |
--extra-arg |
— | 传给二进制的额外 CLI flag(可重复);最后追加,故 last-wins 覆盖早期 flags。如 --extra-arg -search.maxQueryDuration=60s |
--data-dir |
自动 | 覆盖数据目录(默认在 base 目录下) |
--force |
false | 即使正在运行也重建——用于应用改动的端口/监听/保留期/额外参数 |
数据目录是 VictoriaLogs 的日志存储。它跨 pg addon remove 存活(用 --clean-data 才删除),重装同名实例即复活已存储的日志。镜像以 root 运行(与 Prometheus 的 uid 65534 不同),故无需预先 chown 数据目录。
安装
命令会按需拉取镜像、创建容器,并打印 URL 与保留期:
VictoriaLogs 立即可摄入和查询。打开 http://<host>:9428/select/vmui/ 进入内置 Web UI(根路径 / 只是可用端点索引页),或用 HTTP API 查询:
对运行中的实例重复 install:当配置确实变了(端口、监听、保留期、额外参数)时重建,没变则原样不动;--force 即使配置未变也重建容器。
VictoriaLogs 不支持热配置重载,所以所有变更都需要重建容器——与 VictoriaMetrics 一致。这比 Prometheus 简单(无配置文件要渲染、无 reload 信号),但意味着改配置时会有短暂中断。
Web UI
每个 VictoriaLogs 实例都自带 Web UI(VMUI),无需额外安装任何组件。打开:
根路径(/)只是可用 HTTP 端点的索引页;UI 位于 /select/vmui/。
UI 是一个面向日志的 LogsQL 控制台:
- 查询 —— 输入 LogsQL 查询(如
app:demo、level:error、error app:demo-api)并选择时间范围,执行后即时返回结果。 - 视图 —— 可在
Group(按 stream 字段分组)、Table(点击行展开全部字段)、JSON(/select/logsql/query原始响应)与Live(实时 tail 新到日志)之间切换。 - 探索 —— 字段与 stream 选择器会列出已存储的字段名/值(底层调用
/select/logsql/field_names、field_values、facets等),无需预先了解 schema 即可构造查询。
例如,摄入一条日志后在 UI 里搜索它(查询 app:demo):
UI 与 HTTP API 共享同一端口,也共享同样的无鉴权态势:任何能连到端口的人都能查询、也能写入日志。按上文所述用 --listen 127.0.0.1 或防火墙限制访问。
常用查询
查询使用 LogsQL。下面的示例走 HTTP 查询 API;同样的查询串可直接用在 /select/vmui/ 的 VMUI 里。
响应是逐行 JSON,每条日志一个对象。
查生产数据时务必带
_time:过滤条件。不带时 VictoriaLogs 会扫描保留期内的全部日志。
| 目标 | 查询 |
|---|---|
| 最近 5 分钟全部日志 | _time:5m |
| 全文检索 | _time:15m error |
| 按字段过滤 | _time:1h app:demo level:error |
| 精确值(区分大小写) | _time:1h level:=error |
| 正则匹配(RE2) | _time:1h path:~"/api/v[0-9]+/" |
| 短语 / 前缀匹配 | _time:1h _msg:"payment timeout" / _time:1h _msg:timeout* |
| 数值比较 | _time:1h request_duration:>1s |
| AND / OR / NOT | _time:5m level:error -app:buggy / _time:5m (level:error OR level:warn) |
| stream 过滤 | _time:1h {app="demo"} |
| 搜索所有字段 | _time:5m | *:timeout |
| 按时间倒序取前 10 条 | _time:5m error | sort by (_time) desc | limit 10 |
| 只保留部分字段 | _time:5m error | fields _time, level, _msg |
| 按 level 统计 | _time:1h | stats by (level) count() |
| 总数统计 | _time:1h error | stats count() errors |
| 去重值及命中次数 | _time:1h | uniq by (user) with hits |
| Top N 值 | _time:1d level:error | top 10 (path) |
说明:
field:word匹配字段中的词;field:=value是精确匹配,field:~"regex"是正则匹配。取反形式为-filter、!=、!~。- 时间过滤支持相对时长(
5m、1h、30d)、绝对时间(2026-10-23Z、2026-10-23T10:00:00Z)和区间(_time:[2026-10-01Z, 2026-10-23Z])。 - 管道从左到右执行,过滤条件必须写在
sort、limit、stats、top等处理阶段之前。
日志写入:vlagent、HTTP API、采集器
pgcli 只负责运行 VictoriaLogs 后端——日志怎么进来由你决定。共有三种方式。
vlagent(官方 agent)
vlagent 是 VictoriaLogs 的官方日志 agent——vmagent 的日志对应物(vmagent 只负责指标,不要用它写日志)。它在自己的端口(默认 9429)上接受与 VictoriaLogs 相同的 HTTP 摄入协议,可以从磁盘跟踪日志文件,并通过 -remoteWrite.url 转发到 VictoriaLogs,该 URL 必须以 /insert/native 结尾。pgcli 会替你运行并管理它——完整参数见 vlagent 插件页面:
第一种形式不跟踪任何日志文件:没有 --log-path/--include(也没有经 --extra-arg 传入的 -kubernetesCollector)时,vlagent 只接收推到端口 9429 的 HTTP 日志并转发。--log-path 接受任意宿主文件或目录;不给 --include 时,目录默认展开为 <dir>/**/*.log,单个文件默认就是它自身。glob 是在容器内匹配的,所以 pgcli 把每个宿主路径挂载到完全相同的绝对路径。
适合使用 vlagent 的场景:
- 复制 / 高可用 —— 为每个 VictoriaLogs 实例重复传
--remote-write-url。vlagent 会把每条日志复制到所有目标,并为每个 URL 维护独立磁盘缓冲(-remoteWrite.tmpDataPath,可用-remoteWrite.maxDiskUsagePerURL限制大小);后端宕机期间采集的日志会在其恢复后重放。 - Kubernetes Pod 日志 —— 用
--extra-arg -kubernetesCollector,并通过--log-path把宿主/var/log(使用 Docker runtime 时再加/var/lib以解析 runtime 符号链接)挂入。它跟踪/var/log/pods与/var/log/containers,自动附加容器元数据,并把读取位点持久化在数据目录(-tmpDataPath)下。 - 磁盘文件 ——
--log-path /var/log/nginx --include '/var/log/nginx/*.log'(glob 必须指向文件而非目录)。默认hostname与file成为 stream 字段,读取位点与缓冲位于插件的数据目录。
如果只有单个、网络稳定的 VictoriaLogs 实例,直接发送(见下)更简单——这是推荐的默认方式。
HTTP API
VictoriaLogs 在 UI/查询 API 的同一端口上提供多个摄入端点:
| 端点 | 格式 | 说明 |
|---|---|---|
/insert/jsonline |
JSON Lines(ndjson) | 必须带 Content-Type: application/json;缺失时返回 200 但数据被静默丢弃 |
/insert/elasticsearch/_bulk |
Elasticsearch / OpenSearch bulk | Filebeat / Logstash / Fluent Bit 的 Elasticsearch output 指向这里 |
/insert/loki/api/v1/push |
Loki JSON push | Promtail / Grafana Alloy / Loki 客户端指向这里 |
/insert/opentelemetry/v1/logs |
OTLP/HTTP logs | OpenTelemetry Collector 的 exporter 指向这里 |
| syslog TCP / UDP / Unix | RFC3164 / RFC5424 | 经 --extra-arg -syslog.listenAddr.tcp=:514(或 .udp / .unix)启用并重建 |
所有 HTTP 摄入端点都接受同一组调优参数(query string 或 VL-* 请求头):_msg_field(日志消息所在字段,如 message)、_time_field(如 @timestamp)、_stream_fields(用逗号分隔的 stream 字段)、extra_fields(env=prod)、ignore_fields、decolorize_fields,以及 debug=1(只记录 payload 而不入库)。当采集器输出的 schema 无法被自动识别时用它们做映射。
日志采集器
任何能使用上述格式之一的采集器都可以;pgcli 不安装也不配置它们。常用采集器及官方配置指南:
| 采集器 | 发往 |
|---|---|
| Filebeat | Elasticsearch output → /insert/elasticsearch/_bulk |
| Logstash | Elasticsearch 或 HTTP output |
| Fluent Bit / Fluentd | Elasticsearch 或 HTTP output |
| Vector | http sink → /insert/jsonline,或 Elasticsearch sink |
| Promtail / Grafana Alloy | Loki push → /insert/loki/api/v1/push |
| OpenTelemetry Collector | OTLP/HTTP exporter → /insert/opentelemetry/v1/logs |
| Telegraf | http output → jsonline |
| journald | Vector / Fluent Bit 的 journald source → jsonline,或用 vlagent |
| rsyslog / syslog-ng | syslog listener(-syslog.listenAddr.*) |
| Datadog Agent | Datadog intake API |
采集器也可以不直接连 VictoriaLogs,而是指向 vlagent(端口 9429)——需要缓冲、复制或统一出口时更合适。
自定义参数(--extra-arg)
VictoriaLogs 接受许多 CLI flags 来调优缓存大小、查询限制、摄入速率等。经 --extra-arg 传入(可重复):
额外参数最后追加到容器的命令行,故它们经 last-wins 语义覆盖早期 flags。这让你在 pgcli 的一等旋钮之外调优 VictoriaLogs,无需编辑任何配置文件。验证 flags 已生效(二进制不会把 flags 打进日志):
监听与暴露
VictoriaLogs 的 Listen 默认 0.0.0.0:容器共享宿主网络命名空间,所以 UI 与 API 在宿主每个网卡的分配端口上可达。这与 VictoriaMetrics 的默认一致(别的机器上的日志采集器和 Grafana 要能连它)——但背后没有密码。任何能连到端口的人都能执行任意 LogsQL、读你存储的日志。想要仅回环的部署,传 --listen 127.0.0.1(用 --force 重建);pg addon list 会显示 http://127.0.0.1:<port>/。否则请在防火墙层面限制访问。
端口
每个 VictoriaLogs 实例占有一个端口:Web UI/API。它取自以 victorialogs_start_port(默认 9428——VictoriaLogs 惯例 HTTP 端口)为基址的端口池,每多一个实例递增,并跳过任何已被受管容器或 used_ports 列表占用的端口。分配结果写入 pg.yaml,所以重装绝不会把别的插件在用的端口再发一遍。9428 与 VictoriaMetrics(8428)、vmcluster(8480-8484)、Prometheus(9090)、Grafana(3000)、pgAdmin(5050)、nginx(8080)都不重号;显式 --port 仍优先。
配置
没有配置文件——一切都作为 CLI flags 传给 victoria-logs 二进制(-storageDataPath、-httpListenAddr、-retentionPeriod,加上任何 extra_args)。retention 存为时长字符串(省略 = 二进制默认),extra_args 是最后追加的 CLI flags 列表。
列出
没有凭据可揭示——VictoriaLogs 本就没有——所以 --show-password 对它不会多显示任何东西。
启停
容器以 podman 的 unless-stopped 重启策略运行,宿主重启后随 podman 服务自动回来。start/stop 显式管理它们:
开机自启
这会在 pg.yaml 里给实例打标记,并安装/刷新一个开机服务(Linux 上是 systemd --user unit,macOS 上是 LaunchAgent)来执行 pg start --autostart。开机启动只负责把容器拉起来——日志数据目录在 install 时已创建,重启后已有日志仍在。关闭用 pg autostart disable --victorialogs --name vl;完整机制见 开机自启。
移除
rootless podman 下,--clean-data 经 podman unshare rm 删除数据目录——VictoriaLogs 以 root(容器内 uid 0)写文件,它映射到宿主的 subordinate uid,宿主用户无法直接 rm。
排障
- 摄入返回 200 但查询为空 —— JSON Lines 端点要求
Content-Type: application/json;缺失时 VictoriaLogs 接受请求但会静默丢弃数据(/metrics上的vl_rows_ingested_total{type="jsonline"}保持 0)。加上该 header,或改用会自动设置的采集客户端。 - 查询返回空数据 —— 确认 VictoriaLogs 在运行(
pg addon list显示running)、健康端点有响应(curl -s http://<host>:<port>/-/healthy)、且你查的是正确的时间范围与 stream 过滤(_stream:前缀、_time:边界)。查podman logs <container>看摄入错误。 - Install 失败于 “container already exists” —— 容器名由
--name和命名空间派生;用--force重建,或先pg addon remove清理一次不完整的 install。 - 额外参数没生效 ——
--extra-arg在容器创建时应用,而非运行时。用--forcereinstall 以新 flags 重建容器。查podman inspect <container> --format '{{json .Config.Cmd}}'确认 flags 在命令行上(二进制不会打印它们)。 - 端口已被分配 —— 已分配的端口记在
pg.yaml里并为后续实例跳过;先pg addon remove释放,或用--port N另选一个。 - 远程 API 打不开 / 意外可访问 —— 默认
0.0.0.0下打不开是宿主防火墙问题,不是绑定地址;反之若你不想被访问,用--listen 127.0.0.1 --force。
已知限制
- VictoriaLogs 本身是裸日志后端:pgcli 另外提供专门的
vlagent插件做文件/HTTP 采集(也可以用上文直连 HTTP API 或第三方采集器),但不安装任何第三方采集器,也不做告警(自行跑vlalert)。它是你让 Grafana(VictoriaLogs 数据源)和采集器连的长期日志存储。 - 无鉴权。 VictoriaLogs 的 API 摄入并查询任意日志;
0.0.0.0绑定的唯一保护是网络边界。这与 VictoriaMetrics 同一态势——不要把它暴露到不可信网络。 - pgcli 不校验
--extra-arg值(install 阶段没有 pre-flight 检查)。有错的 flag 只会在启动时从容器日志里暴露。 - macOS 上容器跑在 podman machine 里;目前只在 Linux 上验证过。
16.24 - vlagent
pg addon install vlagent 以受管容器方式运行 vlagent——VictoriaLogs 家族的官方日志 agent、vmagent 的日志对应物——直接跑官方 docker.io/victoriametrics/vlagent 镜像。它是顶层插件:不前置某个具体 PostgreSQL 实例,是 VictoriaLogs 插件的天然搭档——VictoriaLogs 存日志,vlagent 从文件或 HTTP 采集日志并转发给一个或多个 VictoriaLogs 实例。
与 VictoriaMetrics、VictoriaLogs 一样,完全不需要配置文件——一切都作为镜像名之后的 CLI flag 传入。pgcli 在其上增加文件采集接线:每个 --log-path 都以只读方式挂载到容器内完全相同的绝对路径,--include/--exclude 使用 vlagent 的原生 glob 语法(不是正则表达式)选择文件。
没有鉴权。 vlagent 的 HTTP 监听端口接受任何能连上的人推送的日志,而它转发的 VictoriaLogs(默认)也没有鉴权。远程暴露的实例只靠网络边界保护——用
--listen 127.0.0.1收紧为仅回环,或在防火墙层面限制访问。
平台:Linux 是已实测的平台。设计上也支持 macOS(镜像多架构;容器在
pgcli-netbridge 上以发布端口的方式跑在 podman machine 里),但尚未在 Mac 上验证过。
参数
| 参数 | 默认值 | 说明 |
|---|---|---|
--name |
va |
实例名(map 键) |
--image |
docker.io/victoriametrics/vlagent:v1.53.0 |
覆盖镜像 |
--port |
自动(基址 9429) | HTTP 摄入宿主端口;自 vlagent_start_port 自动分配 |
--listen |
0.0.0.0 |
绑定地址——默认开放全部网卡;127.0.0.1 收紧为仅回环 |
--remote-write-url |
必填 | 转发的 VictoriaLogs 端点(可重复)。每个 URL 必须以 /insert/native 结尾 |
--log-path |
— | 要跟踪的宿主文件或目录(可重复)。以只读方式挂载到容器内相同绝对路径 |
--include |
自动推导 | 选择文件的 glob(可重复;绝对容器路径)。默认目录为 <dir>/**/*.log,文件为自身 |
--exclude |
— | 排除文件的 glob(可重复;与 --include 按位置配对) |
--extra-arg |
— | 传给二进制的额外 CLI flag(可重复);最后追加,故 last-wins 覆盖早期 flags |
--data-dir |
自动 | 覆盖数据目录(默认在 base 目录下) |
--force |
false | 即使正在运行也重建——用于应用改动的端口/监听/URL/路径/glob |
数据目录存放 vlagent 的磁盘转发缓冲与文件采集检查点(读取位点)。它跨 pg addon remove 存活(用 --clean-data 才删除),所以重装同名实例会从采集停止的位置继续,而不是重发或跳过文件内容。镜像以 root 运行,无需预先 chown。
没有 --password:vlagent 没有本地账号概念,连接两端也都不鉴权。
安装
仅转发——在 :9429 接受 HTTP 推送并转发给本机 VictoriaLogs:
跟踪一个宿主日志目录(最常见):
不给 --include 时,该目录默认展开为 /var/log/nginx/**/*.log——即目录下任意深度的所有 .log 文件。上面的引号只是为了 shell 安全;pgcli 按字面存储 glob。
跟踪单个文件,或多个路径并显式指定 glob 与排除:
每个 --log-path 变成一个只读 bind mount(host:host:ro),所以 pgcli 传给 vlagent 的 glob 就是宿主路径本身。路径必须是绝对路径且已存在。
对运行中的实例重复 install:配置确实变了(端口、监听、URL、日志路径、include/exclude、额外参数)时重建,没变则原样不动;--force 即使配置未变也重建。列表型 flag 会替换整个存储列表:要新增路径,请重传完整集合。
文件采集的工作方式
vlagent 的文件采集由 -fileCollector.glob 驱动,pgcli 的 flag 一一对应:
| pgcli 参数 | vlagent flag | 说明 |
|---|---|---|
--log-path |
(bind mount) | -v <path>:<path>:ro——容器必须能看见该文件 |
--include |
-fileCollector.glob |
可重复;必须匹配文件而非目录 |
--exclude |
-fileCollector.excludeGlob |
与第 N 个 include 按位置配对 |
--data-dir |
-tmpDataPath |
包含缓冲与 file-checkpoints.json |
每条采集到的日志额外带两个字段:file(来源路径)与 hostname。二者默认都是 stream 字段,所以每个文件在 VictoriaLogs 里是独立的 log stream——正是 LogsQL 过滤想要的效果。可用 --extra-arg -fileCollector.streamFields=...、-fileCollector.fileField=... 或 -fileCollector.hostnameField=... 调整。
采集器默认每 10 秒轮询新文件(-fileCollector.refreshInterval)。它支持 create 与 nocreate 两种日志轮转策略:轮转时先读完旧文件再切换;重启后会从保存位点继续读取轮转后的文件。检查点写在数据目录下。
Glob 语法
这不是正则表达式——是 vlagent 自己的 glob 方言:
| 模式 | 匹配 |
|---|---|
* |
任意字符序列,不跨路径分隔符 |
/**/ |
零个或多个目录(递归) |
? |
任意单个字符,不跨路径分隔符 |
[abc]、[a-z] |
字符类;前缀 ^ 或 ! 表示取反 |
{a,b,c} |
备选——匹配逗号分隔项中的任意一个 |
\c |
转义字符字面量 |
示例:
* 不匹配隐藏文件(以 . 开头);需要时请显式写出开头的点(.*.log)。
Exclude 按位置配对
vlagent 把第 N 个 --exclude 与第 N 个 --include 配对,与它自己的 flag 语义一致。空字符串 '' 是占位符,表示“这个位置不排除”:
第一对采集除 nginx 外的所有日志,第二对再单独采集 nginx。没有位置配对的话,nginx 的排除会把第二对也一起排除掉。
pgcli 会输出到最后一个非空 exclude 为止;末尾的空占位符直接丢弃。如果只改 --log-path 而不传 --include,会按新路径重新计算默认 glob,并裁剪超出新配对数量的已存 exclude(想覆盖就在同一次运行里显式传 --exclude)。
复制与高可用
为每个 VictoriaLogs 实例重复传 --remote-write-url:
vlagent 把每条日志复制到所有目标,为每个 URL 维护独立磁盘缓冲,并在后端恢复后重放缓冲日志。可用 --extra-arg -remoteWrite.maxDiskUsagePerURL=... 限制每个缓冲的大小。
其他 HTTP 摄入
即使没有 --log-path,vlagent 也是一个有用的 HTTP 前门:它在监听端口上接受与 VictoriaLogs 相同的协议并转发,因此采集器可以推给 vlagent 而不是直连 VictoriaLogs:
| 端点 | 格式 |
|---|---|
/insert/jsonline |
JSON Lines(要求 Content-Type: application/json) |
/insert/elasticsearch/_bulk |
Elasticsearch / OpenSearch bulk |
/insert/loki/api/v1/push |
Loki JSON push |
/insert/opentelemetry/v1/logs |
OTLP/HTTP logs |
Kubernetes 节点日志:用 --extra-arg -kubernetesCollector,并通过 --log-path 挂入节点 /var/log(Docker runtime 再加 /var/lib)。完整协议与采集器列表见 VictoriaLogs 日志写入一节。
端口
每个 vlagent 实例占一个端口:HTTP 摄入监听。它取自以 vlagent_start_port(默认 9429——vlagent 惯例端口)为基址的端口池,每多一个实例递增,并跳过任何已被受管容器或 used_ports 列表占用的端口。该池与 VictoriaLogs(9428)分开,所以新增 VictoriaLogs 实例绝不会抢走 vlagent 的端口。显式 --port 仍优先。
配置
一切都作为 CLI flags 传给 vlagent 二进制:remote_write_urls 变成重复的 -remoteWrite.url,includes 变成 -fileCollector.glob,excludes 变成 -fileCollector.excludeGlob,log_paths 变成只读 bind mount,data_dir 变成 -tmpDataPath。
列出
启停
容器以 podman 的 unless-stopped 重启策略运行。文件读取位点会检查点写入数据目录,所以 stop/start(或宿主重启)会从原位置继续,不重复、不丢文件内容。
开机自启
这会在 pg.yaml 里给实例打标记,并安装/刷新一个开机服务(Linux 上是 systemd --user unit,macOS 上是 LaunchAgent)来执行 pg start --autostart。开机启动只负责把容器拉起来——带文件检查点与转发缓冲的数据目录在 install 时已创建,agent 会从采集停止的位置继续。启动顺序上 vlagent 排在 VictoriaLogs 之后,所以同机的 remote-write 目标已经先监听。关闭用 pg autostart disable --vlagent --name va;完整机制见 开机自启。
移除
移除 agent 不会删除任何被跟踪的日志文件——它们以只读方式挂载,从未被修改。
排障
- VictoriaLogs 里没有文件日志 —— 确认容器在运行(
pg addon list)、glob 能匹配真实文件(--include必须匹配文件而非目录)、且路径在容器内可见。podman inspect pgcli-vlagent-<ns>-<name> --format '{{json .Config.Cmd}}'可查看确切 flags;podman logs <container>查看采集器错误。 - 部分文件被跳过 ——
*不匹配隐藏文件,轮转名如app.log.1也不匹配*.log。请加显式模式(如--include '/var/log/myapp/app.log.[0-9]')。exclude 按位置配对:第一对里过宽的排除会连带影响后面的配对。 - 写入的 stream 不对 ——
file与hostname默认是 stream 字段;用--extra-arg -fileCollector.streamFields=...覆盖。 - 额外参数没生效 ——
--extra-arg在容器创建时应用。重装(配置变化会自动重建)或用--force;用podman inspect <container> --format '{{json .Config.Cmd}}'确认。 - 容器读不到日志(SELinux 主机) —— pgcli 对日志路径只用
:ro、刻意不用:z,因为给/var/log打标签会改动宿主。在 enforcing 的 SELinux 系统上请自行按工作负载打标签(如共享的container_log_t标签)或使用匹配的 security profile。 - 远程写入失败 —— URL 必须以
/insert/native结尾;检查 VictoriaLogs 健康(curl -s http://<vl>:9428/-/healthy)以及主机间防火墙。可达后缓冲日志会自动重放。
已知限制
- 不支持正则表达式。 vlagent 只支持 glob(
*、/**/、?、[a-z]、{a,b})。复杂文件名规则需要放宽 include,再在查询侧用 LogsQL 过滤。 - 摄入监听与远程写入两侧都没有鉴权;用
--listen 127.0.0.1或防火墙保护。 - pgcli 不校验
--extra-arg的值;错误的 flag 只会在启动时从容器日志里暴露。 - macOS 代码完备但未实测;Linux 是已验证平台。
16.25 - VLCluster
pg addon install vlcluster 以受管容器方式运行 VictoriaLogs 集群版——可水平扩展的日志数据库。与单机版 victorialogs addon(1 容器 1 端口)不同,它管理最多 3 个容器,但三个角色共用同一个镜像(docker.io/victoriametrics/victoria-logs:v1.53.0),角色完全由启动参数区分;每台主机占用 3 个 HTTP 端口。
架构:
| 组件 | 职责 | HTTP 端口 | 通信对象 |
|---|---|---|---|
| vlinsert | 摄入端,按 stream hash 分片写入所有 vlstorage | 9430 | 拨每个 vlstorage:StoragePort |
| vlselect | 查询端,并行查询所有 vlstorage 并合并结果 | 9431 | 拨每个 vlstorage:StoragePort |
| vlstorage | 数据存储(分片,不复制) | 9432 | 在该端口上提供 /internal/insert + /internal/select/* |
vlinsert 和 vlselect 都是无状态的,且拨向每个 vlstorage 的同一个端口——不像 VMCluster 需要 host:insertPort:selectPort 两个内部 TCP 端口。因此存储节点地址就是 host:storagePort。
没有 Web 登录/密码。 与单机版 VictoriaLogs 一样,集群版完全不带鉴权。 它的 HTTP API 可以摄入、查询和删除任意日志,所以远程暴露的实例只靠网络边界 保护——防火墙,或
--listen 127.0.0.1收紧为仅回环。请把它当作一个无鉴权的 读/写控制台来对待。
平台:仅 Linux。VLCluster 需要 host networking 来承载 vlinsert/vlselect 拨向各 vlstorage 的端口。macOS 上安装会被拒绝并给出明确错误提示。
没有副本。 vlinsert 只做分片、不做复制:每条 stream 只存在于一个 vlstorage 节点上。任一 vlstorage 下线,查询会返回 HTTP 502,直到它恢复(摄入仍在继续, 可由 vlagent 或客户端缓冲)。这是上游的设计属性,不是 pgcli 的限制——容量规划 和备份请按此考虑。
部署形态
形态 1 — 单机收敛(默认)
3 个组件全在一台主机上:
形态 2 — 跨主机分片
与 VMCluster 相同的 --roles + --storage-node 模式:每台主机各跑自己的 pg addon install vlcluster,pgcli 从不 SSH。
仅运行 access 角色(--roles insert 和/或 select)的主机必须传 --storage-node,否则安装直接报错——自动推导的 loopback 地址会造出一个谁都不认识的孤立节点。
所有主机必须为同名实例分配相同的端口块,地址才能对上。默认基址是 9430(见 端口);如果某台主机修改了 vlcluster_start_port,其他主机也要改成一样的值。
参数
| 参数 | 默认值 | 说明 |
|---|---|---|
--name |
vlc |
实例名(map key) |
--roles |
insert,select,storage |
本机运行的角色,逗号分隔(跨主机时取子集) |
--storage-node |
自动推导 | vlstorage 地址(可重复;格式 host:storagePort,如 10.0.0.1:9432)。列出集群中所有 vlstorage 节点以分片。单机收敛时省略 |
--insert-port |
自动(基址 9430) | vlinsert HTTP 端口 |
--select-port |
自动(基址 9431) | vlselect HTTP 端口 |
--storage-port |
自动(基址 9432) | vlstorage HTTP 端口 |
--listen |
0.0.0.0 |
绑定地址——127.0.0.1 表示仅回环 |
--retention |
二进制默认 | vlstorage 保留期,如 30d(仅在本机运行 storage 角色时生效) |
--image |
docker.io/victoriametrics/victoria-logs:v1.53.0 |
镜像覆盖(三个角色共用一个镜像) |
--insert-extra-arg |
— | vlinsert 额外 CLI 参数(可重复,追加在最后) |
--select-extra-arg |
— | vlselect 额外 CLI 参数(可重复,追加在最后) |
--storage-extra-arg |
— | vlstorage 额外 CLI 参数(可重复,追加在最后) |
--data-dir |
自动 | vlstorage 数据目录覆盖 |
--force |
false | 即使运行中且配置未变也重建 |
--retention 刻意不设默认值:留空表示使用 victoria-logs 二进制自己的默认保留期。与 vmcluster 的 --retention-months 不同,VictoriaLogs 解析的是 30d、3months、1y 这类时长。
安装
命令会按需拉取镜像,按依赖顺序(storage → insert → select)创建容器,并打印摘要:
用相同参数重装是幂等的(显示 unchanged);参数变化时只重建需要的部分;从 --roles 中移除的角色,其残留容器会被删除。
URL 格式
集群版与单机版 VictoriaLogs 使用完全相同的 API 路径——没有 account ID,也没有额外前缀:
经 vlinsert 摄入:
经 vlselect 查询:
vlselect 还在 http://<host>:9431/select/vmui/ 提供内置 UI。vlagent 的转发目标为 http://<vlinsert>:9430/insert/native。
vlstorage 端口(9432)的 /internal/insert 与 /internal/select/* 供另外两个角色调用;它自己的 LogsQL 端点只查询本节点本地数据,适合单节点排障,但不是集群级查询。
端口
每个 VLCluster 实例从 vlcluster_start_port 池(默认 9430)占用 3 个连续端口:
+0InsertPort(vlinsert HTTP——摄入端点)+1SelectPort(vlselect HTTP——查询端点)+2StoragePort(vlstorage HTTP——内部摄入/查询 API)
即使某台主机只运行部分角色,三个端口也都会分配,这样存储节点地址在各主机间可预测。基址紧挨单机版 VictoriaLogs(9428)和 vlagent(9429);三个端口池互相感知,不会发放同一个端口。
配置
容器内没有配置文件——一切都以 CLI 参数传入。storage_nodes 是跨主机拓扑;留空表示自动推导的单机模式。
增加存储节点
vlinsert/vlselect 在启动时固化 -storageNode 列表,因此增加节点需要重建 access 层容器(摄入会短暂中断,已有数据不受影响):
没有自动 rebalance:旧数据留在原节点,只有新写入会分布到扩容后的节点集合;vlselect 会合并所有节点,所以查询能看到新旧全部数据。如需重新均衡旧数据,可等 retention 自然过期,或手工搬运按天分区(上游没有 rebalance 工具)。
启停
容器以 podman 的 unless-stopped 策略运行,主机重启后会自动恢复。停止顺序与依赖相反(select → insert → storage)。
开机自启
启动是集群粒度的:每台主机只拉起自己的角色,按依赖顺序(storage → insert → select),已存在的容器直接 start,缺失的才重建。跨主机部署需在每台主机上执行。完整机制见 Autostart。
移除
移除会删除本机全部三个角色容器,包括角色变更后遗留的孤儿容器。在 access-only 主机上执行不会影响远端存储节点。
日志
本机未运行的角色会被明确拒绝。
已知限制
- 没有副本。 每条 stream 只有一份;任一 vlstorage 宕机,查询返回 502。摄入会继续,可由上游(如 vlagent 磁盘队列)缓冲。跨集群高可用需要多个 vlagent
--remote-write-url目标加 vmauth 之类路由层——不在本 addon 范围内。 - 没有自动 rebalance。 增加存储节点只影响新写入的数据。
- 没有鉴权。 HTTP API 可摄入和查询任意日志;
0.0.0.0绑定只能靠网络边界保护。 - 仅 Linux。 需要 host networking。
- 没有 TLS。 在前面用 nginx 终止 TLS,或在私有网络内使用。
- 状态是本机视角。
pg addon list只报告当前主机配置的角色,不会探测远端存储节点。 - pgcli 不校验
--*-extra-arg的取值,错误参数只会在容器启动日志中暴露。
16.26 - ZeroFS
ZeroFS 是把一切关在桶里的 S3 文件系 统:不像 JuiceFS 依赖外部元数据引擎,它自己在对象存储上跑一套 LSM 树,且每个 对象都用卷数据密钥加密——该密钥由你的加密口令包裹(wrap)。快照与一致性 由后台 compaction 负责——没有要备份的数据库、没有要养活的 Redis。它经本地 9P 一跳再以 FUSE 挂载,日常使用与普通文件系统无异;而桶本身就是这个卷完整的、 加密的副本。
和 JuiceFS 一样,ZeroFS 是宿主二进制 addon——没有容器。
pg addon install zerofs 下载官方 release 二进制(sha256 校验;PATH 上已有
zerofs 则复用),自己写出卷的 config.toml,并运行两个宿主进程:
- server(
zerofs run -c config.toml)——掌管 S3 与加密侧,在 loopback 端口上讲 9P(默认 5564)。9P 没有认证,所以 pgcli 硬拒非 loopback 的--listen; - client(
zerofs mount 127.0.0.1:<port> <mount-point>)——你实际使用的 FUSE 挂载,server 重启时自动重连。
平台:仅 Linux(宿主 FUSE;与 BeeGFS 同样拒绝 macOS)。挂载点是普通宿主 目录,不会传播进 PG 实例容器。
一切全显式
对象存储是一个外部 S3 服务(云,或另一台机器上的 MinIO),在命令行上点名 ——pgcli 不做同主机 addon 的自动识别或凭据继承:
--s3-endpoint是裸的http(s)://host[:port](不带桶路径、不内嵌凭据); pgcli 在起 server 前先探一次,不可达的主机快速失败,而不是陷进 server 的 重试循环。--access-key/--secret-key必填,且必须成对。- 桶从不由 pgcli 创建——用
pg mc包装命令自己供给;安装时会原样打印那两条 命令。 - ZeroFS 会对请求签名,因此
--s3-region有效(可选——zerofs 自身默认us-east-1,多数 MinIO 兼容存储接受默认值)。
挂载点完全由你指定——默认落在 pgcli 的 addon 目录下,用 --mount-point 指到
任意位置。目录要你亲手建好(mkdir -p),且运行 pg 的用户对其有读写权限——
pgcli 从不创建挂载点:
目录不存在或不可写时,安装(以及之后的每次 mount)都会在下载和启动任何东
西之前失败,并给出逐字的 mkdir -p 补救命令。
两个后端都可以就用 pgcli 自己的服务——先用 pgcli 建好,再作为普通的"外部 URL"显式接线(安装代码从不自动查找它们;换成云桶或远端 Redis 也一模一样):
以上示例中 pg 与存储同机,loopback(默认 127.0.0.1)即可。zerofs 客户端
在别的机器上时,把存储发布到网络、--s3-endpoint 指向它的地址——端点写在
config.toml 里,之后要改就 --force 重渲染:
(redis 默认就绑 0.0.0.0——口令即门禁。注意这里说的只是 S3/redis 后端:
zerofs 自己的 9P --listen 必须保持 loopback——9P 没有认证。)
口令即数据
⚠️ 🔑
pg addon remove会销毁口令的唯一副本——动手前先存好。remove保留桶,但删除配置条目,而口令只活在那个条目和它的config.toml里。没有 原口令,被保留的桶就永远无法解读——不存在任何恢复途径。任何将来可能反悔的remove之前:pg addon password zerofs --name z1 > /某处安全的地方(放在 pgcli 目录之外)。
加密口令包裹着桶的数据加密密钥(DEK)。首次安装自动生成——随时取回:
丢了它,桶里的内容就无法恢复。这对 pg addon remove 尤其关键:它刻意保留
桶,但配置条目(连同口令的副本)没了——于是朴素的 reinstall 会生成新口
令,无法 unwrap 旧 DEK,server 起不来并报 Invalid password or corrupted key。要重新接上保留的桶,把原口令传回去:
(真要推倒重来:清空桶,或换一个 --bucket。)
Fencing:conditional put
ZeroFS 用 S3 conditional put(If-None-Match)对并发写者做 fencing。
--conditional-put 三选一:
| 模式 | 含义 |
|---|---|
auto(默认) |
用存储原生 If-None-Match——需要 MinIO RELEASE.2022-08+(pgcli 默认镜像满足)或真 S3 |
redis |
改经 Redis fencing——给不支持 conditional-put 的存储(如 silo);必须显式给 --redis-dsn redis://…(无同主机自动解析) |
none |
不 fencing——只在绝无他人碰这个桶时才安全 |
若 server 因存储缺少 conditional-put 支持而起不来,安装会识别日志特征并提示你
改用 --conditional-put redis --redis-dsn … 重试。
--redis-dsn 就是一个普通的 redis:// URL——DSN 会原样写进 config.toml,
任何 Redis 都行,包括 pgcli 自己的 redis addon(接法见上文示例;pg addon password redis 裸输出随机口令,/N 选逻辑库)。TLS 端点与 pgcli 其他处一
致:rediss://user:pass@redis-host:6380/1。
9P 端口每个实例独占:bind 撞车的 zerofs server 会静默启动、什么都不监听,所
以 install 和 start 对已被占用的 --port 直接拒绝——多卷并存请各给一个端口。
参数
| 参数 | 默认 | 说明 |
|---|---|---|
--name |
zerofs |
卷名(配置 map 的键) |
--s3-endpoint |
— | 必填。外部 S3 服务,形如 http(s)://host[:port] |
--s3-region |
zerofs 自身的 us-east-1 |
端点的 SigV4 region |
--bucket |
zerofs-<name> |
桶——pgcli 不创建 |
--access-key / --secret-key |
— | 必填且成对。S3 凭据 |
--encryption-password |
生成 | wrap 口令;重接保留的桶时传回原口令 |
--mount-point |
<base>/addon/zerofs/<name>/mnt |
挂载的宿主目录——pgcli 从不创建:先 mkdir -p 建好,且运行 pg 的用户可写 |
--cache-dir |
<base>/addon/zerofs/<name>/cache |
服务端磁盘缓存目录 |
--server-cache-gb |
100 | 服务端缓存大小(GiB) |
--listen |
127.0.0.1 |
9P 绑定地址——必须 loopback(9P 无认证) |
--port |
5564 | 9P 端口(已有应答者时直接拒绝——每个 zerofs 实例一个) |
--conditional-put |
auto |
auto / redis / none(见 Fencing) |
--redis-dsn |
— | Redis fence 后端,--conditional-put redis 时必填 |
--install-dir |
/usr/local/bin |
宿主二进制落点(不可写退到 ~/.local/bin) |
--version |
内置 pin(2.3.5) |
要下载的版本(摘要已 pin 在 pgcli 源码里) |
--no-mount |
关 | 写好配置、起好 server,但不挂载 |
--force |
关 | 重渲染 config.toml 并重启——保留存的加密口令(新口令无法 unwrap 既有数据) |
FUSE(/dev/fuse + fusermount3)只探测、不安装。
日常管理
config.toml、server/client 日志与 pid 文件都在 <base>/addon/zerofs/<name>/
下;口令与 S3 密钥放在 0600 的 config.toml 里——同时,和 pgcli 所有 addon
凭据一样,也明文存在于你的 pg.yaml。
换挂载路径的做法与 JuiceFS 完全一致:先 umount,用
mkdir -p 把新目录建好(可写——与安装契约相同),再带覆盖参数挂载;新路径
会持久化进 pg.yaml,之后的普通 mount(含 autostart)都用它:
仍在挂载时尝试迁移会被拒绝并提示先 umount;FUSE 挂载点与 pgcli 的簿记完全
解耦(9P server 照常跑在自己的端口上,config.toml/日志/pid 始终在
<base>/addon/zerofs/<name>/),迁移从不触碰卷本身。
开机自启
开机时 pg start --autostart 先起 9P server(等端口监听)再起 FUSE
client。后端是外部服务——只有 S3 端点重新可达时开机才能成功。不重渲染、不重
生成:既有的 config.toml 与存的口令原样复用。
16.27 - DuckDB
DuckDB 是嵌入式分析数据库——常被称作"SQLite for analytics"。
C++ 编写的列式引擎,向量化执行,可以直接查询 Parquet、CSV、JSON 和 Arrow 文件,
无需服务器进程。把它当成一个单二进制的 OLAP 引擎,指向你的数据即可使用:
加载 httpfs 扩展后,SELECT * FROM read_parquet('s3://bucket/data.parquet')
开箱即用。
作为 pgcli 插件,DuckDB 是宿主二进制形态:没有容器,没有守护进程。
pg addon install duckdb 从 GitHub releases 下载官方 CLI zip(sha256 校验;
PATH 上已有 duckdb 则直接复用),安装 httpfs 扩展(parquet 和 json
已内置于二进制中),并在 pg.yaml 中记录条目。
平台:仅 Linux,仅 amd64(macOS 及其他架构明确拒绝并给出错误提示)。 DuckDB 是一个独立的 CLI 工具——不作为后台服务运行、不监听端口、不会传播进 PG 实例容器。
为什么在 pgcli 旁边装 DuckDB
DuckDB 填补了 PostgreSQL 行存引擎不擅长的分析场景:对大量 Parquet/CSV 数据集的
即席查询、数据湖探索、加载进 PostgreSQL 之前的 ETL 预处理,或者对已经躺在 S3 里
的文件做快速聚合。pgcli 已经管理了多种 S3 存储(MinIO、silo、rustfs、garage、
seaweedfs)——DuckDB 通过 httpfs 直接查询其中的内容。
用法
安装完成后,启动交互式 shell 或执行一次性查询:
参数
| 参数 | 默认 | 说明 |
|---|---|---|
--name |
duckdb |
实例名(配置 map 的键) |
--version |
1.5.6 |
要下载的版本(sha256 已 pin 在 pgcli 源码里;支持 1.5.6 LTS 和 2.0.0) |
--data-dir |
<base>/addon/duckdb/<name> |
.duckdb 数据库文件的默认目录 |
--install-dir |
/usr/local/bin |
宿主二进制落点(不可写时退到 ~/.local/bin——注意保持其在 PATH 上) |
--no-extensions |
关 | 跳过安装 httpfs 扩展(parquet 和 json 已内置于二进制中) |
--force |
关 | 按命令行参数重建配置条目 |
日常管理
remove 从不删除二进制——它可能被多个命名实例共享。加 --clean-data 删除
数据目录(数据库文件);不加则保留目录并打印提示。
Server/Client 模式(v2.0.0+)
DuckDB v2.0.0 引入了 Quack 协议,支持原生 server/client 模式。 DuckDB 可以通过 HTTP 对外提供数据库服务,多个客户端可以同时查询同一个实例。
启动服务端
服务端默认监听 localhost:9494,启动时会打印自动生成的 auth token。
默认只接受本地连接——如需远程访问,需在前面放一个 TLS 终止反向代理
(参见 DuckDB 文档)。
跨主机访问(绑定 0.0.0.0)
要接受来自任意网络接口的连接,使用 quack_serve() 并指定参数(而非 -serve):
需要三个参数:
listen_uri='quack:0.0.0.0:9494'— 绑定到所有接口(不只是 localhost)allow_other_hostname=true— 绕过仅限 localhost 的限制disable_ssl=true— 使用纯 HTTP(默认是 HTTPS 并自动生成证书)
客户端使用服务端 IP 地址连接:
安全警告:绑定
0.0.0.0并使用disable_ssl=true会将数据库以明文形式暴露到网络。 仅在可信网络或防火墙后面使用。生产环境请使用带 TLS 的反向代理。
客户端连接
-connect 参数会自动发现服务端输出的 token。手动连接时,从服务端启动
信息中复制 token 即可。
注意事项
- stdin 必须保持打开:服务端在 stdin 关闭后会退出(后台运行用
< /dev/zero或tail -f /dev/null |) - 仅限本地:远程访问需要带 TLS 终止的反向代理
- Auth token:自动生成,启动时打印,客户端连接时必须提供
- pgcli 不管理 Quack server:
pg addon start/stop duckdb仍然是无操作——Quack server 是一个独立进程,需要自行管理
扩展
DuckDB 的扩展系统在首次使用时安装共享库。pgcli 在安装时预装 httpfs——
它启用通过 HTTP/S3 读取文件的能力。parquet 和 json 是静态链接在 DuckDB CLI
二进制里的,始终可用,无需网络下载。
httpfs—— 通过 HTTP/S3 读取文件(read_parquet('s3://...'))parquet—— Parquet 文件读写器(内置)json—— JSON 文件读取器(read_json_auto(...))(内置)
后续可以从 DuckDB shell 里安装更多扩展:
如果希望手动管理扩展,或安装时主机无法上网,可在安装时加 --no-extensions。
访问 S3、Parquet、JSON 和 CSV 数据
DuckDB 天生为文件分析查询而生——无需服务器、无需 COPY、无需导入步骤。
直接指向你的数据即可。
Parquet 文件
JSON 文件
CSV 文件
S3 访问(httpfs 扩展)
httpfs 扩展启用 S3 访问。凭据和端点通过 CREATE SECRET(v2.0.0+)或传统
SET 命令(v1.5.x)配置。
DuckDB v2.0.0+ — CREATE SECRET
DuckDB v1.5.x — 传统 SET 命令
连接 pgcli 管理的 S3 存储
pgcli 管理了多种 S3 兼容存储(MinIO、silo、rustfs、garage、seaweedfs)。
DuckDB 直接查询它们——s3_endpoint 就是存储的 HTTP 地址,凭据来自
pg addon password。
先创建 S3 存储实例和桶。MinIO、silo(Pigsty 的 MinIO 分支)和 rustfs
(Rust 重新实现)的设置方式完全相同——它们共用 pg mc 包装命令,DuckDB
端也需要相同的 s3_url_style = 'path':
然后从 DuckDB 查询——MinIO、silo、rustfs 及兼容存储需要
URL_STYLE 'path'(不支持 virtual-host style):
如需更持久的配置,创建 DuckDB 启动文件(~/.duckdbrc),其中的命令
会在每次会话启动时执行:
S3 环境变量
DuckDB 从 DUCKDB_S3_* 环境变量读取端点和 SSL 设置,但凭据必须使用标准 AWS
变量(AWS_ACCESS_KEY_ID、AWS_SECRET_ACCESS_KEY)——DuckDB 的
enable_global_s3_configuration 会自动检测这些变量。
v2.0.0 限制:
URL_STYLE无法通过环境变量设置。它默认为 virtual-host, 会导致 MinIO/silo/rustfs(需要 path-style)无法访问。对于这些存储, 应使用CREATE SECRET并指定URL_STYLE 'path'。
对于 MinIO/silo/rustfs,仍需设置 s3_url_style = 'path',因为这些存储
不支持虚拟主机样式 URL。
16.28 - Node_exporter
pg addon install nodeexporter 以受管容器方式运行 node_exporter——Prometheus 官方的宿主机指标导出器,在 /metrics 上发布整台机器的 CPU、内存、磁盘、网络与文件系统指标——直接跑官方 docker.io/prom/node-exporter 镜像。与 Prometheus、VictoriaMetrics、Grafana 一样,它是顶层插件:它描述的是机器,不是某个 PostgreSQL 实例,故不依附于任何一个。它是 pgcli Prometheus 插件的天然搭档——node_exporter 负责暴露、Prometheus 负责抓取。
node_exporter 与 VictoriaMetrics 形状相同——单容器、无配置文件(一切都作为镜像名之后的 CLI flag 传入)、无热重载、无鉴权——但它有一条别的 web 插件都没有的硬性要求,见下。
不可协商的一条:宿主挂载。 一个容器若读自己的
/proc,报的是容器的指标, 不是宿主的。为导出整台机器,pgcli 写死三条只读 bind 挂载——宿主/proc→/host/proc、/sys→/host/sys、/→/rootfs——并用--path.procfs/--path.sysfs/--path.rootfs把二进制指向它们。这三个挂载固定在 pgcli 里、 不由用户配置;少了它们,addon 会静默导出错误的数字。它们只以:ro挂载(绝不 用:z),所以 pgcli 绝不会给宿主根文件系统重 labeling。
没有登录/密码,也没有 TLS。 node_exporter 只用纯文本提供
/metrics。因为抓取 它的 Prometheus 通常在另一台主机上,Listen默认0.0.0.0——任何能连到端口的 人都能读到细粒度的宿主信息(硬件、挂载表、网络)。请把它限制在你的监控网段内,或 对同机 Prometheus 传--listen 127.0.0.1。
平台:Linux 是已实测的平台(容器用宿主网络,故导出器直接绑宿主端口)。设计上也 支持 macOS(bridge + 发布端口),但尚未在 Mac 上验证过。
参数
| 参数 | 默认值 | 说明 |
|---|---|---|
--name |
nodeexporter |
实例名(map 键) |
--image |
docker.io/prom/node-exporter:v1.12.1 |
覆盖镜像 |
--port |
自动(基址 9100) | 宿主端口;自 nodeexporter_start_port 自动分配 |
--listen |
0.0.0.0 |
绑定地址——默认开放全部网卡(跨主机 Prometheus);127.0.0.1 收紧为仅回环 |
--extra-arg |
— | 传给二进制的额外 CLI flag(可重复);最后追加,故 last-wins 覆盖早期 flags。如 --extra-arg --no-collector.cpufreq |
--force |
false | 即使正在运行也重建——用于应用改动的端口/监听/额外参数 |
node_exporter 无状态——它不落盘,故没有数据目录。pg addon remove 只删容器;重装总是从头开始。
安装
命令会按需拉取镜像、带着三条宿主挂载创建容器,并打印抓取 URL:
因为默认绑定是 0.0.0.0,summary 还会打印一条暴露警告和对应的 Prometheus 命令。验证导出器给的是真实宿主数字:
这些值应与宿主一致(free -b、cat /proc/loadavg)——若它们跟的是容器而非宿主,说明缺了挂载(经 pgcli 不可能缺,但手跑的 node_exporter 经常犯这个错)。
接入 Prometheus
导出器只有被抓取才有意义。pgcli 的 Prometheus 插件靠一行 --scrape job 就能抓它(无需服务发现、无需改 Prometheus 配置):
要把这个导出器加进一个已经在跑、已经在抓别的 job 的 pgcli Prometheus,用
--scrape-append——它把 node-exporter job 折叠进已存的抓取列表(同名 job 合并、
新名字追加)并 SIGHUP 热重载,绝不会清掉现有 target:
(普通的 --scrape 会整体替换 job 集,所以用重跑 install 会丢掉 p1 原本在抓的
其他 job。)之后在 Prometheus 里,up{job="node-exporter"} 为 1、
count(node_cpu_seconds_total) 很大。--scrape / --scrape-append 的完整机制见
Prometheus 插件。
对运行中的实例重复 install:当配置确实变了(端口、监听、额外参数)时重建,没变则原样不动;--force 即使配置未变也重建。node_exporter 没有 SIGHUP reload,所以每次改动都需要重建容器。
自定义参数(--extra-arg)
node_exporter 有几十个 collector flag。经 --extra-arg(可重复)逐个开关 collector,或加 --collector.*/--no-collector.*:
额外参数最后追加到命令行,故经 last-wins 覆盖 pgcli 注入的默认值(包括 pgcli 自己的 --collector.filesystem.mount-points-exclude——它过滤掉 pgcli 刚挂上的那些伪文件系统,让 node_filesystem_* 不全是噪音)。没有配置文件要编辑。
监听与暴露
Listen 默认 0.0.0.0:在 Linux 上容器共享宿主网络命名空间,所以 /metrics 在宿主每个网卡的分配端口上可达。这是刻意的——抓取 node_exporter 的 Prometheus 通常在另一台机器。但没有鉴权,且 node_exporter 暴露细粒度宿主细节(挂载表、网络接口、硬件)。对同机 Prometheus,传 --listen 127.0.0.1(用 --force 重建);否则请在防火墙层面限制端口。(在 macOS bridge 构建下,pgcli 会自动把绑定放宽到 0.0.0.0,好让发布的端口经 podman VM 保持可达。)
端口
每个 node_exporter 实例占有一个端口——指标端点——取自以 nodeexporter_start_port(默认 9100,node_exporter 惯例端口)为基址的端口池,每多一个实例递增,并跳过任何被受管容器或 used_ports 占用的端口。分配结果写入 pg.yaml,故重装绝不会把别的插件在用的端口再发一遍。9100 与 Prometheus(9090)、VictoriaMetrics(8428)、Grafana(3000)、Alertmanager(9093)、nginx(8080)都不重号;显式 --port 仍优先。
配置
没有配置文件——一切都作为 CLI flags 传给 node_exporter 二进制,三条宿主挂载由 pgcli 固定。extra_args 是最后追加的 flag 列表。
列出
没有凭据可揭示——node_exporter 本就没有。
启停
容器以 podman 的 unless-stopped 重启策略运行,宿主重启后随 podman 服务自动回来。start/stop 显式管理它们:
开机自启
要让 node_exporter 实例在宿主开机时自动启动:
这会在 pg.yaml 里给实例打标记,并安装/刷新一个开机服务(Linux 上是 systemd --user unit,macOS 上是 LaunchAgent)来执行 pg start --autostart。开机启动只负责把容器拉起来——node_exporter 无状态,无需准备任何东西。完整机制、状态与关闭方式见 开机自启。
移除
node_exporter 不在磁盘上留存任何东西,故 --clean-data 除了给一条提示外没有任何效果。
日志
排障
- 指标像是容器的而非宿主的 —— 经
pg addon install不可能(挂载写死了),但若你手跑node_exporter,必须传/proc//sys//的只读挂载和对应的--path.*flags。用curl -s …/metrics | grep node_uname_info并把node_memory_MemAvailable_bytes与宿主free -b对比来确认。 - Prometheus 显示 target DOWN —— 确认导出器在运行(
pg addon list)、抓取目标的地址/端口对得上(--scrape node-exporter=<host>:<port>),且一个--listen 127.0.0.1的导出器没被别的机器抓。 - 某个 collector flag 没生效 ——
--extra-arg在容器创建时应用。用--forcereinstall 重建,再查pg logs addon nodeexporter看 flag 是否出现在启动行上。 - 端口已被分配 —— 已分配的端口记在
pg.yaml里并为后续实例跳过;先pg addon remove释放,或用--port N另选一个。
已知限制
- 无鉴权、无 TLS。 node_exporter 提供纯文本
/metrics;0.0.0.0绑定的唯一保护是网络边界。若要离开可信网络,请前置 nginx 或用防火墙限制。 - 无状态 —— 没有数据目录、没有保留期;除运行中的进程外什么都不留存。重装总是从头开始。
- rootful 假设。
-v /:/rootfs:ro挂载读整棵宿主目录树;rootless podman 下部分宿主路径可能读不到。所有目标 VM 都跑 rootful podman,与 pgcli 其余部分一致。 - pgcli 不校验
--extra-arg值;有错的 flag 只会在启动时从容器日志里暴露。 - macOS 上容器跑在 podman machine 里、端口发布出来;目前只在 Linux 上验证过。
16.29 - InfluxDB
pg addon install influxdb 以受管容器方式运行 InfluxDB 1.13.1 OSS——单节点时序数据库,TSM 存储、InfluxQL 查询——直接跑 pgcli 自建的 ghcr.io/mars-base/pgcli/pgcli-influxdb 镜像(官方 linux/amd64 发行版二进制 + Alpine)。与 VictoriaMetrics 一样,它是顶层插件,不前置任何 PostgreSQL 实例。
本插件仅支持 Linux/amd64(镜像为单架构)。InfluxDB 1.x 没有集群/HA、没有内置 Web UI,且上游已把 v1 置于维护模式;pgcli 固定最后一个 1.13.1 发行版。新部署若需要仍在维护的引擎,可考虑 VictoriaMetrics 或 VictoriaLogs;InfluxDB 1.x 适合需要与既有工具保持 InfluxQL 兼容的场景。
认证始终开启。 安装时生成 admin 密码,存入
pg.yaml,可随时用pg addon password influxdb [--name NAME]取回。HTTP API 不允许匿名访问。 备份/恢复 RPC 端口设计上仅绑回环——上游对该协议没有认证,所以无论--listen如何设置,pgcli 都不会把它暴露到网络上。
参数
| 参数 | 默认值 | 说明 |
|---|---|---|
--name |
influxdb |
实例名(map 键;各子命令的默认名同名) |
--version |
1.13.1 |
固定的镜像发行版(决定使用哪个已发布的 pgcli-influxdb tag) |
--data-dir |
<base_dir>/addon/influxdb/<name>/data |
数据目录,绑定挂载到容器内 /var/lib/influxdb;绝对路径或相对 base_dir |
--listen |
0.0.0.0 |
HTTP 绑定地址。RPC 端点始终只绑 127.0.0.1 |
--http-port |
自动(基址 8086) | HTTP 宿主端口;自 influxdb_start_port 自动分配 |
--rpc-port |
自动(基址 8088) | 备份/恢复 RPC 端口,仅回环;每个实例分配唯一端口 |
--admin-user |
admin |
InfluxQL 管理员用户名(无 Web UI) |
--admin-password |
自动生成 | 管理员密码。--force 时默认保留已存密码,除非显式指定 |
--config-option |
— | 额外 influxdb.conf 设置,格式 KEY=VALUE(顶层)或 SECTION.KEY=VALUE(表),值为原始 TOML(可重复)。替换已存覆盖列表并持久化 |
--force |
false | 重写生成的配置并重建容器,保留数据与已存管理员密码 |
生成的配置只覆盖路径、监听和认证,其余全部沿用上游 1.13.1 默认值。渲染后的文件位于 <base_dir>/addon/influxdb/<name>/influxdb.conf,只读挂载进容器。覆盖项同时存入 pg.yaml 和该文件,因此重启与重新渲染都不会丢失。
安装
第二个及之后的实例会自动从各自端口池取下一个空闲 HTTP/RPC 端口(例如 8087/8089),并可用 --data-dir 指向任意目录:
不带 --force 重复 install 是幂等空操作,只报告当前状态。--force 会重写配置并重建容器,但保留数据目录与已存管理员密码(否则既有 admin 用户将无法登录);由于 --config-option 会替换已存列表,需要保留覆盖项时必须重新传入。
凭据
密码保存在 pg.yaml(默认权限 0644——请保护该文件)。所有需要凭据的命令都通过环境变量把凭据传给容器,绝不放进 podman 命令行。
配置覆盖
config set/unset 同时改写生成的配置文件与 pg.yaml;--restart 会重建容器使改动立即生效,不带 --restart 则下次启动生效。值按原始 TOML 处理:字符串要带引号('data.index-version="tsi1"'),数字直接写。
上游默认索引是 inmem。高基数序列请设置 data.index-version="tsi1"——该设置只能在全新数据目录上修改,务必在写入数据前决定。
pg influx CLI
pg influx 从同一镜像启动一个一次性容器运行 influx 客户端——宿主机不安装任何东西,命令结束后容器自动删除。pg 自身解析器不认识的 flag 放在 -- 之后;参数按原样、按顺序透传。
本地实例
主机、端口和凭据会自动注入。
远程 InfluxDB 1.x
两种模式都支持从 INFLUX_USERNAME / INFLUX_PASSWORD 回退取凭据。优先用环境变量而非 --password,后者会出现在进程列表中。--ssl 启用 HTTPS;--unsafe-ssl 跳过证书校验,必须与 --ssl 同用。--dsn 已携带全部连接信息,不能与 --host/--port/--ssl/... 组合;--name 不能与任何远程 flag 组合。
远程模式下的 import 文件
-path 指定的文件会以原绝对路径绑定挂载进一次性容器,所以 pg addon influxdb export(或 influx_inspect export)产出的文件也能导入远程服务器。
# DML 段和 # CONTEXT-DATABASE: 标记是必需的,# DDL 段可选。目标库可能不存在时再带上 DDL——库已存在时 CREATE DATABASE 是空操作。导入目标由 context 标记决定,不是 --database;目标库不存在又没有 DDL 时会报 database not found。没有 # DML 头的文件会按 InfluxQL 解析并报语法错误。
/tmp/demo.txt 是运行 pg 的机器上的文件(它被挂载进容器),不是 db.example.com 上的文件。最简样例:
# DDL 段按 InfluxQL 在数据之前执行(CREATE DATABASE 对已存在的库是空操作)。# DML 之后全部是 line protocol;时间戳可省略(默认纳秒,-precision 可改变解释方式)。可用 -pps N 限速;文件是 gzip 压缩时加 -compressed。远程服务器开启认证时,用 --username/--password 或环境变量 INFLUX_USERNAME/INFLUX_PASSWORD 传凭据。
跨主机操作
pg influx 的远程模式完全在客户端机器上运行——只需要 podman 和 pgcli-influxdb 镜像,不需要本机安装 InfluxDB,也不需要本地实例。
addon 的生命周期和备份命令只操作本机的 podman 实例,无法直接跨主机:pg addon influxdb status|backup|restore|export|import、pg addon start|stop|remove 和 pg logs addon influxdb 都必须在 InfluxDB 所在主机上执行——登录到该主机后直接运行。在其他机器上请使用 pg influx --host/--dsn 或下文介绍的 HTTP API(/query、/write)。
跨主机迁移数据用 export + import:
恢复使用仅回环的 RPC 端点,因此备份目录必须在目标主机上,并在目标主机上执行恢复:
常用操作
数据库
保留策略(retention policy)
每个数据库至少有一个保留策略;默认的 autogen 永不过期。保留策略决定数据保留多久、按什么粒度分片,过期分片会被自动删除。
等价的 InfluxQL:
DURATION支持h、d、w和INF,最小值为1h;SHARD DURATION可选,默认由DURATION推导。- 只能有一个
DEFAULT策略;把另一个设为默认会自动降级原来的默认策略。不带 RP 前缀的查询使用默认策略。 DROP RETENTION POLICY会删除该策略的数据。过期分片由后台保留检查清理(默认每 30 分钟一次),因此磁盘占用会在下一次检查后下降。
交互式 shell
直接输入 InfluxQL(SHOW MEASUREMENTS、SELECT ...、use otherdb 等);exit 或 Ctrl-D 退出。--database 指定初始数据库上下文。
查看 schema
InfluxQL 引号规则:双引号用于标识符("host"、"cpu"),单引号用于字符串值('server01')。查询本身含双引号时,像 SHOW TAG VALUES 示例那样用单引号包住整个 -execute 参数。
查询数据
写入与删除
批量写入用 pg influx ... -- -import -path FILE,见导出与导入。
用户与权限
不要把管理员密码放进 argv(ps 可见);用交互式 shell、INFLUX_USERNAME/INFLUX_PASSWORD,或 --password '' 交互输入。
日志与状态
HTTP API(URL 操作)
插件在 <listen>:<http-port> 上提供标准 InfluxDB 1.x HTTP API,认证始终开启(HTTP Basic)。同样的调用既适用于本机(http://127.0.0.1:8086),也适用于远程(http://db.example.com:8086,或由 nginx 终止 TLS 后的 https://influx.example.com)。
| 参数 | 端点 | 含义 |
|---|---|---|
db |
/query、/write |
目标数据库(多数语句必需) |
rp |
/query、/write |
目标保留策略(默认用数据库的默认策略) |
q |
/query |
InfluxQL 语句;需要 URL 编码(--data-urlencode 会处理) |
pretty=true |
/query |
缩进 JSON 响应 |
epoch |
/query |
ns、u、ms、s、m、h;省略则返回 RFC3339 |
precision |
/write |
写入行协议里的时间戳精度(默认 ns) |
说明:
- 未认证请求返回
401。写入成功返回204;非法数据点返回400和partial write信息,指出被丢弃的点。 curl -u user:pass会让密码出现在进程列表中。建议使用权限 0600 的 netrc 文件:machine db.example.com login admin password secret,然后curl --netrc-file ~/.netrc-influx ...。- 本机日常操作优先用
pg influx(自动通过环境变量传凭据);HTTP API 适合从其他机器、脚本和应用集成。
备份与恢复
portable 备份是在线的——实例保持运行:
默认输出目录为 <实例目录>/backups/<UTC 时间戳>;--out DIR 可覆盖。备份目录包含 .manifest、.meta 和按 shard 的 .tar.gz 文件。
⚠️ 备份文件要放到远程 S3。 备份默认写在 InfluxDB 主机本地,主机损坏时备份会一起丢失。请用任意 S3 客户端把每个备份目录上传,例如
pg mc插件:
恢复时 restore 通过回环 RPC 读取备份目录,因此需要先把备份下载回目标主机再执行 pg addon influxdb restore。
恢复同样是在线的(辅助容器经回环 RPC 与运行中的服务通信;实例已停止时会先启动)。单个数据库完整的"备份 → 删除 → 恢复"流程:
influxd restore 拒绝覆盖已存在的数据库。如果想在保留老库的同时另存一份,用新库名恢复——--newdb 必须与 --database 同用:
恢复完成后实例保持运行。
⚠️ 恢复不是完整的身份/元数据副本。 用户、权限和连续查询不会被恢复,需要手工重建;保留策略及其 duration 可以正确恢复。
导出与导入
export 是离线的:pgcli 先停止容器,让 influx_inspect 读取一致的 TSM 文件,结束后自动重启(--no-start 可保持停止)。
默认文件为 <实例目录>/exports/<database|all>-<UTC 时间戳>.txt(--compress 时加 .gz)。
⚠️ 导入后必须手工重建:用户、权限、连续查询,以及非默认保留策略的真实
DURATION/SHARD DURATION。 导出 DDL 会把 RP 写成DURATION 0s且不带 shard duration,不修正的话 30d 保留期会变成永久。
导出文件是逻辑数据副本,不是完整实例快照:它包含全部数据点(TSM + WAL),按库/保留策略写入 context 标记,并附带 CREATE DATABASE / CREATE RETENTION POLICY DDL。不加 --database 时会导出所有库(含 _internal)。灾备请用 backup/restore,export/import 适合跨实例搬运数据点。
import 需要实例运行中,并接受 export 格式文件:
带 --database 时,pgcli 会把文件复制到实例目录下并改写 # CONTEXT-DATABASE: 行到目标数据库,原导出文件不受影响。导入远程服务器用 pg influx --host ... -- -import -path FILE(见上文)。
与 restore 不同,import 没有"必须换新库名"的限制:它把 line protocol 数据点写入目标库并与已有数据合并(相同 series + 时间戳的点会覆盖旧值)。上面的 demo_copy 只是导入到另一个库的示例——导入回原来的 demo 完全可以。目标库必须已存在(文件里的 # DDL 可以建库),除非 --database 指定的库是你事先建好的。
生命周期
不带 --clean-data 时,数据与生成的配置在 remove 后仍保留,同名重装即可复活已存序列。--clean-data 也会删除自定义的 --data-dir。
开机自启:
开机时 pgcli 只启动既有容器,不会重新执行认证 bootstrap。
生产注意事项
- 没有 HTTPS 监听。 InfluxDB 1.x 只提供明文 HTTP。需要 TLS 时用 nginx 插件 前置,或
--listen 127.0.0.1保持本地访问。 - RPC 仅回环。 备份/恢复 RPC 协议没有认证;pgcli 把它固定在
127.0.0.1,每个实例分配唯一端口。不要对外暴露。 - 没有 HA。 InfluxDB 1.x OSS 是单节点。备份(
backup/export)是唯一的灾备手段;pgcli 不做定时备份,需要时把pg addon influxdb backup接入你自己的定时任务。 - v1 处于维护模式。 版本固定 1.13.1;升级意味着发布新镜像 tag 并重新测试,且不存在从 v1 到 v2/v3 的原地升级路径。
- 高基数: 在写入数据前切换到
data.index-version="tsi1"(见上文)。 - 平台: 仅 Linux/amd64——镜像打包的是官方 amd64 压缩包。
完整 influxdb.conf 参数参考
pgcli 生成的配置是最小化的:只写入下表中的托管项,其余全部沿用 InfluxDB 1.13.1 的默认值。下表默认值来自 1.13.1 的 influxd config 实际输出。
| pgcli 托管键 | 渲染值 | 说明 |
|---|---|---|
reporting-disabled |
true |
关闭上游使用统计上报 |
bind-address(顶层) |
"127.0.0.1:<rpc-port>" |
备份/恢复 RPC;上游对此协议无认证,因此永不对外监听 |
[meta] dir |
/var/lib/influxdb/meta |
位于 data-dir 挂载内 |
[data] dir |
/var/lib/influxdb/data |
位于 data-dir 挂载内 |
[data] wal-dir |
/var/lib/influxdb/wal |
位于 data-dir 挂载内 |
[http] bind-address |
<listen>:<http-port> |
容器使用 host 网络,这就是宿主机实际监听地址 |
[http] auth-enabled |
true |
认证始终开启 |
这七个键会被 --config-option / config set 拒绝——请通过 pg addon install / config 的对应参数修改。其余键均可覆盖。
覆盖方式
值按原始 TOML 写入——字符串、时长、大小要加引号('cache-max-memory-size="2g"'),数字和布尔值不加引号。限制:
- 只支持单表
[section]。重复表[[graphite]]、[[collectd]]、[[opentsdb]]、[[udp]]无法表达;嵌套键(如https-client-ca.paths、http.headers)同样无法表达。 - 这类设置需要直接编辑
<base_dir>/addon/influxdb/<name>/influxdb.conf并重启容器(pg addon stop|start influxdb)。普通启停不会改动文件;install --force以及任何config set/unset都会重新渲染配置,手工修改会丢失。 --config-option会整体替换已保存的覆盖列表;改其中一项时要重新传全部条目。
顶层
| 键 | 默认值 | 说明 |
|---|---|---|
reporting-disabled |
false |
pgcli 渲染为 true(托管) |
bind-address |
"127.0.0.1:8088" |
备份/恢复 RPC 监听地址(托管) |
hardening-enabled |
false |
限制 Flux HTTP 请求只能访问公网地址(SSRF 加固) |
[meta]
| 键 | 默认值 | 说明 |
|---|---|---|
dir |
~/.influxdb/meta(容器内 /var/lib/influxdb/meta) |
元数据/Raft 数据库目录(托管) |
retention-autocreate |
true |
CREATE DATABASE 时自动创建 autogen 保留策略 |
logging-enabled |
true |
输出 meta 服务日志 |
[data]
| 键 | 默认值 | 说明 |
|---|---|---|
dir |
~/.influxdb/data |
TSM 存储目录(托管) |
wal-dir |
~/.influxdb/wal |
WAL 目录(托管) |
wal-fsync-delay |
"0s" |
fsync 前的批量窗口;0s 每次写入都 fsync,0–100ms 对非 SSD 磁盘有帮助 |
index-version |
"inmem" |
分片索引:inmem(默认)或 tsi1(高基数;只能在全新数据目录上设置) |
validate-keys |
false |
拒绝非法 UTF-8/unicode 的 key(少量写入开销) |
skip-field-size-validation |
false |
跳过写入时的字段大小上限检查 |
strict-error-handling |
false |
返回错误而不是静默丢弃非法点(如 SELECT INTO 中的 ±Inf) |
query-log-enabled |
true |
执行前记录查询日志(可能包含敏感数据) |
cache-max-memory-size |
"1g" |
单分片 cache 上限,超出后拒绝写入 |
cache-snapshot-memory-size |
"25m" |
达到该大小即把 cache 快照为 TSM |
cache-snapshot-write-cold-duration |
"10m" |
多久无写入/删除后快照 cache |
compact-full-write-cold-duration |
"4h" |
多久无写入/删除后做 full compaction |
compact-throughput |
"48m" |
compaction 写入限速(字节/秒) |
compact-throughput-burst |
"48m" |
允许的短时突发上限 |
aggressive-points-per-block |
10000 |
激进 compaction 的每块点数 |
ingress-metric-by-measurement-enabled |
false |
按 measurement 统计写入指标 |
ingress-metric-by-login-enabled |
false |
按登录用户统计写入指标 |
max-series-per-database |
1000000 |
每库 series 上限,超出后丢弃写入(0 = 不限) |
max-values-per-tag |
100000 |
每 tag 的 value 上限(0 = 不限) |
max-concurrent-compactions |
0 |
并发 full/level compaction 数;0 = GOMAXPROCS 的 50% |
max-concurrent-optimized-compactions |
0 |
并发 optimized compaction 数(0 = 默认) |
max-index-log-file-size |
"1m" |
TSI 索引 WAL 压缩进索引文件的阈值 |
max-concurrent-deletes |
1 |
每分片同时执行的 DELETE 数 |
series-id-set-cache-size |
100 |
TSI series-id-set 缓存条目数(0 关闭) |
series-id-set-cache-max-size |
0 |
自适应扩容上限(0 = 关闭自适应) |
series-id-set-cache-target-hit-rate |
0.0 |
自适应缩容的目标命中率(0.0 = 关闭,1.0 非法) |
series-id-set-cache-shrink-conservatism |
2.5 |
缩容策略的标准差余量 |
series-file-max-concurrent-snapshot-compactions |
0 |
并发 series-file 快照压缩数(0 = 默认) |
trace-logging-enabled |
false |
TSM 引擎详细 trace 日志 |
tsm-use-madv-willneed |
false |
对 TSM 文件使用内核 MADV_WILLNEED 建议 |
tar-stream-buffer-size |
"1m" |
备份/改名/复制时 tar 流缓冲区大小 |
[coordinator]
| 键 | 默认值 | 说明 |
|---|---|---|
write-timeout |
"10s" |
写入等待超时 |
max-concurrent-queries |
0 |
并发查询上限(0 = 不限) |
query-timeout |
"0s" |
查询超时后被终止(0s = 不限) |
log-queries-after |
"0s" |
慢查询日志阈值(0s = 关闭) |
log-timedout-queries |
false |
记录被 query-timeout 杀掉的查询 |
max-select-point |
0 |
单 SELECT 最大处理点数(0 = 不限) |
max-select-series |
0 |
单 SELECT 最大 series 数(0 = 不限) |
max-select-buckets |
0 |
GROUP BY time() 的最大桶数(0 = 不限) |
termination-query-log |
false |
收到 SIGTERM 时打印正在执行的查询 |
[flux-controller]
| 键 | 默认值 | 说明 |
|---|---|---|
query-concurrency |
0 |
并发 Flux 查询数(0 = 默认) |
query-initial-memory-bytes |
"0" |
每个 Flux 查询的初始内存预算 |
query-max-memory-bytes |
"0" |
每个 Flux 查询的内存上限 |
total-max-memory-bytes |
"0" |
所有 Flux 查询的总内存上限 |
query-queue-size |
0 |
Flux 查询队列长度 |
[retention]
| 键 | 默认值 | 说明 |
|---|---|---|
enabled |
true |
启用保留策略执行 |
check-interval |
"30m" |
保留策略检查间隔 |
[shard-precreation]
| 键 | 默认值 | 说明 |
|---|---|---|
enabled |
true |
数据到达前预创建分片 |
check-interval |
"10m" |
预创建检查间隔 |
advance-period |
"30m" |
在分片组结束前多久创建下一组 |
[monitor]
| 键 | 默认值 | 说明 |
|---|---|---|
store-enabled |
true |
记录内部统计 |
store-database |
"_internal" |
目标数据库 |
store-interval |
"10s" |
记录间隔 |
[http]
| 键 | 默认值 | 说明 |
|---|---|---|
enabled |
true |
启用 HTTP API |
bind-address |
":8086" |
HTTP 监听地址(托管:<listen>:<http-port>) |
auth-enabled |
false |
要求认证(托管:true) |
realm |
"InfluxDB" |
Basic-auth 质询 realm |
log-enabled |
true |
HTTP 请求日志 |
suppress-write-log |
false |
从 HTTP 日志中隐藏写请求 |
access-log-path |
"" |
访问日志文件;空则写 stderr |
access-log-status-filters |
[] |
状态码过滤(NNN、NNX、NXX);空则全部记录 |
write-tracing |
false |
详细写日志 |
flux-enabled |
false |
启用 Flux 查询端点 |
flux-log-enabled |
false |
记录 Flux 查询 |
flux-testing |
false |
暴露 Flux 测试端点 |
pprof-enabled |
true |
启用 /debug/pprof |
pprof-auth-enabled |
false |
pprof 要求管理员认证 |
debug-pprof-enabled |
false |
启动时额外在 localhost:6060 绑定 pprof |
ping-auth-enabled |
false |
/ping、/metrics、/status 要求认证 |
prom-read-auth-enabled |
false |
Prometheus remote-read API 要求认证 |
https-enabled |
false |
提供 HTTPS |
https-certificate |
"/etc/ssl/influxdb.pem" |
TLS 证书(PEM) |
https-private-key |
"" |
TLS 私钥;空表示与证书合并存放 |
https-insecure-certificate |
false |
允许不安全的证书文件权限 |
https-ignore-sanity-checks |
false |
加载未通过服务端认证检查的证书 |
https-client-auth-type |
未设置 | 双向 TLS 客户端认证模式(NoClientCert、RequestClientCert、RequireAnyClientCert、VerifyClientCertIfGiven、RequireAndVerifyClientCert) |
https-client-ca |
未设置 | 客户端 CA:{ paths = [...], include-system = false } |
shared-secret |
"" |
JWT 校验密钥 |
max-row-limit |
0 |
分块结果行数上限(0 = 不限) |
max-connection-limit |
0 |
并发 HTTP 连接上限(0 = 不限) |
unix-socket-enabled |
false |
通过 Unix socket 提供 HTTP |
unix-socket-permissions |
"0777" |
Unix socket 权限 |
bind-socket |
"/var/run/influxdb.sock" |
Unix socket 路径 |
max-body-size |
25000000 |
请求体最大字节数(0 = 不限) |
max-concurrent-write-limit |
0 |
并发写入上限(0 = 不限) |
max-enqueued-write-limit |
0 |
排队写入上限(0 = 不限) |
enqueued-write-timeout |
"30s" |
写入最长排队等待时间 |
user-query-bytes-enabled |
false |
按用户统计查询响应字节数 |
user-write-bytes-enabled |
false |
按用户统计写入请求字节数 |
[http.headers] |
未设置 | 任意响应头,如 X-Header-1 = "value" |
[logging]
| 键 | 默认值 | 说明 |
|---|---|---|
format |
"auto" |
日志编码:auto、logfmt、json |
level |
"info" |
最低级别:error、warn、info、debug |
suppress-logo |
false |
不打印启动 logo |
[subscriber]
| 键 | 默认值 | 说明 |
|---|---|---|
enabled |
true |
启用订阅(把每次写入复制到指定端点) |
http-timeout |
"30s" |
订阅写入超时 |
insecure-skip-verify |
false |
跳过订阅端点的 TLS 校验 |
ca-certs |
"" |
PEM CA 包路径;空则使用系统根证书 |
root-ca |
未设置 | 额外的订阅 CA:{ paths = [...], include-system = false } |
certificate |
"" |
向订阅端点出示的客户端证书(双向 TLS) |
private-key |
"" |
certificate 对应的私钥 |
insecure-certificate |
false |
允许不安全的证书/私钥文件权限 |
ignore-cert-sanity-checks |
false |
加载未通过服务端认证检查的证书 |
write-concurrency |
40 |
写协程数 |
write-buffer-size |
1000 |
channel 中缓冲的写入数 |
total-buffer-bytes |
"0" |
总缓冲预算(0 = 不限) |
重复监听表
这些是数组表([[graphite]]、[[collectd]]、[[opentsdb]]、[[udp]]),无法用 --config-option 设置;需要时直接编辑生成的文件。每张表默认关闭。
| 段 | 键 | 默认值 | 说明 |
|---|---|---|---|
[[graphite]] |
enabled |
false |
启用 Graphite 监听 |
bind-address |
":2003" |
监听地址 | |
database |
"graphite" |
目标数据库 | |
retention-policy |
"" |
目标保留策略 | |
protocol |
"tcp" |
tcp 或 udp |
|
consistency-level |
"one" |
写入一致性级别 | |
batch-size |
5000 |
刷盘前缓冲点数 | |
batch-pending |
10 |
挂起批次数 | |
batch-timeout |
"1s" |
最长刷盘等待 | |
udp-read-buffer |
0 |
UDP 读缓冲(0 = 系统默认) |
|
separator |
"." |
模板 measurement 各部分的连接符 | |
tags |
[] |
默认 tag,如 ["region=us-east"] |
|
templates |
[] |
模板规则(默认一条) | |
[[collectd]] |
enabled |
false |
启用 collectd 监听 |
bind-address |
":25826" |
监听地址 | |
database |
"collectd" |
目标数据库 | |
retention-policy |
"" |
目标保留策略 | |
typesdb |
"/usr/share/collectd/types.db" |
types DB 文件或目录 | |
security-level |
"none" |
none、sign、encrypt |
|
auth-file |
"/etc/collectd/auth_file" |
签名/加密流量的认证文件 | |
batch-size |
5000 |
刷盘前缓冲点数 | |
batch-pending |
10 |
挂起批次数 | |
batch-timeout |
"10s" |
最长刷盘等待 | |
read-buffer |
0 |
UDP 读缓冲(0 = 系统默认) |
|
parse-multivalue-plugin |
"split" |
多值插件按 split 或 join 解析 |
|
[[opentsdb]] |
enabled |
false |
启用 OpenTSDB 监听 |
bind-address |
":4242" |
监听地址 | |
database |
"opentsdb" |
目标数据库 | |
retention-policy |
"" |
目标保留策略 | |
consistency-level |
"one" |
写入一致性级别 | |
tls-enabled |
false |
提供 TLS | |
certificate |
"/etc/ssl/influxdb.pem" |
TLS 证书 | |
private-key |
"" |
TLS 私钥;空表示与证书合并存放 | |
insecure-certificate |
false |
允许不安全的证书文件权限 | |
ignore-cert-sanity-checks |
false |
加载未通过检查的证书 | |
client-auth-type |
未设置 | 双向 TLS 客户端认证模式 | |
client-ca |
未设置 | 客户端 CA:{ paths = [...], include-system = false } |
|
log-point-errors |
true |
记录每个格式错误的点 | |
batch-size |
1000 |
刷盘前缓冲点数(仅 telnet) | |
batch-pending |
5 |
挂起批次数 | |
batch-timeout |
"1s" |
最长刷盘等待 | |
[[udp]] |
enabled |
false |
启用 line protocol UDP 监听 |
bind-address |
":8089" |
监听地址 | |
database |
"udp" |
目标数据库 | |
retention-policy |
"" |
目标保留策略 | |
precision |
"n" |
输入精度:""、n、u、ms、s、m、h |
|
batch-size |
5000 |
刷盘前缓冲点数 | |
batch-pending |
10 |
挂起批次数 | |
batch-timeout |
"1s" |
最长刷盘等待 | |
read-buffer |
0 |
UDP 读缓冲(0 = 系统默认) |
|
writers |
1 |
并行写协程数 |
[continuous_queries]
| 键 | 默认值 | 说明 |
|---|---|---|
enabled |
true |
执行连续查询 |
log-enabled |
true |
记录 CQ 执行日志 |
query-stats-enabled |
false |
把 CQ 统计写入监控库 |
run-interval |
"1s" |
CQ 检查间隔 |
[tls]
| 键 | 默认值 | 说明 |
|---|---|---|
ciphers |
Go 默认 | 允许的加密套件(Go 名称) |
min-version |
Go 默认 | 最低 TLS 版本,如 "tls1.2" |
max-version |
Go 默认 | 最高 TLS 版本,如 "tls1.3" |
插件里的 HTTPS: pgcli 从不开启
[http] https-enabled。需要 TLS 时使用 nginx 插件 做终止,或只监听回环地址。[tls]段只调整已经启用 TLS 的监听器的加密套件/版本协商。
16.30 - Pg_exporter
pg addon install pgexporter 以受管容器方式运行 pg_exporter——Pigsty 的 PostgreSQL 指标导出器——直接跑官方 docker.io/pgsty/pg_exporter 镜像。node_exporter 插件描述的是机器,而 pg_exporter 描述的是一个数据库:它连上某个 PostgreSQL 服务,在 /metrics 上发布约 460 个 pg_* 指标(活动、锁、复制、膨胀、WAL、按库统计……)。与 Prometheus、VictoriaMetrics、Grafana、node_exporter 一样,它是顶层插件:本身不依附任何实例——而是指向它监控的目标。
它与 node_exporter 并存的理由是统一采集:目标既可以是 pgcli 纳管的实例,也可以是任意可达、pgcli 并不托管的 PostgreSQL 服务。这正是它的全部意义——一种导出器形状,同时采集 pgcli 内外的数据库指标。
三种目标模式,互斥——只传其一。
--pg-name <inst>监控一个 pgcli 纳管的实例;其 admin DSN 每次启动都从pg.yaml现推(故轮换过的密码或改动的端口永不会失效)。对 pgcli 不托管的服务器,目标只用一个 URL 给出——而你传哪个 URL 决定 pgcli 是否碰这个库:
--admin-dsn postgres://…——一个 admin 权限 URL。pgcli 用它一次性在目标上建一个低权限监控角色,随后丢弃它:只保留其不含凭据的地址(host/port/database),导出器的 host/port/database 就取自它。此后导出器只以监控角色连库。--dsn postgres://…——原样拨号。导出器就用这个 URL 自带的凭据连接,pgcli 在目标上绝不执行任何 SQL。适合角色已存在、或根本不想让 pgcli 碰库的场景。
--dsn与--admin-dsn从不同时出现——不需要第二个 URL,因为目标的 host/port/database 都来自你传的那一个。
本地与
--admin-dsn两种模式下,导出器从不用 admin 身份连库:pgcli 创建一个低权限监控角色(pg_monitor成员、CONNECTION LIMIT 5),并把该角色的 URL 交给容器。要建这个角色,pgcli 需要一次 admin 权限:--pg-name时 admin 凭据自动派生;--admin-dsn时你给出一个 admin URL,它只用于那一批CREATE ROLE/GRANTSQL、绝不落进pg.yaml。生成的监控密码只打印一次,之后用pg addon password pgexporter取回。在原样拨号的--dsn模式下 pgcli 什么都不建——角色已存在是你(或某次先前的--admin-dsn安装)的事。
导出器端点没有鉴权、没有 TLS,且 pg_exporter 在同一端口上还服务管理端点(
/reload、/explain、/stat)——任何能连到端口的人既能抓指标、也能触发重载。因为抓取它的 Prometheus 通常在另一台主机,Listen默认0.0.0.0,唯一保护是网络边界。请把它限制在监控网段内,或对同机 Prometheus 传--listen 127.0.0.1。
平台:Linux 是已实测平台(宿主网络,故导出器直接绑宿主端口、并能拨本地实例的
127.0.0.1目标)。在 macOS 上容器跑在 pgcli-net bridge 里,够不到本地实例的127.0.0.1DSN——那里用远程模式(--dsn或--admin-dsn)指向一个可达地址(远程路径可用、本地路径不可用)。
参数
| 参数 | 默认值 | 说明 |
|---|---|---|
--name |
pgexporter |
实例名(map 键) |
--pg-name |
— | 按名监控一个 pgcli 纳管实例(admin DSN 每次启动现推;与 --dsn、--admin-dsn 互斥) |
--admin-dsn |
— | 远程建角色模式:一个 admin 权限 URL。pgcli 用它一次性建监控角色后即丢弃——只保留其不含凭据的地址(host/port/database),绝不含 admin 密码;此后导出器以监控角色连接。与 --dsn、--pg-name 互斥 |
--dsn |
— | 远程原样拨号模式:导出器就用这个 URL 自带的凭据连接,pgcli 绝不在目标上跑 SQL——适合角色已存在、或不想让 pgcli 碰库。与 --admin-dsn、--pg-name 互斥 |
--monitor-user |
pgexporter |
pgcli 在目标上创建的低权限角色名(pg_monitor 成员;仅限普通标识符)。多个 exporter 监控同一目标时,每个实例传不同的值 |
--image |
docker.io/pgsty/pg_exporter:1.5.0 |
覆盖镜像 |
--port |
自动(基址 9630) | 宿主端口;自 pgexporter_start_port 自动分配 |
--listen |
0.0.0.0 |
绑定地址——默认开放全部网卡(跨主机 Prometheus);127.0.0.1 收紧为仅回环 |
--config-file |
— | 自定义 pg_exporter 采集器 YAML 的路径,只读挂到 /etc/pg_exporter.yml(不设则用二进制内建的默认采集器集) |
--extra-arg |
— | 传给二进制的额外 CLI flag(可重复);最后追加,故 last-wins 覆盖早期 flags。如 --extra-arg --auto-discovery=false |
--force |
false | 即使正在运行也重建——用于应用改动的端口/监听/目标/额外参数 |
pg_exporter 无状态——它不落盘,故没有数据目录。pg addon remove 只删容器(目标上建的监控角色会保留,因为它可能被共享或手工管理);重装总是从头开始。
安装
监控一个 pgcli 纳管实例(监控角色自动创建):
命令派生本地 admin DSN、在 demo 上建监控角色、按需拉取镜像、用监控 URL 起容器,并打印抓取信息:
监控一个 pgcli 不托管的库、并让 pgcli 建监控角色——只给一个 admin URL;目标的 host/port/database 就取自它:
--admin-dsn 跑一次性的 CREATE ROLE/GRANT pg_monitor 后即被丢弃——pg.yaml 里只留下不含凭据的地址 postgres://db.example.com:5432/app。此后导出器只以 pgexporter 连,pg addon password pgexporter --name ext 取回该角色密码。
若监控角色已存在、你宁可 pgcli 完全不碰目标,就改传一个带可用凭据的 --dsn(与 --admin-dsn 互斥——只给一个 URL,不是两个):
这里 pgcli 不跑任何 SQL:导出器就用这个 URL 及其自带凭据连接。
(要手工建角色,最小集是 CREATE ROLE pgexporter LOGIN CONNECTION LIMIT 5 PASSWORD '…'; GRANT pg_monitor TO pgexporter;——在 PostgreSQL 10–13 上再加 GRANT SELECT (oid) ON pg_subscription。)
验证导出器给的是目标的真实数字:
pg_up 1 表示监控角色连上了。把 pg_version_info 与 pg exec demo -c "SHOW server_version" 对照校验——并确认角色已落到目标上:pg exec demo -c "\du pgexporter"(或查 pg_roles / pg_auth_members 里的 pg_monitor 成员关系)。默认带 --auto-discovery 时,指标会按目标上的每个库带一个 datname 标签。
重复安装与共享的监控用户
建监控角色的 SQL 是幂等的,所以重复 install 绝不会损坏目标上的角色:对一个在跑、配置未变的实例做普通重装是空操作(根本不执行 SQL);--force 重装则对已存在的角色做 ALTER——其 OID 与授权都保留,pg_monitor 成员关系也绝不重复。
--force / 配置变化的重装会做一件事:轮换生成的密码(该实例自身没问题——新密码与注入是同步的——但它会覆盖你手工设过或复用过的密码;用 pg addon password pgexporter 取回当前的)。正因为每次这样的安装都会给角色写入一个新密码,两个 exporter 实例共享同一目标、又都用默认的 pgexporter 用户就会冲突:装第二个时轮换了这个共享角色的密码,第一个手里那份就此过期——它已建立的连接池还能用,但下一次重连会认证失败、掉到 pg_up 0。要对同一目标跑多个 exporter 时,给每个实例配一个自己的角色:
接入 Prometheus
导出器只有被抓取才有意义。pgcli 的 Prometheus 插件靠一行 --scrape job 就能抓它:
要把这个导出器加进一个已经在跑、已经在抓别的 job 的 pgcli Prometheus,用 --scrape-append——它把 pg job 折叠进已存的抓取列表(同名 job 合并、新名字追加)并 SIGHUP 热重载,绝不会清掉现有 target:
(普通的 --scrape 会整体替换 job 集,所以用重跑 install 会丢掉 p1 原本在抓的其他 job。)之后在 Prometheus 里,up{job="pg"} 为 1、count(pg_activity_count) 为正数。--scrape / --scrape-append 的完整机制见 Prometheus 插件。
复用健康探针
pg_exporter 的 /primary 与 /replica 只在目标处于该角色时返回 200,故它们可兼作 HAProxy 式存活探针用于读写路由——把 pgcli 的 HAProxy 接上某个 PostgreSQL 做指向探见的接线见 HAProxy 插件。/up(目标是否可达)与 /read 是另外两个探针。
对运行中的实例重复 install:不带 --force 时是空操作(你传的字段会存下,但容器照旧服务)。--force 则重建容器——--pg-name 模式总会重跑幂等建角色(轮换密码),--admin-dsn 模式只有再传一个新鲜的 --admin-dsn 才重跑(否则沿用已存的监控密码,因为 pgcli 刻意没保留 admin 权限),--dsn 模式从不跑 SQL。改端口 / 监听 / 额外参数需要 --force(重建容器)——只有采集器配置可热重载(见下)。
自定义采集器(--config-file)与重载
pg_exporter 把一整套默认采集器内建在二进制里,并在运行时按服务版本/角色/扩展来规划,所以要拿到有用的指标不需要任何配置文件——大多数安装都不带 --config-file。当你确实要自定义采集器集时,把 --config-file 指向一份 pg_exporter 采集器 YAML;它被只读挂到 /etc/pg_exporter.yml 并经 PG_EXPORTER_CONFIG 选中。在宿主上编辑该文件后,用 SIGHUP 就地热重载(不重启容器):
对没带 --config-file 安装的实例做 reload 是无害的空操作——pgcli 会明说(内建默认集在磁盘上没有可重读的东西)。URL / 端口 / 监听改动不被 SIGHUP 覆盖;用 install --force 重建。
自定义参数(--extra-arg)
pg_exporter 在 env 配置的 URL 之外还有丰富的 flag 面。经 --extra-arg(可重复)开关特性:
额外参数最后追加到命令行,故经 last-wins 覆盖 pgcli 注入的 flags。常见的有:--auto-discovery=false(不再按库加 datname)、--exclude-database(跳过某些库)、--tag(加常量标签)、--namespace(给每个指标加前缀)。--force 之后,用 curl -s http://127.0.0.1:9630/metrics 确认一个 --auto-discovery=false 的目标只报 postgres 库的标签。
监听与暴露
Listen 默认 0.0.0.0:在 Linux 上容器共享宿主网络命名空间,所以 /metrics 在宿主每个网卡的分配端口上可达。这是刻意的——抓取它的 Prometheus 通常在另一台机器。但没有鉴权,且 pg_exporter 在同一端口上还服务 /reload、/explain、/stat,故任何能连到端口的人不仅能抓指标、还能读配置和触发重载。对同机 Prometheus 传 --listen 127.0.0.1(用 --force 重建);否则请在防火墙层面限制端口。(在 macOS bridge 构建下,pgcli 会自动把绑定放宽到 0.0.0.0,好让发布的端口经 podman VM 保持可达。)
端口
每个 pg_exporter 实例占有一个端口——指标端点——取自以 pgexporter_start_port(默认 9630,pg_exporter 惯例端口)为基址的端口池,每多一个实例递增,并跳过任何被受管容器或 used_ports 占用的端口。分配结果写入 pg.yaml,故重装绝不会把别的插件在用的端口再发一遍。9630 与 node_exporter(9100)、Prometheus(9090)、VictoriaMetrics(8428)、Grafana(3000)、Alertmanager(9093)都不重号;显式 --port 仍优先。
(同一目标上每多一个实例都需要自己的 --monitor-user——见「重复安装与共享的监控用户」。)
配置
三个目标字段互斥——只设其一:target_instance(pgcli 纳管实例,DSN 每次启动现推)、admin_dsn(pgcli 在其上建了监控角色的远程服务器——pgcli 只保留其不含凭据的地址,你传的 admin URL 绝不持久化)、或 dsn(原样拨号的远程服务器,连同自带凭据原样存,且从不跑建角色 SQL)。monitor_dsn / monitor_password 是 pgcli 生成并注入容器的(dsn 模式下没有,此时导出器直接拨 dsn 本身)。extra_args 是最后追加的 flag 列表;config_file 是可选的、只读挂载的宿主路径。
列出
远程模式下 Target 行会被遮盖(DSN 里的密码绝不出现)。监控密码不显示——用 pg addon password pgexporter 取回。
密码
这是导出器连接所用的 pgexporter 角色密码——不是某个 PostgreSQL admin 密码。
启停
容器以 podman 的 unless-stopped 重启策略运行,宿主重启后随 podman 服务自动回来。start/stop 显式管理它们:
start 会先重新派生监控 DSN(对 --pg-name 目标,故轮换过的实例密码或改动的端口在重建时生效)。
开机自启
这会在 pg.yaml 里给实例打标记,并安装/刷新一个开机服务(Linux 上是 systemd --user unit,macOS 上是 LaunchAgent)来执行 pg start --autostart。开机启动只负责用已存的监控凭据把容器拉起来——它不重跑建角色的 SQL(那只在 install 时做)。完整机制、状态与关闭方式见 开机自启。
移除
pg_exporter 不在磁盘上留存任何东西,故 --clean-data 除了给一条提示外没有任何效果。目标上建的监控角色不会被删掉——它可能被共享或手工管理。要自行移除,需先撤销它的按库授权(只要它在某个库拿到过 pg_subscription 的列授权,裸 DROP ROLE 就会失败):在每个被监控的库里执行 REVOKE SELECT (oid) ON pg_catalog.pg_subscription FROM "pgexporter";,之后再 DROP ROLE "pgexporter";。
日志
排障
pg_up 0/ 没有pg_*指标 —— 监控角色连不上。确认/up返回 200、目标的pg_hba.conf放行容器的来源地址、已存密码与角色一致(pg addon password pgexporter;若已偏离,重装来轮换它)。对--dsn目标,确认该 DSN 的 host/port 从导出器所在主机可达(一个127.0.0.1的 DSN 意味着目标在导出器本机)。- 刚装完同目标的第二个 exporter,第一个就掉成
pg_up 0—— 两者共用默认的pgexporter角色,第二个安装轮换掉了它的密码、让第一个手里那份过期。给第一个做install … --force重建即可重新同步,或给每个实例配各自的--monitor-user(见「重复安装与共享的监控用户」)。 - 指标有但很稀疏/偏空 —— 漏了
GRANT pg_monitor会得到一个已连上但采集器不全的导出器。重装(建角色是幂等的)并确认成员关系:pg exec <inst> -c "SELECT r.rolname, m.rolname FROM pg_auth_members a JOIN pg_roles r ON r.oid=a.member JOIN pg_roles m ON m.oid=a.roleid WHERE r.rolname='pgexporter'"。 - Prometheus 显示 target DOWN —— 导出器在跑(
pg addon list),但抓取地址/端口对不上(--scrape pg=<host>:<port>),或一个--listen 127.0.0.1的导出器被别的机器抓。 --dsn and --admin-dsn are mutually exclusive(--dsn与--admin-dsn互斥) —— 你传了两个远程 URL;只给一个:--admin-dsn让 pgcli 建监控角色(其 host/port/database 即成为目标),或--dsn用你自己管理的凭据原样拨号。has an admin_dsn target but no stored monitor password—— 在一个--admin-dsn实例上做--force重装、而已存的监控密码丢失(pgcli 从未存 admin 凭据)。重新传--admin-dsn postgres://…让 pgcli 再建一次角色。- 某个 flag / discovery 改动没生效 ——
--extra-arg与目标/URL 改动在容器创建时应用;用--forcereinstall。只有采集器配置(--config-file)经 SIGHUP 重载。 - macOS 本地模式什么都不出 —— bridge 容器够不到宿主的
127.0.0.1PG;在 Mac 上用指向可达地址的--dsn。 - 端口已被分配 —— 先
pg addon remove释放,或用--port N另选一个。
已知限制
- 无鉴权、无 TLS,且管理端点暴露。 pg_exporter 提供纯文本
/metrics外加/reload、/explain、/stat;0.0.0.0绑定的唯一保护是网络边界。若要离开可信网络,请前置 nginx 或用防火墙限制。 - 监控 DSN(含其密码)存在
pg.yaml里。 那是低权限pg_monitor角色的凭据,不是 admin 凭据;用于建它的--admin-dsn绝不持久化。请相应地保护pg.yaml。 - 官方镜像基于
scratch—— 它不带系统 CA 集,故对一个外部服务器的 TLS(sslmode=require)--dsn可能证书校验失败;请经镜像供给信任、或在可信网络上连接。 - 无状态 —— 没有数据目录、没有保留期;除运行中的进程外什么都不留存。重装总是从头开始,且目标侧的监控角色绝不自动删除。
- macOS 本地实例模式不可用(bridge 容器够不到宿主回环);远程
--dsn模式在 Mac 上可用。Linux 是已实测平台。 - pgcli 不校验
--extra-arg值;有错的 flag 只会在启动时从容器日志里暴露。
16.31 - influx2tsdb
pg addon install influx2tsdb 以受管容器方式运行 influx2tsdb-proxy 1.3.2——
一个 InfluxDB 1.x HTTP API 适配器(Line Protocol 写入、InfluxQL 查询),数据落到
启用了 TimescaleDB 扩展的 PostgreSQL。镜像由 pgcli 自建:
ghcr.io/mars-base/pgcli/pgcli-influx2tsdb。与 InfluxDB 插件 不同,
这里没有 TSM 引擎:每个数据点都存进 TimescaleDB hypertable,代理本身无状态。
本插件仅支持 Linux/amd64(镜像为单架构,基于上游发行版二进制 + debian:12-slim)。
代理容器可以随时重建,数据因存放在 PostgreSQL 中而不会丢失。
HTTP API 没有认证。 上游代理只提供明文 HTTP、绑定所有网卡,且不校验 InfluxDB 凭据。需要 TLS 和访问控制时用 nginx 插件 前置,或直接 防火墙隔离端口。不可在不可信网络上使用。
适用场景与取舍
原生 InfluxDB 1.x 使用自己的内存索引 + TSM 文件,直接服务 InfluxQL。 influx2tsdb 则把每个请求翻译成 SQL,把数据行存进 PostgreSQL/TimescaleDB。这层 翻译加上关系型存储引擎会带来吞吐和延迟开销:纯时序写入/查询负载下,原生 InfluxDB 比本代理更快,差距在大范围聚合扫描和高基数场景下更明显。
换来的好处是时序数据变成了普通的数据库数据:
- ACID 事务与一致性。 数据点与关系数据处于同一个事务存储中;批量写入要么 整体提交、要么失败,时序数据可以与业务表在一条 SQL 里 join。
- SQL 生态。 TimescaleDB 把 hypertable 暴露成普通表:join、视图、CTE、窗口 函数、ORM 和 BI 工具都能用。InfluxQL 只是写入/查询入口,不是封闭的孤岛。
- 成熟的备份、PITR、复制与 HA。
pg_dump/pgBackRest、WAL 归档、流复制、 Patroni 等 PostgreSQL 运维手段原样适用——不需要再维护第二套存储和灾备流程。 - 统一的运维、认证与审计。 用户、角色、TLS、监控和合规控制都复用 PostgreSQL,不必额外加固一个数据库。
- 灵活的数据生命周期。 保留与压缩是 TimescaleDB 原生策略(见下文),同时
仍可对任意行
UPDATE/DELETE、做 schema 迁移和表结构重塑。 - 复用现有 PostgreSQL 基础设施与 DBA 技能。 如果 PostgreSQL 本来就有专业 运维,新增一个时序负载的边际成本很低。
如果最看重 InfluxQL/TSM 性能和忠实还原 InfluxDB 1.x 行为,选 InfluxDB 插件;如果首要目标是把一切留在 PostgreSQL 里,选 influx2tsdb。
前置条件
-
目标 PostgreSQL 数据库已安装 TimescaleDB 扩展。代理启动时会尝试
CREATE EXTENSION IF NOT EXISTS timescaledb,但实例中必须有可安装的扩展包。 pgcli 实例上执行: -
目标必须在代理启动前处于运行状态。本地目标会预先检查 (
pg start -i tsdb);PostgreSQL 不可达时代理会立即退出。
安装
本地目标——DSN 在每次启动时从 pgcli 实例重新推导,端口和密码变化会自动同步:
远程或非受管目标——传完整 DSN:
DSN(含密码)会存入 pg.yaml,同时出现在容器命令行里,可通过
podman inspect 看到;请保护配置文件,并使用只授权目标数据库的最小权限角色。
第二个实例会从 influx2tsdb_start_port(默认 8087)起自动分配下一个空闲端口:
不带 --force 重复 install 是幂等空操作。--force 会重写目标/端口设置并重建
容器;代理无数据,因此不影响任何已存数据。
参数
| 参数 | 默认值 | 说明 |
|---|---|---|
--name |
influx2tsdb |
实例名(配置 map 键) |
--pg-name |
— | 使用本地受管 pgcli 实例作为目标;与 --dsn 互斥 |
--dsn |
— | 远程/非受管 PostgreSQL/TimescaleDB 的完整 DSN |
--database |
目标库名 | 默认 InfluxDB 数据库(代理 -db);对应一个 PostgreSQL schema |
--port |
自动(基址 8087) | InfluxDB 协议 HTTP 宿主端口;0 = 自动分配 |
--pool-size |
10 |
PostgreSQL 连接池大小(-pool) |
--verbose |
false | 输出翻译后的 SQL 和查询细节(-verbose) |
--version |
1.3.2 |
固定的镜像发行版 |
--force |
false | 重写设置并重建无状态容器 |
没有 --data-dir 和 --listen:代理不保留本地状态,使用 host 网络,且始终
绑定所有网卡。
数据模型
每个 InfluxDB 数据库对应目标库中的一个 PostgreSQL schema:
CREATE DATABASE "demo"→CREATE SCHEMA "demo"- measurement 变成该 schema 下的 TimescaleDB hypertable
- tag 变成文本列;field 变成带类型的列;
time是 hypertable 的分区列 - 代理元数据位于目标库的
publicschema:_influx_databases、_influx_meta、_retention_policy
数据就是普通 PostgreSQL 数据,可以直接用 pg exec 查看和 join:
写入与查询
API 遵循 InfluxDB 1.x:写入成功返回 204,非法 line protocol 返回 400。
当前版本支持的常用语句:
| 类别 | 语句 |
|---|---|
| 数据库 | CREATE DATABASE、DROP DATABASE、SHOW DATABASES |
| 保留策略 | CREATE / ALTER / DROP / SHOW RETENTION POLICIES |
| 写入/删除 | line protocol 写入、DELETE FROM、DROP MEASUREMENT |
| 元数据 | SHOW MEASUREMENTS / TAG KEYS / TAG VALUES / FIELD KEYS(后三者需带 FROM <measurement>) |
| 查询 | 带 tag/time 过滤的 SELECT、mean/sum/min/max/count、first/last、GROUP BY time(...) 与 tag、子查询 |
使用 InfluxDB 1.x 命令行客户端
官方 influx CLI(1.13.1)可以直接操作代理。pgcli 通过一次性容器运行它,
remote 模式指向代理即可(代理无认证,无需用户名密码):
数据库与保留策略:
写入(line protocol):
查询与元数据:
删除与输出格式:
批量导入(influx 1.x import 格式):
实测可用:数据库与保留策略 DDL、INSERT(line protocol)、SELECT(含
GROUP BY time(...)、tag/time 过滤和 -format column/csv/json)、
SHOW MEASUREMENTS、带 FROM 的 SHOW TAG KEYS / FIELD KEYS / TAG VALUES、
DELETE、DROP MEASUREMENT 以及 -import。
与原生 InfluxDB 的差异:
- 元数据命令必须带
FROM <measurement>:SHOW TAG KEYS、SHOW FIELD KEYS和SHOW TAG VALUES不带FROM会返回missing FROM clause(原生 InfluxDB 允许省略);WITH KEY =~ /regex/也不支持。 - 不支持
SHOW SERIES(返回unsupported query: SHOW SERIES)。 - 默认保留策略不能直接
DROP(与原生 InfluxDB 一致),先把另一个策略设为DEFAULT。 -precision的ns/u/ms/s/m/h都生效;rfc3339不生效,仍输出纳秒整数。
时间精度注意事项
-
时间戳一律按纳秒解释。 在 E2E 实测中,当前版本忽略
/write的precision=查询参数;请发送纳秒时间戳(InfluxDB 默认精度),避免数据时间 偏移。 -
时间字面量两种写法都可用。 实测中,
WHERE time ...既接受'YYYY-MM-DD HH:MM:SS',也接受 RFC3339(如'2023-11-14T22:13:20Z',含 毫秒或+08:00时区偏移);Grafana 经$timeFilter插值出的 epoch 毫秒 形式(如1793083539315ms)同样可用: -
now() - 1d这类表达式可用。
已知上游问题:DROP MEASUREMENT 元数据缓存
执行 DROP MEASUREMENT 后,代理会删除 _influx_meta 中该 measurement 的行,
但保留内存中的 schema 缓存。之后用相同名字重新创建 measurement 时,元数据
不会被重新插入,SHOW MEASUREMENTS / SHOW TAG KEYS / SHOW FIELD KEYS 可能
为空,而 SELECT 仍然可用。自动化中请使用每次唯一的 measurement 名,或在删除
并重建后重启代理。仓库自带测试脚本采用前者。
Grafana 集成
Grafana 内置的 InfluxDB 数据源可以直接接入本代理(已在 Grafana 13.2.4 上 端到端验证):数据源类型选 InfluxDB,Query Language 选 InfluxQL (不是 Flux),URL 指向代理,即可查询底层 TimescaleDB 中的 hypertable。
| 字段 | 值 |
|---|---|
| Type | InfluxDB |
| Query Language | InfluxQL |
| URL | http://<proxy-host>:8087 |
| Database | tsdb_db(或你的默认库) |
| User / Password | 留空 |
| HTTP Method | GET(默认) |
数据源的 “Save & test” 健康检查可用;Grafana 查询编辑器和面板生成的请求
($timeFilter、epoch=ms、fill(null)、GROUP BY time)已实测返回正确的
时间序列,SHOW MEASUREMENTS / TAG KEYS / TAG VALUES / FIELD KEYS 也受支持,
因此查询构建器里的 measurement/tag/field 下拉框可正常使用。
注意事项:
- 代理无认证、无 TLS;Grafana 与代理不在可信网络时用 nginx 插件 前置。
- 只支持 InfluxQL 子集;不支持的表达式可能返回错误或空结果,也不支持 Flux (Flux 依赖 InfluxDB v2 API,本代理不提供)。
- 代理忽略写入的
precision=参数;Grafana 用epoch=ms读取,展示不受 影响。
通过 Grafana HTTP API 集成
自动化场景不必点 Web UI:Grafana 的 HTTP API 可以直接创建数据源,并验证 “代理写入 → Grafana API 读取”的完整链路(以下命令已在 Grafana 13.2.4 上 实测)。
写入返回 204;第 4 步返回该 measurement 的 last(value),time 列为
epoch 毫秒(与 epoch=ms 一致)。
要点:
access: "proxy"让 Grafana 服务端转发查询,浏览器不直连代理,也不涉及 CORS。jsonData中的version: "InfluxQL"和httpMode: "GET"与面板查询使用 的模式一致,创建时显式给出。- 删除数据源:
curl -u "admin:$GRAFANA_PW" -X DELETE "$G/api/datasources/uid/$UID"(返回200)。
保留策略、压缩与 TimescaleDB 任务
支持 InfluxDB 语义的 CREATE / ALTER / DROP RETENTION POLICY(按数据库隔离、
只有一个默认策略、最短 15m、INF/0s 表示无限)。在存储层,代理会立即对该
数据库 schema 中的每个 hypertable 调用 TimescaleDB 自带的策略函数:
清理本身是自动的,不需要 pg_cron。 任务运行在 TimescaleDB 自带的后台
job scheduler 中,可在 timescaledb_information.jobs 查看:
一个 1 天保留策略会产生类似这样的任务:
运维要点:
- 调度器不是代理。 真正执行任务的是 TimescaleDB;只要 PostgreSQL 在运行, 停止或删除代理容器并不会停止数据过期清理。反之,代理停机期间新建的 hypertable 会在下一次同步时补上策略。
- 同步节奏。
CREATE / ALTER / DROP RETENTION POLICY会立即触发一次同步; 另有后台循环每 5 分钟对所有 hypertable 重新下发策略(覆盖新表,以及因锁 超时被跳过的 DDL)。 - 任务节奏。 代理会按推导值设置调度间隔:保留策略小于 24h 时请求每 5 分钟、
否则每小时;压缩小于 24h 时请求每分钟、否则每小时。但上游 1.3.2 存在已知
quirk:选择间隔时会遍历所有数据库的策略,且结果受 map 遍历顺序影响,多库
部署下实际间隔可能与推导值不同(实测 1h 保留策略的 retention 任务被排成
1 小时)。需要精确节奏时以
timescaledb_information.jobs.schedule_interval为准;代理每次同步都会重新下发推导出的间隔。 - 只有默认策略会真正生效。 schema 内所有 hypertable 都使用该数据库默认策略
的时长(若没有默认策略,则取第一个有限时长的策略)。非默认策略只是元数据;
代理不会按策略拆分 schema,也不会按 measurement 单独保留。没有有限时长策略
(或默认策略为
INF)时数据会持续增长。 - 删除以 chunk 为粒度。 TimescaleDB 整块删除 chunk,因此过期数据最多会多留
一个 chunk 间隔,稳态数据量约为「保留时长 + 最多一个 chunk 间隔」,之后不再
无限增长。chunk 间隔来自
SHARD DURATION或按保留时长推导(最小 1h, retention/24,上限 1 天)。 - 把策略改成
INF会在下次同步时移除 TimescaleDB 保留任务,数据不再过期。 - 压缩自动启用。 tag 作为
compress_segmentby,时间倒序作为compress_orderby;compress_after由保留时长推导(retention/4,上限 1 天, 最小15m)。
在 PostgreSQL 侧核查:
安全与暴露面
- 代理没有任何凭据;能连到端口的人就能读写目标 DSN 暴露的所有数据库。
- 只绑可信网络,或用 nginx 插件 前置做 TLS 与 IP/Basic Auth 限制。 代理端口本身没有 TLS。
- PostgreSQL 密码会出现在
pg.yaml和容器 argv 中。请使用仅授权目标库的专用 角色,并保护配置文件。
生命周期与开机自启
remove 从不删除数据:数据在 PostgreSQL 里。--clean-data 为与其他插件保持
一致而接受,但没有可删除的内容。
开机自启会重建无状态代理。本地目标会先启动实例,再重新推导 DSN:
备份、恢复与 HA
代理侧没有需要备份的内容,直接使用 PostgreSQL 的常规工具:
pg backup <instance>/ pgBackRest /pg_dump——所有 schema、hypertable 以及public._influx_*元数据表都会包含在内。- PITR / WAL 归档 / 流复制 / Patroni——原样适用。故障切换或迁移后,把代理指向
新主库(
--dsn)并重建;本地实例则在实例 DSN 变化后重启代理。
代理容器是一次性的:pg addon install influx2tsdb --force 用同一目标重建即可。
测试脚本
test/addon/test_influx2tsdb.sh 覆盖常用时序操作:ping、单点/批量写入(含 tag
和多种 field 类型)、count、时间范围与 tag 过滤、mean/sum/min/max、
GROUP BY time() 与 tag、first/last、SHOW 元数据、可选的 PostgreSQL
行数/hypertable 检查、DELETE、DROP MEASUREMENT、数据库生命周期、保留策略
CRUD,以及非法写入的处理。
由于上述 DROP MEASUREMENT 元数据缓存问题,脚本使用每次运行唯一的 measurement
和数据库名。
test/addon/test_influx2tsdb_retention.sh 专门验证保留策略的自动化,覆盖:
- 自动创建
policy_retention/policy_compression任务,drop_after/compress_after与 RP 推导一致; CALL run_job(<id>)真正删除过期 chunk,新数据保留;- 停掉代理后直接向 PostgreSQL 插入过期行,任务仍能删除(证明调度不依赖代理);
- 默认 RP 切到
INF后保留任务消失、压缩任务保留并回退到 1 天; DROP DATABASE后该 schema 的任务全部清理;- 目标库未安装
pg_cron。
脚本通过 pg exec 访问目标 PostgreSQL,因此需要 --instance 或 --pg-dsn;
它会在运行中停止并重启本地代理(除非 --skip-proxy-stop)。
限制
- 仅 Linux/amd64。
- 代理无认证、无 TLS;请用 nginx 前置或做网络隔离。
- InfluxQL 子集。 当前版本只实现上文列出的语句,不是完整 InfluxQL;例如
SHOW SERIES不支持,SHOW TAG KEYS / FIELD KEYS / TAG VALUES必须带FROM。不支持的语法会返回错误或空结果。不支持 Flux。 - 时间戳按纳秒解释;当前版本忽略写入的
precision=参数。查询输出精度由epoch=ms控制(Grafana 即使用该参数)。 - 仅支持按数据库的保留策略。 一个默认策略作用于 schema 内所有 hypertable。
- 时序负载性能低于原生 InfluxDB(见上文)。
- 代理的元数据表(
_influx_databases、_influx_meta、_retention_policy)位于 目标库的publicschema。
CLI 参数参考
pg addon install influx2tsdb
| 参数 | 默认值 | 说明 |
|---|---|---|
--name NAME |
influx2tsdb |
实例名 |
--pg-name NAME |
— | 本地 pgcli 实例作为目标;与 --dsn 互斥 |
--dsn DSN |
— | postgres://user:pass@host:port/db 目标;与 --pg-name 互斥 |
--database DB |
目标库名 | 默认 InfluxDB 数据库(-db) |
--port N |
自动(基址 8087) |
HTTP 宿主端口;0 = 自动分配 |
--pool-size N |
10 |
PostgreSQL 连接池大小 |
--verbose |
false | 输出翻译后的 SQL |
--version V |
1.3.2 |
固定的镜像发行版 |
--force |
false | 重写设置并重建容器 |
pg addon start|stop|remove influx2tsdb
| 命令 | 参数 | 说明 |
|---|---|---|
pg addon start influx2tsdb |
--name NAME |
启动(重建)容器;本地目标会先做运行检查 |
pg addon stop influx2tsdb |
--name NAME |
停止容器 |
pg addon remove influx2tsdb |
--name NAME、--clean-data |
删除容器与配置条目;数据留在 PostgreSQL |
pg addon influx2tsdb status
| 参数 | 默认值 | 说明 |
|---|---|---|
--name NAME |
influx2tsdb |
要查看的实例(状态、ping、目标、数据库、端口) |
pg logs addon influx2tsdb
| 参数 | 默认值 | 说明 |
|---|---|---|
--name NAME |
influx2tsdb |
要查看容器日志的实例 |
-n N、--tail N |
50 |
行数 |
-f、--follow |
false | 持续跟踪 |
pg autostart enable|disable
| 参数 | 说明 |
|---|---|
--influx2tsdb |
选择该代理插件 |
--name NAME |
要切换的实例(默认 influx2tsdb) |
全局参数
| 参数 | 说明 |
|---|---|
-c, --config FILE |
配置文件(默认 ~/.pgcli/pg.yaml) |
-o, --output FILE |
输出配置路径 |
-i, --instance NAME |
PostgreSQL 实例选择器——此处不使用;请用 --name |
16.32 - ClickHouse
pg addon install clickhouse 以受管容器方式运行 ClickHouse 26.8 LTS——单机列式 OLAP 数据库——直接跑官方 docker.io/clickhouse/clickhouse-server 多架构镜像。与 VictoriaMetrics、InfluxDB 一样,它是顶层插件,不前置任何 PostgreSQL 实例。
ClickHouse 用两个端口承载两种协议:HTTP(curl、JDBC/ODBC 以及大量驱动)和原生 TCP 协议(clickhouse-client)。pgcli 以 host 网络运行它,因此两者都是直接的宿主端口。认证始终开启——安装时创建 admin 账号,密码存入 pg.yaml。
admin 密码在安装时生成,可随时用
pg addon password clickhouse [--name NAME]取回。 HTTP 与原生监听对每个请求 都要求认证,不存在匿名访问。--listen默认127.0.0.1——需要其他主机连接时 显式传0.0.0.0,并用 nginx 前置做 TLS。
参数
| 参数 | 默认值 | 说明 |
|---|---|---|
--name |
clickhouse |
实例名(map 键;也是各子命令的默认名) |
--version |
26.8.36.3 |
上游镜像 tag,直接从 docker.io/clickhouse/clickhouse-server 拉取 |
--data-dir |
<base_dir>/addon/clickhouse/<name>/data |
数据目录,绑定挂载到 /var/lib/clickhouse;必须是绝对路径 |
--listen |
127.0.0.1 |
HTTP 与原生监听的绑定地址。0.0.0.0 会把两者暴露到网络 |
--http-port |
自动(基址 8123) |
HTTP 宿主端口;0 = 自 clickhouse_http_start_port 自动分配 |
--tcp-port |
自动(基址 9000) |
原生协议宿主端口;0 = 自 clickhouse_tcp_start_port 自动分配(与 S3 API 池共享区间) |
--admin-user |
admin |
由镜像 entrypoint 创建的 admin 账号 |
--admin-password |
自动生成 | admin 密码。--force 时默认保留已存密码,除非显式指定 |
--config-option K=V |
— | 额外服务端设置,点分 XML 路径(logger.level=debug、max_server_memory_usage=2147483648);可重复;替换已存覆盖 map |
--force |
false | 重新渲染配置与 secret 并重建容器;保留数据、已存密码与已存覆盖项 |
ClickHouse 不适用 --image——请用 --version。
生成的配置只固定监听、端口、日志与备份盘;其余服务端设置全部沿用上游默认值。覆盖项存在 pg.yaml 的 addons.clickhouse.<name>.settings 下并渲染进生成的 XML,因此重启与容器重建都不会丢失。
安装
第二个实例会自动从各自端口池取下一个空闲 HTTP/TCP 端口(例如 8124/9001),并可使用自定义数据目录:
不带 --force 重复 install 是幂等空操作,只报告当前状态。--force 会重新渲染配置、重建容器,并保留数据目录、已存 admin 密码与已存设置;只有想替换覆盖 map 时才需要重新传 --config-option。
镜像直接从 Docker Hub 拉取,不涉及 pgcli 自建镜像。--version 接受任意合法的上游 tag,但该 tag 必须存在于 registry,否则拉取失败。pgcli 默认固定当前 LTS 线(26.8.36.3)。
端口
| 端点 | 默认 | 协议 | 端口池 |
|---|---|---|---|
| HTTP | 8123 |
HTTP(查询、/ping、大量驱动) |
clickhouse_http_start_port,默认 8123 |
| Native | 9000 |
ClickHouse 原生 TCP(clickhouse-client) |
clickhouse_tcp_start_port,默认 9000,与 S3 API 池共享 |
两个池共享同一个“已分配”集合,因此一个池中的显式端口不会与另一个池自动分配的端口相撞。--http-port 与 --tcp-port 必须不同,且所有 ClickHouse 实例的 HTTP/TCP 端口必须全局唯一。自动分配会跳过宿主上已在监听的端口。
当 --listen 为通配地址(0.0.0.0、::)时,pgcli 自己的客户端命令会拨 127.0.0.1;打印的端点同样遵循此规则。LAN --listen 绑定在容器内仍会保留 loopback,保证容器内的 clickhouse-client 始终可用。
pgcli 创建的内容
单个实例的全部状态位于 <base_dir>/addon/clickhouse/<name>/:
| 宿主路径 | 容器路径 | 说明 |
|---|---|---|
config.d/ |
/etc/clickhouse-server/config.d |
只读;替换镜像自带的 config.d(否则会监听所有网卡)。zz-pgcli.xml 是生成的覆盖文件 |
data/ |
/var/lib/clickhouse |
数据库数据与系统表 |
logs/ |
/var/log/clickhouse-server |
clickhouse-server.log 与 clickhouse-server.err.log(也可经 podman logs 查看) |
backups/ |
/var/lib/clickhouse-backups |
backup/restore 使用的受管 backups 盘 |
secrets/admin_password |
/run/secrets/admin_password |
权限 0600,entrypoint 经 CLICKHOUSE_PASSWORD_FILE 读取 |
生成的 zz-pgcli.xml 设置:
listen_host(完全等于--listen的选择)与listen_tryhttp_port/tcp_portlogger.level、logger.log、logger.errorlog、logger.console、日志轮转(size、count)- 位于
/var/lib/clickhouse-backups的本地盘backups,并允许其用于BACKUP/RESTORE
容器以 --network host、--restart unless-stopped、30 秒停止超时和 nofile=262144 运行。数据在容器内的属主是 uid/gid 101(镜像 entrypoint 以 root 完成挂载目录 chown 后再降权)。
凭据
镜像 entrypoint 会创建 admin(或 --admin-user 指定的名字)并赋予全部权限,同时开启访问管理(CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT=1),因此本 CLI 发出的 CREATE USER / GRANT 可以生效。密码存入 pg.yaml(默认权限 0644——请保护该文件),并同步写入挂载进容器的 0600 secret 文件。
所有与 ClickHouse 通信的 pgcli 命令都通过环境变量传递凭据、在容器内展开;密码绝不出现在 podman 命令行或宿主进程列表中。SQL 经 stdin 传入。
连接
用打印出的端点即可从任意宿主工具连接:
pgcli 不会在宿主上安装 clickhouse-client——请用下面的 query/shell 子命令,或让外部客户端连原生端口。/ping 无需认证并返回 Ok.,是 pgcli 启动时轮询的健康检查。
Shell 与查询
query 以 admin 用户运行 clickhouse-client,结果写到 stdout,可直接管道或重定向。语句由位置参数拼接;--file 读取宿主文件(宿主读取后经 stdin 传入,无需挂载);- 表示从 stdin 读取。客户端调用时未加 --multiquery,因此每次调用只传一条语句。
query、database、user、grant、backup、restore 都要求实例正在运行;shell 在容器停止时仍会尝试打开,但会打印警告。
数据库
create 接受裸 engine 名(默认 Atomic;不暴露 engine 参数)。drop 是破坏性操作,除非传 --yes,否则会要求确认。database list 展示 system.databases 中的 name、engine 与 data_path。db 是 database 的别名。
用户与权限
用户以 IDENTIFIED WITH sha256_password 创建。未给任何 host 参数时默认 --host-any;--host-local、--host-ip(IP 或 CIDR)与 --host-name 会追加 HOST 子句。--readonly 追加 SETTINGS readonly = 1。自动生成的密码只打印一次且不会存入 pg.yaml——请自行保存。--if-not-exists 让 user create 幂等。
--read 即 SELECT,--write 即 INSERT,--ddl 展开为 CREATE, ALTER, DROP, TRUNCATE, OPTIMIZE,--all 授予 ALL,--privileges 接受逗号分隔列表(会转大写并与 flag 选择去重)。不给 --database 时目标是 *(所有数据库);--table 需要显式 database。--revoke 发出 REVOKE ... FROM。grant list 运行 SHOW GRANTS FOR。
这些命令都以实例的 admin 账号执行;CLI 从不保存每个用户的密码或凭据。
配置
服务端设置用点分 XML 路径寻址;叶子值就是生成 XML 中该元素的文本。
config set KEY=VALUE ...接受一个或多个键值对,校验后重写 XML 与pg.yaml,然后重启运行中的容器使改动立即生效。传--no-restart可推迟到下次启动。config unset KEY ...删除覆盖项(一个或多个),重启规则相同。unset 不存在的键会报错。config get打印自定义覆盖或受管设置的有效值;config list先列受管设置,再按 key 排序列出自定义覆盖。config show渲染与写入config.d/zz-pgcli.xml相同的 XML;不触碰文件或容器。- 安装时的
--config-option会替换整个覆盖 map;不传则保留已存内容。
logger.level 特殊处理:它折叠进受管的 <logger> 块,而不是产生第二个 <level> 元素。合法级别为 trace、debug、information、warning、error、fatal 和 none。
pgcli 受管的键会被拒绝,因为覆盖它们要么产生重复 XML,要么与已存路径/端口静默冲突:listen_host、listen_try、http_port、https_port、tcp_port、interserver_http_port、interserver_http_host、mysql_port、postgresql_port、path、tmp_path、user_files_path、format_schema_path、access_control_path、backups.*、user_directories、profiles、users、storage_configuration.disks、storage_configuration.policies,以及除 logger.level 外的所有 logger.* 键。含空格的键或含控制字符的值同样会被拒绝。
常用设置:
本 CLI 不支持服务端设置的热加载;每次有效变更都会重启或重建容器(ClickHouse 在启动时读取该生成文件)。
pgcli 只校验 key 形式与受管键规则——值由 ClickHouse 在启动时校验。被它拒绝的值(例如低于 merge/mutation 健全性检查要求的 background_pool_size)会中止启动,而容器的 unless-stopped 策略会把它变成重启循环。按下面步骤恢复,不会丢数据:
备份与恢复
备份使用 ClickHouse 的 SQL BACKUP 命令写入本地 backups 盘(/var/lib/clickhouse-backups)。备份是在线的,实例必须正在运行。这里只暴露受管的本地盘——没有配置 S3 目标。
- 两个命令都必须给
--database:它表示备份中记录的数据库名(v1 没有“全部数据库”形式)。 --out在备份目录内时按相对路径解析,否则按绝对路径;绝对路径必须位于该目录内,且文件必须以.zip结尾。默认名为<database>-YYYYMMDD-HHMMSS.zip(UTC)。restore接受备份目录内的文件,外加记录中的数据库名。恢复到已存在的数据库会失败(ClickHouse 拒绝覆盖);用--as NEW并存恢复。- restore 本身是在线的,但需要足够空间与运行中的实例;
backups按最新优先列出.zip文件。
备份目录就是普通宿主目录——真正的灾备请把 .zip 复制到别的机器。没有定时备份;需要时把 pg addon clickhouse backup 接进自己的定时任务。
生命周期
start 在启动前会从 pg.yaml 重新渲染生成的配置与密码文件,因此停止中的实例会拾取 config set --no-restart 的改动与变更后的 admin 密码。HTTP /ping 可应答时,status 报告 running (ping ok)。
不带 --clean-data 时,remove 会在磁盘上保留 <实例目录>(数据、配置、日志、备份、secret)与自定义 --data-dir;同名重装即可复活数据,并复用保留的 admin 密码(用 --admin-password 可覆盖)。--clean-data 会同时删除实例目录与自定义数据目录(uid 101 属主的文件经 podman unshare 删除),但从不删除已拉取的镜像。
重启后自动启动:
开机启动同样会先重渲染配置与 secret,再启动现有容器。pg start 会在 nginx 之前启动所有启用 autostart 的 ClickHouse 实例,因此指向 ClickHouse HTTP 端口的 nginx 后端在代理启动时已经就绪。
生产注意事项
- 仅单机。 本插件没有复制、分片与 HA。ReplicatedMergeTree/Keeper 拓扑不在范围内;备份是灾备路径。
- 无 TLS 监听。 HTTP 与原生端口都是明文。需要带 TLS 在网络中暴露 ClickHouse 时,用 nginx 插件 前置 HTTP 端口(或使用外部代理);不要把原生端口直接暴露给不可信网络。
- 默认仅回环。
--listen默认127.0.0.1。认证虽然始终开启,但网络绑定仍会扩大攻击面;只在可信网络中使用0.0.0.0。 - 密码存储。 admin 密码位于
pg.yaml(默认0644);请保护该文件,并优先用pg addon password clickhouse取用,而不是四处复制。 - 资源占用。 pgcli 不限制容器的 CPU/内存;ClickHouse 自身的内存预算与推荐设置见下文 内存限制。
- 升级。
--force --version <新版本>会拉取新 tag 并在同一数据目录上重建容器。ClickHouse 只支持向前升级——绝不要让旧版本服务端启动在新版本写过的数据上。升级前先备份。 - 数据属主。 数据文件属主为 uid 101。请用
--clean-data而不是手工删目录;pgcli 会处理权限回退。 - 平台。 仅 Linux(host 网络);官方镜像为多架构(amd64/arm64)。
内存限制
pgcli 不会给 podman 传 --memory/--cpus,因此容器没有 cgroup 内存上限;内存由 ClickHouse 自己约束:
- 服务端级。
max_server_memory_usage是进程级上限(字节)。默认值0表示继续按max_server_memory_usage_to_ram_ratio(默认宿主内存的0.9)计算。这对独享宿主没问题,共享宿主则很危险。共享时应显式设置绝对值,并给 OS page cache 和其他服务留出余量;独享宿主常见起点是内存的 50%–70%。 - 并发。
max_concurrent_queries与background_pool_size也是服务端设置,可用config set修改。服务端上限覆盖所有并发查询,因此要保证单查询峰值内存 × 并发数落在预算内。 - 查询级。
max_memory_usage、max_bytes_before_external_group_by、max_bytes_before_external_sort、max_threads属于用户/profile 设置,不是服务端 XML,config set不会应用它们。可在pg addon clickhouse shell启动的clickhouse-client中用SET只影响当前会话(一次性的query调用不共享会话状态),或在 SQL 创建用户时用CREATE USER ... SETTINGS ...持久定义。 - 溢写而非 OOM。
max_bytes_before_external_group_by/max_bytes_before_external_sort让重聚合和排序在超过阈值时溢写到磁盘,以延迟换更低的内存上限。JOIN通常是内存大户——按右表大小估算,或避免大表 JOIN。
查看当前生效值:
示例——把整个服务端限制在 8 GiB,并允许 20 个并发查询:
数值单位是字节;config set 默认会重启运行中的容器(--no-restart 推迟到下次启动)。参考:2 vCPU / ~2 GiB 的测试 VM 用 max_server_memory_usage=805306368(768 MiB)可以跑小数据量 OLAP;大数据量、JOIN 或高基数聚合密集型负载需要按比例增加内存。
CLI 参数速查
pg addon install clickhouse
| 参数 | 默认值 | 说明 |
|---|---|---|
--name NAME |
clickhouse |
实例名(map 键);[A-Za-z0-9][A-Za-z0-9_-]*,最长 48 字符 |
--version TAG |
26.8.36.3 |
docker.io/clickhouse/clickhouse-server 的上游镜像 tag |
--data-dir DIR |
<base_dir>/addon/clickhouse/<name>/data |
绝对宿主数据目录,挂载到 /var/lib/clickhouse |
--listen ADDR |
127.0.0.1 |
两个监听的绑定地址 |
--http-port N |
自动(基址 8123) |
HTTP 宿主端口;0 = 自动分配 |
--tcp-port N |
自动(基址 9000) |
原生宿主端口;0 = 自动分配;必须与 --http-port 不同 |
--admin-user USER |
admin |
admin 账号名;[A-Za-z0-9_.-]{1,64} |
--admin-password PW |
自动生成 | admin 密码;--force 时默认保留已存值,除非显式指定 |
--config-option K=V |
— | 点分 XML 路径设置;可重复;替换已存覆盖 map |
--force |
false | 重渲染配置/secret 并重建容器,保留数据、密码与设置 |
pg addon clickhouse status / shell / query [SQL...]
| 参数/flag | 默认值 | 说明 |
|---|---|---|
--name NAME |
clickhouse |
目标实例 |
SQL... |
— | 仅 query:由位置参数拼接的 SQL |
--database DB |
— | 仅 query:追加 --database DB |
--file FILE |
— | 仅 query:从宿主文件读取 SQL(- = stdin) |
-(位置参数) |
— | 仅 query:从 stdin 读取 SQL |
pg addon clickhouse database create|list|drop
| 参数/flag | 默认值 | 说明 |
|---|---|---|
NAME |
— | 数据库名(create/drop,位置参数) |
--engine NAME |
Atomic |
裸 engine 名(不含参数) |
--comment TEXT |
— | 数据库注释 |
--if-not-exists |
false | 仅 create |
--if-exists |
false | 仅 drop |
--yes |
false | 仅 drop:跳过确认 |
db 可作为 database 的别名。
pg addon clickhouse user create|list|drop
| 参数/flag | 默认值 | 说明 |
|---|---|---|
NAME |
— | 用户名(create/drop,位置参数) |
--password PW |
自动生成 | 密码;省略时生成并打印一次 |
--default-database DB |
— | DEFAULT DATABASE 子句 |
--host-any |
隐含 | 允许任意主机(没有任何 host flag 时生效) |
--host-local |
false | 仅 LOCAL(回环) |
--host-ip IP|CIDR |
— | 可重复的 IP host 子句 |
--host-name NAME |
— | 可重复的 NAME host 子句 |
--readonly |
false | 追加 SETTINGS readonly = 1 |
--if-not-exists |
false | 仅 create |
--if-exists |
false | 仅 drop |
--yes |
false | 仅 drop:跳过确认 |
pg addon clickhouse grant USER / grant list USER
| 参数/flag | 默认值 | 说明 |
|---|---|---|
USER |
— | 目标用户(位置参数) |
--read |
false | 授予 SELECT |
--write |
false | 授予 INSERT |
--ddl |
false | 授予 CREATE, ALTER, DROP, TRUNCATE, OPTIMIZE |
--all |
false | 授予 ALL |
--privileges LIST |
— | 额外逗号分隔权限,与上述 flag 合并 |
--database DB |
全部(*) |
限定到某个数据库 |
--table TABLE |
全部表 | 限定到某张表;需要 --database |
--revoke |
false | 发出 REVOKE ... FROM 而非 GRANT ... TO |
--with-grant-option |
false | 追加 WITH GRANT OPTION(仅授予时) |
pg addon clickhouse backup / restore FILE / backups
| 参数/flag | 默认值 | 说明 |
|---|---|---|
FILE |
— | 仅 restore:备份目录内的 .zip 文件 |
--database DB |
必需 | 要备份的数据库 / 备份中记录的数据库名 |
--out NAME |
<db>-<UTC 时间戳>.zip |
仅 backup:备份目录内的输出路径 |
--as NEW |
— | 仅 restore:恢复到新数据库名 |
pg addon clickhouse config list|get|set|unset|show
| 参数/flag | 默认值 | 说明 |
|---|---|---|
KEY=VALUE... |
— | 仅 set:一个或多个点分 XML 设置 |
KEY... |
— | get/unset:一个设置键(get 只接受一个) |
--name NAME |
clickhouse |
目标实例 |
--no-restart |
false | 仅 set/unset:下次启动生效,而不是立即重启 |
生命周期、凭据、日志、autostart
| 命令 | 参数 | 说明 |
|---|---|---|
pg addon start clickhouse |
--name NAME |
启动(不存在则创建),等待 /ping |
pg addon stop clickhouse |
--name NAME |
停止容器,保留数据 |
pg addon remove clickhouse |
--name NAME、--clean-data |
移除容器与配置条目;--clean-data 同时删除实例目录与自定义数据目录 |
pg addon password clickhouse |
--name NAME |
打印 admin 密码 |
pg logs addon clickhouse |
--name NAME、-n N(默认 50)、-f |
容器日志 |
pg autostart enable|disable |
--clickhouse、--name NAME |
切换单个实例的开机启动 |
全局参数
| 参数 | 说明 |
|---|---|
-c, --config FILE |
配置文件(默认 ~/.pgcli/pg.yaml) |
-o, --output FILE |
输出配置路径(默认同一文件) |
-i, --instance NAME |
PostgreSQL 实例选择器——ClickHouse 不使用;请改用 --name |
16.33 - Kafka
pg addon install kafka 以托管容器方式运行 Apache Kafka 4.4.0,直接使用官方
docker.io/apache/kafka 多架构镜像。Kafka 4.x 是 KRaft-only:元数据 controller
内建在 broker 进程里(process.roles=broker,controller,combined 模式),不安装也不依赖
ZooKeeper。
和 ClickHouse 一样,Kafka 是顶层插件,不代理某个 PostgreSQL 实例。pgcli 负责渲染
server.properties、固定 KRaft cluster id、绑定挂载数据目录,并以 host 网络启动容器。
单条命令安装一个单机 combined 节点;加上 --cluster、--node-id 和 --peer
则安装跨主机静态 controller quorum 中的一个节点,每台主机各自执行一次
pg addon install kafka(pgcli 不会通过 SSH 远程操作)。见多机集群。
没有认证。 Kafka 默认只使用 PLAINTEXT,
--listen默认127.0.0.1。TLS 是可选功能 (--tls),只加密传输,不认证客户端(ssl.client.auth=none),且 PLAINTEXT 监听 仍然保留以保持兼容。只有在可信内网或防火墙/代理之后才对外暴露。任何能连到任一监听端口 的人都可以读写所有 topic。
参数
| 参数 | 默认值 | 说明 |
|---|---|---|
--name |
kafka |
实例名(map key,也是子命令默认名字) |
--version |
4.4.0 |
上游镜像 tag,直接从 docker.io/apache/kafka 拉取 |
--data-dir |
<base_dir>/addon/kafka/<name>/data |
数据目录,挂载到 /var/lib/kafka/data;必须是绝对路径 |
--listen |
127.0.0.1 |
PLAINTEXT、SSL(启用 --tls 时)与 CONTROLLER 监听地址。0.0.0.0 会暴露所有已启用的监听端口;集群模式下必须是 0.0.0.0 或本节点 advertise 地址 |
--advertise-host |
127.0.0.1 |
告知客户端使用的地址,必须是具体 IP(不能是 0.0.0.0);单机模式下同时作为 controller voter 地址,集群模式下是本节点的 voter 地址,因此必须是具体非 loopback IP,且全新集群安装必须显式传入 |
--broker-port |
自动(基准 9092) |
broker 宿主机端口;0 = 从 kafka_broker_start_port 自动分配 |
--controller-port |
自动(基准 19093) |
KRaft controller 宿主机端口;0 = 从 kafka_controller_start_port 自动分配。集群安装必须显式传入:所有 peer 都会把它写进 voter 列表 |
--cluster NAME |
— | 加入名为该值的多机集群;所有主机必须相同,KRaft cluster id 由它确定性推导 |
--node-id N |
单机 1 |
本节点 1-based KRaft node id;全新集群安装必填,集群内唯一;单机模式下会被拒绝 |
--peer ID@HOST:PORT |
— | 另一个 controller voter。每个其他节点各写一次(本节点自动折叠进去);每台主机都要传完整 peer 列表。host 必须是 IP,port 必须与该节点的 --controller-port 一致 |
--tls |
false | 在明文 broker 之外增加 SSL 监听,使用 pgcli 自签 CA 签发的证书;PLAINTEXT 监听保留 |
--tls-pem |
— | 用这个合并后的 PEM(证书链 + 私钥)提供 TLS,替代自动生成的证书。隐含 --tls;文件会先校验再复制为 config/tls/server.pem。支持 PKCS#8、PKCS#1 与 SEC1 私钥。集群模式下证书必须覆盖本节点的 --advertise-host |
--tls-port |
自动(基准 9093) |
SSL 监听宿主机端口;0 = 从 kafka_tls_start_port 自动分配;需要 --tls |
--cluster-id |
集群推导 / 单机生成 | 集群模式下 id 由 --cluster 推导,显式值必须与之相等;单机模式可显式采用 22 字符 KRaft cluster id,否则采纳 meta.properties 或生成 |
--heap SIZE |
镜像默认(1G) |
JVM 堆大小,以 KAFKA_HEAP_OPTS=-XmsSIZE -XmxSIZE 传入(512m、2g、4g);集群中每节点各自设置;修改需要 --force |
--config-option K=V |
— | 额外的 server.properties 配置(num.partitions=3、log.retention.hours=168);可重复;替换已存储的覆盖表 |
--force |
false | 重新渲染 server.properties 并重建容器;保留数据、已存储的 cluster id 与 node id;增删 TLS 必须带 --force。集群身份字段(cluster、node id、cluster id、peers、advertise host、controller port)即使带 --force 也不可修改 |
Kafka 不使用 --image,用 --version。
controller 默认端口是 19093 而不是 9093——9093 属于 Alertmanager 的默认端口池(TLS 池
虽然从 9093 开始,但会跳过其他插件已占用的端口)。拓扑相关键由 pgcli 自己渲染,
--config-option 只能添加非托管配置:process.roles、node.id、listeners、
advertised.listeners、协议映射、controller 监听/voters、log.dirs、SSL keystore/
客户端认证键、内部 topic 的 replication factor、default.replication.factor、
min.insync.replicas 等都会被拒绝。
安装
第二个实例会从各自的端口池取下一个空闲端口(例如端口未被占用时的 9093/19094):
不带 --force 重复 install 是幂等的,只打印当前状态。--force 会重写配置并重建容器,
保留数据目录、cluster id 与 node id。对已格式化的数据目录改 --cluster-id 会被拒绝——
数据属于原来的 cluster id。
镜像直接从 Docker Hub 拉取,不涉及 pgcli 自建镜像。--version 接受任意合法的上游 tag,
但 tag 必须真实存在,否则 pull 失败。
多机集群
集群是静态 KRaft quorum:每个节点都携带完整的 controller.quorum.voters 列表,
成员关系不能在线变更。每台主机用一个节点安装,使用相同的 --cluster 名字、集群内唯一的
--node-id、本机的 --advertise-host、显式的 --controller-port,并为每个其他
节点传一条 --peer。命令由你在每台主机上分别执行;pgcli 不会通过 SSH 批量操作。
以三台主机 192.0.2.11、192.0.2.12、192.0.2.13 为例:
身份信息是这样拼出来的:
- Cluster ID。 由
--cluster确定性哈希推导,所有主机无需传--cluster-id也能得到 相同值。若显式传入--cluster-id,必须等于推导值。 - Voters。
ID@HOST:PORT按 node id 排序;本节点的条目来自--node-id、--advertise-host和--controller-port,其余由 peer 提供。两个 voter 不允许共享 同一个 controller endpoint。 - Controller 端口。 它出现在所有 peer 的 voter 列表里,所以全新集群安装必须显式
传
--controller-port(默认且推荐值:19093)。自动分配可能在其他主机选到不同端口。 - 端口与地址。 不同主机可以使用相同的 broker/controller 端口。
--listen必须是0.0.0.0(或本节点的 advertise 地址);loopback 会被拒绝。
前两台安装会打印 controller quorum 尚未就绪的警告——在最后一个 voter 装好之前这是正常的。 集群模式下就绪等待是 15 秒而不是 90 秒。所有节点起来后:
Topic 默认副本数跟随 quorum 大小:--replication-factor 0 表示 min(3, voters)
(三节点即 3),min.insync.replicas = min(2, RF)。允许两 voter 集群,但零容错——
失去任一节点就失去元数据 quorum——因此 pgcli 会打印警告。HA 请用 3 或另一个奇数。
安装后集群身份不可变,即使带 --force:--cluster、--node-id、--cluster-id、
--peer 集合、--advertise-host 和 --controller-port 变化都会被拒绝。端口、TLS、
版本、--data-dir 与 --config-option 仍可通过 --force 修改。要改变拓扑,只能在本机
remove 后重装;本插件不提供在线增删 controller。
端口
| 端点 | 默认值 | 协议 | 端口池 |
|---|---|---|---|
| Broker | 9092 |
Kafka 客户端协议(PLAINTEXT) | kafka_broker_start_port,默认 9092 |
| Controller | 19093 |
KRaft 元数据 quorum(PLAINTEXT) | kafka_controller_start_port,默认 19093 |
| SSL(TLS) | 9093(启用 --tls 时) |
TLS 上的 Kafka 客户端协议 | kafka_tls_start_port,默认 9093 |
三个池共享同一个"已分配"集合,并预占其他流式插件(Prometheus、ClickHouse、InfluxDB、
influx2tsdb 和对象存储)的显式端口,避免互相抢占。Kafka 端口分配还会显式避让
Alertmanager 已占端口(Alertmanager 池也反向预占 Kafka 端口),因为两者的默认池都在
9093 附近。broker、controller 与 TLS 端口必须互不相同,且所有 Kafka 实例的端口必须
全局唯一。自动分配会跳过宿主机上已在监听的端口。
--listen 为通配地址时,pgcli 自己的 topic/健康检查命令走 127.0.0.1,打印的本地
controller 端点同理。单机模式的静态 voter 是 AdvertiseHost:ControllerPort,所以如果
把 --advertise-host 设成内网地址,必须同时把 --listen 设为 0.0.0.0(或同一个
内网地址),否则 controller 连不上自己。
集群模式下 controller 端口不只是本地监听——它会被其他所有主机写进自己的
controller.quorum.voters。所有节点请使用相同的 --controller-port(除非你刻意让每个节点
用不同端口,此时每条 --peer 必须携带对应节点的正确端口),并确保该端口在所有主机上
都空闲。端口不一致意味着 voter 列表不同,quorum 永远无法形成。broker 端口是按节点广播的,
可以不同,但全部使用 9092 能让客户端 bootstrap 列表更简单。
副本默认值
副本数跟随静态 voter 数量,单机和集群都不需要手工修改 server.properties:
| Voters | Topic 默认 RF | min.insync.replicas |
可容忍故障(acks=all) |
|---|---|---|---|
| 1 | 1 | 1 | 不支持任何 broker 故障 |
| 2 | 2 | 2 | 不支持任何故障(两个副本必须都在同步中) |
| 3+ | 3 | 2 | 停 1 台 |
- 同样的值会写入
default.replication.factor、min.insync.replicas以及内部 topic(offsets、transaction.state.log、share coordinator),因此新集群在创建 任何业务 topic 之前就能提交 offset 和事务。 pg addon kafka topic create NAME使用该默认值;可用--replication-factor N按 topic 覆盖。RF 不能超过实际承载该分区数据的 broker 数。- 集群再大 RF 也封顶为 3:更多副本只会增加写入成本和延迟,在
min.insync.replicas=2下并不能多容忍一台机器。 min.insync.replicas=2时,ISR 少于 2 的分区会拒绝acks=all写入(Kafka 3.0 起生产者的默认值)。用pg addon kafka topic describe NAME查看 ISR。
TLS
用 --tls 启用 SSL 监听:
install 与 status 会同时打印两个端点和 CA 路径:
PLAINTEXT 监听保持开启,已有客户端不受影响。TLS 只在创建或 --force 时生效;
只传 --tls-port 而不传 --tls 会报错。关闭用 --tls=false --force——生成的文件仍留在
config/tls/,重新启用时复用。
证书由 pgcli 自签 CA(与 MinIO/silo/rustfs 相同的 helper)签发,SAN 覆盖 localhost、
宿主机所有网卡地址和 --advertise-host。客户端需要信任 config/tls/ca.crt。
集群 TLS
每个节点各自签发独立的 CA 和叶子证书,因此可能连接任意 broker 的客户端需要把它们 放进同一个信任文件:
SSL 监听是按节点开启的:需要统一的 SSL bootstrap 列表时请给所有节点启用 --tls。
如果只有部分节点启用了 TLS,这些节点会广播 SSL://... 端点,其余节点只广播
PLAINTEXT://...;使用 security.protocol=SSL 的客户端仍能连到启用了 TLS 的节点,
但纯明文节点保持明文。请逐台执行 --tls --force,保证三 voter 的 quorum 始终保留两个。
--tls 只增加面向客户端的 SSL 监听。CONTROLLER quorum 与 broker 间流量始终是
PLAINTEXT,所以增删 TLS 不会破坏 quorum。
使用自带证书(pg cert)
Kafka 的 PEM keystore 只读一个文件:先是证书链,随后是私钥。pg cert 会把证书和私钥
分别写出,因此安装前先手动合并:
有中间证书时把叶子证书放在最前:cat leaf.crt intermediate.crt key.key > kafka-server.pem。
安装时 pgcli 会校验:文件包含证书且只有一个未加密私钥、两者匹配、证书当前有效、允许
服务端认证用途、且不是 CA 证书。还会检查 SAN 是否覆盖 --advertise-host:集群模式下
证书不覆盖本节点 advertise IP 是硬错误(其他 voter 和客户端会用该地址访问本节点);
单机模式只打印警告。支持 PKCS#8(BEGIN PRIVATE KEY,即 pg cert 的输出)、
PKCS#1(BEGIN RSA PRIVATE KEY)与 SEC1(BEGIN EC PRIVATE KEY);加密私钥会被拒绝。
pgcli 会把合并后的文件复制为 config/tls/server.pem(0600,属主 uid 1000),不会
生成 ca.crt 或 public.crt/private.key,源文件保持不变。自动续期不适用:更换源 PEM 后
需要带 --force 重新 install,才能把新证书装进新容器。
自签的 pg cert 证书需要客户端直接信任证书文件——把下文示例中的 ca.crt 换成
kafka.crt。如果证书由公共或私有 CA 签发,则让客户端信任该 CA 链,并把中间证书一并
放进 kafka-server.pem。
集群中请为每个节点单独签发覆盖其 advertise IP 的证书,并且只传给该节点的 install:
如果某节点的证书没有覆盖它的 --advertise-host,集群安装会在重建容器之前被拒绝。
要从自带证书切回 pgcli 生成的证书,用 --tls --tls-pem '' --force 重新安装;
--tls-pem 传空值会清除已存储的路径。
Java 客户端(client.properties):
librdkafka 系客户端:
用 OpenSSL 验证监听:
在容器内通过 TLS 读写(TLS 目录随 config 目录只读挂载):
- TLS 只负责加密:
ssl.client.auth=none,客户端无需证书,能连到 SSL 端口的人依然可以连接。 监听地址仍是唯一的安全边界。 - 请用 IP 连接(或证书中存在的名字)。不在宿主机网卡地址中的 DNS 名不在 SAN 列表里, 校验会失败。
- 叶子证书在剩余有效期不足 30 天时自动重新签发(install、start 或
--force时检查)。 CA 保持不变,客户端无需更换信任文件。该行为只适用于 pgcli 生成的证书;使用--tls-pem时续期由你负责。
pgcli 创建的内容
单个实例的全部状态位于 <base_dir>/addon/kafka/<name>/:
| 宿主机路径 | 容器路径 | 说明 |
|---|---|---|
config/server.properties |
/mnt/shared/config(目录,只读) |
生成的拓扑配置;镜像入口会把它合并进运行时配置 |
config/tls/ |
/mnt/shared/config/tls(只读) |
仅 --tls:server.pem(证书链 + 私钥,0600,属主 uid 1000);由 pgcli 生成证书时还有 ca.crt(0644)、public.crt 与 private.key。使用 --tls-pem 时只写 server.pem——即你提供的合并 PEM 的规范化副本 |
data/ |
/var/lib/kafka/data |
日志段、meta.properties 与 KRaft 元数据 |
生成的 server.properties 关键内容:
启用 --tls 后,监听与安全配置变为:
三节点集群示例(展示 node 2 of prod;--listen 0.0.0.0):
voter 列表在三台主机上完全相同并按 node id 排序;只有本节点的 node.id 一行不同。
副本数为 min(3, voters),ISR 为 min(2, RF)。
再加上内部 topic 与自动创建 topic 的副本配置:单 voter 时
offsets.topic.replication.factor=1、transaction.state.log.replication.factor=1、
transaction.state.log.min.isr=1、share coordinator 的同名项、
default.replication.factor=1、min.insync.replicas=1。--config-option 覆盖项按
key 排序追加在托管配置之后。
容器以 --network host、--restart unless-stopped、30 秒停止超时运行,并显式传入
-e CLUSTER_ID=<id>,确保镜像用存储的 id 格式化数据目录(否则镜像会注入自己的固定
默认值)。
官方镜像以 uid/gid 1000(appuser) 运行。首次启动前,pgcli 用一次性的
podman unshare chown -R 1000:1000 把数据目录交给该用户。一旦 meta.properties
存在,说明目录属主已正确,后续 start 和 --force 重建都不会再递归遍历日志目录。
启用 --tls 时只把合并后的 config/tls/server.pem 交给 uid/gid 1000(镜像以
appuser 读取 PEM keystore);config 目录、CA 与原始私钥仍归宿主机所有,目录保持可遍历
以支持只读挂载。
Topic
--partitions默认使用 broker 的num.partitions(上游默认1)。--replication-factor 0(默认值)表示"本实例的 RF"——单机为1,集群为min(3, voters)(三节点即3)。--config KEY=VALUE可重复,直接透传给kafka-topics.sh。delete默认要求确认,--yes跳过;删除 topic 会删除其全部消息。retention NAME [DURATION]查看或设置retention.ms。时长支持ms/s/m/h/d/w后缀(30m、12h、7d、4w)、纯数字毫秒, 或-1/infinite/forever表示永久保留。不带时长时打印 topic 的 动态覆盖项;结果为空表示使用 broker 默认值。保留时间按时间清理旧 segment, 即使消息从未被消费也会被删除。- 所有 topic 命令都要求实例正在运行。
用容器内的 Kafka 脚本写入和读取(脚本位于 /opt/kafka/bin,不在 PATH 中):
外部客户端使用打印出的 broker 端点作为 --bootstrap-server。对默认 loopback 监听,
宿主机客户端直接用 127.0.0.1:9092 即可。
集群中每个 broker 广播自己的地址,客户端可以从任意节点 bootstrap,随后被重定向到分区 leader。生产者和消费者可以运行在不同主机上:
用 kcat 在宿主机测试
kcat 是轻量的原生生产/消费工具,适合在容器外做
冒烟测试(Debian/Ubuntu apt install kcat,macOS brew install kcat)。从任意节点
bootstrap 即可,客户端会自动跟随 advertised leader。示例使用上文创建的 e2e
topic(3 分区、RF=3):
TLS 场景下把 kcat 指向集群 TLS 一节拼出的 CA bundle,并选择 SSL
(TLS 监听端口为 9093):
-o beginning/-o -1分别从第一条 / 最后一条开始;-e读到 EOF 退出,-c N消费 N 条后退出;-q只输出消息体。-b使用 advertised 地址即可,客户端会被重定向到分区 leader,无需逐分区配置。- 这些示例不加入消费组:kcat 直接读分区,不提交 offset,因此
pg addon kafka group list里不会出现任何 group。要以消费组方式读取,加-G NAME(topic 改为位置参数),见消费组。
消费组
kafka-consumer-groups.sh 的日常查询封装成了两个子命令:
list列出在本集群提交过 offset 的 group id。describe输出 group 状态、成员,以及每个分区的CURRENT-OFFSET/LOG-END-OFFSET/LAG和消费者 id、host。LAG 持续增长说明消费速度跟不上。- group 在第一次提交 offset 后才会出现;
group.id由客户端设置,pgcli 不创建 消费组。冒烟测试可以先带--group消费一次:
在宿主机上用 kcat 做同样的事则用 -G;组模式下 topic 是位置参数,不能用 -t:
上文各节的 console-consumer 和 kcat 示例刻意不带 group(读完即退出的冒烟
测试),因此不会创建消费组。
- 两个命令都要求实例正在运行。消费者停止且未提交 offset 时 group 会留在
Empty状态,offset 和 lag 仍可见,直到 offset 过期。
状态与 quorum
cluster status 执行 kafka-metadata-quorum.sh --bootstrap-controller <本地 controller> describe --status;健康的单机会打印 cluster id、leader 和一个 voter。集群中它会报告
leader 以及所有 voter 的日志延迟和是否已追上——在任意节点执行均可:
N 个 voter 容忍 (N-1)/2 台节点停机——三节点容忍一台,两节点一台都不能停。停掉多数派
会失去元数据 quorum:broker 的现有分区只能按 min.insync.replicas 允许的范围继续读写,
在 quorum 恢复前无法进行 leader 选举或元数据变更。
install 和 start 最多等待 90 秒,直到 kafka-broker-api-versions.sh 在本地 broker
端点返回结果(集群模式为 15 秒,因为完整 quorum 要等最后一个 voter 装好才形成)。
一直不就绪会让命令失败并打印最后几行日志;数据会保留。
配置
安装时可通过 server.properties 覆盖项补充配置:
--config-option 替换已存储的覆盖表(不是合并)。覆盖项保存在 pg.yaml 的
addons.kafka.<name>.settings 下,每次启动都会重新渲染,因此重启和重建容器都不会丢失。
本版本没有单独的 config set 命令;修改覆盖项请带 --force 重新安装,并传入完整的
覆盖表。
key 允许字母、数字、点、下划线和连字符;value 不允许 NUL、CR、LF。pgcli 托管的 key 会被拒绝,而不是被静默覆盖。
生命周期
不带 --clean-data 时,remove 会留下 <实例目录>(配置 + 数据)以及自定义
--data-dir。同名重装会采纳已格式化的存储:自动复用 meta.properties 里的
cluster.id 与 node.id,而不是生成新集群——因此重装可以恢复原有日志。
--clean-data 删除实例目录以及(若不同)自定义数据目录(uid 1000 的文件通过
podman unshare 删除),但不会删除已拉取的镜像。如果把实例指向属于其他 cluster id
或 node id 的数据目录,会在创建容器前被拒绝。
集群中 remove 只影响本机——pgcli 不会联系其他节点,它们的静态 voter 列表仍然引用
本节点。pg addon remove 会先打印 quorum 影响。三个 voter 时,移除(或停止)一个节点
集群仍可用;移除第二个会失去 quorum。之后重新加入必须使用相同的 --cluster、
--node-id 和 --peer 列表;--clean-data 后数据目录为空,broker 会用相同的推导
cluster id 重新格式化。
开机自启同样是按主机的——请在每个节点上启用:
开机启动会先重新渲染 server.properties,并且是幂等的(pg start 也会启动
autostart 启用的 Kafka 实例)。
生产注意事项
- 静态 quorum,不能在线扩缩。
--cluster安装的是固定 KRaft quorum 中的一个节点, 没有在线增删 controller 的能力:扩容或缩容需要用新的 voter 列表重装节点。HA 请使用 3(或其他 ≥3 的奇数)个 voter;允许两 voter,但零容错。 - 默认仍是单机。 不带
--cluster时没有副本也没有 HA:broker 停止后该实例无法读写。 - KRaft combined 模式。 broker 和 controller 共用一个进程,适合开发和小规模部署; 上游建议大型关键集群使用独立的 controller/broker 角色,本插件暂不提供。
- 无认证;TLS 只负责传输加密。 PLAINTEXT 监听始终开启;
--tls增加的加密监听因ssl.client.auth=none而不认证客户端。能连到任一端口的人都可以读写所有 topic。 集群模式下 CONTROLLER quorum 与 broker 间流量同样是 PLAINTEXT——--tls只覆盖客户端 连接。保持默认 loopback,或只在可信内网暴露。如需要真正的认证(SASL/mTLS),请自行在 Kafka 前放置代理/网关。 - 没有 ZooKeeper。 Kafka 4.x 是 KRaft-only,不存在 ZooKeeper 容器;
zookeeper.connect作为托管 key 会被拒绝。 - 副本不是备份。 topic 副本保障的是可用性,不能防误删或逻辑损坏。 离线备份请停止实例后复制数据目录;本版本没有 backup/restore 命令,且单个集群节点的 离线拷贝并不是完整的集群备份。
- 升级。
--force --version <新版本>会拉取新 tag 并在同一数据目录上重建容器。 Kafka 不保证新版本写入的数据能回退到旧版本——升级前请做离线拷贝。--version必须是真实存在的上游 tag。 - JVM 堆。 镜像默认
-Xms1G -Xmx1G。节点承载大量分区/连接时用--heap调大,但要给 OS page cache 留足内存——Kafka 主要读 page cache,堆超过约 6–8 GB 通常弊大于利。集群中每节点独立设置;修改会用--force重建该节点容器, 逐台执行。 - 数据属主。 数据文件属主为 uid/gid 1000。清理请用
--clean-data,不要手工删除; pgcli 会处理权限回退。 - 端口。 broker 默认
9092;controller 默认19093,避开 Alertmanager 默认的9093;启用 TLS 时 SSL 从9093起分配并跳过已占端口。host 网络下它们就是普通 宿主机端口。 - 平台。 Linux(host 网络);官方镜像多架构(amd64/arm64)。
命令行参数参考
pg addon install kafka
| 参数 | 默认值 | 说明 |
|---|---|---|
--name NAME |
kafka |
实例名;[A-Za-z0-9][A-Za-z0-9_-]*,最多 48 字符 |
--version TAG |
4.4.0 |
上游 docker.io/apache/kafka tag |
--data-dir DIR |
<base_dir>/addon/kafka/<name>/data |
绝对路径宿主机数据目录,挂载到 /var/lib/kafka/data |
--listen ADDR |
127.0.0.1 |
broker、SSL(启用时)与 controller 的监听地址;集群模式要求 0.0.0.0 或 advertise 地址 |
--advertise-host IP |
127.0.0.1 |
对外广播的 broker 地址(也是本节点的 controller voter 地址);不能是通配地址;全新集群安装必须显式传入 |
--broker-port N |
自动(基准 9092) |
broker 宿主机端口;0 = 自动分配 |
--controller-port N |
自动(基准 19093) |
controller 宿主机端口;0 = 自动分配;全新集群安装必须显式传入,因为每个 peer 都会内嵌它 |
--cluster NAME |
— | 多机集群名;所有主机相同;KRaft cluster id 由它推导 |
--node-id N |
单机 1 |
1-based node id,集群内唯一;全新集群安装必填;单机模式会被拒绝 |
--peer ID@HOST:PORT |
— | 另一个节点的 controller voter;每个其他节点各一条;所有主机传相同 peer 列表 |
--tls |
false | 增加 SSL 监听;PLAINTEXT 监听保留 |
--tls-pem FILE |
— | SSL 监听使用的合并 PEM(证书链 + 私钥);隐含 --tls;路径可为相对或绝对;集群模式下必须覆盖 --advertise-host |
--tls-port N |
自动(基准 9093) |
SSL 监听宿主机端口;0 = 自动分配;需要 --tls |
--cluster-id ID |
集群推导 / 单机生成 | KRaft cluster id;集群模式下必须等于由 --cluster 推导出的值 |
--heap SIZE |
镜像默认(1G) |
JVM 堆,以 KAFKA_HEAP_OPTS=-XmsSIZE -XmxSIZE 传入(512m、2g、4g);集群中每节点各自设置;修改需要 --force |
--config-option K=V |
— | 额外 server.properties 配置;可重复;替换已存储的覆盖表 |
--force |
false | 重新渲染配置并重建容器,保留数据和 id;增删 TLS 必须使用;不能修改集群身份字段 |
pg addon kafka status / shell
| 参数 | 默认值 | 说明 |
|---|---|---|
--name NAME |
kafka |
目标实例 |
-- COMMAND... |
/bin/bash |
仅 shell:执行命令而不是打开交互式 shell |
pg addon kafka topic create|list|describe|delete|retention
| 参数 | 默认值 | 说明 |
|---|---|---|
NAME |
— | topic 名(create/describe/delete/retention 的位置参数) |
DURATION |
— | 仅 retention:新的 retention.ms(30m、12h、7d、4w、纯毫秒,或 -1/infinite);省略则查看当前覆盖项 |
--partitions N |
broker 默认(1) |
仅 create:分区数 |
--replication-factor N |
0 = 实例 RF(单机 1,集群 min(3, voters)) |
仅 create:副本数 |
--config K=V |
— | 仅 create:topic 配置;可重复 |
--exclude-internal |
false | 仅 list:隐藏 Kafka 内部 topic |
--yes |
false | 仅 delete:跳过确认 |
pg addon kafka group list|describe
| 参数 | 默认值 | 说明 |
|---|---|---|
GROUP |
— | 消费组 id(仅 describe 的位置参数) |
--name NAME |
kafka |
查询哪个实例的本地 broker |
pg addon kafka cluster status
| 参数 | 默认值 | 说明 |
|---|---|---|
--name NAME |
kafka |
查询哪个实例的本地 controller |
生命周期、日志与开机自启
| 命令 | 参数 | 说明 |
|---|---|---|
pg addon start kafka |
--name NAME |
启动(不存在则创建),等待就绪 |
pg addon stop kafka |
--name NAME |
停止容器,保留数据 |
pg addon remove kafka |
--name NAME、--clean-data |
删除容器与配置条目;--clean-data 同时删除实例目录和自定义数据目录 |
pg logs addon kafka |
--name NAME、-n N(默认 50)、-f |
容器日志 |
pg autostart enable|disable |
--kafka、--name NAME |
开关单个实例的 boot 启动 |
全局参数
| 参数 | 说明 |
|---|---|
-c, --config FILE |
配置文件(默认 ~/.pgcli/pg.yaml) |
-o, --output FILE |
输出配置路径(默认与输入相同) |
-i, --instance NAME |
PostgreSQL 实例选择器——Kafka 不使用,请用 --name |
16.34 - RabbitMQ
pg addon install rabbitmq 以托管容器方式运行 RabbitMQ 4.4.0,直接使用官方
docker.io/library/rabbitmq 的 -management 镜像(内含 Web UI 与 HTTP API)。和
Kafka、ClickHouse 一样,RabbitMQ 是顶层插件:pgcli 负责渲染 rabbitmq.conf、管理
管理员账号、绑定挂载数据目录,并以 host 网络启动容器。
插件支持单节点,或作为多机集群的一个成员。集群使用 etcd 自动发现
(rabbitmq_peer_discovery_etcd):每台主机以相同的 --cluster 名和 etcd endpoint 列表安装
一个成员,首次启动时成员自动注册并互相发现——不需要静态 peer 列表,也不需要手动
join_cluster。不配置 federation、shovel、MQTT 或 STOMP;vhost、用户、权限、策略、
exchange 等高级管理通过 management UI 或 pg addon rabbitmq shell 里的 rabbitmqctl
完成。常用队列操作已封装为 pg addon rabbitmq queue 子命令(见队列与消息)。
集群模式下 declare 默认创建 quorum 队列(见集群)。
认证始终开启。 AMQP 与 management UI 都需要 pgcli 创建的管理员账号(默认
admin,20 位自动生成密码)。--listen默认127.0.0.1。对外暴露时,密码就是安全 边界:TLS 只加密传输,不认证客户端(verify_none、fail_if_no_peer_cert = false)。
参数
| 参数 | 默认值 | 说明 |
|---|---|---|
--name |
rabbitmq |
实例名(map key,也是子命令默认名字) |
--version |
4.4.0 |
上游镜像 tag,实际拉取 docker.io/library/rabbitmq:<version>-management |
--admin-user |
admin |
管理员账号,首启创建,每次启动对账 |
--admin-password |
自动生成(20 位) | 管理员密码;同时存入 pg.yaml 与 0600 secret 文件。集群模式拒绝明文,请用 --secrets-from-etcd 或 --admin-password-file |
--admin-password-file FILE |
— | 从 FILE 读取管理员密码(单行)。集群成员首次安装必填(使用 --secrets-from-etcd 时除外);单机实例也可使用 |
--data-dir |
<base_dir>/addon/rabbitmq/<name>/data |
数据目录,挂载到 /var/lib/rabbitmq;必须是绝对路径 |
--listen |
单机 127.0.0.1,集群 0.0.0.0 |
AMQP、AMQPS(启用 --tls 时)与 management 监听共用的绑定地址。0.0.0.0 会暴露所有已启用监听;集群模式不能是 loopback |
--cluster NAME |
— | 加入/创建名为 NAME 的 RabbitMQ 集群。空 = 单机;所有成员必须相同 |
--node-name NODE@HOST |
— | 本成员的 Erlang 节点名(如 rabbit@192.0.2.11)。集群成员首次安装必填;必须唯一。host 为 IP 时无需名称解析;为主机名时必须在每个成员上可解析 |
--etcd m1,m2 |
— | 用本地 etcd addon 成员作为发现 endpoint;与 --etcd-endpoints 二选一(后者优先) |
--etcd-endpoints host:port,... |
— | 外部 etcd 发现 endpoint;可重复或逗号分隔。仅支持明文 HTTP,可带 http:// 前缀(会被去掉) |
--erlang-cookie-file FILE |
— | 从 FILE 读取共享的 Erlang cookie。集群成员首次安装必填(使用 --secrets-from-etcd 时除外);值保存后不可变 |
--secrets-from-etcd |
false | 从 pg addon rabbitmq secret generate 写入的 etcd key 读取两个共享密钥(推荐,无需在主机间复制文件)。不能与 --erlang-cookie-file 或 --admin-password-file 同时使用 |
--amqp-port |
自动(基准 5672) |
明文 AMQP 端口;0 = 从 rabbitmq_start_port 自动分配 |
--management-port |
自动(基准 15672) |
Management HTTP UI/API 端口;0 = 从 rabbitmq_management_start_port 自动分配 |
--tls |
false | 在明文监听之外增加 AMQPS 与 management HTTPS,使用 pgcli 自签 CA 签发的证书 |
--tls-cert FILE |
— | 自带服务端证书(PEM 链);必须与 --tls-key 同时传入;隐含 --tls;--listen 不是 0.0.0.0 时必须覆盖该地址 |
--tls-key FILE |
— | 自带私钥(PKCS#8/PKCS#1/SEC1),与 --tls-cert 配对 |
--tls-port |
自动(基准 5671) |
AMQPS 端口;0 = 从 rabbitmq_tls_start_port 自动分配;需要 --tls |
--config-option K=V |
— | 额外的 rabbitmq.conf 配置(heartbeat=60、log.file.level=debug);可重复;替换已存储的覆盖表 |
--force |
false | 重新渲染配置并重建容器;保留数据与已存储的密码。集群模式下 --cluster、--node-name 与 Erlang cookie 不可变,--etcd-endpoints 可以更新 |
RabbitMQ 不使用 --image,用 --version。--version 接受任意合法的上游 tag,但 tag 必须
真实存在,否则 pull 失败;镜像引用始终是 docker.io/library/rabbitmq:<version>-management。
安装
⚠️ 单机版创建的是持久化 classic 队列,但消息默认不持久化。classic 的消息持久化是
逐条消息的属性,必须由发布端在每次 publish 时设置(pg addon rabbitmq queue publish
加 --property delivery_mode=2,应用客户端设置 delivery_mode=2),不存在队列参数或
policy 级别的开关。未设置时 broker 重启后队列仍在、消息丢失。集群模式使用 quorum 队列,
会自动持久化所有消息。
第二个实例会从各自的端口池取下一个空闲端口(例如 AMQP 5673、management 15673、
EPMD 4370、distribution 25673):
不带 --force 重复 install 是幂等的,只打印当前状态。带 --force 时,pgcli 会重写
rabbitmq.conf、重写 enabled_plugins、重建容器并重新对账管理员账号;数据目录与密码保留。
密码只在安装时打印一次,之后可随时取回:
Management UI 与 API
Management Web UI 的地址:
执行 pg addon rabbitmq status --name rmq 可打印本实例准确的 Management: 地址(随
--listen 与 --management-port 变化)。浏览器打开该地址,用管理员账号登录,密码通过
pg addon password rabbitmq --name rmq 获取。集群模式下任意成员都提供相同的 UI,
Overview → Nodes 可看到整个集群。
同一端口也提供 HTTP API:
CLI 未封装的能力——vhost、用户、权限、策略与 exchange 定义——都通过 UI 完成。
常用队列操作已提供 pg addon rabbitmq queue ... 命令(见队列与消息)。
端口
| 端点 | 默认 | 端口池 |
|---|---|---|
| AMQP(明文) | 5672 |
rabbitmq_start_port,默认 5672 |
| AMQPS(TLS) | 5671(启用 --tls 时) |
rabbitmq_tls_start_port,默认 5671 |
| Management HTTP UI/API | 15672 |
rabbitmq_management_start_port,默认 15672 |
| Management HTTPS | 15671(启用 --tls 时) |
rabbitmq_management_tls_start_port,默认 15671 |
| Erlang EPMD | 4369 |
rabbitmq_epmd_start_port,默认 4369 |
| Erlang distribution | 25672 |
rabbitmq_dist_start_port,默认 25672 |
每个池独立分配:实例按名字排序,显式端口不会被覆盖,宿主已监听的端口会被跳过。所有 RabbitMQ 实例的端口必须互不重复。
EPMD 与 distribution 是基础设施端口,不是客户端端点。单机实例以
RABBITMQ_NODENAME=rabbit@localhost、ERL_EPMD_ADDRESS=127.0.0.1 和独立的
EPMD/distribution 端口运行,rabbitmq.conf 里固定
distribution.listener.interface = 127.0.0.1——Erlang distribution 永不对外暴露,多个单机
实例可在同一宿主共存,也不会与宿主的 Erlang 安装冲突。
集群成员不同:每个成员固定使用 EPMD 4369 与 distribution 25672,distribution 监听
--listen(默认 0.0.0.0)。因此一台主机最多运行一个集群 RabbitMQ 实例,且 4369、
25672 必须能被其他成员访问(见集群)。
不启用 rabbitmq_prometheus 插件。 pgcli 挂载自己的 enabled_plugins(单机只有
rabbitmq_management,集群模式为 rabbitmq_management 与 rabbitmq_peer_discovery_etcd),
保证每个实例行为一致,也不会多开一个无认证的固定 metrics 监听。
集群
集群通过 etcd 自动发现组建,不使用静态 peer 列表。每台主机以相同的 --cluster 名和
etcd endpoint 列表运行一个成员;首次启动时成员在 etcd 中注册自己,并加入已经注册的节点。
前置条件:
- etcd 3.4+,每个成员都能以明文 HTTP 访问。测试时单成员 etcd 即可,但生产环境的发现
存储本身应当冗余。本版本 pgcli 不支持该 etcd 连接的 TLS 或认证,请放在可信网络中。可以
用 pgcli 的 etcd addon(
pg addon install etcd ...),也可以用--etcd-endpoints指向 外部 etcd。 - 节点名唯一。
--node-name rabbit@host在集群内必须唯一。host 部分可以是主机名或 IPv4;拒绝 loopback 与 IPv6。使用 IP 时成员直接互连,完全不需要名称解析——没有内网 DNS 时推荐这种方式。使用主机名时,每个成员都必须能解析它(DNS 或/etc/hosts): etcd 发现只注册rabbit@host,不提供地址。FQDN、IP 会让 RabbitMQ 自动使用 Erlang long names。 - 共享密钥。 所有成员必须使用相同的 Erlang cookie 和相同的管理员密码。先在管理机
执行一次
pg addon rabbitmq secret generate:它生成本地0600文件并把内容写入 etcd; 各成员安装时用--secrets-from-etcd读取,无需在主机间复制文件。pgcli 会把两个值保存到pg.yaml和0600secret 文件,因此start与 autostart 不再依赖 etcd 或原始文件。 仍支持手动分发文件时使用--erlang-cookie-file/--admin-password-file。这两个都是 集群级密钥,AMQP 客户端都不需要。 - 版本一致。 每个成员的
--version相同。 - 集群名一致。 不同的
--cluster会在同一个 etcd 中形成另一套集群;pgcli 通过cluster_formation.etcd.cluster_name隔离。
在管理机上生成并发布一次共享密钥:
命令不会打印密钥值。它会写入 /tmp/rmq.cookie 与 /tmp/rmq.admin(权限 0600),并把
相同内容写入以下 etcd key(不是文件):
这两个本地文件各有明确用途:
| 文件 | 用途 |
|---|---|
/tmp/rmq.cookie |
共享 Erlang cookie。仅用于集群成员之间的 Erlang distribution 握手(EPMD 4369、distribution 25672),AMQP 与管理客户端从不使用。 |
/tmp/rmq.admin |
共享的broker 管理员密码,用于创建/对账 admin 账号。它是安装输入,不是客户端凭据文件:应用应使用自己创建的 RabbitMQ 用户连接。需要以管理员身份登录时,用 pg addon password rabbitmq --name mq 取回已保存的密码即可,不必保留该文件。 |
这两个文件都是安装引导/分发用的中间产物。安装时每个成员都会把值保存到 pg.yaml 和
自己的 0600 secret 文件,start/autostart 不会读取 /tmp 下的文件,因此所有成员安装
完成后即可删除。若不用 --secrets-from-etcd 而选择手动分发,则把这两个文件复制到各主机
(内容必须一致),并通过 --erlang-cookie-file / --admin-password-file 传入;各主机的
路径可以不同。成员安装完成后绝不要重新生成 Erlang cookie——它不可变,cookie 不一致的
节点无法加入集群。
在各成员主机上安装(只有 --node-name 不同;用各主机 IP 可免去 /etc/hosts/DNS):
每个成员都读取相同的值,不需要手动复制文件。若倾向手动分发,可跳过 secret generate,
改用两个 --*-file 参数。
etcd 中这些值是明文的。任何能访问 etcd client 端口的人都能读到集群凭据,因此请把
2379 限制在成员主机与管理机之间;不要把密钥放进其他租户可读的共享 etcd。
第一个成员会启动一个只有自己的集群并打印提示;后续成员通过 etcd 发现并自动加入。发现只在 Mnesia 为空时执行——已加入的成员重启时联系 last-seen peers。
发现 key 位于
因此只要 --cluster 名不同,多套集群可以共用一个 etcd。
队列语义
- 集群模式下
pg addon rabbitmq queue declare默认--type quorum。Quorum 队列会在所有成员 上复制(默认 quorum size 为 3,以成员数为上限),是 RabbitMQ 4.x 唯一支持复制的队列类型。 --type classic仍可使用,但 classic 队列不复制:它只存在于声明它的节点上,该节点 宕机时队列不可用。仅建议用于非关键或可重建的数据。--durable=false、quorum +--auto-delete等限制不变(见队列与消息)。- 每个节点都能服务客户端,management API 也会展示整个集群,因此客户端可以连接任意成员。 生产环境请配置多个 endpoint,或前置 TCP 负载均衡。
查看与下线成员
cluster status 输出 rabbitmqctl cluster_status,包含所有 running nodes。cluster forget
用于把已停止、永久下线的成员从集群元数据中移除;它拒绝 forget 本机节点。移除成员后,
重新平衡 quorum 队列的副本分布:
只是临时停机的成员不需要任何操作:重启后会通过 last-seen peers 自动重新加入。不要在节点
仍计划恢复时执行 cluster forget——旧节点将无法重新加入,只能清空数据重建(或在对端 forget
后用空数据目录重新添加)。
集群约束
--cluster、--node-name与 Erlang cookie 不可变。用不同的值执行install --force会失败;--etcd-endpoints可以更新。- 单机实例不能转换为集群成员(反之亦然):用
--clean-data删除后重新安装。集群首次安装 也要求数据目录为空。 --advertise-host、--node-id、--peer、--join对 RabbitMQ 不适用;节点身份由--node-name唯一决定。集群模式拒绝明文--admin-password。- 管理员密码是集群级凭据,所有成员必须保存相同的值。轮换时先在管理机执行
pg addon rabbitmq secret generate --force --keep-cookie ...发布新密码(或改用--admin-password-file ...),然后在每个成员上重新执行install --force --secrets-from-etcd(或对应的--admin-password-file ...)。 成员安装后绝不能重新生成 Erlang cookie:它不可变,变化会让install --force失败。 - etcd 中
/pgcli/rabbitmq/clusters/<name>/secrets/下的密钥是明文。请把 etcd client 端口限制在成员主机与管理机之间。这两个密钥只属于集群:AMQP 客户端都不需要 cookie 或 管理员密码,业务应用应创建独立的 RabbitMQ 用户。 - 集群互联流量(
25672上的 Erlang distribution)不加密;--tls只保护客户端监听。 请把4369与25672限制在私有网络,且只对成员开放。
TLS
TLS 是增量的:--tls 保留明文 AMQP 与 management 监听,另外增加 AMQPS 与
management HTTPS。
install 与 status 会同时打印两套端点和 CA 路径:
自动生成的证书由 pgcli 自签 CA(与 MinIO/silo/rustfs、Kafka 共用的 helper)签发,覆盖
localhost、127.0.0.1、::1、宿主所有网卡地址以及具体的 --listen 地址。客户端必须
信任 config/tls/ca.crt:
启用 TLS 后的 AMQP URL(例如 Python/pika 或 Go 客户端):
- TLS 只负责传输加密。
ssl_options.verify = verify_none与fail_if_no_peer_cert = false表示客户端不提供证书;任何能连到已启用端口且知道密码的人 仍然可以访问。监听地址与密码才是安全边界。 - 叶子证书在到期前 30 天内(安装、启动或
--force时)重新签发,CA 保留,因此客户端 不需要换信任文件。该行为只适用于 pgcli 生成的证书。 --tls-port没配--tls会被拒绝。用--tls=false --force关闭 TLS;生成的文件保留在config/tls/,重新启用时复用。
自带证书(pg cert)
--tls-cert 与 --tls-key 必须成对传入,且隐含 --tls。在任何镜像拉取或容器操作之前,
pgcli 会校验:
- 证书可解析且当前有效;
- 允许 server authentication,且不是 CA 证书;
- 私钥与证书匹配且未加密;
- 证书覆盖具体的
--listen地址(不覆盖是硬错误;可改用--listen 0.0.0.0或把该地址 加入 SAN)。
文件会复制到实例的 config/tls/:证书为 public.crt、私钥为 private.key,证书副本同时
作为 ca.crt。pg cert 生成的自签叶子证书本身就是信任锚,客户端可直接信任 rmq.crt。
如果证书由其他 CA 签发,请改为信任该 CA 链:ca.crt 只包含你提供的文件,可能不包含根证书。
使用自带证书时,源文件不会被改动,pgcli 也不会自动续期——替换源文件后重新执行
install --force(或 --tls=false --force 回到明文)。
队列与消息
queue list、purge、delete 走容器内的 rabbitmqctl,不依赖 management HTTP 密码。
其余队列命令——declare、info、publish、get、bind、unbind、bindings——通过
management HTTP API 使用已保存的管理员凭据,要求实例正在运行且宿主能访问 management 端口。
list打印name、type、messages、ready、unacked、consumers;空 vhost 打印no queues。declare在单机实例上默认创建持久化 classic 队列,在集群成员上默认创建 持久化 quorum 队列(见集群)。RabbitMQ 4.x 已移除 transient(非持久化且 非 exclusive)队列,因此--durable=false会在发出请求前直接报错。--type classic或--type quorum可显式覆盖默认类型;quorum 队列必须持久化,且不能 auto-delete。 可重复的--argument KEY=VALUE设置队列参数;能按 JSON 解析的值保留 JSON 类型(如--argument x-max-priority=10)。- 消息保留时间。
--message-ttl 30m(或毫秒数)设置x-message-ttl:在队列中等待超过 该时间的消息会被丢弃;若队列配置了x-dead-letter-exchange则转死信。单条消息也可用--property 'expiration="60000"'设置自己的 TTL。 - 自动删除。
--auto-delete在最后一个消费者断开后删除队列。--expires 24h设置x-expires:队列连续闲置该时长后,队列本身连同其中剩余消息一起删除。只有存在 消费者、被重新声明、或通过basic.get访问才算“使用过”;单纯发布消息不会重置 计时器。该值必须为正数(0会被 RabbitMQ 拒绝)。两者也可通过--argument设置。 info显示 type、state、durable、auto-delete、消息计数、consumers、memory、policy、 arguments 与 node;--json输出 management API 原始文档。计数由 RabbitMQ 统计模块异步 刷新,可能比 publish/get 滞后几秒。publish发送一条消息。--payload与--payload-file必须二选一(-表示 stdin)。--exchange默认amq.default(默认 exchange,按 routing key 直接路由到同名队列);--routing-key默认队列名。可重复的--property KEY=VALUE设置消息属性,例如--property delivery_mode=2 --property 'content_type="application/json"'。消息未被路由时 只打印警告,命令仍成功。- 单机持久化。 单机实例默认声明持久化 classic 队列(不复制),但 durable 只作用于
队列本身,不作用于消息。classic 的消息持久化是每条消息的属性,必须由发布端在每次
publish 时设置:命令行加
--property delivery_mode=2,应用客户端则在 AMQP 发布代码中 设置delivery_mode=2。队列参数或 policy 无法开启该行为(--argument delivery_mode=2不生效)。未设置时消息是 transient,broker 重启(stop/start、 容器重建或宿主机重启)后队列还在、消息丢失。设置后只要实例数据目录保留(不要remove --clean-data),消息即可恢复;磁盘损坏则无法恢复。集群的 quorum 队列不依赖 delivery mode,所有消息都会持久化并复制。 get默认 peek(ack_requeue_true);--consume确认并删除取出的消息 (ack_requeue_false)。--count默认 1;--json输出 API 原始响应。bind/unbind必填--exchange,并拒绝amq.default(默认 exchange 是隐式绑定)。--routing-key默认队列名。可重复的--argument支持基于参数(headers)的绑定。unbind不带--argument时删除该 exchange 下所有同 routing key 的绑定;带--argument时只删除参数完全一致的那条。bindings列出队列的绑定及其参数(--json输出原始文档)。purge清空队列中的消息但保留队列;delete删除队列本身。两者默认要求确认,--yes可跳过。--if-empty/--if-unused(仅 delete)透传 RabbitMQ 的条件检查:队列 非空或有消费者时删除失败,而不是直接删掉。- 所有队列命令都接受
--name与--vhost(默认/),并要求实例正在运行。
限制保留时间样例
不带保留参数的队列会无限期保留消息:默认没有 TTL。消息只会因为被消费、purge、 进入死信,或队列本身被删除而离开。常见限制方式:
--message-ttl 与 --expires 只在队列首次声明时生效。要修改已存在队列的保留策略而不
删除队列,使用 policy:
policy 按队列名正则匹配,对新建和已有队列都生效,并优先于同名的 x-* 队列参数。也可以
在 management UI 的 Admin → Policies 中创建。
同样的保留限制也可以不重新声明队列,直接通过 RabbitMQ policy(message-ttl、
expires、max-length、max-length-bytes)施加;pgcli 暂未封装 policy,请使用
management UI 或 rabbitmqctl set_policy。
pg addon rabbitmq shell 打开容器内的 /bin/bash;rabbitmqctl 与
rabbitmq-diagnostics 都在里面,可处理 CLI 和 API 未覆盖的操作。
状态与生命周期
install、start 与 --force 在单机实例上最多等待 90 秒,轮询
rabbitmqctl -q await_startup;集群成员最多等待 180 秒,覆盖首次 peer discovery
与 Mnesia 引导。集群模式下每次尝试上限 10 秒——有 peer 时该命令可能已执行成功却静默
不退出;若被空闲 watchdog 终止,pgcli 改为探测 AMQP 端口,监听器在启动末尾打开,
端口可连即视为就绪。就绪后 pgcli 用
rabbitmqctl change_password / add_user / set_user_tags administrator /
set_permissions 对账管理员账号。
集群模式下 status 还会打印集群名、Erlang 节点名与 etcd endpoint;
pg addon rabbitmq cluster status 展示 rabbitmqctl cluster_status 的实时视图。
不带 --clean-data 的 remove 会保留数据目录、生成的配置和 0600 密码 secret。同名重装会
复用该密码,因此 remove/reinstall 之后客户端配置无需修改。
开机自启:
启动时会重写 rabbitmq.conf、启动容器并重新对账管理员账号。pg start 也会带上已启用
autostart 的 RabbitMQ 实例。
pgcli 创建的内容
单个实例的全部文件位于 <base_dir>/addon/rabbitmq/<name>/:
| 宿主路径 | 容器路径 | 说明 |
|---|---|---|
config/rabbitmq.conf |
/etc/rabbitmq/rabbitmq.conf(只读) |
渲染后的监听、management、管理员 tags/权限与 distribution 配置 |
config/enabled_plugins |
/etc/rabbitmq/enabled_plugins(只读) |
单机 [rabbitmq_management].;集群 [rabbitmq_management,rabbitmq_peer_discovery_etcd]. |
config/tls/ |
/etc/rabbitmq/tls(只读,仅 --tls) |
ca.crt(0644)、public.crt(含链,0644)、private.key(0600,chown 给 uid 999);ca.key 留在宿主侧且不挂载 |
data/ |
/var/lib/rabbitmq |
Mnesia、消息存储、definitions 与日志;集群模式额外包含 .erlang.cookie(0400,chown 给 uid 999) |
secrets/admin_password |
不挂载 | 0600,重装时复用 |
明文实例生成的 rabbitmq.conf:
--tls 时会额外写入 listeners.ssl.default、ssl_options.* 与 management.ssl.*。
集群成员改为绑定 0.0.0.0(或显式 --listen 地址),并追加发现配置:
任何 secret 都不会写进 rabbitmq.conf。管理员密码通过容器环境变量传入
(RABBITMQ_DEFAULT_USER / RABBITMQ_DEFAULT_PASS 只用于播种空的 Mnesia,之后 pgcli
每次启动用 rabbitmqctl 对账),RABBITMQ_NODENAME 与 RABBITMQ_USE_LONGNAME 同样走
环境变量。集群模式下 pgcli 还会在容器启动之前把共享 Erlang cookie 写入
data/.erlang.cookie(权限 0400,属主 uid 999):官方 4.4 镜像忽略
RABBITMQ_ERLANG_COOKIE,任其自行生成随机 cookie 的节点无法与 peer 完成 distribution
握手。
容器以 --network host、--restart unless-stopped 和 30 秒 stop timeout 运行,身份是镜像的
uid/gid 999(rabbitmq);pgcli 只把 TLS 私钥与集群 cookie 文件交给该 uid(rootless 下
通过 podman unshare,root 下用 os.Chown)。数据目录由镜像入口脚本自行 chown。
配置覆盖
安装时可追加 rabbitmq.conf 条目:
--config-option 替换已存储的覆盖表(不合并)。覆盖项保存在 pg.yaml 的
addons.rabbitmq.<name>.settings 下,每次启动重新渲染。键可包含字母、数字、点、下划线和
连字符;值不能包含 NUL、CR 或 LF。
pgcli 托管的键会被拒绝而不是被静默覆盖:listeners.*、ssl_options.*、management.*、
prometheus.*、cluster_formation.*、default_user、default_pass、
default_user_tags.*、default_permissions.*、loopback_users.*、
distribution.listener.*。
密码与账号语义
RABBITMQ_DEFAULT_USER/RABBITMQ_DEFAULT_PASS只在首次启动、Mnesia 为空时播种。 因此 pgcli 每次启动都用rabbitmqctl change_password对账(用户不存在时退回add_user),再设置administratortag 以及 vhost/上的.* / .* / .*权限。- 在已有实例上修改
--admin-user会创建/对账新账号,但不会删除旧账号。请通过 management UI 或rabbitmqctl delete_user清理。 - 修改
--admin-password(或复用已保存的 secret)会在下一次install --force或start时生效。集群中的密码是共享的:所有成员必须保存相同的值,轮换时要在所有成员上同步操作。 - 集群模式的共享密钥来自
--secrets-from-etcd(推荐,key 由pg addon rabbitmq secret generate写入),或来自--admin-password-file与--erlang-cookie-file。明文--admin-password被拒绝。两个值随后都会持久化到pg.yaml与0600secret 文件。 - 密码同时保存在
pg.yaml与0600secret 文件中;pg addon password rabbitmq --name NAME打印存储值。
生产注意事项
- 单节点或一个集群成员。 单机实例没有 HA:容器停止即 broker 不可用。集群模式的可用性 来自跨成员复制的 quorum 队列;classic 队列不复制,会随所在节点一起不可用。不配置 federation、shovel、MQTT 或 STOMP。
- 集群网络。
4369(EPMD)与25672(Erlang distribution)只在私有网络中、仅对成员 开放。即使启用--tls,distribution 流量也不加密;TLS 只覆盖客户端监听。一台主机只能有 一个集群实例。 - 发现链路是明文的。 etcd endpoint 通过明文 HTTP 访问,没有认证。任何能访问 etcd 的人
都能往集群注册恶意节点,或读取
pg addon rabbitmq secret generate写入的共享密钥; 请把 etcd 隔离在可信网络中。本版本不支持 etcd TLS/认证。 - 不启用 Prometheus 插件。
enabled_plugins固定为 management(集群模式另加 etcd 发现插件)。需要 metrics 时可通过 management API 采集,或自行在容器内启用插件——但要 注意多个实例会争抢同一个默认 metrics 端口。 - TLS 不是客户端认证。 请使用
--listen 127.0.0.1,或在 broker 前加私有网络/防火墙。 Management UI 是完整的管理面,不要随意暴露。 - TLS 是增量监听。 启用
--tls后明文amqp://与http://仍然可用;需要加密的 客户端必须显式选择amqps:///https://端点。 - 升级。
--force --version <new>会拉取新 tag 并在原数据目录上重建容器。RabbitMQ 不保证 能被更新版本写入的数据可降级——升级前请先做离线拷贝。--version必须是真实存在的上游 tag。 - 备份。 停止容器后复制数据目录,或用 management API 导出 users/vhosts/policies 的
definitions。本版本没有
pg addon rabbitmq backup命令。 - 数据属主。 数据文件属 uid/gid 999。请用
--clean-data删除,而不是手工rm;pgcli 会通过podman unshare处理权限回退。 - 平台。 Linux(host 网络);官方镜像为多架构(amd64/arm64)。rootful 宿主上需以 root
运行 pgcli 才能把 TLS 私钥交给 uid 999;rootless 下走
podman unshare。
CLI 参数参考
pg addon install rabbitmq
| 参数 | 默认值 | 说明 |
|---|---|---|
--name NAME |
rabbitmq |
实例名;[A-Za-z0-9][A-Za-z0-9_-]*,最长 48 字符 |
--version TAG |
4.4.0 |
上游 docker.io/library/rabbitmq tag;自动追加 -management 后缀 |
--admin-user USER |
admin |
管理员账号;[A-Za-z0-9_.-]{1,64} |
--admin-password PASS |
自动生成 | 管理员密码(不能含 NUL/CR/LF);集群模式拒绝 |
--admin-password-file FILE |
— | 从文件读取密码;集群成员首次安装必填(使用 --secrets-from-etcd 时除外) |
--data-dir DIR |
<base_dir>/addon/rabbitmq/<name>/data |
绝对宿主路径,挂载到 /var/lib/rabbitmq |
--listen ADDR |
127.0.0.1(集群 0.0.0.0) |
AMQP、AMQPS、management HTTP 与 management HTTPS 的绑定地址;集群模式不能是 loopback |
--cluster NAME |
— | 集群名;空 = 单机 |
--node-name NODE@HOST |
— | 本成员的 Erlang 节点名;首次安装必填 |
--etcd M1,M2 |
— | 用本地 etcd addon 成员做发现 |
--etcd-endpoints HOST:PORT,... |
— | 外部 etcd 发现 endpoint;优先于 --etcd |
--erlang-cookie-file FILE |
— | 从文件读取共享 Erlang cookie;集群成员首次安装必填(使用 --secrets-from-etcd 时除外) |
--secrets-from-etcd |
false | 从 secret generate 写入的 etcd key 读取两个共享密钥;不能与两个 --*-file 同时使用 |
--amqp-port N |
自动(基准 5672) |
明文 AMQP 端口;0 = 自动分配 |
--management-port N |
自动(基准 15672) |
Management HTTP 端口;0 = 自动分配 |
--tls |
false | 增加 AMQPS 与 management HTTPS;明文监听保留 |
--tls-cert FILE |
— | 自带证书链(PEM);必须与 --tls-key 配对;隐含 --tls |
--tls-key FILE |
— | 自带私钥;必须与 --tls-cert 配对 |
--tls-port N |
自动(基准 5671) |
AMQPS 端口;0 = 自动分配;需要 --tls |
--config-option K=V |
— | 额外的 rabbitmq.conf 配置;可重复;替换已存储覆盖表 |
--force |
false | 重写配置并重建容器,保留数据与密码;cluster/node/cookie 不可变 |
pg addon rabbitmq status / shell / cluster ...
| 参数 | 默认值 | 说明 |
|---|---|---|
status --name NAME |
rabbitmq |
显示状态、端点、TLS、管理员、EPMD/dist、路径;集群模式附集群名/节点/etcd |
shell --name NAME |
rabbitmq |
打开容器内 /bin/bash |
cluster status --name NAME |
rabbitmq |
输出 rabbitmqctl cluster_status |
cluster forget NODE --name NAME [--yes] |
— | forget 一个已停止并下线的成员;拒绝本机节点,除非 --yes 否则要求确认 |
pg addon rabbitmq secret generate
| 参数 | 默认值 | 说明 |
|---|---|---|
--cluster NAME |
— | 要发布密钥的集群名(必填) |
--etcd M1,M2 |
— | 保存密钥的本地 etcd addon 成员 |
--etcd-endpoints HOST:PORT,... |
— | 保存密钥的外部 etcd endpoint;优先于 --etcd |
--cookie-file FILE |
/tmp/rmq.cookie |
Erlang cookie 的本地副本(0600);仅集群节点使用,客户端不需要 |
--admin-password-file FILE |
/tmp/rmq.admin |
管理员密码的本地副本(0600);安装输入,不是客户端凭据 |
--force |
false | 覆盖已存在的文件与 etcd key |
--keep-cookie |
false | 与 --force 连用:保留现有 Erlang cookie,只轮换管理员密码(成员已安装后使用) |
pg addon rabbitmq queue ...
所有队列子命令都接受 --name NAME(默认 rabbitmq)与 --vhost VHOST(默认 /)。
| 命令 | 参数 | 默认值 | 说明 |
|---|---|---|---|
list |
— | — | 列出队列及消息/消费者计数 |
declare QUEUE |
--durable |
true | 声明持久化队列(RabbitMQ 4.x 拒绝 false) |
--auto-delete |
false | 最后一个消费者断开后删除队列 | |
--type TYPE |
单机 classic,集群 quorum |
classic 或 quorum |
|
--message-ttl DURATION |
— | 设置 x-message-ttl(如 30m、24h 或毫秒) |
|
--expires DURATION |
— | 设置 x-expires:队列闲置该时长后删除(必须 > 0) |
|
--argument K=V |
— | 额外队列参数;可重复;JSON 值保留类型 | |
info QUEUE |
--json |
false | 输出 management API 原始文档 |
publish QUEUE |
--payload TEXT |
— | 消息体(与 --payload-file 互斥) |
--payload-file FILE |
— | 从文件读取消息体;- 表示 stdin |
|
--exchange NAME |
amq.default |
目标 exchange | |
--routing-key KEY |
队列名 | routing key | |
--property K=V |
— | 消息属性;可重复;单机 classic 消息必须在每次 publish 时设置 delivery_mode=2 才能跨 broker 重启保留(不存在队列级开关);JSON 值保留类型 |
|
get QUEUE |
--count N |
1 | 最多取出的消息数 |
--consume |
false | ack 并删除消息(默认 peek 后重新入队) | |
--json |
false | 输出 management API 原始响应 | |
bind QUEUE |
--exchange NAME |
— | 源 exchange(必填;拒绝 amq.default) |
--routing-key KEY |
队列名 | routing key | |
--argument K=V |
— | 绑定参数;可重复 | |
unbind QUEUE |
--exchange NAME |
— | 源 exchange(必填) |
--routing-key KEY |
队列名 | routing key | |
--argument K=V |
— | 仅删除参数完全一致的绑定;省略则删除所有匹配项 | |
bindings QUEUE |
--json |
false | 输出 management API 原始文档 |
purge QUEUE |
--yes |
false | 跳过确认提示 |
delete QUEUE |
--yes |
false | 跳过确认提示 |
--if-empty |
false | 队列非空时失败而不是删除 | |
--if-unused |
false | 队列有消费者时失败而不是删除 |
生命周期、日志、autostart
| 命令 | 参数 | 说明 |
|---|---|---|
pg addon start rabbitmq |
--name NAME |
启动(缺失则创建),等待就绪并对账管理员 |
pg addon stop rabbitmq |
--name NAME |
停止容器,保留数据 |
pg addon remove rabbitmq |
--name NAME、--clean-data |
删除容器与配置条目;--clean-data 同时删除实例目录与自定义 data dir |
pg logs addon rabbitmq |
--name NAME、-n N(默认 50)、-f |
容器日志 |
pg autostart enable|disable |
--rabbitmq、--name NAME |
切换单个实例的开机自启 |
pg addon password rabbitmq |
--name NAME |
打印存储的管理员密码 |
全局参数
| 参数 | 说明 |
|---|---|
-c, --config FILE |
配置文件(默认 ~/.pgcli/pg.yaml) |
-o, --output FILE |
输出配置路径(默认与输入相同) |
-i, --instance NAME |
PostgreSQL 实例选择器——RabbitMQ 不使用,请用 --name |
16.35 - RocketMQ
pg addon install rocketmq 以托管插件方式运行 Apache RocketMQ 5.x,使用官方
docker.io/apache/rocketmq 镜像。和 Kafka、ClickHouse、RabbitMQ 一样,它是顶层插件,
不代理某个 PostgreSQL 实例。
每个实例都是 host 网络上的三个容器:NameServer、broker 和 proxy。单机与 集群使用相同的形态,因此端口、命令、日志和生命周期行为保持一致:
- 单机——一个不带 controller 的 NameServer、一个无副本的 async master broker,以及 proxy。这是最快能收发消息的模式。不使用 4.x 的 master/slave 或 DLedger broker 部署模式。
- 跨主机集群——每个 NameServer 内嵌 DLedger(Raft)controller,broker 以 controller 模式运行:一个 broker 被选为 master,故障时另一个自动接管;proxy 作为 客户端入口。这是 RocketMQ 5.x 的自动切换部署模式。
没有认证,也没有 TLS。 RocketMQ 使用 host 网络监听,集群端口固定。任何能连到 NameServer、broker 或 proxy 端口的人都可以读写所有 topic。只应在可信内网或防火墙之后 运行,并限制
9876、9877、10911、10912、8080、8081。本插件也没有 Web UI 和 ACL。
参数
| 参数 | 默认值 | 说明 |
|---|---|---|
--name |
rocketmq |
实例名(配置 map 的 key,也是子命令的默认实例名) |
--version |
5.5.0 |
上游镜像 tag,镜像为 docker.io/apache/rocketmq:<version>;只接受 5.x |
--data-dir |
<base_dir>/addon/rocketmq/<name>/data |
实例数据根目录:broker store 为 <dir>/broker,集群模式的 controller store 为 <dir>/controller;除相对 base_dir 外必须是绝对路径 |
--advertise-host |
单机 127.0.0.1;集群必填 |
向客户端和对端通告的地址。集群模式必须是非 loopback 的具体 IPv4 地址;主机名、IPv6 和 0.0.0.0 都会被拒绝。单机若需远程客户端访问,应设置为宿主机的局域网 IP |
--cluster NAME |
— | 开启跨主机集群模式。每台主机必须使用相同名称;它同时作为 DLedger controller group 和 broker cluster/group 名。集群身份安装后不可变 |
--controller-id ID |
— | 本节点的 controller 成员 id(n0、n1……)。集群首次安装必填,组内唯一,匹配 [A-Za-z][A-Za-z0-9]{0,31};单机模式拒绝 |
--peer ID@HOST |
— | 另一个 controller 成员。按其他节点逐条重复,host 为裸的非 loopback IPv4、不带端口(controller 端口固定 9877)。每台主机都要传入其他成员的完整列表;本地成员会自动补全。单机模式拒绝 |
--namesrv-port |
单机自动(9876) |
NameServer 主机端口;0 = 从 RocketMQ 端口池自动分配。仅单机可用,集群成员固定 9876 |
--broker-port |
单机自动(10911) |
broker 监听端口;HA 端口为 broker_port + 1,VIP 端口为 broker_port - 2。仅单机可用,集群成员固定 10911 |
--proxy-port |
单机自动(8080) |
proxy remoting 端口。仅单机可用,集群成员固定 8080 |
--grpc-port |
单机自动(8081) |
proxy gRPC 端口(5.x SDK)。仅单机可用,集群成员固定 8081 |
--controller-port |
— | 内嵌 controller 端口。单机模式不接受;集群成员固定 9877 |
--heap |
512m |
broker JVM 堆,写入 JAVA_OPT_EXT=-Xms<size> -Xmx<size> |
--namesrv-heap |
256m |
NameServer JVM 堆 |
--proxy-heap |
256m |
proxy JVM 堆 |
--config-option K=V |
— | 追加到 broker.conf 的设置(flushDiskType=SYNC_FLUSH、brokerPermission=4);可重复,且替换已存储的覆盖列表。pgcli 托管的键会被拒绝(身份、监听、store 路径、controller 设置、复制设置、autoCreateTopicEnable) |
--force |
false | 重新渲染三个配置并重建三个容器,保留 broker store 和 controller store。集群身份和固定端口即使加 --force 也不能修改 |
--image、--listen、--tls、--tls-pem、--tls-cert、--tls-key、--node-id、
--cluster-id、--join 均不适用于 RocketMQ,会被拒绝。选择镜像请使用 --version。
安装单机实例
安装会按需拉取镜像、渲染配置、创建目录,按 namesrv → broker → proxy 启动,并等待
NameServer、broker 注册和两个 proxy 监听端口就绪:
若需要其他主机上的客户端访问,应通告宿主机的局域网地址(proxy 配置和 broker 都使用它):
第二个单机实例会从各端口池取下一个空闲端口,不会复用第一个实例的端口:
在内存较小的 VM 上,降低三个 JVM 的堆,避免三个进程超出内存:
不带 --force 重复执行 install 是空操作,只报告当前状态。带 --force 时,pgcli 会重写
namesrv.conf、broker.conf 和 rmq-proxy.json,重建三个容器,并保留 broker store 与
controller store。
多机集群
集群模式使用 RocketMQ 5.x 的 controller 模式:controller 以 DLedger(Raft)group 的
形式内嵌在每个 NameServer 中,controller 模式的 broker 选举 master 并在故障时自动切换。
每台主机各自执行一次 pg addon install rocketmq;pgcli 不会 SSH 到其他主机。
要求:
- 每台主机的
--cluster名称相同。 - 每台主机使用唯一的
--controller-id(n0、n1、n2……)。 - 本机的
--advertise-host是具体的非 loopback IPv4 地址。 --peer ID@HOST列出其他 controller 成员;每台主机都要传完整对端集合,本地成员会 自动补全。- 每台主机上的固定集群端口空闲:
9876、9877、10911、10912、8080、8081。 一台主机只能运行一个集群 RocketMQ 实例。 - 至少 3 个成员才有容错。 DLedger 的 quorum 是多数派,2 个成员无法容忍任何故障; pgcli 会为 2 成员集群打印 warning。
逐台安装。以 10.0.0.11、10.0.0.12、10.0.0.13 三台主机为例:
前两台安装预计会以 warning 结束:
这是正常现象:只有多数 NameServer 起来后 controller quorum 才会形成。安装仍会成功返回, 容器保持运行。第三台安装完成后,controller 选出 broker master,broker 完成注册。
逐台检查:
status 显示三个容器状态、controller id、完整 peer 列表,以及集群可达时已注册的 broker
数和 master 数。cluster status 原样输出 clusterList 表格;controller 分配的
BID 列(旧版客户端里叫 BrokerId)中,当前 master 为 0,副本为非 0:
列布局和行顺序由 mqadmin 产生,不同 5.x 版本可能略有差异;pgcli 只依赖 cluster 名和
broker id 列。
主备切换
master broker 停止后,controller 在心跳超时后从 in-sync 副本中选出新 master。通过 proxy 或 NameServer 连接的客户端会自动重试。观察一次切换:
配置中渲染了 enableElectUncleanMaster=false,不在 in-sync state set 中的副本不会被选为
master。如果 controller quorum 本身失去多数派(例如三台 NameServer 中两台停止),就无法
选出 master,写入会一直阻塞到 quorum 恢复。停止或移除一个成员不会改写其他主机上的
peer 列表;pgcli 不会联系其他节点。
端口
| 端口 | 角色 | 单机 | 集群 |
|---|---|---|---|
9876 |
NameServer 客户端/RPC | 自动,基准 9876 |
固定 |
9877 |
内嵌 DLedger controller | 不使用 | 固定 |
10911 |
broker 监听 | 自动,基准 10911 |
固定 |
10912 |
broker HA 复制 | broker_port + 1 |
固定 |
10909 |
broker VIP | broker_port - 2 |
固定 |
8080 |
proxy remoting | 自动,基准 8080 |
固定 |
8081 |
proxy gRPC | 自动,基准 8081 |
固定 |
单机实例可以从端口池分配端口并连同派生的 HA/VIP 端口一起保留,因此一台主机上可共存多个 实例。集群模式所有端口固定,因为 controller 成员关系和 broker 地址会写入每个节点的配置。
客户端
两种模式下 proxy 都是客户端入口,并以 CLUSTER 模式运行:
- 5.x gRPC SDK(推荐的 5.x 客户端):连接
<advertise-host>:8081。 - Remoting SDK(4.x 风格客户端):连接
<advertise-host>:8080,或直接连接 broker<advertise-host>:10911。 - NameServer 地址(
9876、controller9877)是集群内部通信地址,不要暴露给远程客户端。
单机默认 127.0.0.1,其他主机上的客户端需要使用设置了 --advertise-host 的安装。集群模式
每台成员都要求非 loopback 的 IPv4 --advertise-host,各主机上的 proxy 通告本机地址。
Topic 与消息
为兼容启用了 topic 自动创建(autoCreateTopicEnable=true),但建议显式创建,以保证队列数
可预期:
topic create 在整个集群上创建 topic(-c <cluster-or-name>),--queues 同时设置读写
队列数(默认 3)。topic list 默认隐藏 RocketMQ 内部 topic(%RETRY%…、%DLQ%…、
TBW102、SELF_TEST_TOPIC、事务/调度/trace topic、5.x 的 rmq_sys_* / RMQ_SYS_*、
*_REPLY_TOPIC 以及以 cluster 名命名的内部 topic);加 --system 显示全部。
topic describe 输出 topicStatus 的按 broker 队列和 offset 表。
发送一条消息并按 key 查询:
send 会打印 sendMessage 结果,包括 message id、发送状态和队列。用带边界的一次性调试消费者把消息拉回来:
consume 在一次性容器里运行 mqadmin consumeMessage,把消息打印到终端。它把 topic 各队列当前的消息拉完就退出;--timeout(默认 30s)是容器内的硬性安全上限,Ctrl-C 可提前结束。它只做拉取和打印,不删除消息,也不提交消费位点。
--count 是 mqadmin 的消息预算(默认 10);mqadmin 按队列计算,实际打印的条数可能略少于请求值,--count 0 表示不设预算。--queue/--offset 只读一个队列,broker 名默认取实例的单 broker 名(--broker 可覆盖)。--begin/--end 接受 epoch 毫秒或 yyyy-MM-dd#HH:mm:ss:SSS;mqadmin 会把时间边界换算成队列 offset,因此结束边界可能包含恰好在其后的一条消息。
删除 topic 会删掉它的所有消息,除非加 --yes,否则会先要求确认:
消息保留
消费不会删除消息。broker 以 commitlog 段(同一 broker 上所有 topic 共用的存储单位)为
单位删除:段年龄超过 fileReservedTime 小时,并且时钟到达 deleteWhen 指定的整点时
删除;磁盘占用超过 diskMaxUsedSpaceRatio 百分比时提前删除。保留策略是 broker 级别的,
不能按 topic 单独设置。
查看运行中 broker 的实际值并修改:
默认值为 72 小时、04 点、75%。set 至少要给一个参数:--hours(1–8760)、
--delete-when(一个或多个 0–23 的整点,用 ; 分隔)、--disk-ratio(1–100)。
它通过 updateBrokerConfig 立即修改运行中的 broker,并把相同的值写入 pg.yaml 和
重新渲染的 broker.conf,因此 pg addon stop/start 后仍然生效,无需重建容器。
过期段不保证在整点立刻删除,而是在 broker 下一次调度清理时移除。把比例调高到当前磁盘
占用以上可以停止磁盘压力触发的提前删除。
保留策略按 broker 保存。集群中要在每台成员主机上分别执行;每个 broker 只清理自己的 commitlog。
投递与持久性
单机。 broker 是唯一节点(brokerId=0、brokerRole=ASYNC_MASTER),没有副本。消息写入
data/broker 下的 broker store,因此优雅停止/启动不会丢失。渲染的刷盘策略是 RocketMQ 默认的
ASYNC_FLUSH,宿主机掉电可能丢失最近的写入。需要更强持久性时渲染 SYNC_FLUSH(会降低
吞吐):
集群。 broker 以 controller 模式复制。pgcli 渲染 allAckInSyncStateSet=true,3 个及以上
成员时 minInSyncReplicas=2(2 成员时为 1),因此只有所有 in-sync 副本都接受消息后
produce 才会成功。刷盘默认仍是 ASYNC;如果要求所有副本同时掉电也不丢已确认写入,请使用
flushDiskType=SYNC_FLUSH。
复制保护的是 controller quorum 健康时的 broker 故障,并不意味着 controller store 可以
随意删除:data/controller 保存 DLedger 状态和 epoch 文件。删除它、清空成员,或用
--clean-data 重装,都会破坏该成员的 controller 状态,并可能破坏 quorum。被移除后重新加入
的成员必须以空数据目录启动,并使用相同的 cluster/controller/peer 身份。
pg addon remove rocketmq --clean-data 会删除实例目录(配置、broker store、controller store
和日志)以及独立的自定义数据目录。不带 --clean-data 时会保留数据和配置目录。
配置、升级与不可变性
- 加
--force可修改: 版本、JVM 堆、broker.conf 覆盖项,以及单机实例的监听端口。--config-option会替换已存储的覆盖 map,强制重装时需重新传入全部覆盖项。 - 即使加
--force也不可修改: 集群模式的--cluster、--controller-id、--advertise-host、--peer和固定端口。这些值已写入每个对端的配置,原地修改会被拒绝。 - 拓扑变更必须重建: 单机 ↔ 集群不是原地变更。使用
--clean-data删除实例后,按目标 模式重新安装。 - 自定义数据目录: 把
--data-dir指向新路径会使用新的空 store;pgcli 不会迁移已有数据。
pgcli 托管的 broker.conf 键(在 --config-option 中会被拒绝)包括
brokerClusterName、brokerName、brokerId、brokerRole、brokerIP1/brokerIP2、
listenPort、haListenPort、namesrvAddr、enableControllerMode、controllerAddr、
所有 controller* 键、storePathRootDir、storePathCommitLog、storePathEpochFile、
autoCreateTopicEnable、allAckInSyncStateSet、minInSyncReplicas、
enableControllerInNamesrv、enableElectUncleanMaster、notifyBrokerRoleChanged。
生命周期、日志与开机自启
停止按逆序(proxy → broker → namesrv),启动按正序(namesrv → broker → proxy)。集群模式
的启动不等待 controller quorum:多数派尚未可达时,broker 会保持等待状态,命令仍会成功返回。
其他成员起来后,用 pg addon rocketmq status 和 cluster status 确认。
开机自启与其他顶层插件一致:
它会重新渲染三个配置并按顺序启动容器。对于集群成员,自启不等待 controller quorum,因此各
成员可以任意顺序启动。pg addon start(全局自启启动器)行为相同。
限制
- 没有认证、TLS 或 ACL。 请限制网络;proxy 和 NameServer 对任何能连到端口的人开放。
- 没有 Web UI。 使用
pg addon rocketmq status、cluster status、topic 命令和日志。 - 不支持在线扩缩容或滚动升级。 集群成员关系和固定端口不可变;成员变更需要有计划地重建。 成员仍在 quorum 中时不能删除其 controller store。
- 2 成员集群没有容错能力。 生产环境请使用 3 个及以上成员。
- 仅 Linux。 插件依赖 host 网络和官方镜像的
uid/gid 3000。 - 不做消息迁移、consumer group 管理或 broker/storage 重平衡。 可通过
pg addon rocketmq shell使用标准 RocketMQ 管理工具。
17 - 健康巡检
pg inspect 用一条命令回答一个问题:这个库健康吗,该先修什么? 它对 PostgreSQL 标准统计视图做一次单快照采样,产出确定性报告——0-100 的健康分,加上按 CRITICAL / WARNING / NOTE / GOOD 分级的诊断条目,每条带证据、修复建议,以及防止"自信地给出错误建议"的限定说明。
它严格只读:每条语句都包在 READ ONLY 事务里执行,绝不写入、除普通读取外不加任何锁,可随时对生产库运行。
适用场景
- 每周 / 值班例行检查 —— 对所有实例跑一遍,只看分数不到 100 的;有没有退化一目了然,不用盯仪表盘。
- “数据库感觉变慢了” —— 在翻 EXPLAIN 计划之前,先拿到 30 秒全貌:缓存命中率、耗时 Top SQL(需
pg_stat_statements)、膨胀、会话堆积。 - 安装 / 升级之后 —— 确认没有危险参数混进配置(比如
fsync = off),autovacuum 确实跟得上。 - 容量决策之前 —— 看清哪些索引从未被扫描(删掉能回收多少空间)、连接数是否在逼近
max_connections。 - 审计与交接 ——
--html生成单个自包含文件,可直接发邮件或归档;--json喂给脚本和监控 agent。
能提前发现的问题
| 问题 | 不巡检你会错过什么 |
|---|---|
| 事务 ID 年龄逼近冻结墙 | 一次被迫停机的 vacuum 紧急事故 |
连接数逼近 max_connections |
高负载时登录失败 |
| 缓存命中率低 | shared_buffers / effective_cache_size 配小了 |
| 超过 1 MiB 的从未使用索引 | 空间浪费与写放大 |
| 表膨胀、从未 vacuum 或关了 autovacuum 的表 | 扫描越来越慢,vacuum 风暴 |
| 大表重顺序扫描 / 非 HOT 更新 | 缺索引和热点更新模式 |
| 长事务与 idle in transaction 会话 | 清理被阻塞、锁等待 |
| 偏离编译默认值的危险参数 override | 悄无声息的持久性或调优错误 |
各项检查独立降级——没装 pg_stat_statements 的库照样出完整报告,只多一条说明该板块为空的 NOTE。
用法
18 - 配置调优
pg tune 把一台机器的硬件规格和一类工作负载,换算成一套自洽的 postgresql.conf
参数——目标感知,移植进 pgcli 原生实现。它同时服务 pgcli 纳管的两种部署:普通实例(写入
postgresql.conf 的托管调优块)和 Patroni 高可用集群(通过 edit-config 写入 DCS,
由 Patroni 滚动下发到每个成员)。
默认只打印建议,什么都不改。--apply 才落盘,--revert 移除已应用的调优。
适用场景
- 新实例装完之后 —— 容器默认值是"安全"而非"快"。一条
pg tune -i <name> --apply就能把内存划分、WAL 尺寸、planner 代价常量调到契合这台硬件。 - 升 / 降配之后 —— RAM 和 CPU 变了,重跑同一条命令,派生值随之更新。
pg inspect报参数可疑时 —— 巡检报告会指出shared_buffers偏小 / 缓存命中率 偏低,pg tune就是那条修复路径。
输入
硬件规格默认探测命令所在的宿主机(Linux 从 /proc/meminfo 取总内存,CPU 数取自
运行时)。覆盖项:
| 参数 | 含义 |
|---|---|
--type |
工作负载:web、oltp、dw、desktop、mixed(默认) |
--hd |
存储类型:ssd(默认)、hdd、san、nvme |
--ram |
总内存,如 16G——共享主机探测不准时用它覆盖 |
--cpus |
CPU 数——跨主机 Patroni 集群必须显式传,按最小成员规格算 |
--conns |
覆盖 max_connections(默认按负载预设 200/300/40/20/100) |
--db-size |
库大小 vs RAM,如 200G(<RAM→装得下,≤4×RAM→中等,否则→远大于) |
工作负载一句话选型:应用侧多为短查询 → web/oltp;分析型、大扫描 → dw;与库同机
的单用户应用 → desktop;拿不准 → mixed。
目标服务器本身会被探测 server_version_num 和每个参数的当前值,所以表格是 CURRENT 与
RECOMMENDED 并排;版本门控参数(jit、io_method 等)只在目标版本存在时才出现。
应用
普通实例(-i <name>)
--apply 把参数记录进实例配置,并写入 postgresql.conf 的托管调优块。该块在每次
pg start 都会重新施加,因此调优能挺过重启和容器重建。凡是用 ALTER SYSTEM 设过
的(即存在于 postgresql.auto.conf)会被跳过并计数——auto.conf 永远优先。
postmaster 级参数(shared_buffers、wal_buffers、max_connections 等)需要重启:
交互式下会 y/N 询问;非交互式下 pgcli 只打印 pg stop && pg start 提示,绝不擅自重启
数据库。其余改动直接 config reload 立即生效。
Patroni 集群(--ha <scope>)
这里参数经 patronictl edit-config 进入集群 DCS 配置——在 Patroni 的世界里这是改
postgresql.conf 值的唯一正当途径。随后 Patroni 在每个成员上施加,遇 postmaster 级改动
滚动重启。因为这有破坏性,--apply 和 --revert 都会先确认;--auto-restart
跳过确认(脚本场景)。你拒绝确认时,DCS 里什么都不会写。
建议表按 pgcli 运行所在主机的规格计算——跨主机集群成员规格可能不同,所以要传
--ram/--cpus 对齐最小成员。
校验与 --apply-conf
--apply-conf 指定一个 key = value 文件(postgresql.conf 风格:# 注释、值可带引号和
单位)。不加 --apply 时写出该文件(首行头注释记录输入,便于复现);加 --apply
时读取该文件,以它为参数来源。
每条 apply 路径都先校验。 每个参数都对照目标的 pg_settings 检查:未知参数名
(如 PG15 上的 io_method)、非法 bool、超出枚举集合、整数/浮点越出 [min_val, max_val]
——逐条列出后整体拒绝。一批调优里只写进去一半是最坏结果。
--dsn:只出建议
远程目标只探测、只建议;--apply/--revert 会被拒绝——拿一套启发式去改别人的库,这个
决定 pgcli 不替你做。
--web-tune:交互式站点
启动 HTTP 服务器,提供交互式调优计算器。页面有完整的输入表单(版本、OS、负载、RAM+单位、 CPU、连接数、存储、库大小),实时重算,postgresql.conf / ALTER SYSTEM 输出带一键复制。 无网络、无依赖;宿主机 RAM/CPU/OS 会预填表单。页面内的计算器是 Go 算法的 JS 移植; 页面自带 Go 现算的 golden 样例,加载时自检,两套实现一旦对不上就显示红条警示。
按 Ctrl+C(或发送 SIGTERM)优雅关闭服务器。
数值怎么来的
公式忠实且确定(内部以 kB 计算,每步推导都取整):
shared_buffers= RAM/4(desktop /16);effective_cache_size= 3/4 RAM。maintenance_work_mem= RAM/16(dw /8),封顶 8 GB(Windows ≤PG17 封顶 2047 MB)。work_mem= (RAM − shared_buffers) / ((max_connections + max_worker_processes) × 3), 再按负载缩放(dw/mixed ½、desktop ⅙)与库大小缩放(装得下 ×1.3、远大于 ×0.9),下限 4 MB。wal_buffers= shared_buffers 的 3%,夹在 [32 kB, 16 MB]。random_page_cost:hdd 及 dw-on-SSD 取 4(并带上"会选到灾难性索引扫描"的警告), 其余 1.1;effective_io_concurrency按存储类型(仅 Linux)。- 并行度按 CPU 数(<4 核不出并行块);
autovacuum_max_workers在 ≥16/≥32 核才加; 版本门控:jit=off≥12、wal_compressionon/lz4 ≥10/≥15、io_method/io_workers≥18 (io_method恒推荐worker——io_uring额外要求--with-liburing编译支持加内核支持, 不满足会导致实例启动失败,故不自动推荐)、 desktop 专属wal_level=minimal+max_wal_senders=0。
逐参数注意事项(lz4 编译开关、DW 代价警告)在表内就近打印;离谱的机器规格 (<256 MB、>100 GB RAM)给全局警告。
19 - KV 缓存
pg kv 将一组 KV 缓存函数部署到 PostgreSQL 实例,将其转变为轻量级 Redis 替代方案,用于纯键值缓存。它不是完整的 Redis 替代——没有计数器、没有有序集合、没有发布/订阅——只有快速、原子化、基于 TTL 的键值存储,支持自动过期。
提供两个变体:BYTEA(二进制值,通用缓存)和 JSONB(结构化 JSON,支持字段级更新和包含查询)。两者可以一起或分别部署。
适用场景
- 会话存储 —— 存储用户会话、OAuth 令牌或 API 密钥,自动过期。当应用已经连接 PostgreSQL 时,比 Redis sidecar 更简单。
- 配置缓存 —— 缓存不频繁变更的应用配置或功能开关。JSONB 变体支持部分字段更新,无需重写整个对象。
- 限流 —— 按用户/IP 跟踪请求计数或时间戳。UPSERT 原子性防止并发写入时的竞态条件。
- 临时数据 —— 任务队列、锁令牌、一次性代码。UNLOGGED TABLE 意味着无 WAL 开销;对于临时缓存,崩溃丢数据可接受。
- 原型开发 —— 避免为副项目启动 Redis。一条命令(
pg kv -i myinst --database myapp)即可获得可用缓存。
不适用场景
- 高吞吐(持续 >500 QPS)—— 使用 Redis 或 Memcached。PostgreSQL 未针对每秒数百万个小 KV 操作优化。
- 有序集合、计数器、发布/订阅 —— 这些是 PostgreSQL 不复刻的 Redis 特性。使用 Redis 或应用层逻辑。
- 大值(每个键 >1 MB)—— BYTEA 存储可行但效率低。使用对象存储(S3/MinIO)或文件系统。
- 需要持久化 —— UNLOGGED TABLE 意味着崩溃或非正常关机时数据丢失。如果缓存丢失不可接受,使用普通表或带 AOF 的 Redis。
用法
--database 标志是必填的——pgcli 不会自动创建数据库,目标必须已存在。命令是幂等的:多次运行是安全的(函数使用 CREATE OR REPLACE,表使用 IF NOT EXISTS)。
BYTEA 变体
通用二进制键值缓存。值存储为 BYTEA(任意二进制数据)。用于字符串、序列化对象或原始字节。
表
函数
| 函数 | 签名 | 返回值 | 描述 |
|---|---|---|---|
kv_set |
(p_key TEXT, p_value BYTEA, p_ttl_seconds INT DEFAULT 300) |
VOID |
以 UPSERT 原子性写入缓存。默认 TTL 300秒。 |
kv_set_with_result |
(p_key TEXT, p_value BYTEA, p_ttl_seconds INT DEFAULT 300) |
BOOLEAN |
与 kv_set 相同,成功返回 TRUE,错误返回 FALSE。 |
kv_get |
(p_key TEXT) |
BYTEA |
读取缓存(自动过滤过期)。未找到或已过期返回 NULL。 |
kv_get_text |
(p_key TEXT) |
TEXT |
读取缓存并转换为文本(convert_from UTF-8)。未找到返回 NULL。 |
kv_del |
(p_key TEXT) |
VOID |
删除缓存条目。 |
kv_cleanup_batch |
(max_rows INT DEFAULT 1000) |
INT |
删除最多 max_rows 条过期条目。返回实际删除数量。 |
示例
JSONB 变体
结构化 JSON 缓存,支持字段级更新和包含查询。值存储为 JSONB,带 GIN 索引用于高效的 @> 查询。
表
GIN 索引服务于用户编写的临时查询(WHERE value @> '{"status":"active"}')。函数 API 使用主键查找,因此 json_get/json_set 不使用该索引。
函数
| 函数 | 签名 | 返回值 | 描述 |
|---|---|---|---|
json_set |
(p_key TEXT, p_value JSONB, p_ttl_seconds INT DEFAULT 300) |
VOID |
以 UPSERT 原子性写入 JSON 缓存。默认 TTL 300秒。 |
json_set_with_result |
(p_key TEXT, p_value JSONB, p_ttl_seconds INT DEFAULT 300) |
BOOLEAN |
与 json_set 相同,成功返回 TRUE。 |
json_get |
(p_key TEXT) |
JSONB |
读取整个 JSON 对象。未找到或已过期返回 NULL。 |
json_get_field |
(p_key TEXT, p_field TEXT) |
TEXT |
以文本读取指定字段(->> 操作符)。键或字段缺失返回 NULL。 |
json_get_field_json |
(p_key TEXT, p_field TEXT) |
JSONB |
以 JSONB 读取指定字段(-> 操作符)。键或字段缺失返回 NULL。 |
json_contains |
(p_key TEXT, p_query JSONB) |
BOOLEAN |
检查缓存的 JSON 是否包含查询子集(@> 操作符)。键缺失返回 FALSE。 |
json_update_field |
(p_key TEXT, p_field TEXT, p_new_value TEXT, p_ttl_seconds INT DEFAULT NULL) |
BOOLEAN |
部分更新指定字段。智能类型转换(数字/布尔/数组/对象/字符串)。p_ttl_seconds NULL 保留原过期时间;非 NULL 重置它。p_new_value SQL NULL 返回 FALSE(防止意外字段删除)。键缺失返回 FALSE。 |
json_del |
(p_key TEXT) |
VOID |
删除 JSON 缓存条目。 |
json_cleanup_batch |
(max_rows INT DEFAULT 1000) |
INT |
删除最多 max_rows 条过期条目。返回实际删除数量。 |
示例
并发与原子性
两个变体都使用 ON CONFLICT(UPSERT)实现原子写入。对同一键的并发 kv_set 调用不会竞态——一个获胜,另一个覆盖。除单行外不持有锁。
读操作(kv_get、json_get)无副作用且永不阻塞。过期条目通过 WHERE expires_at > NOW() 过滤——它们不会物理删除,直到 kv_cleanup_batch 运行。
过期与清理
条目基于 TTL(默认 300 秒)过期。过期条目不会自动删除——它们保留在表中,直到调用 kv_cleanup_batch 或 json_cleanup_batch。
推荐的清理策略:
- 应用层 cron —— 每 5-10 分钟从应用或 cron 任务调用
kv_cleanup_batch(1000)。 - pg_cron 扩展 —— 如已安装,通过
cron.schedule调度SELECT kv_cleanup_batch(1000)。 - 惰性清理 —— 每 N 次写入后调用清理(例如
if (rand() < 0.01) kv_cleanup_batch(1000))。
max_rows 参数限制每次清理,防止长事务。每批 1000 行,即使有数百万过期条目,清理也只需 <10ms。
限制
- 无持久化 —— UNLOGGED TABLE 意味着崩溃时数据丢失。缓存可接受,主数据不可接受。
- 无复制 —— UNLOGGED TABLE 不被逻辑复制复制(它们被物理流复制复制,但仅表结构,非数据)。
- 无分区 —— 所有键在单表中。对于超大缓存(>1000万键),考虑按键前缀分片或使用 Redis。
- 无 TTL 索引 —— 过期条目未单独索引。清理使用普通 B 树索引扫描
expires_at < NOW()。
验证
部署后,验证函数是否存在:
--type all 的预期输出:
测试
scripts/test-kv-cache.sh 提供了测试脚本。覆盖所有 15 个函数(6 个 BYTEA + 9 个 JSONB),包括边缘情况(NULL 处理、TTL 保留、过期条目清理)。
如果所有测试通过,脚本以代码 0 退出;如果有任何失败,以代码 1 退出。在 CI 或升级后使用它确认函数按预期工作。
卸载
没有 pg kv remove 命令。函数和表保留在数据库中,直到手动删除:
CASCADE 子句自动删除依赖函数。
20 - 工具箱
在核心实例管理之外扩展 pgcli 能力的工具与使用指南。
- ZFS 存储 — 安装 ZFS 并管理 pool、dataset 和快照
- PGMQ 消息队列 — 在 PostgreSQL 内部实现持久化消息队列
- SQLite + Litestream — 管理宿主机 SQLite 数据库并持续备份到 S3
- TimescaleDB — 在 PostgreSQL 上使用时序 hypertable 和 time_bucket 分析
20.1 - ZFS 存储管理
pg zfs 把一台 Linux 主机变成 ZFS 存储服务器:安装软件包、检查磁盘、创建带冗余的
pool,并管理 dataset 和 snapshot。它直接与内核交互,不读写 pg.yaml,也不依赖任何
PostgreSQL 实例。
命令按“资源 → 动作”组织:pg zfs pool create、pg zfs dataset create、
pg zfs snapshot rollback。没有封装的能力可通过 pg zfs raw 原样透传。
支持的系统
pg zfs install 从 /etc/os-release 识别发行版,并自动准备软件源后完成安装:
| 发行版 | ZFS 来源 | 实测版本 |
|---|---|---|
| Debian 12 (bookworm) | Debian contrib,DKMS |
2.1.11 |
| Debian 13 (trixie) | Debian contrib,DKMS |
2.3.9 |
| Ubuntu 24.04 (noble) | Ubuntu universe |
2.2.2,使用 Ubuntu 内核模块包 |
| Ubuntu 26.04 | Ubuntu universe |
2.4.1 |
| Fedora 44 | OpenZFS release RPM,DKMS | 2.4.4 |
| Rocky Linux 10 | OpenZFS release RPM + EPEL,DKMS | 2.2.11 |
这些是安装器测试时上游仓库提供的版本,实际安装到的版本由仓库决定;
pg zfs status 总会打印已安装的用户态与内核模块版本。
其他发行版只会被识别并给出手动安装指引,不会自动安装。仅支持 Linux。
安装
命令零参数、幂等:
- 检查权限(root,或可用的
sudo -n)。 - 自动启用所需软件源(Debian
contrib、Ubuntuuniverse、 Fedora/Rocky 的 OpenZFS release 仓库)。 - 安装 ZFS、内核头文件/模块和 ZED 守护进程。
- 加载模块并把 ZFS 的 systemd 单元设为开机自启。
- 打印用户态与内核模块版本。
在健康的机器上重复执行是 no-op。安装不提供 --version、--backports、
--force 等开关——需要特定 ZFS 版本时请用发行版包管理器升级,之后交由
pg zfs 管理。
随时查看状态:
RAID 级别与 ZFS 的对应关系
ZFS 没有标准的 RAID2 / RAID3 级别,对应关系如下:
--type |
ZFS 布局 | 冗余 | 类比 |
|---|---|---|---|
stripe(raid0) |
条带 vdev | 无 | RAID0 |
mirror(raid1) |
镜像 | 1 份以上副本 | RAID1 |
raidz1 |
单校验 | 1 块盘 | RAID5 |
raidz2(raid2) |
双校验 | 2 块盘 | RAID6 |
raidz3(raid3) |
三校验 | 3 块盘 | 三校验 RAID |
为方便起见也接受 raid0/raid1/raid2/raid3 别名。
快速开始
pool 挂载在 /tank,dataset 挂载在 /tank/backups。回滚会丢弃快照之后的所有
改动,执行前需要确认。
复用旧 pool 的磁盘
ZFS 会在盘上保留 vdev 分区和标签,export 不会清除它们,因此安全校验会拒绝
这些盘。创建新 pool 前先销毁旧 pool 并清理残留:
如果整盘 labelclear 报错,改清成员分区(/dev/vdd1、/dev/vde1)。旧 pool
已被销毁时跳过第 1 步。
磁盘安全
pg zfs disk list 会列出每块整盘的挂载点、ZFS 归属以及 pgcli 是否认为它可以安全
交给 ZFS 使用。
创建 pool 时拒绝:
- 根盘,或带有已挂载分区的磁盘;
- 分区、loop、ROM、CD-ROM 设备;
- 带有文件系统(
ext4、xfs、swap、LVM、LUKS、mdraid 等)的磁盘; - 已有 ZFS 标签的磁盘;
- 只读磁盘;
- 重复设备(包括通过不同路径指向的同一块盘);
- 已被其他 pool 使用的磁盘。
--force 只跳过确认提示,不会映射为 ZFS 的 -f,也不绕过任何安全检查。
创建 pool
自动布局
位置参数的数量决定布局:
| 盘数 | 布局 |
|---|---|
| 1 | stripe(告警:无冗余) |
| 2 | mirror |
| 3 | raidz1 |
| 4 及以上 | raidz2 |
用 --type 覆盖:
同一 vdev 内请尽量使用容量一致的磁盘(性能也应相近)。ZFS 不会拒绝混合容量, 但 vdev 的可用容量由最小的成员盘决定,大盘多出的空间会被浪费。同一 pool 的不同 vdev 之间可以容量不同。
多个 vdev
使用可重复的 --data 分组,并显式给出 --type。每个分组是一个 vdev,pool 在
这些 vdev 之上做条带:
--type 作用于每个 --data 组内部,而不是 pool 整体:每个组建成该布局的一个
vdev,pool 在这些 vdev 之上条带。所有组共用同一个 --type;pg zfs 不支持
一次创建中混用 mirror、raidz2 等布局,混合拓扑需使用
pg zfs raw zpool create。
pool 容量为各 vdev 可用容量之和;冗余按 vdev 计算,任一 vdev 失去超出其容错能力 的磁盘都会导致整个 pool 不可用。
SLOG、special、cache 与热备
--special 跟随 --type(这里是 mirror)。--log 两块及以上自动 mirror,单块为
stripe 并给出告警。--cache 与 --spare 接受独立的设备列表。
同一块盘只能出现在 data、special、log、cache、spare 中的一处。
建池后也可以用 pool add 追加热备盘:
默认属性
除非指定 --no-defaults:
| 对象 | 属性 | 值 |
|---|---|---|
| pool | ashift |
12 |
| pool | autoexpand |
on |
| pool | autotrim |
所有数据盘均为非旋转盘且支持 discard 时为 on |
| 根 dataset | compression |
zstd |
| 根 dataset | atime |
off |
| 根 dataset | xattr |
sa |
| 根 dataset | acltype |
posixacl |
| 根 dataset | dnodesize |
auto |
pool 属性用 --prop/-p 覆盖(对应 zpool -o),根 dataset 属性用
--fs-prop/-P 覆盖(对应 zpool -O):
-o 是 pgcli 的全局输出参数,所以 pool/dataset 属性使用 -p/-P。在
pg zfs raw 内部,ZFS 原生参数照常可用。
管理 pool
扩容与修复:
attach 针对 mirror vdev。给 raidz vdev 增加磁盘(raidz expansion)需要
OpenZFS 2.3 及以上;Debian 12(2.1)和 Ubuntu 24.04(2.2)不支持。新版本上可用
pg zfs raw zpool attach 操作。
卸载与彻底删除 pool
pg zfs pool export 会卸载所有 dataset 并把 pool 从主机上分离,但数据仍完整
保存在成员盘上:
已 export 的 pool 不会在开机时自动导入。重新接管时先列出可导入的 pool,再导入:
pg zfs pool destroy 会删除 pool、所有 dataset 和全部数据:
destroy 只能作用于已导入的 pool。如果 pool 已 export,需要先导入再销毁:
不加 --force 时这两个命令都会要求确认;EOF 和默认答案均为“否”。非交互自动化
必须显式传 --force。--force 只跳过确认,绝不会映射为 ZFS 的 -f,也不绕过
任何安全校验。export 可逆,destroy 不可逆。
持久化与开机自动导入
pool 和 dataset 默认在重启后自动恢复,无需额外配置:
pg zfs install会启用zfs-load-module、zfs-import-cache、zfs-mount和zfs-zed(单元存在时)。zpool create会把 pool 记录到/etc/zfs/zpool.cache;开机时zfs-import-cache据此导入。- ZFS 不使用
/etc/fstab。每个 dataset 的mountpoint是 ZFS 属性,开机时 由zfs-mount完成挂载。pool 的根 dataset 挂载在/<pool>,子 dataset 挂载在其下(例如/fast/data)。 - pool 状态保存在成员盘上,异常断电或非正常关机后数据仍可导入。
检查配置:
重启后 pg zfs pool list 应能看到该 pool,findmnt /fast 应显示已挂载。
真实硬件上建议用 /dev/disk/by-id/... 等稳定路径创建 pool。/dev/sdX
名称可能在重启后变化,设备找不到时 pool 不会导入。把磁盘移到其他主机前先执行
pg zfs pool export tank,对方即可用 pg zfs pool import tank 接管。
Dataset 与 snapshot
带子 dataset 的 dataset 必须加 --recursive 才能销毁。
回滚
pg zfs snapshot rollback tank/data@snap1 把 dataset 恢复到快照记录的状态,
之后的所有改动都会被丢弃。如果存在更新的快照,ZFS 会拒绝回滚;加 --recursive
会先删除这些更新的快照。依赖该快照的 clone 也会阻止回滚——先删除 clone,或用
pg zfs raw zfs rollback -R(会一并删除依赖的 clone)。非交互运行需加
--force 跳过确认。
克隆
pg zfs dataset clone tank/data@snap1 tank/clone 从快照创建一个新的可写
dataset。未改动的数据块按写时复制共享,不会复制整份数据。用 --prop 设置新
dataset 的属性:
clone 依赖的快照在 clone 存在期间不能销毁。需要删除旧快照时,先用
pg zfs raw zfs promote tank/clone 提升 clone,使其独立于原 dataset。
raw 透传
raw 绕过所有安全校验和确认,是未封装参数的逃生通道——请像以 root 身份直接运行
zpool/zfs 一样谨慎。二进制管道也可以工作,例如:
权限模型
所有会调用 zpool 或 zfs 的命令都会先检查权限:
-
root:直接执行;
-
非 root 且
sudo -n可用:所有 ZFS 命令通过sudo -n执行; -
否则立即失败:
pgcli 从不弹出 sudo 密码提示。pg zfs status 和 pg zfs disk list 在无权限时仍会
输出静态信息,并把 pool 详情标记为不可用。
命令参考
| 命令 | 用途 |
|---|---|
pg zfs install |
安装 ZFS(零参数、幂等) |
pg zfs status |
发行版、软件包、模块与 pool 概览 |
pg zfs disk list [--all] |
整盘、挂载、ZFS 归属与安全性 |
pg zfs pool create NAME DEVICE... |
创建 pool |
pg zfs pool list |
列出 pool |
pg zfs pool status [NAME] [--brief] |
详细状态 |
pg zfs pool add NAME DEVICE... |
增加 vdev |
pg zfs pool attach/detach NAME DEVICE [NEW] |
扩大/缩小 mirror |
pg zfs pool replace NAME OLD NEW |
替换磁盘 |
pg zfs pool offline/online NAME DEVICE |
下线/上线磁盘 |
pg zfs pool export/import [NAME] |
在系统间迁移 pool |
pg zfs pool scrub NAME [--stop] |
校验数据完整性 |
pg zfs pool destroy NAME |
销毁 pool 及全部数据 |
pg zfs dataset create/list/set/clone/destroy |
dataset 生命周期 |
pg zfs snapshot create/list/rollback/destroy |
snapshot 生命周期 |
pg zfs raw zpool/zfs ARGS... |
绕过封装的透传 |
故障排查
pg zfs install 找不到软件包。 检查发行版归档源是否可达、contrib/universe
是否已启用。安装器只修改发行版官方归档源(绝不动第三方源),并在每个被修改的文件旁
留下 *.pgcli.bak。
DKMS 构建失败。 确认当前内核的头文件已安装(Debian/Ubuntu 为
linux-headers-$(uname -r),Fedora/Rocky 为 kernel-devel-$(uname -r))。安装器
会给出构建日志位置;它不会重启或更换内核。内存很小的 VM 请先加 swap。
Secure Boot 已开启。 DKMS 模块需要签名。pgcli 只报告该情况,不会自动登记密钥; 请用 MOK 签名模块,或在测试机上关闭 Secure Boot。
模块未加载。 执行 modprobe zfs 后运行 pg zfs status。安装器会启用
zfs-load-module、zfs-import-cache、zfs-mount 和 zfs-zed,使 pool 开机
自动导入。
磁盘因分区或旧标签被拒绝。 如果确认磁盘空闲,清理残留:
在 OpenZFS 2.2 及更新版本上,若标签位于 ZFS 成员分区内,清理整盘会报
failed to clear label for /dev/vdb。此时改为清理该分区,再清分区表:
绝不要对已导入 pool 的成员盘这样做——pg zfs disk list 会显示所属 pool,安全检查
也会拒绝这类磁盘。
SELinux(Fedora/Rocky)。 ZFS 挂载点使用标准文件上下文。若服务无法访问某个
dataset,请查看 ausearch/journalctl 中的 AVC 拒绝记录,而不是全局关闭 SELinux。
重启后 pool 没有自动导入。 以 root 运行 systemctl status zfs-import-cache
和 zpool import。用 /dev/sdX 名称创建的 pool 会受设备重命名影响;真实硬件上
建议使用稳定路径(/dev/disk/by-id/...)创建 pool。
20.2 - PGMQ 消息队列
pgmq 把 PostgreSQL 变成持久化消息队列:不需要 Redis、RabbitMQ 或后台进程,
只有 pgmq schema 下的一组表和 SQL 函数。生产者发送 JSON 消息,消费者按可见性
超时读取,消息在被显式删除或归档之前一直保留在队列里。
安装
pgcli 从 Pigsty 仓库安装扩展包,并在实例的默认数据库执行
CREATE EXTENSION IF NOT EXISTS pgmq:
同一实例的其他数据库需要单独创建扩展:
查看扩展版本:
快速开始
通过 pgcli 执行同样的语句:
队列在磁盘上的形态
每个队列都是 pgmq schema 里的一张表:
| 对象 | 用途 |
|---|---|
pgmq.q_<name> |
在队消息 |
pgmq.a_<name> |
pgmq.archive 写入的归档表 |
pgmq.meta |
list_queues 和 metrics 使用的队列表册 |
一条在队消息包含 msg_id、read_ct(投递次数)、enqueued_at、vt
(再次可见时间)和 message jsonb。较新版本还增加了 last_read_at 和
headers。
消息就是表里的行,因此 pgmq.send 参与事务:随所在语句一起提交或回滚。
队列流量也是普通表流量——高频 send/delete 会像其他高写入表一样产生膨胀。
发送消息
send 返回新的 msg_id,send_batch 为每条消息各返回一个 id。批量场景应使用
send_batch,而不是循环调用 send——它只有一次网络往返,且整批原子写入。
消息必须是合法 JSON,存储类型为 jsonb:键会重排、重复键会被去掉。不要把大块
二进制内容放进队列。
较新版本支持 headers jsonb 参数,可携带 trace id、FIFO 分组键等元数据。
读取与确认
read(queue, vt, qty) 最多返回 qty 条可见消息,并在 vt 秒内让它们对其他
消费者不可见。读取不会删除消息,每条都必须显式确认:
pgmq.pop('jobs') 读取并删除一步完成;只有在消费者崩溃时丢一条在途消息可以接受时
才使用它。
长期运行的消费者不要用紧凑轮询:
它最多等待 max_poll_seconds(5 秒),每 poll_interval_ms(100 毫秒)检查一次,
有消息到达就立即返回。
可见性超时与重试
可见性超时是 PGMQ 的投递模型:
read把vt设为now() + timeout,并递增read_ct;- 在
vt之前,其他消费者看不到这条消息; delete或archive将其移除。若消费者崩溃或没有及时确认,消息在vt到期后 重新可见,并被投递给另一个消费者。
因此跨故障场景下投递语义是 at-least-once(至少一次):vt 要大于最坏处理
耗时,消费者要保证幂等。长任务可以在不重新读取的情况下续期:
read_ct 可以直接当重试计数器使用。
归档与死信
delete 是永久删除。archive 把消息移到 pgmq.a_<queue>,便于保留、审计或
重放:
一个简单的死信模式是归档重试次数过多的消息:
没有这类规则时,毒消息会被无限重投。
监控
metrics 返回 queue_length、queue_visible_length、最新/最老消息的年龄以及
total_messages。较旧的扩展版本返回的列更少。
也可以直接查询队列表:
queue_length 持续增长或 oldest_msg_age_sec 不断变大时应当告警:要么消费者
挂了,要么消费速度跟不上。
维护
create_unlogged用崩溃安全性换写入速度,只用于可重新生成的数据。create_partitioned依赖pg_partman扩展。它避免超高吞吐队列把单表撑大; 旧分区会自动丢弃。purge_queue清空但保留队列;drop_queue彻底删除。- 队列名最长 47 个字符。
普通队列在高频 send/delete 下会产生 dead tuple。确认 autovacuum 能跟上;写入量 很大时优先考虑归档代替删除,或使用分区队列。
权限
PGMQ 函数以调用者的权限执行。应用角色需要 schema、函数和队列表的访问权限:
pgmq.create 应以队列所有者身份执行;建表权限通常不应授予应用角色。新建队列需要
单独授权,或预先配置 default privileges。
函数参考
| 函数 | 用途 |
|---|---|
pgmq.create(name) |
创建队列 |
pgmq.create_unlogged(name) |
创建 unlogged 队列 |
pgmq.create_partitioned(name, ...) |
创建分区队列 |
pgmq.send(name, msg [, delay]) |
发送单条消息,返回 msg_id |
pgmq.send_batch(name, msgs jsonb[]) |
原子批量发送 |
pgmq.read(name, vt, qty) |
读取并隐藏消息 |
pgmq.read_with_poll(name, vt, qty, ...) |
长轮询读取 |
pgmq.pop(name [, qty]) |
读取并删除 |
pgmq.delete(name, id) / pgmq.delete(name, ids[]) |
确认并移除消息 |
pgmq.archive(name, id) / pgmq.archive(name, ids[]) |
移入归档表 |
pgmq.set_vt(name, id(s), seconds) |
延长可见性超时 |
pgmq.list_queues() / pgmq.metrics(name) |
查看队列状态 |
pgmq.purge_queue(name) |
删除全部消息,保留队列 |
pgmq.drop_queue(name) |
删除队列及其归档表 |
故障排查
extension "pgmq" is not available。 实例镜像里没有扩展包,或该数据库没有
对应构建。执行 pg extension install pgmq -i <instance>,并在目标数据库中创建
扩展。
消息被重复投递。 处理时间超过 vt,或消费者在 delete 前退出。增大 vt、
在长任务中调用 set_vt,并保证处理逻辑幂等。
队列长度不下降。 消费者失败或太慢。检查 read_ct、应用日志和
metrics().oldest_msg_age_sec。PGMQ 会无限重投失败消息,除非归档或删除它们。
性能随时间下降。 普通队列就是普通表。检查 dead tuple 与 autovacuum,批量
发送和读取;持续高吞吐场景考虑 create_partitioned。
permission denied for schema pgmq。 给应用角色授予 schema 的 USAGE、
函数的 EXECUTE,以及每个队列表的相应表权限。
20.3 - SQLite + Litestream
pg sqlite 在 Linux 宿主机上管理 SQLite 数据库,并可选地用
Litestream 把 WAL 变更持续复制到 S3 兼容对象存储。
SQLite 直接以宿主机进程运行:不使用容器、不引入 Go SQLite driver,也不依赖 cgo。
所有 SQL 都由 sqlite3 命令行执行;每个数据库对应一个独立的 Litestream 后台进程。
复制是异步的(默认 sync interval 下 RPO 约 1 秒)。它属于持续备份,不是高可用: SQLite 仍然只有一个写入者,故障切换意味着要在别处恢复备份。
目前仅支持 Linux。
工作原理
- 每个受管数据库在
pg.yaml中有一条记录,并在<base>/sqlite/<name>/下有独立 的状态目录。 pg sqlite create会启用journal_mode=WAL,该设置持久化在数据库文件头中。pg sqlite replication enable生成专属的litestream.yml(权限0600),并在 后台启动litestream replicate。Litestream 跟随 WAL,把 LTX 对象上传到指定 bucket。- S3 凭据只写入该
litestream.yml,绝不会进入pg.yaml,任何pg sqlite命令也不会打印它。
安装
命令是幂等的,会安装两个宿主机二进制:
- 如果
sqlite3不在PATH上,则通过发行版包管理器安装(apt-get、dnf、yum、 pacman、zypper 或 apk)。只有这一步可能需要 root;pgcli 使用免密sudo -n,不会交互式索要密码,失败时会给出明确提示。 - Litestream
0.5.17,从 GitHub 下载并校验官方 SHA256。可写时安装到/usr/local/bin,否则安装到~/.local/bin。
常用参数:
Litestream 0.3.x 与生成的配置不兼容;安装器会忽略 PATH 上的旧版本,直接安装
0.5.x。
创建与管理数据库
create 不会截断已有文件:指向一个已存在的 SQLite 数据库时会接管该文件、启用
WAL,并保留原有内容。--path 必须是绝对路径,且只移动数据库文件——配置、日志、
pid 和 Litestream 本地状态始终留在 <base>/sqlite/<name>/ 下。
info 读取 journal_mode、page size/count 和 freelist,默认执行
PRAGMA quick_check。大库可用 --integrity none 跳过检查,或
--integrity full 执行完整 integrity_check。
remove 只注销数据库并停止复制,数据库文件保留。加 --clean-data 才会删除
数据库、WAL 附属文件、Litestream 本地状态和日志(交互确认,--force 跳过)。
远端 S3 备份永远不会被自动删除。
启用 S3 复制
bucket 必须已经存在——Litestream 不会创建 bucket。请先用 mc mb、云控制台或
其他 S3 工具创建好。
本地 MinIO(测试用)
pgcli 可以直接用 MinIO addon 在宿主机上起一个对象存储:
如果 9000 端口已被占用,请改用 pg addon list 中显示的实际端点。然后把复制指向
这个本地存储:
其他 MinIO 或 S3 兼容存储使用相同的 flags,替换成自己的端点和凭据:
AWS S3,使用默认凭据链(命令行不传 key):
| 参数 | 默认值 | 说明 |
|---|---|---|
--s3-bucket |
必填 | 必须已存在 |
--s3-endpoint |
AWS 默认 | 完整 http(s)://host[:port],不能带路径或凭据 |
--s3-path |
sqlite/<name> |
对象 key 前缀;前导 / 会被去掉 |
--s3-region |
us-east-1 |
|
--s3-access-key / --s3-secret-key |
默认凭据链 | 必须同时提供或同时省略 |
--s3-uri-style |
path |
path 或 virtual-host |
--s3-ca-file |
无 | 通过 SSL_CERT_FILE 传给 Litestream,用于私有 CA |
--s3-skip-verify |
false | 关闭 TLS 校验(自签名端点) |
--sync-interval |
1s |
WAL 上传间隔 |
--snapshot-interval |
24h |
全量快照间隔 |
--retention |
24h |
快照保留时间 |
--no-start |
false | 只写配置,不启动 Litestream |
--force |
false | 覆盖已有配置 |
enable 会先确认数据库处于 WAL 模式,解析可用的 0.5.x Litestream(或用
--litestream-binary PATH 指定),以 0600 写入 litestream.yml,清理本地
Litestream 旧状态,启动进程并检查它没有立即退出。若立即退出,会打印日志最后一行
并报错。
在复制运行中,用完全相同的非敏感配置再次执行 enable 是幂等的。配置不同则默认
拒绝;--force 会停止旧进程、重写配置、reset 本地状态并重启。
凭据存放位置
| 路径 | 内容 | 权限 |
|---|---|---|
<base>/pg.yaml |
名称、路径、binary/version、endpoint、bucket、前缀、region、URI style、CA file、skip-verify —— 不含 key | 0644 |
<base>/sqlite/<name>/<name>.db |
数据库文件 | 应用默认 |
<base>/sqlite/<name>/litestream.yml |
完整 S3 配置,含 access/secret key | 0600 |
<base>/sqlite/<name>/litestream.log |
进程日志 | 0644 |
<base>/sqlite/<name>/litestream.pid |
运行中的 pid | 0644 |
<base>/sqlite/<name>/.<db>-litestream/ |
Litestream 本地 LTX 状态 | 应用默认 |
备份或复制 litestream.yml 时要像对待其他凭据文件一样谨慎。
replication disable 会删除它,因此下次 enable 需要重新提供凭据。
复制日常操作
status 结合进程状态与 litestream status -json(本地事务 id 和 WAL 大小),
并显示日志中最近的错误行。即使状态查询失败,仍会展示进程状态并正常退出。
检查远端备份文件
pg mc 是内置 MinIO 客户端的包装器,mc 自己的 flags 必须写在 -- 之后。使用
「本地 MinIO」示例中创建的 alias,可以列出 Litestream 上传到 --s3-path 下的对象:
有数据时类似这样:
0000/ 存放逐事务的 WAL 帧;编号更大的层级(0001/、0009/ 等)是合并、
压缩后的文件。刚 enable 完时前缀为空只说明还没有写入被同步——插入一行数据,
等待至少一个 --sync-interval(默认 1s)后再列一次。replication status
显示的本地事务 id 应与这些文件名中的最大 txid 一致。
disable 保留数据库、本地 LTX 状态和日志,只删除 pg.yaml 中的 replication
块并删除 litestream.yml。远端备份不受影响。
开机自启
Litestream 是独立的宿主机进程,不会在主机重启后自行恢复。把数据库注册到 pgcli 的开机服务:
--name 必填(SQLite 没有默认数据库名)。复制必须已经启用且 litestream.yml
仍然存在——开关命令会检查这两点,否则报错并提示重新 enable。开机时
pg start --autostart 会在对象存储 addon 之后重启 Litestream,因此同机的
MinIO/silo/rustfs/garage/SeaweedFS 已经先开始监听。开机路径只做启动:绝不
重写 litestream.yml,也不修改数据库文件。
pg sqlite replication disable 会自动清除该标志。systemd/launchd 服务细节见
开机自启。
恢复
Litestream 可以恢复最新状态或指定时间点。支持两种模式:
--timestamp 与 --txid 互斥;都不传则恢复最新状态。--timestamp 表示绝对
时间点,请写成带 Z 后缀的 UTC 时间(如 2026-01-01T12:00:00Z),避免恢复
时间点受宿主机时区影响。--target 不能与 --restart 同用;目标已存在时必须加
--force,此时旧的 target 及其 -wal/-shm 会先被移入
<target>.pre-restore-<UTC>/。
时间点恢复演练
在做出想撤销的变更之前,先记录一个事务边界:
LTX 对象名为 <min-txid>-<max-txid>.ltx。按 --txid 恢复会包含该事务及之前
的状态。记录变更前的 txid(或一个安全早于变更的 RFC3339 UTC 时间戳),然后恢复
一份副本并验证:
时间戳方式流程相同:
不确定时先加 --dry-run:它只打印将要使用的事务范围,不写任何文件。恢复到
--target 不会停止复制,因此可以在检查副本的同时继续处理线上流量。建议再恢复
一次最新状态(不传 --timestamp/--txid),确认较新的数据存在;验证无误后,
再用原地恢复把副本正式替换上线。
原地恢复
不传 --target 时会替换配置中的数据库:
流程刻意保守:
- 停止 Litestream(会执行最终 sync);其他打开该数据库的应用必须提前自行停止。
- 当前数据库及 WAL 附属文件移入
<db>.pre-restore-<UTC>/(不加--force时 会先确认)。 - 备份先恢复到数据库同目录下的临时目录,因此恢复失败也不会影响已经保留的旧副本。
- 检查恢复出的数据库,必要时切回 WAL,然后通过同一文件系统内的原子 rename 移动到最终位置。
- 重置 Litestream 本地状态;若 reset 失败,则删除旧的本地状态目录。
- 默认保持复制停止;
--restart会重新启动。
恢复后把复制重启到相同的 --s3-path,可能把恢复出的时间线与远端旧数据混在一起。
优先用新的 --s3-path 重新 enable;使用 --restart 前请确认理解旧时间线。
如果数据库文件完全丢失,原地恢复不需要 --force;但每个数据库的
litestream.yml 必须仍然存在。
WAL 与应用侧设置
CLI 只把 PRAGMA journal_mode=WAL 持久化到数据库文件中。以下设置按连接在应用侧
调整:
注意事项:
- WAL 模式要求本地文件系统,不要把数据库放在 NFS 或 SMB 上。
- 同一时刻仍然只有一个写入者。长写事务会阻塞其他写入者;长读事务会阻止 WAL checkpoint,可能让 WAL 无限增长。
- Litestream 复制的是 WAL,不是应用一致快照。涉及多条语句的不变量请使用事务。
故障排查
litestream exited during startup —— 执行
pg sqlite replication logs <name> -n 50。常见原因是 bucket 不存在、凭据错误,
或宿主机无法访问 endpoint。
no Litestream 0.5.x binary found —— 执行 pg sqlite install。PATH 上的
0.3.x 会被报告但不会使用。
连接 MinIO/RustFS 的 TLS 错误 —— 纯 HTTP 端点要写完整
http://host:port;自签名 HTTPS 用 --s3-ca-file 或 --s3-skip-verify。
database ... uses journal_mode=delete —— 数据库不是由 pg sqlite create
创建的,或被其他工具改过模式。在没有其他连接时执行
PRAGMA journal_mode=WAL;,再重试 enable。
litestream.yml not found —— start、status 和 restore 都依赖
enable 写出的配置;请重新执行 enable(凭据不在 pg.yaml 中)。
限制
- 仅支持 Linux;macOS 的 host-binary 路径未实现。
- Litestream 是异步备份,不是同步复制或高可用。默认 RPO 约 1 秒,
stop/disable时的最终 sync 最长可能耗时 30 秒。 - 每个数据库一个 Litestream 进程、一个 WAL 写入者。
--clean-data和原地恢复具有破坏性;默认会确认,保留的.pre-restore-*目录需要手动清理。
20.4 - TimescaleDB
TimescaleDB 是面向时序负载的 PostgreSQL 扩展。它把普通表变成 hypertable: 数据按时间自动切分成 chunk,应用仍然使用普通 SQL、索引和事务。
pgcli 安装的是 Pigsty 的 TSL 构建(TimescaleDB 2.31.0)。Hypertable、
time_bucket、chunk 管理、列存/压缩、连续聚合和保留策略均可使用。 TSL 允许免费内部使用,但禁止对外提供时序数据库服务。
使用场景
TimescaleDB 适合以时间为主要访问维度、以追加写入为主的数据:
- IoT 与设备遥测 — 按设备采集读数、查询最新值和按时间窗口聚合。
- 基础设施与应用监控 — CPU、内存、延迟、请求量等指标,用于看板和告警。
- 工业与能源计量 — 高频传感器数据流,保留周期长。
- 金融行情数据 — tick、报价和成交;用
time_bucket和first/last生成 OHLC 类聚合。 - 产品与事件分析 — 用户事件、会话和 API 调用,按最近时间窗口统计。
- 智能家居与楼宇自动化 — 传感器历史数据和状态变化。
典型形态是 (entity_id, time) 的追加型数据:查询过滤最近一段时间,过期数据按
整个 chunk 删除。
不适合没有时间维度的 OLTP、键值/文档存储和大对象存储。TSL 构建包含压缩、 连续聚合和保留策略,但 TSL 不允许对外提供时序数据库服务。
安装
pgcli 会使用 Pigsty 的 TSL 包构建扩展镜像,把 timescaledb 放到
shared_preload_libraries 的首位,重启 PostgreSQL,并在实例的默认数据库执行
CREATE EXTENSION IF NOT EXISTS timescaledb。
同一实例的其他数据库需要单独创建扩展:
查看版本和许可证:
Pigsty 包返回 timescale。
快速开始
1. 创建实例
pg create 写入配置并打印管理员密码;pg start 启动 PostgreSQL 和备份容器。
默认数据库为 tsdb_db。
2. 安装扩展
pgcli 构建扩展镜像,预加载 timescaledb,重启 PostgreSQL,并在 tsdb_db 中
执行 CREATE EXTENSION IF NOT EXISTS timescaledb。
3. 创建 hypertable
连接实例:
创建普通表并转换为 hypertable:
也可以一步创建;此时使用默认 chunk 间隔,之后仍可调整:
chunk 间隔按查询和保留窗口选择;后续调整只影响新 chunk。
4. 写入数据
5. 日常运维
追加数据、查看 chunk 和后台任务、启用自动维护:
常用运维命令:
压缩、保留策略、连续聚合和 chunk 的完整操作见下文对应章节。
InfluxDB 兼容 API:influx2tsdb 插件
如果现有工具只支持 InfluxDB 协议,可用 influx2tsdb 插件 在 TimescaleDB 之上运行一个受管的 无状态代理,提供 InfluxDB 1.x HTTP API(Line Protocol 写入与 InfluxQL 查询):
- 每个 InfluxDB 数据库对应一个 PostgreSQL schema,每个 measurement
对应一个 hypertable,
time为分区列; CREATE / ALTER / DROP RETENTION POLICY会被翻译成 TimescaleDB 自带的add_retention_policy/add_compression_policy任务——不需要pg_cron, 且代理停机期间 chunk 清理照常进行;- 代理本身不存数据,备份、PITR、复制与 HA 仍然完全走 PostgreSQL。
代价是吞吐:请求需要翻译成 SQL,纯时序写入/查询性能低于原生 InfluxDB。代理 没有认证和 TLS,需要 nginx 前置或做网络隔离。参数、数据 模型、保留语义和测试脚本见 插件文档。
常用查询
每条序列的首末值:
每条序列的最新一行:
按指定时区做天级分桶:
补齐时间桶序列中的空档:
time_bucket_gapfill 需要明确的起点和终点:可以作为第二、三个参数传入,或在
WHERE 中给出时间范围。
压缩与列存
较早的 chunk 可以转入列存(columnstore),压缩数据并加速分析查询:
查看压缩状态和空间占用:
保留策略
超出保留窗口的 chunk 会自动删除:
这些任务由 TimescaleDB 自带的后台调度器执行,不需要 pg_cron。
influx2tsdb 插件 使用同一机制:其
CREATE / ALTER / DROP RETENTION POLICY 会被自动翻译成这些任务,代理停机期间
清理照常进行。
连续聚合
预先聚合最近的数据,并由后台任务持续刷新:
像普通表一样查询聚合结果:
策略只自动刷新 start_offset 到 end_offset 之间的近期窗口;回填更早的数据后
需要手动刷新。
管理 chunk
Chunk 间隔
创建时指定间隔,或之后调整(只影响新 chunk):
timestamptz 列的 chunk 边界按 UTC 对齐。间隔要保证单个 chunk 能较轻松地放进
内存;间隔过小会产生大量 chunk,并增加查询规划开销。
查看 chunk
older_than / newer_than 按 chunk 的时间范围过滤,而不是按创建时间。
查看 chunk 大小
拆分 chunk
在 chunk 的时间范围内选一个时间点进行拆分;chunk 名称来自
show_chunks('sensor'):
split_chunk 是 procedure,必须用 CALL,不能用 SELECT。
删除 chunk
drop_chunks 直接删除文件,不产生 dead tuple。不要用大范围 DELETE 清理
历史数据。自动过期优先使用保留策略;只有需要自定义调度时才安装 pg_cron:
手动转换列存
配置好压缩参数后(见上文「压缩与列存」),可以不等策略而手动转换单个 chunk:
这两个 procedure 接收 chunk 名称,而不是 hypertable 名称。
元数据视图
jobs 中可以看到上面创建的列存、保留策略和连续聚合任务,以及内部的 job 历史
任务。
功能可用性
| 功能 | 状态 |
|---|---|
Hypertable、time_bucket、first/last、show_chunks、drop_chunks、hypertable_size、元数据视图 |
✅ Apache |
用 add_dimension / by_hash 做空间分区 |
✅ Apache |
列存与压缩(ALTER TABLE ... SET (timescaledb.compress)、CALL add_columnstore_policy) |
✅ TSL |
连续聚合(CREATE MATERIALIZED VIEW ... WITH (timescaledb.continuous)) |
✅ TSL |
保留策略(add_retention_policy) |
✅ TSL |
time_bucket_gapfill |
✅ TSL |
标记为 ✅ Apache 的功能遵循 Apache-2.0;标记为 ✅ TSL 的功能来自 Timescale License,允许免费内部使用,但禁止对外提供时序数据库服务。
备份与恢复
逻辑备份和 pg export/pg import 正常工作。物理恢复(pg backup/pg restore
使用的 pgBackRest)还需要 TimescaleDB 的恢复钩子:
pg restore 不会自动调用这两个函数,恢复完成后需要手动执行
timescaledb_post_restore()。正式依赖前请先演练一次。
权限
CREATE EXTENSION 需要超级用户;pg exec 以实例管理员身份连接。表的所有者
可以对自己的表执行 create_hypertable 并管理 chunk。应用角色只需要普通的表
权限。
故障排查
extension "timescaledb" is not available。 实例镜像里没有扩展包,或目标
数据库没有创建扩展。执行 pg extension install timescaledb -i <instance>,并在
该数据库中创建扩展。
timescaledb is not preloaded。 扩展已在 shared_preload_libraries 中,但
PostgreSQL 没有重启。pg extension install 会自动处理;可用
SHOW shared_preload_libraries; 检查并重启实例。
函数报许可证错误。 实例镜像中还是 Apache 构建(旧版 pgcli 或手动安装的
PGDG 包)。执行 pg extension install timescaledb -i <instance> --auto-restart
切换到 Pigsty TSL 包。
cannot create a unique index without the column "time"。 hypertable 上的
唯一索引和主键必须包含分区列:
chunk 数量过多。 用 set_chunk_time_interval 调大间隔;已有 chunk 不会
重写。
旧数据没有被清理。 用 add_retention_policy 添加保留策略,或手动执行
drop_chunks;可在 timescaledb_information.jobs 中检查策略任务。
函数参考
| 函数 | 用途 | 状态 |
|---|---|---|
create_hypertable('t', by_range('time')) |
把表转换为 hypertable | ✅ |
by_range('time', INTERVAL '1 day') |
指定时间分区和 chunk 间隔 | ✅ |
set_chunk_time_interval('t', INTERVAL ...) |
调整新 chunk 的间隔 | ✅ |
time_bucket('1 hour', time [, timezone]) |
时间分桶聚合 | ✅ |
first(value, time) / last(value, time) |
分组内首末值 | ✅ |
show_chunks('t' [, older_than/newer_than => ...]) / drop_chunks('t', ...) |
查看、过滤和删除 chunk | ✅ |
hypertable_size('t') / hypertable_detailed_size('t') |
hypertable 总大小及明细 | ✅ |
add_dimension('t', by_hash('col', N)) |
添加空间分区 | ✅ |
add_retention_policy(...) / remove_retention_policy(...) |
自动过期清理 | ✅ TSL |
CALL add_columnstore_policy(...) |
列存/压缩策略 | ✅ TSL |
CALL convert_to_columnstore(chunk) / CALL convert_to_rowstore(chunk) |
手动转换 chunk 存储格式 | ✅ TSL |
hypertable_columnstore_stats('t') / chunk_columnstore_stats('t') |
列存状态与空间统计 | ✅ TSL |
CREATE MATERIALIZED VIEW ... WITH (timescaledb.continuous) |
连续聚合 | ✅ TSL |
add_continuous_aggregate_policy(...) |
连续聚合的自动刷新策略 | ✅ TSL |
time_bucket_gapfill(...) |
补齐缺失的时间桶 | ✅ TSL |
21 - 日志
pg logs 命令用于查看 PostgreSQL 实例和插件组件(如 PgBouncer 连接池、etcd 成员)的控制台输出日志。
PostgreSQL 实例日志
查看最近的日志
实时跟踪日志
显示所有可用日志
默认行为:不指定 -i 时,显示 default 实例的日志。
插件日志
插件日志(如 PgBouncer 连接池)使用 addon 子命令。
本地插件
查看附加到本地 PostgreSQL 实例的插件日志:
远程插件
查看针对远程数据库的独立 PgBouncer 实例的日志:
etcd 成员
etcd 属于顶层 infra 插件,用 --name 指定成员(省略时默认为 etcd):
PgDog 代理
pgdog 同样属于顶层 infra 插件,用 --name 指定代理(省略时默认为 pgdog):
PgDog 以结构化 INFO 行记录连接池事件(新建服务器连接、认证、客户端连接 / 断开)。
HAProxy 实例
haproxy 同样属于顶层 infra 插件,用 --name 指定实例(省略时默认为 haproxy):
HAProxy 会记录健康检查状态变化(Server lb_rw/node2 is UP/DOWN, reason: ...)
与连接事件,适合实时观察故障切换。
MinIO 实例
minio 同样属于顶层 infra 插件,用 --name 指定实例(省略时默认为 minio):
MinIO 日志走 stdout:启动行(其服务的 API: / Console: 地址)与请求错误。
Patroni 成员
Patroni 不是通过 pg addon 管理的组件——它是独立的顶层命令 pg ha 集群——
因此有自己的简写形式 pg logs ha <scope> --member <name>:
pg logs ha 等价于 pg logs addon patroni --scope <scope> --name <member>;
scope 必填,成员名在该集群内定位一个节点。
Patroni 成员每个周期都记录 HA 主循环(no action. I am (node1), a secondary, and following a leader (node3))以及 PostgreSQL 启动/恢复输出——适合发现卡住的
副本(反复出现 record with incorrect prev-link / waiting for WAL to become available)。
选项说明
| 选项 | 简写 | 描述 |
|---|---|---|
--follow |
-f |
跟踪日志输出(类似 tail -f) |
--tail N |
-n N |
显示最后 N 行(默认:50,0 表示全部) |
--instance NAME |
-i NAME |
实例名称(默认:default) |
--pg-name NAME |
远程插件名称(用于远程 PgBouncer) | |
--name NAME |
etcd 成员 / PgDog 代理 / HAProxy / MinIO 实例名称(默认:etcd / pgdog / haproxy / minio);Patroni 成员名(与 --scope 搭配,必填) |
|
--scope NAME |
Patroni 集群 scope(仅用于插件类型 patroni) |
|
--member NAME |
-m NAME |
Patroni 成员名(仅用于 pg logs ha <scope>) |
示例
注意事项
- PostgreSQL 日志包括查询执行、连接事件和系统消息
- 插件日志显示连接池活动(连接、断开、池统计)
- etcd 成员日志为 JSON 格式的 raft/选主/peer 事件,适合排查 quorum 问题
- PgDog 代理日志为结构化 INFO 行,记录连接池事件(服务器连接、认证、客户端连接 / 断开)
- HAProxy 日志记录健康检查状态变化与连接事件——可实时观察故障切换过程
- MinIO 日志走 stdout,输出启动行与请求错误
- 跟踪模式(
-f)会保持连接直到按 Ctrl+C 中断 - 远程插件使用
--pg-name而不是-i来标识目标连接池;etcd 成员、PgDog 代理、HAProxy 实例和 MinIO 实例使用--name - Patroni 成员不属于
pg addon管理,用pg logs ha <scope> --member <name>查看(等价于pg logs addon patroni --scope --name)
22 - 销毁
destroy 会停止并移除容器,然后从配置文件中删除该实例。默认情况下,主机的数据目录会被保留。使用 --clean-data 可以同时移除数据、WAL 归档和 pgBackRest 仓库的 stanza。
基本用法
执行过程
运行 pg destroy 时:
- 停止容器 — PostgreSQL 优雅关闭
- 移除容器 — 删除 Podman 容器
- 移除配置 — 从
~/.pgcli/pg.yaml中删除该实例条目 - 保留数据(默认)— 主机数据目录
--base-dir/<instance>保留
使用 --clean-data 时:
- 以上所有步骤,加上:
- 移除主机数据 — 删除数据目录
- 移除 WAL 归档 — 删除主机上的所有 WAL 文件
- 移除备份 stanza — 删除该实例的 pgBackRest 仓库 stanza
重建实例
销毁后,可以重新创建实例以获得全新开始:
重要: 不使用 --clean-data 时,旧的数据目录会被保留。当你重新创建实例时,PostgreSQL 会使用现有数据,而 init.sh(创建用户和设置管理员密码)不会再次运行。这可能导致以下问题:
- 数据是用不同的用户或密码创建的
- 你更改了配置中的默认用户
- 你想要完全全新开始
需要彻底清理时使用 --clean-data。
确认提示
默认情况下,pg destroy 会在执行前询问确认:
使用 --force 跳过提示:
使用场景
1. 配置更改后的干净重启
如果你更改了配置中的默认用户、密码或 PostgreSQL 版本,销毁并重建:
2. 移除测试实例
测试完成后,移除不再需要的实例:
3. 修复损坏的状态
如果实例处于异常状态(例如启动失败、数据损坏),销毁并重建:
4. 释放资源
销毁不再活跃使用的实例以释放:
- Podman 容器(CPU 和内存)
- 主机磁盘空间(使用
--clean-data) - 配置文件中的条目
与副本的关系
销毁副本时,主实例上的复制槽不会自动移除。你需要单独清理:
这种两步流程确保如果你计划稍后重新创建副本,不会意外丢失复制槽。
安全注意事项
- 数据丢失:
--clean-data会永久删除该实例的所有数据、WAL 和备份。谨慎使用。 - 无法撤销:销毁后,除非有外部备份,否则实例无法恢复。
- 配置丢失:实例条目会从配置文件中移除。如果以后需要该配置,请先备份。
标志
| 标志 | 默认值 | 描述 |
|---|---|---|
--force |
false |
跳过确认提示 |
--clean-data |
false |
同时移除主机数据、WAL 归档和备份 stanza |
-i, --instance |
default |
要销毁的实例名称 |
相关命令
pg create— 创建新实例pg start— 启动实例pg stop— 停止实例(容器保留)pg replica drop— 从主实例移除复制槽