背景#

我的 homelab 现在是双集群结构:

  1. homelab 跑在 Proxmox 上,负责 Vault、ZITADEL、ArgoCD、Grafana、Kopia 这类核心服务。
  2. oracle-k3s 跑在 Oracle Cloud A1 免费机上,负责 Homepage、Miniflux、KaraKeep、Uptime Kuma、Timeslot 等轻量工作负载。

前一阵我先把 homelab 主集群从 Flannel 切到了 Cilium。迁移完成后,整个架构留下了一个非常明显的不对称:一边是 Cilium,一边还是 K3s 默认 Flannel。

这件事短期看没有坏处,但中长期会越来越烦:

  1. 网络问题的排障路径不一致。
  2. 两套集群文档会慢慢分叉。
  3. 以后不管是做 ClusterMesh PoC,还是只是想统一认知模型,都会被这块技术债反咬。

所以这次我决定把 oracle-k3s 也迁到 Cilium。

这次迁移真正的目标#

表面目标是把 CNI 从 Flannel 换成 Cilium,但真正目标其实有三个:

  1. 统一双集群数据面:以后提到 Pod 网络、Hubble、NetworkPolicy,两个集群说的是同一套东西。
  2. 验证恢复能力:既然是重装 K3s,就顺手检验一下 GitOps、Vault、备份、PVC 恢复到底是不是纸上谈兵。
  3. 识别复杂度来源:哪些问题是 Cilium 带来的,哪些其实是之前就存在,只是这次被重建过程暴露出来了。

迁移步骤概览#

最终流程大致是这样:

备份本地 PVC 数据
-> 卸载 K3s
-> 关闭 Flannel,重装 K3s
-> 安装 Cilium Helm chart
-> 重装 ESO / Traefik 配置 / 各类 manifests
-> 修 Vault Secret / Cloudflare Tunnel / 探针 / 数据库连接
-> 恢复 PVC 数据
-> 验证所有对外服务

其中真正耗时间的不是安装 Cilium,而是安装完以后把整条依赖链重新扶起来。

Cilium 配置选择#

这次我没有追求特别激进的配置,基本原则是:先求一致,再求花哨

关键配置如下:

kubeProxyReplacement: false
routingMode: tunnel
tunnelProtocol: vxlan
ipam:
  operator:
    clusterPoolIPv4PodCIDRList:
      - 10.52.0.0/16
hubble:
  enabled: true
  relay:
    enabled: true
  ui:
    enabled: true
gatewayAPI:
  enabled: false

有几个选择是有意为之:

  1. 继续保留 kube-proxy,不在这次迁移里同时引入 kube-proxy replacement 变量。
  2. 继续使用 routingMode: tunnel,避免把 Oracle Cloud 的底层网络特性也卷进来。
  3. 不启用 Cilium Gateway API,继续让 Traefik 管现有流量入口。

原因很简单:这次目标是把 Oracle 集群稳定迁到 Cilium,不是顺手再做一轮网关重构。

第一个坑:Cloudflare Tunnel 连不上#

Cilium 装好后,最先暴露的问题不是业务应用,而是 cloudflared

Pod 日志里一直是这个错误:

failed to dial to edge with quic: timeout: handshake did not complete in time

看起来像网络问题,但细看会发现它很有指向性:Cloudflared 默认优先走 QUIC,也就是 UDP。

而 Oracle Cloud 这台机器的现实情况是:UDP/QUIC 这条路径并不稳定。在新的网络栈下,这个问题被立刻放大了。

最终修复并不复杂,直接强制 cloudflared 使用 HTTP/2:

args:
  - tunnel
  - --no-autoupdate
  - --protocol
  - http2
  - --metrics
  - 0.0.0.0:2000
  - run

改完后两个 cloudflared Pod 很快都恢复到 1/1 Running

这次让我重新确认了一件事:

在 homelab 场景里,稳定往往比“默认最优协议”更重要。QUIC 很好,但如果底层网络环境不友好,强上只会让问题更隐蔽。

第二个坑:不是 Secret 丢了,是整条 Secret 供应链断了#

集群起来后,应用却没有跟着起来。ExternalSecret 几乎全红。

先查 ESO,再查 ClusterSecretStore,最后定位到一个非常基础但致命的问题:

  1. oracle 集群访问 Vault 还是走旧的 NodePort 31144
  2. 实际 homelab 上 Vault 已经换成 31333

也就是说,不是密钥值错了,而是 oracle 集群从一开始就连错了入口。

这个修好后,问题还没结束。

继续往下查,发现这次真正暴露的是另一个现实:我以前以为“已经放进 Vault”的东西,其实并没有全部标准化入库

最后我补齐和恢复了这几类 Secret:

  1. oracle-k3s/cloudflare
  2. oracle-k3s/uptime-kuma
  3. oracle-k3s/stirling-pdf
  4. oracle-k3s/oauth2-proxy
  5. homelab/miniflux
  6. homelab/karakeep
  7. homelab/redpanda-connect
  8. oracle-k3s/timeslot

这部分工作非常不优雅,但很真实:

  1. 有的是从现有配置反推。
  2. 有的是从 Cloudflare API 重新取 tunnel token。
  3. 有的是从 shell history 和旧部署痕迹里挖回来。

如果没有这次“重建式迁移”,这些隐性缺口以后只会在更糟糕的时间点爆出来。

第三个坑:应用没坏,是健康检查和连接串坏了#

Secret 都恢复后,Pod 还是没有全绿。

Miniflux#

miniflux 一直 CrashLoop,日志里很明显:

dial tcp [::1]:5432: connect: connection refused

原因是 DATABASE_URL 里仍然写着 localhost:5432,但 PostgreSQL 实际是单独的 Deployment,不在同一个 Pod 里。

修复方式就是把连接串改成 Service 名:

postgresql://miniflux:<password>@rss-postgres.rss-system.svc.cluster.local:5432/miniflux?sslmode=disable

RSSHub#

rsshub 的问题更像“漂移”:应用其实启动了,但健康检查一直失败。

我进去手测才发现,当前镜像的健康接口已经不是 /healthcheck,而是 /healthz

所以这次修的不是业务逻辑,而是文档和 manifest 对镜像行为的过期认知。

第四个坑:真正的恢复演练发生在 PVC#

这次我提前备份了 oracle 节点上的本地 PVC 目录,后面果然派上用场。

恢复的对象包括:

  1. miniflux-db-pvc
  2. karakeep-data
  3. meilisearch-data
  4. uptime-kuma-data
  5. stirling-pdf-configs
  6. timeslot-pvc

恢复过程并不高级,就是非常朴素的:

  1. 先 scale down 对应 Deployment
  2. 把旧 PVC 目录内容拷回新的 PV 路径
  3. 先起 PostgreSQL,再起依赖它的应用
  4. 最后逐个验证服务和数据是否回来

这一步的意义很大,因为它证明了一件比“Cilium 安装成功”更重要的事:

我的 oracle 集群即使完全重装,状态数据也不是不可恢复的。

对 homelab 来说,这比任何一个 fancy 网络特性都更值钱。

Timeslot 也顺手补进去了#

这次还顺手把 Timeslot 重新部署并恢复了数据。

这个服务比较特别:它不是主 kustomize 流里直接管的,而是通过 Helm chart 单独装,再打一个 patch 修 chart bug。

这件事本身也说明了当前架构里一个很值得继续收敛的问题:

  1. 大多数工作负载已经 declarative 了。
  2. 但仍有少数应用需要额外的 imperative 补丁步骤。

这类“例外路径”越多,系统越难维护。

迁移后的结果#

迁移完成后,我做了三类验证:

集群健康#

所有 oracle-k3s Pod 最终都恢复到 Running / Ready。

对外可用性#

这些入口都恢复正常:

  1. home.meirong.dev
  2. tool.meirong.dev
  3. pdf.meirong.dev
  4. rss.meirong.dev
  5. status.meirong.dev
  6. slot.meirong.dev
  7. squoosh.meirong.dev
  8. keep.meirong.dev

其中 302 的入口是预期行为,因为它们会跳去登录页或 SSO。

运维配套#

  1. Uptime Kuma monitor 已重新 provision
  2. 文档里的当前状态已经同步成双 Cilium
  3. cloud/oracle/justfile 里之前会被 heredoc 卡死的 recipe 也一起修掉了

这次迁移让我重新排序了后续优先级#

迁移前我原本有点想尽快研究 ClusterMesh,但做完之后反而更确定:现在不该急着上

原因不是 ClusterMesh 不好,而是当前更值钱的事情另有其人:

  1. 把剩余的手工 bootstrap 继续 declarative 化
  2. 给文档和自动化脚本加一层 drift 检查
  3. 做一次真正的恢复演练,把“能恢复”从推测变成证据

换句话说,下一步最需要的不是更多特性,而是更少例外。

我现在对这套架构的判断#

如果你也在做双集群 homelab,我现在的建议会是:

  1. 先统一基础能力,再追求高级功能。双集群 CNI 一致,排障成本会显著下降。
  2. 不要把所有问题都归因于 CNI。这次很多暴露出来的问题,本质上是 Secret 管理、探针漂移、文档过期。
  3. 重建过程才是最好的验收测试。能不能从零把一套集群扶回来,比平时绿不绿更有价值。
  4. 对 ClusterMesh 保持克制。在真正需要跨集群 Service 发现前,Tailscale underlay + NodePort / public URL 已经够用了。

结语#

这次 Oracle K3s 迁到 Cilium,表面上是一次网络升级,实际上更像一次架构体检。

Cilium 本身并没有制造太多麻烦。真正值得重视的,是迁移把之前那些“平时还能凑合”的隐性问题都拎到了台面上:

  1. 文档是不是反映真实状态
  2. Secret 是否真的集中管理
  3. 数据恢复是不是可执行
  4. 自动化脚本是不是能从头跑通

从这个角度看,这次迁移很值。

它不只是让我把两个集群都切到了 Cilium,也让我对这套 homelab 终于有了更强的把握感。

如果你感兴趣,完整的实施记录和当前架构文档都在这里:

  1. docs/plans/2026-03-07-homelab-oracle-architecture-optimization.md
  2. docs/architecture/simplification-recommendations-2026-03.md
  3. cloud/oracle/README.md