本页目录
wildwindstudio.com 证书自动续签 + 腾讯云 CLB 换绑
宿主机:bj-middleware-1(root,TencentOS,腾讯云北京)
1. 整体流程
日志统一写到 /var/log/certbot-renewal.log;certbot 自身调试日志在 /var/log/letsencrypt/。
关键设计点
- 逐张串行 + sleep 60:根本目的是避开腾讯云 DNS 接口限流(见第 4 节根因)。不要回退成一条
certbot renew批量跑所有证书。 - deploy-hook 模型:只有证书"真的续期"了才会触发上传/换绑,避免无谓地重复上传同一张证书。这点比旧脚本(无条件重传)更正确,但也意味着——一旦续期失败,CLB 就不会被更新,所以续期本身的健康必须被监控(见第 6 节)。
2. 证书 ↔ CLB 映射
region 固定 ap-beijing,当前均为单域名证书:
| 证书(certbot cert-name) | 域名 | CLB | Listener |
|---|---|---|---|
jsvc.wildwindstudio.com-0001 | jsvc.wildwindstudio.com | lb-fkcz6lot | lbl-ehxkll9d |
passport.wildwindstudio.com | passport.wildwindstudio.com | lb-b6j4kn47 | lbl-0jzb58rx |
cn-reserve-api.wildwindstudio.com | cn-reserve-api.wildwindstudio.com | lb-mm70ra69 | lbl-naeanxnv |
映射维护在
clb-deploy-hook.sh的case分支里。新增/变更证书时改这里 +renew-and-update-clb.sh顶部的CERTS=(...)列表。
jsvc证书的 cert-name 带-0001后缀是历史遗留(旧的同名三域名 SAN 谱系占用了jsvc.wildwindstudio.com这个名字),功能无影响。 resflow.wildwindstudio.com 已退役(无 DNS、无在线服务),其旧 CLBlb-mm70ra69现已复用给 cn-reserve-api。
3. 文件清单
| 路径 | 说明 | 权限 |
|---|---|---|
/etc/crontab | 定时入口(已不含明文密钥) | 644 |
/usr/local/bin/renew-and-update-clb.sh | 续签编排脚本 | 755 |
/usr/local/bin/clb-deploy-hook.sh | certbot deploy-hook:上传 + 换绑 CLB | 755 |
/usr/local/bin/clb-bind-one.sh | 手动单张换绑工具(应急用) | 755 |
/etc/letsencrypt/tccli.env | tccli 凭据(上传/换绑用) | 600 |
/etc/letsencrypt/tencentcloud.ini | certbot DNS 插件凭据(DNS-01 写 TXT 用) | 600 |
/var/log/certbot-renewal.log | 续签链路日志 | - |
注意:DNS 插件和 tccli 用的是两套不同的腾讯云 API Key。前者只需 DNSPod 权限,后者需要 SSL + CLB 权限。
4. 排查记录(2026-06-24)
现象
- CLB
lb-fkcz6lot(jsvc.wildwindstudio.com)证书到期,"续期脚本没反应",线上发的是已过期证书。 - 同批的 passport、jsvc 都于 2026-06-24 过期。
排查步骤与发现
- 定位入口:
/etc/crontab里每月 1/15 号 02:00 跑renew-and-update-clb.sh,日志/var/log/certbot-renewal.log。 - 读日志:06-15 那次
certbot renew对 jsvc/resflow 报
即 certbot 续期其实一直在失败。failed to determine base domain, please report to dev. Tried: ['wildwindstudio.com', 'jsvc.wildwindstudio.com'] - 读插件源码
certbot_dns_tencentcloud/certbot_tencentcloud_plugins.py的determine_base_domain():
任何 API 异常(限流 / QPS / 瞬时错误)都被吞掉,最后抛出这条误导性的 "base domain" 错误。= # ← 把任何 API 异常都当成"不拥有这个域名" continue ... - 对照:cn-reserve-api 同账号、同
tencentcloud.ini、同根域,06-16 单独签发就成功了;区别只在"单独跑 vs 批量连跑"。 - staging / 真签复现:单张
certbot renew --cert-name jsvc...直接越过 base-domain 这步、续期成功 → 实锤是批量连跑触发限流,被插件吞成了 base-domain 错误。 - 旧脚本掩盖了问题:06-16 之前的旧脚本逻辑是"不管 certbot 成不成功,都无条件把现存证书重新上传并换绑 CLB"。所以 04~06 月每次都"✓ 换绑成功",但绑的一直是同一张 03-26 签发、06-24 到期的旧证书,制造了"续期在正常工作"的假象。06-16 改成 deploy-hook 模型后,续期失败就不再换绑,问题才暴露。
根因
预约(cn-reserve)服务新增 2 个域名后,单次续签批要处理的域名增多,短时间内密集调用腾讯云 DNS 接口 → 触发限流 → certbot DNS 校验拿不到结果 → 续期失败 → 证书到期未能自动更新。
5. 本次处置
应急续期
- 逐张(串行、隔离)手动续签 jsvc / passport,避开限流;用
clb-bind-one.sh上传并换绑对应 CLB。 - 结果:lb-fkcz6lot、lb-b6j4kn47 在线证书均更新,有效期到 2026-09-22。
治本(脚本改造,已上线 + 自测 EXIT=0)
renew-and-update-clb.sh改为逐张串行 + 每张 sleep 60 +--no-random-sleep-on-renew,从源头规避限流。clb-deploy-hook.sh修正证书名映射,保留"只在真续期后换绑"。
清理
- 删除过期冗余的 jsvc 三域名 SAN 谱系(含已退役 resflow)。
- 删除含明文密钥的旧
.bak脚本。 - 把明文 tccli 密钥从 644 的
/etc/crontab迁到 600 的/etc/letsencrypt/tccli.env,三脚本统一从此读取;crontab 备份降权 600。
6. 运维 Runbook
手动续签 + 换绑一张证书(应急)
# 1) 续签(务必单张、隔离跑,别一次跑多张)
# 2) 上传 + 换绑(凭据自动从 /etc/letsencrypt/tccli.env 读)
验证 CLB 在线证书
| |
查看证书清单 / 到期
新增一个域名/证书
certbot certonly --dns-tencentcloud --dns-tencentcloud-credentials /etc/letsencrypt/tencentcloud.ini -d <域名>- 在
clb-deploy-hook.sh的case加映射;在renew-and-update-clb.sh的CERTS=(...)加 cert-name。 - 首次手动
clb-bind-one.sh绑一次。
待办 / 改进项
-
轮换 tccli API Key(
AKIDqgLrCLI2...):曾在 644 crontab 里明文存放数月,按泄漏处理。 - 加证书剩余天数告警(接现有 Prometheus + 钉钉),剩 < 14 天即告警;别再依赖"脚本没报错"。
-
可考虑给
certbot-dns-tencentcloud插件加重试/退避(注意:改 site-packages 升级会被覆盖;当前靠脚本层串行+错峰已足够)。