从 Cilium Gateway 到 CoreDNS:一次跨层级的 K8s 连锁故障排查
目录
背景#
这次故障一开始看起来像两个互不相干的问题:
- ArgoCD 里
gatewayApplication 一直是Degraded - 其他几个 Application 又出现了
OutOfSync或者后续的Unknown
如果只看表面,很容易把它归类成“Cilium Gateway API 不稳定”或者“ArgoCD 自己抽风”。
但真正麻烦的地方在于,这次故障不是单点失效,而是一条跨层级的链路问题:
ArgoCD 健康状态异常
-> 先看到 Gateway Degraded
-> 再发现 repo-server 拉 Git 也失败
-> 往下追到 CoreDNS 外部解析异常
-> 再继续追到 ZITADEL 根本没有把 backend Service 建出来
-> 最后发现 Vault 里的 ZITADEL master key 长度也不对
也就是说,最上层暴露出来的是 Gateway 和 ArgoCD,真正的根因却横跨了:
- Cilium Gateway API
- ArgoCD repo-server
- CoreDNS
- systemd-resolved
- Vault + External Secrets
- ZITADEL Helm 安装链路
这篇文章就按我实际排查的顺序,把整个问题怎么拆开、怎么验证、最后怎么收敛讲清楚。
初始症状#
最早看到的是 ArgoCD 状态不对:
gateway是Synced/Degradedpersonal-services、zitadel一度有漂移- 后面甚至出现
Unknown
与此同时,Uptime Kuma 也有两个监控项报错,所以一开始很难判断哪些是同一条故障链,哪些只是噪音。
这类时候我比较在意一件事:不要一上来就把所有红的东西都当成同一个根因。
所以我先分了三条线:
- GitOps 漂移线:为什么
OutOfSync - Gateway 健康线:为什么
Degraded - 运行时连通性线:为什么后面又出现
Unknown
第一层:先把“假问题”剥掉#
ExternalSecret 漂移其实不是故障,只是默认字段不一致#
personal-services 和 zitadel 最早的 OutOfSync,根因其实不复杂。
live 对象里的 ExternalSecret 被控制器补了一些默认字段,比如:
deletionPolicyconversionStrategydecodingStrategymetadataPolicytemplate.engineVersiontemplate.mergePolicy
Git 里没写,ArgoCD 就一直觉得 live 和 desired 不一致。
这个问题的处理很直接:
- 把这些默认字段显式补回 Git
- 重新同步
做完以后,这一批 OutOfSync 基本就消掉了。
这一步的价值不是“修了一个大问题”,而是把后面真正的红灯从噪音里抠出来。
第二层:为什么 gateway 一直是 Degraded#
一开始我怀疑的是 Cilium GatewayClass#
最先看到的 live 状态是:
Gateway已经Accepted=True- 但是
Programmed=False - reason 是
AddressNotAssigned
继续看下去,发现 Cilium 给 Gateway 生成的是 LoadBalancer Service,但集群里并没有可用的 CiliumLoadBalancerIPPool,于是它的 external IP 一直是 <pending>。
这说明:
- Gateway 控制器本身不是完全挂掉
- 但默认的
ciliumGatewayClass 和当前 homelab 网络模型并不匹配
所以我先做了第一轮修复:
- 新增
CiliumGatewayClassConfig - 把 Service 类型改成
NodePort - 新增
GatewayClass cilium-nodeport - 让
Gateway改用这个新的 class
这个修改是必要的,因为它解决了“默认 Cilium GatewayClass 需要 LB IPAM,而我当前集群没有”的错配问题。
但问题并没有彻底结束。
真正拖住 gateway 健康的是一条坏掉的 HTTPRoute#
后面在继续查 condition 的时候,我发现 gateway 这个 Application 之所以还在 Degraded,并不完全是 Gateway 自身有问题。
罪魁祸首其实是 zitadel 对应的一条 HTTPRoute:
ResolvedRefs=False
BackendNotFound
Service "zitadel" not found
这一下定位就完全变了。
也就是说:
- Gateway 主资源已经没那么糟了
- 但是它下面挂着的 route 有一个后端引用无效
- ArgoCD 最终把这整个应用判成了
Degraded
这一步很重要,因为它把问题从“Cilium Gateway API 失效”改写成了“Gateway 下面有一个 backend 根本不存在”。
第三层:为什么 ZITADEL backend Service 根本不存在#
既然 HTTPRoute 指向的 Service/zitadel 不存在,那就不能只修 route,得继续往回看 ZITADEL 为什么没起来。
这时候看到两个连续问题。
问题 A:ZITADEL master key 长度错误#
ZITADEL 的 setup job 一直失败,报错本质上是:
masterkey must be 32 bytes
根因不在 Helm,也不在 Gateway,而在 Vault 里的密钥值本身不合法。
这类问题很容易误导人,因为表面上你看到的是:
- Helm release 失败
- Service 没创建
- HTTPRoute backend 不存在
- Gateway degraded
但最底层其实只是一个 secret 值的格式错了。
修复动作是:
- 旋转 Vault
secret/homelab/zitadel里的master-key - 确保它是合法的 32-byte 值
- 重新触发 ZITADEL 安装
做到这里,理论上 ZITADEL 应该能起来了。
但实际上还差一步。
问题 B:Helm 还在因为外部 DNS 失败而拉不到 chart#
即使 master key 修好了,ZITADEL 还是一度装不起来,因为集群内部对 charts.zitadel.com 的解析不稳定。
这时候问题正式从应用层掉进了基础设施层。
第四层:ArgoCD 为什么后来变成 Unknown#
在我修 manifest 漂移和 Gateway 的过程中,ArgoCD 状态又发生了变化:
原本是 Degraded / OutOfSync,后来一些应用直接变成了 Unknown。
这通常不是资源本身坏了,而是 ArgoCD 连“判断你现在是什么状态”都做不到了。
看 argocd-repo-server 的报错,真正的问题是:
lookup github.com on 10.43.0.10:53: server misbehaving
这说明 repo-server 访问 GitHub 失败,原因不是 GitHub 挂了,而是集群内部 DNS 解析失败。
于是问题链继续往下游走:
ArgoCD Unknown
-> repo-server 无法解析 github.com
-> CoreDNS 解析外部域名失败
第五层:CoreDNS 其实在转发给一个容器里根本不可达的 stub resolver#
最后定位到的 CoreDNS 根因,是这次我觉得最值得记住的地方。
现场状态#
CoreDNS 当时的配置逻辑大致是:
forward . /etc/resolv.conf
而节点上的 /etc/resolv.conf,实际又是 systemd-resolved 的 stub:
127.0.0.53
在宿主机上这可能没问题,因为本机能访问自己的本地 stub。
但 CoreDNS Pod 是一个容器。
当它把外部查询继续转发到 127.0.0.53 时,那个地址指向的是容器自己的 loopback,不是宿主机的 systemd-resolved。
结果就是:
- CoreDNS 对外域名解析
SERVFAIL - repo-server 不能解析
github.com - Helm 不能解析
charts.zitadel.com - cloudflared 也可能解析失败
这一层才是整条链里最隐蔽、但影响范围最大的故障点。
修复动作#
1. 让 kubelet 给 Pod 传递真实 resolver 列表,而不是 stub#
在 K3s 配置里加入:
kubelet-arg:
- resolv-conf=/run/systemd/resolve/resolv.conf
这样 dnsPolicy: Default 的 Pod,比如 CoreDNS,就不再继承 /etc/resolv.conf 里的 127.0.0.53,而是继承真实上游列表。
2. 给节点的 systemd-resolved 增加 fallback DNS#
即使 Tailscale MagicDNS 在网络抖动后出现负缓存或者不可用,节点也还有公共解析器兜底:
[Resolve]
DNS=8.8.8.8 1.1.1.1
FallbackDNS=8.8.4.4
3. live 先救火,CoreDNS 直接改成公共 forwarders#
为了尽快恢复集群,我先在 live 上把 CoreDNS 改成:
forward . 1.1.1.1 8.8.8.8
这一步让 repo-server 和 Helm 安装链路马上恢复工作能力。
4. 再把这件事固化进仓库#
只做 live patch 不够,因为下次重装、重启、或者 addon 重建时它可能又漂走。
所以我后来把 CoreDNS 的目标状态写回了 k8s/ansible:
setup-k3s.yamlfix-dns-fallback.yaml
现在这两个入口都会在 K3s ready 之后重新把 CoreDNS 的 external forwarders 调整到公共解析器,而不是继续依赖某次手工 patch。
这一步很关键,因为它把“临时救火”变成了“可重复执行的恢复能力”。
最后一跳:把坏掉的 HTTPRoute zitadel 重建掉#
当 ZITADEL backend 已经真实存在以后,HTTPRoute zitadel 其实还出现过一次“状态没刷新”的情况。
它继续报:
ResolvedRefs=False
BackendNotFound Service "zitadel" not found
这时候最有效的动作不是继续等,而是:
- 删除这条 stale 的
HTTPRoute - 触发一次 ArgoCD sync
- 让它按 Git 中的定义重新创建
重建之后,condition 就恢复成:
ResolvedRefs=True
Service reference is valid
随后 gateway Application 也终于从 Degraded 变回了 Healthy。
这次顺手修掉的另外两类问题#
虽然主线是 Gateway/ArgoCD/CoreDNS/ZITADEL,但这次我也顺手把另外两块技术债一起收了。
Oracle 业务 secret 归位#
之前 Oracle 集群的一部分业务 secret 还留在 secret/homelab/* 下,和当前约定不一致。
这次我做了两步:
- Git manifest 统一改为
secret/oracle-k3s/<service> - live Vault 中把已经迁移完的旧重复路径清掉
保留例外只有 homelab/kopia,因为 Oracle 的备份任务本来就是复用 homelab 的 Kopia 服务。
Uptime Kuma 误报清理#
最后剩下的两个报错监控,并不是服务真的挂了,而是预期 HTTP 状态码过时了。
我直接把监控配置里的 accepted status codes 修正后,heartbeat 立刻恢复正常。
我从这次故障里记住的 5 个点#
1. Gateway Degraded 不等于 Gateway 本体坏了#
在 Gateway API 体系里,父资源的健康状态可能被某条子 route 的 backend 引用拖垮。
先看 Gateway 自己的 condition,也要继续看下面每条 HTTPRoute 的 ResolvedRefs。
2. ArgoCD Unknown 优先查 repo-server 能不能做 DNS#
只要 repo-server 连 github.com 都解析不了,后面很多健康判断都会失真。
这时候修 YAML 通常没意义,先让 Git 拉取得回来。
3. forward . /etc/resolv.conf 在 systemd-resolved stub 场景下很危险#
宿主机的 127.0.0.53 对宿主机本身有效,不代表对容器也有效。
这类问题平时不容易暴露,但一旦碰到外部 chart 拉取、Git 仓库访问、Tunnel 出站解析,就会一起炸。
4. Secret 格式错误会在上层表现成完全不同的症状#
这次 ZITADEL master-key 长度错误,最终在界面上看起来像:
- Helm chart 失败
- Service 缺失
- HTTPRoute backend 不存在
- Gateway degraded
所以排查链路里一定要愿意往 Secret 和 Job 日志那一层下钻。
5. live 修复必须回写 repo,不然只是“延迟复发”#
这次如果只停在:
- patch CoreDNS
- 改一下 Vault
- 手动重跑 ZITADEL
那下次节点重建或者服务重建还是会踩回去。
真正有价值的是把这些结论变成:
- Git manifest
- Ansible playbook
- Runbook
- 事故复盘文档
最终收尾状态#
这次处理完以后,最终状态是:
- ArgoCD 主要应用恢复
Synced/Healthy gateway恢复健康- ZITADEL backend 和 route 都恢复正常
- CoreDNS 外部解析链路恢复
- CoreDNS 修复已固化到 Ansible
- Oracle 旧重复 Vault 路径已清理
- Uptime Kuma 误报已消除
换句话说,这次不是简单地“把红灯点灭了”,而是把一条跨越 GitOps、DNS、身份系统和网关的故障链,拆成了几个能验证、能回放、能写进仓库的修复点。
结语#
我现在越来越觉得,homelab 最难的不是“把组件装起来”,而是当多个控制面同时出问题时,能不能保持一条稳定的排查顺序。
这次故障给我的一个提醒是:
当你看到的是 Gateway 和 ArgoCD 报错,真正该怀疑的可能是 DNS、Secret,甚至是某个根本没成功创建出来的 backend Service。
如果以后再碰到类似现象,我大概率会按这个顺序重新走一遍:
- 先剥离 GitOps 漂移噪音
- 再看 Gateway / Route conditions
- 再看 backend Service 是否真的存在
- 再看 ArgoCD repo-server 的 DNS
- 最后再往 CoreDNS 和节点 resolver 下钻
这条链,这次已经替我验证过一次了。