数据库备份、日志清理、证书检查、健康探测——全是 crontab 里那一堆散落的定时任务。K8s CronJob 把它们搬进集群,自带重试、历史记录、并发控制。
crontab 里有 8 条定时任务:3 点备份数据库,4 点清理日志,5 点检查证书,6 点健康探测。分布在 4 台机器上。上周迁移一台机器,漏了备份任务,两天后才发现。数据差点没了。
Linux crontab 的问题很直接:任务跟机器绑定,机器没了任务就没了,也没有执行记录和失败告警。K8s CronJob 把定时任务变成集群资源——机器挂了自动调度到其他节点,执行失败自动重试,历史记录随时可查。
CronJob vs crontab
| 维度 |
Linux crontab |
K8s CronJob |
| 位置 |
每台机器各自维护 |
集群级资源,集中管理 |
| 高可用 |
机器挂了任务就没了 |
自动调度到其他节点 |
| 重试 |
没有重试 |
失败自动重试 |
| 历史 |
只能看日志文件 |
保留最近 N 次执行记录 |
| 并发控制 |
不管 |
可限制并发执行 |
| 执行环境 |
用机器当前环境 |
每次在独立 Pod 中运行 |
| 资源限制 |
无 |
可限制 CPU/内存 |
CronJob 结构
下面是一个 MySQL 备份 CronJob 的完整定义:
apiVersion: batch/v1
kind: CronJob
metadata:
name: mysql-backup
namespace: database
spec:
schedule: "0 3 * * *" # 每天 3 点执行
concurrencyPolicy: Forbid # 禁止并发执行
successfulJobsHistoryLimit: 3 # 保留 3 次成功记录
failedJobsHistoryLimit: 5 # 保留 5 次失败记录
jobTemplate:
spec:
backoffLimit: 2 # 失败重试 2 次
activeDeadlineSeconds: 1800 # 超时 30 分钟自动终止
template:
spec:
restartPolicy: OnFailure
containers:
- name: backup
image: mysql:8.0
env:
- name: MYSQL_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: password
command:
- /bin/sh
- -c
- |
mysqldump -h mysql.database.svc -u root -p$MYSQL_PASSWORD \
--all-databases --single-transaction | \
gzip > /backup/mysql-$(date +%Y%m%d).sql.gz
volumeMounts:
- name: backup
mountPath: /backup
volumes:
- name: backup
nfs:
server: 192.168.1.105
path: /volume1/backups/mysql
逐个参数解释:
schedule:Cron 表达式
schedule 使用标准五位 Cron 表达式,格式如下:
┌───────────── 分钟 (0-59)
│ ┌───────────── 小时 (0-23)
│ │ ┌───────────── 日 (1-31)
│ │ │ ┌───────────── 月 (1-12)
│ │ │ │ ┌───────────── 星期 (0-6, 0=周日)
│ │ │ │ │
* * * * *
常用示例:
schedule: "0 3 * * *" # 每天 3:00
schedule: "0 3 * * 0" # 每周日 3:00
schedule: "*/30 * * * *" # 每 30 分钟
schedule: "0 0 1 * *" # 每月 1 号 0:00
schedule: "0 */6 * * *" # 每 6 小时
concurrencyPolicy:并发策略
concurrencyPolicy: Forbid # 上次还没跑完,跳过本次(推荐)
concurrencyPolicy: Replace # 上次还没跑完,杀掉重新跑
concurrencyPolicy: Allow # 允许并发执行(默认)
HomeLab 场景推荐 Forbid:防止备份任务因为卡住而堆积。
历史记录
successfulJobsHistoryLimit: 3 # 成功的保留 3 次
failedJobsHistoryLimit: 5 # 失败的保留 5 次
每次执行创建一个 Job + Pod,执行完后保留这些记录。可以用 kubectl get jobs 查看执行历史。
实战示例
示例 1:Redis 数据持久化备份
apiVersion: batch/v1
kind: CronJob
metadata:
name: redis-backup
namespace: cache
spec:
schedule: "0 4 * * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 7
failedJobsHistoryLimit: 3
jobTemplate:
spec:
backoffLimit: 1
template:
spec:
restartPolicy: OnFailure
containers:
- name: redis-backup
image: redis:7-alpine
command:
- /bin/sh
- -c
- |
redis-cli -h redis.cache.svc -a $REDIS_PASSWORD BGSAVE
sleep 10
cp /data/dump.rdb /backup/dump-$(date +%Y%m%d).rdb
# 保留最近 7 天
find /backup -name "dump-*.rdb" -mtime +7 -delete
env:
- name: REDIS_PASSWORD
valueFrom:
secretKeyRef:
name: redis-secret
key: password
volumeMounts:
- name: redis-data
mountPath: /data
readOnly: true
- name: backup
mountPath: /backup
volumes:
- name: redis-data
persistentVolumeClaim:
claimName: redis-pvc
- name: backup
nfs:
server: 192.168.1.105
path: /volume1/backups/redis
示例 2:自动清理 Docker 镜像缓存
apiVersion: batch/v1
kind: CronJob
metadata:
name: image-cleanup
namespace: infra
spec:
schedule: "0 5 * * 0" # 每周日凌晨 5 点
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: cleaner
image: docker:24
command:
- sh
- -c
- |
# 清理未使用的镜像和构建缓存
docker image prune -a --filter "until=168h" --force
docker builder prune --force --keep-storage 5GB
echo "清理完成"
docker system df
示例 3:SSL 证书到期检查
apiVersion: batch/v1
kind: CronJob
metadata:
name: cert-checker
namespace: monitoring
spec:
schedule: "0 8 * * *" # 每天早上 8 点检查
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: checker
image: alpine:latest
command:
- /bin/sh
- -c
- |
DOMAINS="blog.homelab.local vault.homelab.local rss.homelab.local"
WARN_DAYS=7
for domain in $DOMAINS; do
expire_date=$(echo | openssl s_client -connect $domain:443 2>/dev/null \
| openssl x509 -noout -enddate 2>/dev/null \
| cut -d= -f2)
if [ -z "$expire_date" ]; then
echo "ERROR: $domain 无法获取证书信息"
continue
fi
expire_ts=$(date -d "$expire_date" +%s)
now_ts=$(date +%s)
days_left=$(( (expire_ts - now_ts) / 86400 ))
if [ $days_left -le $WARN_DAYS ]; then
echo "WARNING: $domain 证书将在 ${days_left} 天后到期!"
else
echo "OK: $domain 证书还有 ${days_left} 天到期"
fi
done
示例 4:K8s 集群健康检查
apiVersion: batch/v1
kind: CronJob
metadata:
name: cluster-health-check
namespace: kube-system
spec:
schedule: "*/30 * * * *" # 每 30 分钟
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
serviceAccountName: health-checker
containers:
- name: kubectl
image: bitnami/kubectl:latest
command:
- /bin/sh
- -c
- |
echo "=== 节点状态 ==="
kubectl get nodes -o wide
echo "=== 异常 Pod ==="
kubectl get pods --all-namespaces \
--field-selector=status.phase!=Running,status.phase!=Succeeded
echo "=== PVC 使用率 ==="
kubectl top pods --all-namespaces --sort-by=memory | head -20
echo "=== 检查完成: $(date) ==="
管理和调试
# 查看所有 CronJob
kubectl get cronjobs -A
# 查看 CronJob 详情
kubectl describe cronjob mysql-backup -n database
# 查看执行历史
kubectl get jobs -n database
# 查看某次执行的日志
kubectl logs job/mysql-backup-28472320 -n database
# 手动触发一次执行
kubectl create job --from=cronjob/mysql-backup manual-trigger -n database
# 临时暂停 CronJob
kubectl patch cronjob mysql-backup -n database -p '{"spec":{"suspend":true}}'
# 恢复
kubectl patch cronjob mysql-backup -n database -p '{"spec":{"suspend":false}}'
常见坑
坑 1:时区问题。 K8s 默认用 UTC,如果你在东八区,0 3 * * * 实际是北京时间 11 点。解决:
spec:
schedule: "0 19 * * *" # UTC 19:00 = 北京时间 03:00
# 或者在 CronJob 容器中设置 TZ
坑 2:Pod 一直卡在 Pending。 可能是资源不足或 PVC 未绑定。检查:
kubectl describe pod <pod-name> -n <namespace>
坑 3:任务执行时间超过间隔。 两次执行重叠,用 concurrencyPolicy: Forbid 防止堆积。
小结
把定时任务从 crontab 迁到 K8s CronJob,你获得了:集中管理、高可用、自动重试、执行历史、资源限制。这些在传统 crontab 下需要额外工具才能实现。
下一篇是本系列收官——把这些自动化工具串起来,搭建一个完整的运维巡检系统。