开场:从 618 元的冲动到 3 个月的折腾
去年双 11,我花了 618 元买了三块树莓派 4B(8GB 版单价 385 元,含散热片和电源)。当时脑子里的画面是:一个闪闪发光的生产级 K8s 集群,跑着自建的 GitLab Runner,每晚自动构建博客镜像。理想很丰满,现实是 Day 1 我就被 SD 卡读写速度按在地上摩擦——kubeadm init 跑了 20 分钟没反应,最后发现是 I/O 等待率 97%。
我们团队后来用 K3s 替换了 kubeadm,但踩坑远没结束。这篇文章不教你怎么复制粘贴官方文档,而是把我在 3 个月里遇到的 7 个真实问题、3 次数据丢失和最终稳定运行的配置方案,按时间线讲透。
为什么是 K3s 而不是 MicroK8s 或 k3d?
先泼冷水:K3s 不是为生产设计的,它是为边缘计算和资源受限场景设计的。官方文档说最低 512MB 内存就能跑,但我们实测:1GB 内存的树莓派 3B+ 上,只跑系统 + K3s 就吃掉 78% 内存,调度一个 Nginx Pod 直接 OOM。
我们团队的选型过程其实很粗暴。第 2 周我试了 MicroK8s,它的 Snap 安装方式在树莓派上简直是灾难——snap install microk8s --classic 跑了 15 分钟,然后 microk8s status 卡在 waiting for DNS 无限循环。k3d 需要 Docker,但 Docker 在树莓派上吃 300MB 内存,算下来比 K3s 多出 25% 的资源占用。
K3s 的杀手锏是二进制体积小(约 65MB),且自带 SQLite 存储,不需要 etcd。但我们后来发现,SQLite 在集群节点超过 3 个时性能急剧下降。K3s 的 etcd 模式(--datastore-endpoint 指向外部 etcd)才是正解,这会在后文详细说。
说实话,选择 K3s 还有一个隐藏原因:Rancher 团队对 ARM 架构的适配比 Kubernetes 社区好得多。你可以在树莓派上直接跑 curl -sfL https://get.k3s.io | sh - 装好,但 kubeadm 需要你自己编译 hyperkube 镜像——这活我干了 3 天,最后放弃了。
硬件选型:别在这 4 个地方省钱
我们测试了三套配置,最终稳定运行的是下面这套,总成本 1280 元:
| 硬件 | 型号 | 数量 | 单价(元) | 备注 |
|---|---|---|---|---|
| 主控节点 | 树莓派 4B 8GB | 1 | 385 | 必须 8GB,4GB 会 OOM |
| 工作节点 | 树莓派 4B 4GB | 2 | 295 | 实测 4GB 足够跑 20 个 Pod |
| 存储 | 三星 EVO Plus 128GB SD 卡 | 3 | 89 | 别买杂牌,掉速会毁掉集群 |
| 电源 | 树莓派官方 5V/3A USB-C | 3 | 35 | 非官方电源会导致频繁重启 |
关键教训:SD 卡寿命比想象中短。我们第 3 周就遇到主控节点的 SD 卡损坏,dmesg 全是 I/O error,整个集群瘫痪了 6 小时。后来换成了 PXE 网络启动,把系统装到 USB 固态硬盘里,但这是后话,至少 Day 30 之前先把 SD 卡用明白。
另外,网络必须用有线千兆。我们一开始用 Wi-Fi,kubectl get nodes 偶尔显示 NotReady,排查了两天发现是 Wi-Fi 延迟抖动导致心跳超时。后来改用网线直连一个 8 口千兆交换机(二手 120 元),问题消失。
第 1 个坑:K3s 安装脚本的 --write-kubeconfig-mode 参数
Day 1 晚上,我执行官方安装命令:
curl -sfL https://get.k3s.io | sh -
然后 kubectl --kubeconfig /etc/rancher/k3s/k3s.yaml get nodes 报错:permission denied。
困惑了很久,因为普通用户读不了 /etc/rancher 目录下的文件(权限 640,属主 root)。官方文档根本没提这个,我翻到 GitHub Issue #1870 才找到答案:安装时加 --write-kubeconfig-mode 644:
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="--write-kubeconfig-mode 644" sh -
但这还不够。工作节点加入时,K3S_URL 和 K3S_TOKEN 是明文传到命令行里的,在 /proc 里能被任何用户看到。我们团队后来改用了 --token 变量从文件读取,具体在 GitHub Actions 里用 secrets 管理,但这又是一个故事。
第 2 个坑:内置 Traefik 吃掉了 512MB 内存
安装完 K3s 默认自带了 Traefik(Ingress Controller),这玩意在树莓派上就是个内存怪兽。我们 kubectl top pods -n kube-system 一看,traefik 占 412MB,svclb-* 又占 120MB,整个集群可用内存直接少了一半。
解法是安装时禁用默认的 Traefik:
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="--disable=traefik" sh -
然后自己装一个轻量级的 ingress-nginx(ARM 版镜像约 80MB),实测内存占用只有 150MB。但问题来了:我们团队的 Java 微服务需要 WebSocket 长连接,ingress-nginx 的 proxy-read-timeout 默认 60 秒,调大后内存又飙了 30MB。说到底,在树莓派上跑 Ingress 就必须接受资源天花板,这不是 K3s 的锅。
第 3 个坑:外部 etcd 的必要性
第 4 周,我们把工作节点扩展到 5 个,主控节点的 SQLite 开始频繁超时。kubectl get pods 响应时间从 20ms 暴涨到 2.5 秒,而且 journalctl -u k3s 里全是 failed to get node 的报错。
K3s 支持 --datastore-endpoint 指向外部 etcd,但坑在于 etcd 本身也吃资源。我们用一个独立树莓派跑 etcd(etcd --listen-client-urls http://0.0.0.0:2379),但没做 TLS 加密。第 6 周,有扫描器扫描到我 2379 端口,把整个 etcd 数据删了,集群直接不可用。说实话,那次事故让我意识到:K3s 的默认配置只适合内网,暴露到公网(哪怕是端口转发)就是找死。
正确的 etcd 配置至少需要:
- 使用
--etcd-certfile和--etcd-keyfile开启双向 TLS - 设置
--etcd-quota-backend-bytes=2147483648(2GB,否则 etcd 默认 2GB 会频繁压缩)
但这样一来,etc d 的运维复杂度比 K3s 本身还高。我们团队最后折中方案是:保留 SQLite,但将节点数限制在 3 个,每 2 周备份一次 SQLite 文件。
数据对比:SQLite vs etcd vs 外部存储
这是我们在 5 个节点集群上的实测数据(用 kubectl bench 压测 1 小时):
| 存储方案 | API 响应时间 (p99) | 内存占用 (主控) | 数据丢失风险 | 部署复杂度 |
|---|---|---|---|---|
| SQLite(默认) | 2.5s | 380MB | 中等(SD 卡损坏即丢) | 低 |
| 外部 etcd(未 TLS) | 850ms | 450MB+ 独立节点 | 高(易被攻击) | 高 |
| 外部 etcd(TLS) | 890ms | 452MB+ 独立节点 | 低 | 极高 |
| 本地 SQLite + 定时备份 | 2.4s | 382MB | 低(备份恢复 < 30 分钟) | 低 |
说实话,对树莓派集群来说,99% 的场景用不上外部 etcd。我们的压测结果里,SQLite 的 2.5 秒 p99 延迟对个人博客或 CI 跑批完全够用,但如果你要跑实时 API 网关,建议直接上两台 PC 搭集群,别在树莓派上浪费生命。
第 4 个坑:持久化存储的三种方案
树莓派的 SD 卡不适合做持久化,我们有 3 次 Pod 重建后数据丢失的记录。后来试了三种方案:
- HostPath:最简单,但 Pod 漂移到另一节点就没数据了,适合测试。
- NFS:在 NAS(或另一台树莓派)上挂载,我们用
nfs-server镜像,但树莓派的 NFS 性能只有 40MB/s 读写,跑 MySQL 备份勉强及格。 - Longhorn:Rancher 自家的分布式存储,但 ARM 镜像支持一直不完善,v1.6 的
longhorn-arm镜像在树莓派 4B 上总报incompatible mount错误。我们试了 2 周,放弃了。
最终我们的稳定方案是:NFS 挂载 + 定时 mysqldump 到外部 NAS。这不是优雅方案,但在资源受限环境下实用。
上个月,有个国内开发者问我:“树莓派集群能跑生产吗?”我的回答是:能跑,但你要接受 30% 的性能损失和每周一次的手动检查。这不是 K3s 的问题,是硬件天花板。
性能实测:3 个树莓派能跑什么
我们用 kind 在 PC 跑同样的工作负载做对比,数据如下:
| 工作负载 | 树莓派集群 (3 节点 8GB) | PC (i5-12400F, 32GB) | 性能比例 |
|---|---|---|---|
| Nginx 部署 (10 个副本) | 38 秒完成 | 12 秒 | 31.6% |
| MySQL 主从复制 | 50 TPS | 980 TPS | 5.1% |
| Redis 基准 (30 并发) | 1200 ops/s | 21000 ops/s | 5.7% |
| 批量计算 (CPU 密集) | 3 分 21 秒 | 47 秒 | 23.4% |
说实话,这个结果让我怀疑人生。树莓派的 CPU 性能只相当于 2015 年的入门笔记本,8GB 内存还要分给 OS 和 K3s 本身。我们团队后来把 CI 任务分散到 PC 上,树莓派只跑轻量 API 网关和状态检查。
回头看,如果让我重新选择,我会直接买两台二手 Intel NUC(800 元一台),性能是树莓派的 4-5 倍,能耗差不多。但这不是 K3s 的失败,而是 ARM 单板机的物理极限。
踩坑时间线回顾
- Day 1:kubeadm 卡死,换 K3s,解决安装问题。
- 第 2 周:MicroK8s Snap 卡死,确认 K3s 是唯一靠谱 ARM 方案。
- 第 3 周:SD 卡损坏,集群瘫痪 6 小时,学会备份。
- 第 4 周:SQLite 性能瓶颈,配置外部 etcd(未 TLS)。
- 第 6 周:etcd 被扫描器删数据,回滚 SQLite,加入定时备份。
- 第 8 周:Longhorn 放弃,改用 NFS + 定时 dump。
- 第 12 周:集群稳定运行,但性能比 PC 差 70%。
但我也要承认 K3s 不如 kubeadm 的地方:当你需要自定义 kubelet 参数(比如 --max-pods 或 --cpu-manager-policy),K3s 把它们埋在 --kubelet-arg 里,调试起来很痛苦。我们有一个节点因为内存压力,Pod 一直触发 OOM,journalctl 里看不到 K3s 层的提示,最后翻 kubelet 日志才发现是 memory.oom.group 设置问题。
话说回来,kubeadm 在树莓派上能不能跑通?能,但需要你手动处理 cgroup 驱动(kubeadm config 里设 cgroupDriver: systemd)、手动安装 CNI 插件、手动配置 ARM 镜像仓库——这套流程做下来,最快也要 2 天。而 K3s 把 80% 的脏活都干了,代价是 20% 的灵活性。
这世界没有银弹,只有权衡。
你愿意花多少时间换性能?这个问题,留给你自己回答。