晚上刷朋友圈习惯性地打开自己的博客看看状况,结果 Chrome 直接甩给我一个刺眼的红屏警告:ERR_CERT_DATE_INVALID(您的连接不是私密连接)。
掐指一算,距离上次用 K3s 部署 Halo 博客刚好过去了三个月。很明显,Let's Encrypt 的 90 天免费证书到期了,而且 cert-manager 的自动续签彻底翻了车。
稍微花了点时间排查,最后发现是一个非常微小的坑。记录一下debug过程,希望能帮到遇到类似问题的朋友。
首先是看看K3s集群的状态
连上服务器,通过 kubectl get pods -A 发现,default 命名空间下居然有一个 cm-acme-http-solver 的 Pod 运行了 9 天。
正常情况下,这个用于响应 Let's Encrypt HTTP-01 验证的临时 Pod 活不过一分钟。跑了 9 天,说明验证过程彻底卡死了。
顺藤摸瓜,看一下具体的 Challenge 报错:
Bash
sudo kubectl get challenges -n default
sudo kubectl describe challenge <你的challenge名字> -n default
在 Events 里看到了最核心的报错: Waiting for HTTP-01 challenge propagation: failed to perform self check GET request ... context deadline exceeded
意思很简单:cert-manager 准备好了验证文件,但在正式呼叫 Let's Encrypt 之前,它自己先尝试通过公网访问了一下 http://mooncell-tirpitz.xyz/.well-known/... 进行自检,结果超时了。连自己这关都没过,自然就卡死了。
DNS和防火墙测试
自检不通,我的第一反应是 80 端口被墙了或者 DNS 解析有问题,导致外部访问不了。
查安全组和防火墙:登录云控制台看了一圈,UFW 是关的,安全组的 TCP 80 和 443 端口也老老实实地对
0.0.0.0/0开放着。排除拦截问题。查 DNS 解析:在服务器里
ping mooncell-tirpitz.xyz,返回的居然是172.29.86.43这个内网 IP....
后来到云DNSPod 控制台上检查,才想起来之前做大创的时候给@主机记录加了一个A类的内网ip没删
临门一脚
为了强制重新验证,我暴力删除了旧的 challenge 和 secret,让它重新跑一遍。结果出现了非常诡异的一幕:
Bash
ubuntu@VM-0-13-ubuntu:~$ sudo kubectl get challenges -n default
NAME STATE DOMAIN AGE
mooncell-tls-1-4219397777-3781850124 pending mooncell-tirpitz.xyz 5m59s
mooncell-tls-1-4219397777-929624076 valid www.mooncell-tirpitz.xyz 5m59s
带 www 的子域名秒过(valid),但不带 www 的主域名(@)一直卡在 pending。
又打开云解析 DNSPod 控制台一对比,发现@记录上存在一个AAAA类的解析记录,Let's Encrypt 的验证机制有一个硬性优先级:当它去解析你的域名时,如果发现同时存在 A 记录和 AAAA 记录,它会强制优先通过 IPv6 访问你的 80 端口。如果连不通,它直接报错,绝对不会好心地去降级尝试 IPv4。
在 K3s 和 Traefik没有配置完善的双栈(Dual-stack)网络的情况下,Traefik 并没有正确监听 IPv6 的 80 端口。Let's Encrypt 顺着 AAAA 记录找过来,直接吃了闭门羹,导致整个多域名证书的申请彻底卡死。
解决办法:
找到了病根,解决起来就很快了:
第一步:把那条 AAAA 记录直接暂停,日后完善IPV6访问时再考虑启用。
第二步:清理 K8s 里所有卡死的资源。 因为之前的在 Let's Encrypt 那边已经废了,不能只删 secret,得直接delete:
Bash
# 清理流水线上的所有垃圾
sudo kubectl delete challenge --all -n default
sudo kubectl delete order --all -n default
sudo kubectl delete certificaterequest --all -n default
# 删除当前的证书资源和旧 Secret,逼迫 cert-manager 重新打卡上班
sudo kubectl delete certificate mooncell-tls -n default
sudo kubectl delete secret mooncell-tls -n default
清理完毕后,使用 sudo kubectl get certificate -n default -w ,READY 状态变成了 True,问题解决。
总结
如果你也是用 K3s + Traefik + cert-manager 搞自动化 HTTPS,当发现续签超时、特别是某个域名通而另一个不通时,一定要去查查你的 DNS 解析里是不是偷偷混进了 AAAA 记录。
在基建没有完全准备好拥抱 IPv6 之前,老老实实用 IPv4 的 A 记录,能省去大部分的折腾。
就这样,继续跑代码去了。