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一把。