把证书续期从 crontab 搬进 Kubernetes CronJob
本文最后更新于:2026年11月8日 晚上
📝Notes: 本文讲的是 Linux crontab → K8s CronJob 的迁移设计与验证策略。acme.sh + DNSPod 的签发流程、腾讯 CDN 与七牛的推送细节,在另一篇里已经写过,这里只做背景交代,一笔带过。
背景:续期跑在一台台式机上
书接上回,旧方案三句话就能说完:
- 一台 Ubuntu 台式机、
- 一个
~/.acme.sh、 - 一行 crontab。
能跑,但问题不少:
- 单点。机器休眠、换硬盘、重装系统,续期链路就断了。链路断了那证书就要过期了。
- 不可观察。crontab 跑没跑、失败没失败,只能翻日志。没有 Job 历史,没有失败计数,告警无从接起。
- 配置漂移。crontab 里那一行命令和实际脚本之间没有唯一事实来源,改过一次就再也说不清当时改了什么。
为什么是 CronJob
Kubernetes 的 CronJob 是这套环境里唯一同时具备「声明式」和「有历史记录」两个属性的定时器。
能拿到的是这些:
- 写进 Git,走 GitOps 流程统一 review,配置漂移归零。
- Job 历史保留,成功失败都能
kubectl get jobs查到,不用登录某台机器翻日志。 - 重试次数、并发策略、超时时间都是字段,不用自己写 shell 判断。
- 日志进 Loki,指标进 Prometheus,告警接 Alertmanager,链路都是现成的。
CronJob 结构
骨架大致是这个样子,字段按实际环境调整。
1 | |
详细说明如下:
-
concurrencyPolicy: Forbid是必须的。两轮重叠时同时去写 DNS TXT 记录,会互相覆盖,签发直接失败。 -
startingDeadlineSeconds: 3600给了一小时容错窗口。集群短暂不可用时不至于整轮丢失。 -
timeZone字段要求 K8s 1.27 以上(我的昨天刚升级到 1.37,最新 stable 版本的 k3s),低于这个版本得靠容器内TZ环境变量兜底。 -
restartPolicy: Never配合backoffLimit: 2,失败两次就停,避免无限重试把 DNS API 打爆。
密钥管理:仓库里只有占位符
密钥管理一定一定要注意,避免泄露:
- 仓库内只放占位符。
- 密钥通过 External Secret Operator 单独管理
📌示例:占位符的作用是让 spec 可以提交、CI 可以跑、PR 可以 review,而 Secret 本体永远不进 Git 历史。
密钥挂载方案:CronJob 通过 Secret 挂载环境变量,readOnly,不落盘到镜像层,也不出现在 kubectl describe pod 的明文输出里。
上线检查清单
第一跑之前,一条一条过一遍:
- 确认 Pod 里挂载的是密钥的真实值,不是仓库里的占位符。
- ServiceAccount 只给必需的 verb,能读 Secret 就够,别给
*。 concurrencyPolicy: Forbid确认生效。startingDeadlineSeconds不小于一轮执行时长。- 第一轮手动触发,不要干等 schedule。
- 迁移前记录当前证书指纹,作为比对基准。
- crontab 保留至少一个完整周期再删。
- Job 失败告警接进 Alertmanager。
最终成功完成迁移。🎉🎉🎉
总结
Linux crontab 到 K8s CronJob 的迁移,YAML 部分照着官方文档就能写出来。难点是实践过程中的细节考量。
本文总结了我从 Linux crontab 迁移证书续期到 Kubernetes CronJob 的设计、密钥管理、与上线前检查等步骤。收益是:
- 更可靠稳定
- 有历史日志(Loki),有告警(AlertManager)
- GitOps: git 是唯一事实来源。
相关代码已经打包成 helm chart,感兴趣的可以看看:https://github.com/east4ming/homelab2/tree/master/apps/acme-ewhisper
希望对你有所帮助。
🎉🎉🎉