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安装),见多节点集群。