前言
春节假期终于有时间折腾下 Homelab 了, 之前曾和别人谈论 Homelab 要不要装 K8s 时对其 “家用的话 docker-compose 就完全足够了” 深有同感, 当然那是建立在我对声明式没概念的前提下 — 现在我宁愿单机 k8s-master 自己跑 pod, 也不愿意回到 docker-compose 了 (不过我是自己搭了机柜, 老哥们手头资源紧张的还是掂量下资源消耗吧).
正好借从 docker-compose 单机迁移到 k8s 集群的机会, 将旧的应用 docker-compose.yaml 文件整理了一下改成资源声明. 想着 OutlineWiki 在初次部署时由于 MinIO 传来 Console 大砍的消息而没部署 S3 储存 (顺带给没关注的老哥们说句, MinIO 在今年 2.13 归档了, 而且 Release 和镜像都停留在超级 CVE 之前, 桀桀桀), 这次可以找个替代品, 于是想着部署个 SeaweedFS 到有超高速 NVMe 的机器, 结果发生了下叙的一系列事情, 折腾了两天以为是自己有问题, 最后还是壮胆提了个 issue, 如果不是作者光速回复可能我真的还是挂个 local-path 将就用着了.
在此将相关排查记录存档一下, 也方便各位使用或准备使用 SeaweedFS 的老哥们排错.
运行环境
- PVE 9.1.5
- Debian 13.3
- Kubernetes 1.33.7
- Ingress-nginx/controller 1.14.3
- SeaweedFS 4.12
- OutlineWiki 1.5
- S3 Browser 12.1.5
- *由于出口限制, 使用了非标准https端口, 由于出口还没实际转发出去 (docker-compose 机在切换完成前还需要提供服务), 实际上仅仅是内网配置 CoreDNS 和 win 机配置 host 做临时指向使用
问题复现
看了下官方文档, 使用其他服务的 yaml 简单修改了下, 给 seaweedfs 四个容器 (master, volume, filer, s3) 塞进 pod. 启动命令如下:
master: ["master", "-ip=$(POD_IP)", "-mdir=/data/master"]
volume: ["volume", "-mserver=127.0.0.1:9333", "-dir=/data/video", "-max=100"]
filer: ["filer", "-master=127.0.0.1:9333"]
s3: ["-v", "4", "s3", "-filer=127.0.0.1:8888", "-ip.bind=0.0.0.0"] # -v 4 参数为后续发现无法调取后加上, 用于打印更详细日志接着将 8333 端口暴露出去即可, 不过毕竟是 s3, 而且为了安全要做路径校验, 因此给配了 ingress, 用 cert-manager 挑战了一份证书来做 https 监听, 然后在 CoreDNS 配置了解析
不过出于对 MinIO Console 不给创建桶的奇怪逻辑的记忆, 抱着 “不想下 minio/mc, 要不用 S3 Browser 连上去先创建个桶再说吧” 的想法, 然后问题就开始了:

我反复检查了 Key ID 和 Secret Key, 和 Secret 声明提供的完全一致, 也确实使用的 path-style 模式, 那么问题出在哪呢? 是 Service 或者 Ingress 声明有问题吗?
那么绕过 Ingress 试试? 使用 kubectl port-forward 强行放出 pod 的 8333 端口, 再次使用 S3 Browser 连接 (关闭 tls) 则毫无问题
与此同时也在 OutlineWiki 测试上传和下载, 上传直接通过, 但是生成的下载路径丢失了端口
那么是 Ingress 的问题吗? 进入 ingress-nginx 容器查看配置, Host header 确实被设置为 $http_host, 那么理论上应该 442 端口被转发到 s3 了:

此时心急 delete 掉了包含 namespace 的整个声明, 把 keycloak 给一起杀了, 还好迁移准备的备份多, 花了一天去修复, 这一天时间 keycloak 就变成 zitadel 了(笑)
回到问题现场, 既然 ingress 会将正确的 port 传递到后方, 那么可以用 tcpdump 在后方截流 (alpine 容器使用 apk add tcpdump 来安装, 不过非调试不建议这么做) 看看到底传了个啥 header 过来, 显然, s3容器收到了正确的请求头:

ps. 此时脑子一抽还非常弱质地把证书给 s3 挂上并配置 https 监听然后走内网 https 二次测试抓包, 然后盯着一堆密文一脸懵逼
给容器配置上 -v 4 查看所有日志, 终于抓到元凶: 容器用来校验的头根本没带 port:

S3 的签名 (SigV4) 是把整个 Header 序列化后做 Hash 的,哪怕少了一个端口号,哈希值也对不上
这么说, 是 SeaweedFS 的问题?
从提 Issue 到光速 Merge
(这一段也作为给各位有想要提 issue 冲动但又不敢的老哥的一个鼓励)
怀疑到 30k star 超级项目有点不自量力, 但是 s3 非常规端口 https 监听确实不算什么常见问题. 至少类似的问题之前我已经在另一个项目见过, 但那个应用可以纯内网用就没去纠结.
那么, 要放弃 s3 吗? 回头看看我还在用着 MinIO 的 Misskey, 顶 CVE 作案终究是走不长远.
这是我第一次在 GitHub 上提 issue, 我码了域名, 把我的环境和测试记录简短的描述了一下, 询问是否是我配置有误.
已经早上两点了, 我也没时间等作者回复, 想着睡一觉起来还是先以 local-path 为前提完成迁移再说. 起来后直接开始改OutlineWiki 的 ConfigMap, 迁移完文件后突然想起似乎还提过一个 issue, 于是找到标签页查看.
看到 Closed 时心头一凉, 还以为是问的太差被直接关闭了. 往下想看看关闭是不是机器人操作的, 结果发现作者 chrislusf 在我发布 issue 之后两小时就处理完了.

赶快点开作者发的 PR 路径查看, 发现原来问题不只是我想象的 “端口被去除” (实际上是由于未配置 X-Forwarded-Port 头情况下 X-Forwarded-Host 头所带的 port 被默认的 ingress 配置忽略处理为 443), 还有请求头 Host (H是大写) 没被 go 的 http.Request.Host 正确识别的问题 (默认只识别 host), 佬在三十分钟内就完成了 PR, 想来我自己是会 go 的, 反倒完全只是作为运维侧在问配置而没关心这个端口在逻辑上是怎么读取的问题, 实在惭愧.
上线测试
既然佬这么给面子, 也说了 try the fix, 那不测试一下确实是说不过去了.
以往这个项目基本上七天一个 Release, 距离上一次 Release 才三天, 四天后春节都过完了.
那只好手动打镜像了, 官方提供了一个教程:
git clone https://github.com/seaweedfs/seaweedfs.git
cd seaweedfs
cd weed
make build_docker
mv weed ../docker/
cd ..
cd docker
docker build -f Dockerfile.local --no-cache -t chrislusf/seaweedfs:pr-8386 .将镜像导入然后替换声明中的 image 段, 恢复 ingress 配置, 重新上线, 至此已可以正常连接使用.
结语
距离我上次写文章已经很久了, 期间发生了很多事情, 说到底, 终究是有些倦怠了.
不过, 作为久礼奈为, 也许被观测与否并不是那么重要.
