故障时间:2026-08-10 ~ 08-13
影响范围:gitlab.maoyulong.club、blog.maoyulong.club返回 521,持续约 3 天
根因:Calico v3.19.1 → v3.25.2 升级后,CNI 默认不再为 Pod 分配 IPv6,导致 DCDN 的 IPv6 回源链路断裂
修复方式:修正 CNI conflist + configMap 模板 + 清理 IPAM 脏数据ps: 本次事故复盘由ai辅助编写
一、问题现象:GitLab 突然开始返回 5xx
8 月 10 号左右,访问 gitlab.maoyulong.club 和 blog.maoyulong.club 时,浏览器开始报错——GitLab 返回 521,博客也打不开。
521 是 Cloudflare 系(这里实际是腾讯 EdgeOne/DCDN)的特定错误码,含义是:源站主动拒绝了 CDN 节点的连接。也就是说,问题不在应用层(GitLab 本身没崩),而在 CDN 节点到源站之间的回源链路上。
我家的服务是通过 DCDN 回源到 homelab 内网的,回源地址配的是 mastera 节点的 IPv6 地址(240e:...::270)加 NodePort。所以第一反应就是:IPv6 回源链路断了。
二、排查过程:层层剥洋葱
2.1 第一层:确认 521 是 IPv6 回源问题
用 curl 直接测两个域名:
curl -6 https://gitlab.maoyulong.club/ # IPv6 访问 → 失败
curl -4 https://gitlab.maoyulong.club/ # IPv4 访问 → 正常
结果很明确:IPv4 全链路正常,IPv6 彻底不通。问题锁定在集群的 IPv6 数据面。
2.2 第二层:Pod 根本没有 IPv6 地址
进一步查 GitLab Pod 的网络信息,发现一个奇怪的现象:
GitLab Pod podIPs: 100.91.240.49/32 ← 只有 IPv4,没有 IPv6
而它的 Service 却是 RequireDualStack(要求同时有 IPv4 + IPv6),并且分配了 IPv6 ClusterIP fd00::424d。也就是说:
Service 要求双栈,但后端 Pod 只有 IPv4,IPv6 ClusterIP 的 DNAT 根本找不到目标。
2.3 第三层:IPv6 数据面规则完全缺失
继续往下查,发现更底层的证据:
| 检查项 | 结果 |
|---|---|
ip6tables-nft 中的 cali-* 链 |
0 个(IPv4 有 220 个) |
IPv6 ClusterIP fd00::424d 可达性 |
code=000,不通 |
| IPv6 NodePort 24725 | code=000,不通 |
| Calico IPv6 IPAM block | 有 3 个 fc00::/122,但 affinity 为 <none> |
Calico 的 IPv6 转发链(cali-*)完全没有生成,这说明 Calico 数据面根本没有为 IPv6 工作。
2.4 第四层:CNI conflist 缺了关键字段
顺着 Calico 的 CNI 日志往下挖,找到了决定性证据:
CNI 日志:IPAM request count IPv4=1 IPv6=0
Pod 创建时,CNI 只向 IPAM 申请了 IPv4,根本没申请 IPv6。而最后一条 IPv6=1 的记录停在 2026-06-16。
这个时间点太关键了——正是我把 Calico 从 v3.19.1 升级到 v3.25.2 的那天。
对比升级前后的 CNI 配置,根因浮出水面:
// v3.19.1 时期(正常):ipam 块默认就会分配 IPv6
{ "ipam": { "type": "calico-ipam", "ipv4_pools": [...], "ipv6_pools": [...] } }
// v3.25.2 之后(异常):assign_ipv6 默认变 false,Pod 只拿 IPv4
{ "ipam": { "type": "calico-ipam" } }
Calico v3.25 中,assign_ipv6 的默认值是 false,而 v3.19 的行为不同。升级时只替换了 manifest,没有显式加上 assign_ipv6: "true",于是从升级那一刻起,所有新建 Pod 都不再获得 IPv6。
2.5 第五层:真正的持久根因在 configMap
一开始我以为改一下各节点上的 /etc/cni/net.d/10-calico.conflist 就够了。但深挖 install-cni 的机制后发现:
Calico 的 install-cni initContainer 每次启动时,都会用 configMap
calico-config里的cni_network_config模板重新生成 conflist,覆盖节点上的文件。
所以即使手动改了节点文件,只要 calico-node 重启,改动就会被模板覆盖回退。真正的持久根因在 configMap 的模板里——它的 ipam 块同样只有 "type": "calico-ipam",缺 assign_ipv6。
此外还发现一个连带问题:masterb、masterc 两个节点上的 conflist,nodename 字段都错写成了 mastera(历史手动覆盖污染)。这导致这两个节点上的 Pod 会跑去 mastera 的 IPAMBlock 里抢 IP,埋下了后面 IPAM 脏数据的隐患。
三、根因时间线还原
把整件事串起来,其实是一条清晰的因果链:
2026-06-16 Calico v3.19.1 → v3.25.2 升级
↓ 升级后 assign_ipv6 默认 false,configMap 模板也没写死
从此所有新建 Pod 都不再获得 IPv6
↓ 但老 Pod 还带着 IPv6,短期内服务"看起来正常"
2026-08-10 之前某次 GitLab/WordPress Pod 重建,新 Pod 变成纯 IPv4
↓ DCDN 源站 IPv6 回源彻底断链
gitlab.maoyulong.club / blog.maoyulong.club 返回 521
一句话总结:两个月前的一次 CNI 升级,静默地关掉了 IPv6 分配,直到业务 Pod 重建才把雷引爆。
四、修复过程
4.1 修复 CNI conflist(三个节点)
在 conflist 的 ipam 块补上两个字段,并顺带修正 masterb/masterc 的 nodename:
{
"ipam": {
"type": "calico-ipam",
"assign_ipv4": "true",
"assign_ipv6": "true",
"ipv4_pools": ["default-ipv4-ippool"],
"ipv6_pools": ["default-ipv6-ippool"]
}
}
4.2 持久化修复 configMap 模板
这一步才是治本。修改 kube-system/calico-config 的 cni_network_config,让 install-cni 未来生成 conflist 时天然带上 assign_ipv6,防止下次 calico-node 重启时回退。
4.3 重建关键 Pod
修改完成后,滚动重建 GitLab 和 WordPress,让它们重新获取双栈 IP:
gitlab-ee: 100.91.240.15 + fc00::8f6a:a5eb:fa40:23c5:467a ✅ 双栈
wordpress: 100.91.240.38 + fc00::...:4650 ✅ 双栈
4.4 验证结果
| 验证项 | 结果 |
|---|---|
blog.maoyulong.club(IPv6) |
HTTP 200 ✅ |
gitlab.maoyulong.club(IPv6) |
HTTP 302 ✅(正常跳转登录页) |
| masterb/masterc 测试 Pod | 均拿到双栈 IP ✅ |
两个域名的 IPv6 回源链路彻底恢复。
五、清理 IPAM 脏数据(连带问题)
修复回源后,发现 nodename 错误的遗留影响还没清干净:7 个跑在 masterb 上的 Pod,历史性地从 mastera 的 IPAMBlock(100.91.240.0/26)里分配了 IP,形成跨节点占用。
其中 .1 / .8 / .50 / .62 在 IPAM 里已经显示为 free(会再次被分配出去),这正是之前 WordPress 反复报 route already exists tunl0 冲突的元凶。
处理方式:逐个重建这 7 个 Pod,让它们回到各自节点的 IPAMBlock:
| Pod | 原错误 IP(mastera block) | 新正确 IP |
|---|---|---|
| minio | 100.91.240.50 | 100.91.240.49 + IPv6 |
| coredns | 100.91.240.1 | 100.91.240.11 + IPv6 |
| mindustry | 100.91.240.66 | 100.127.90.251 + IPv6 |
| jellyfin | 100.91.240.67 | 100.127.90.245 + IPv6 |
| share-manager(harbor scandata) | 100.91.240.8 | 100.127.90.224 + IPv6 |
| share-manager(minio 4Ti) | 100.91.240.65 | 100.127.90.250 + IPv6 |
| instance-manager(masterb) | 100.91.240.62 | 100.127.90.246 + IPv6 |
清理后重新跑了一遍 IPAM 一致性分析:
- IPAM 泄漏(已分配但无 Pod):0
- 反向泄漏(Pod 有 IP 但 IPAM 无记录):0(仅剩 hostNetwork Pod,属正常)
一个小插曲
重建 instance-manager(masterb 的卷管理组件)时,masterb 上所有 Longhorn 卷短暂中断,连锁导致 harbor-database-0 重启 → harbor-core 短暂连不上库 → harbor-jobservice 崩溃了 3 次。这是删除卷管理组件的预期副作用,几分钟内全部自愈,无需干预。
六、经验与反思
6.1 升级 CNI 一定要核对”默认值变化”
这次最大的坑是 Calico v3.25 把 assign_ipv6 的默认值改成了 false。升级 manifest 时只关注了显式配置项(CIDR、IPIP、镜像源等),漏掉了这种”默认行为变化”。教训是:大版本升级时,除了 diff 自己的配置,还要去翻 changelog 看默认值有没有变。
6.2 别被”表面正常”骗了
故障其实是两个月前就埋下的,但因为老 Pod 还带着 IPv6,服务一直”看起来正常”。直到某次 Pod 自然重建,新 Pod 变成纯 IPv4,才把雷引爆。“配置变更后短期正常”不等于”没问题”,尤其是 CNI 这种影响所有新建资源的底层组件。
6.3 改节点文件是治标,改 configMap 才是治本
一开始改 conflist 只是让当前 Pod 恢复了。如果不追到 install-cni 从 configMap 生成配置这一层,下次 calico-node 一重启,改动就会被静默覆盖回退,故障必然复发。排查时多问一句”这个文件是谁生成的、什么时候会重新生成”,能少走很多弯路。
6.4 两个看似独立的问题,可能是同一个根因
这次事件里,IPv6 回源失败、WordPress 的 route already exists tunl0 冲突,表面看是两个问题,实际都指向同一个根因链:Calico 升级 + nodename 污染。排查时把零散症状串起来看,往往能找到更深层的共同源头。
