晚上刷朋友圈习惯性地打开自己的博客看看状况,结果 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 解析有问题,导致外部访问不了。

  1. 查安全组和防火墙:登录云控制台看了一圈,UFW 是关的,安全组的 TCP 80 和 443 端口也老老实实地对 0.0.0.0/0 开放着。排除拦截问题。

  2. 查 DNS 解析:在服务器里 ping mooncell-tirpitz.xyz,返回的居然是 172.29.86.43 这个内网 IP....

后来到云DNSPod 控制台上检查,才想起来之前做大创的时候给@主机记录加了一个A类的内网ip没删

临门一脚

为了强制重新验证,我暴力删除了旧的 challengesecret,让它重新跑一遍。结果出现了非常诡异的一幕:

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 -wREADY 状态变成了 True,问题解决。

总结

如果你也是用 K3s + Traefik + cert-manager 搞自动化 HTTPS,当发现续签超时、特别是某个域名通而另一个不通时,一定要去查查你的 DNS 解析里是不是偷偷混进了 AAAA 记录

在基建没有完全准备好拥抱 IPv6 之前,老老实实用 IPv4 的 A 记录,能省去大部分的折腾。

就这样,继续跑代码去了。