Kubernetes CronJob 定时任务
分 时 日 月 星期
每个字段的取值范围:
| 字段 | 允许值 | 特殊字符 |
|--------|----------------|------------------|
| 分 | 0-59 | `* / , -` |
| 时 | 0-23 | `* / , -` |
| 日 | 1-31 | `* / , - ?` |
| 月 | 1-12 或 JAN-DEC | `* / , -` |
| 星期 | 0-6 或 SUN-SAT | `* / , - ?` |
> **注意**:在 Kubernetes 中,`星期`字段的 `0` 和 `7` 都代表周日(与 Vixie cron 不同,后者 7 无效)。
### 特殊字符
- `*` 匹配所有值。
- `,` 分隔多个值,例如 `1,3,5`。
- `-` 表示范围,例如 `1-5`。
- `/` 表示步长,例如 `*/15` 表示每 15 分钟。
- `?` 在日或星期字段中表示不指定,避免冲突(通常用于其中一个字段为 `*` 时)。
### 常用示例
| 表达式 | 含义 |
|-------------------------|--------------------------------------------------------|
| `0 2 * * *` | 每天凌晨 2:00 执行 |
| `*/10 * * * *` | 每 10 分钟执行一次 |
| `0 9-17 * * 1-5` | 工作日上午 9 点到下午 5 点之间每小时执行 |
| `30 8 1 * *` | 每月 1 号上午 8:30 执行 |
| `0 0 * * 0` | 每周日午夜执行 |
| `@hourly` | 每小时执行一次(同 `0 * * * *`) |
| `@daily` / `@midnight` | 每天午夜执行(同 `0 0 * * *`) |
| `@weekly` | 每周日午夜执行(同 `0 0 * * 0`) |
| `@monthly` | 每月第一天午夜执行(同 `0 0 1 * *`) |
| `@yearly` / `@annually` | 每年 1 月 1 日午夜执行(同 `0 0 1 1 *`) |
Kubernetes 还支持一个可选的 **秒** 字段(置于最前面),此时格式变为六字段:`秒 分 时 日 月 星期`,但这需要启用 `CronJobV2` 功能门控(较新版本默认支持)。建议尽量使用五字段,避免兼容性问题。
---
## 创建第一个 CronJob
下面是一个最简单的 CronJob 示例,它每分钟执行一次 `echo "Hello from CronJob"` 命令,并使用 `busybox` 镜像。
```yaml
apiVersion: batch/v1
kind: CronJob
metadata:
name: hello-cron
spec:
schedule: "*/1 * * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: hello
image: busybox:latest
command: ["/bin/sh", "-c", "echo Hello from CronJob; date"]
restartPolicy: OnFailure
将上述内容保存为 cronjob.yaml,然后运行:
kubectl apply -f cronjob.yaml
查看 CronJob 状态:
kubectl get cronjob hello-cron
查看由该 CronJob 创建的 Job:
kubectl get jobs
查看 Pod 日志:
# 先获取 Pod 名称
kubectl get pods
# 查看日志
kubectl logs <pod-name>
你会看到每隔一分钟有一个新的 Job 被创建并执行。注意,根据 startingDeadlineSeconds 等配置,有时可能会跳过某个时间点。
CronJob 核心字段说明
CronJob 的 YAML 定义主要包含以下几个部分,了解它们有助于精确控制任务行为。
spec.schedule
必填字段,Cron 表达式字符串,定义任务执行的时间计划。
spec.jobTemplate
必填字段,指定要创建的 Job 模板。其结构与标准的 Job 对象的 spec 相同,你可以在此定义容器、命令、环境变量、挂载卷等。设置 restartPolicy 时,对于 Job 应为 Never 或 OnFailure,不能使用 Always。
spec.concurrencyPolicy
并发策略,决定当上一个 Job 尚未完成,而新的调度时间到达时如何处理。可选值:
Allow(默认):允许并发执行,新旧 Job 同时运行。Forbid:禁止并发,如果前一个 Job 未完成则跳过本次执行。Replace:取消正在运行的旧 Job,并用新 Job 替代它。
示例:
spec:
concurrencyPolicy: Forbid
spec.successfulJobsHistoryLimit
保留的成功完成 Job 的数量,默认为 3。设为 0 表示不保留任何成功的 Job。
spec.failedJobsHistoryLimit
保留的失败 Job 的数量,默认为 1。设为 0 表示不保留任何失败的 Job。
spec.startingDeadlineSeconds
可选字段,表示如果因为某种原因错过了调度时间(例如控制器重启),仍然允许启动 Job 的最大秒数。如果超过这个截止时间,则直接跳过该次调度。默认无限制。例如设置为 200 表示若错过调度,在 200 秒内仍可补启动,否则放弃。
spec:
startingDeadlineSeconds: 200
spec.suspend
布尔值,如果设为 true,挂起后续的所有调度,但不影响已经运行的 Job。可用于临时停止定时任务。
spec:
suspend: true
spec.timeZone
Kubernetes 1.24 起(默认启用 CronJobTimeZone 功能),允许为 CronJob 设置时区。若不指定则使用控制器所在时区(一般为 UTC)。要使用时区,格式为 IANA 时区数据库名称,如 "America/New_York"。
spec:
timeZone: "Asia/Shanghai"
schedule: "0 9 * * 1-5"
这样上述 CronJob 会在北京时间工作日上午 9 点执行。
并发策略与历史记录限制
在实际应用中,合理设置并发策略和历史保留数量至关重要。
并发策略选择指南
- 数据一致性要求高的任务:使用
Forbid,防止多个实例同时操作同一份数据。 - 可重入或幂等的任务:使用
Allow,增加吞吐。 - 希望总是执行最新请求的任务:使用
Replace,但需注意旧任务可能产生副作用(如未保存状态)。
历史 Job 清理
如果不限制保留的 Job 数量,集群中可能会堆积大量已完成的 Job 资源,影响 kubectl 操作性能并占用 etcd 存储。建议根据集群规模和任务频率设置合理的值。例如每小时执行一次的任务,保留 3 个成功 Job 通常足够;每天都执行的任务,可以保留 7 个便于查看一周记录。
启动截止时间与挂起
startingDeadlineSeconds 和 suspend 是控制 CronJob 行为的两个高级特性。
启动截止时间的妙用
假设你有一个每 5 分钟执行一次的任务,但有时集群控制器因故障暂停了 10 分钟,当控制器恢复后,应该如何处理错过的两次调度?
- 如果不设
startingDeadlineSeconds,控制器会补执行这些错过的 Job(可能会瞬间创建大量 Job)。 - 如果设置
startingDeadlineSeconds: 100,控制器检查每个错过的调度,若从应该执行的时间点到现在的差值超过 100 秒,则直接丢弃,不再补创建。
这在需要保证任务的时效性而非完整性的场景中很有用。
挂起 CronJob
你可以动态调整 suspend 字段来暂停或恢复任务:
kubectl patch cronjob hello-cron -p '{"spec":{"suspend":true}}'
恢复:
kubectl patch cronjob hello-cron -p '{"spec":{"suspend":false}}'
挂起后,即使到了 schedule 时间,控制器也不会创建新的 Job。已经运行的 Job 不受影响。
常见使用场景与示例
数据库备份 CronJob
以下示例每晚 2:30 执行 PostgreSQL 备份,并将备份文件上传到 AWS S3(需要挂载密钥)。
apiVersion: batch/v1
kind: CronJob
metadata:
name: db-backup
spec:
schedule: "30 2 * * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 2
jobTemplate:
spec:
template:
spec:
containers:
- name: backup
image: postgres:14
env:
- name: PGHOST
value: "postgres-service"
- name: PGUSER
valueFrom:
secretKeyRef:
name: db-secret
key: username
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: password
command:
- /bin/bash
- -c
- |
pg_dump -Fc mydb > /backups/mydb_$(date +%Y%m%d_%H%M%S).dump
aws s3 cp /backups/mydb_*.dump s3://my-bucket/backups/
volumeMounts:
- name: backup-storage
mountPath: /backups
volumes:
- name: backup-storage
emptyDir: {}
restartPolicy: OnFailure
定时调用 HTTP 接口
每 10 分钟向某个 webhook 发送请求:
apiVersion: batch/v1
kind: CronJob
metadata:
name: webhook-trigger
spec:
schedule: "*/10 * * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: curl
image: curlimages/curl:latest
command:
- /bin/sh
- -c
- 'curl -X POST -d "time=$(date)" https://hooks.example.com/trigger'
restartPolicy: OnFailure
证书自动续期(配合 cert-manager)
若使用 cert-manager,可结合 CronJob 解决某些边缘情况,但通常 cert-manager 本身就有 Certificate 资源自动续期,此处仅作示例:
apiVersion: batch/v1
kind: CronJob
metadata:
name: cert-renew
spec:
schedule: "0 0 * * 0" # 每周日
jobTemplate:
spec:
template:
spec:
containers:
- name: renew
image: certbot/certbot
args:
- renew
- --quiet
- --deploy-hook
- "kubectl -n default create configmap renewed-certs --from-file=/etc/letsencrypt/live/"
restartPolicy: Never
调试与故障排查
当 CronJob 没有按预期执行时,可以通过以下步骤定位问题。
检查 CronJob 状态
kubectl describe cronjob <名称>
输出中会包含 Last Schedule Time 和 Active Jobs 等信息。如果 Last Schedule Time 一直为 None,可能是 schedule 表达式有误或 suspend 为 true。
检查 Job 对象
kubectl get jobs --selector=job-name=<cronjob名称>-<时间戳>
通过 describe job 查看事件,判断是否因为资源不足、镜像拉取失败等原因导致 Job 无法启动。
查看 Pod 日志
kubectl logs job/<job-name>