ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

【Kubernetes从入门到精通】第24篇:Job和CronJob——一次性任务和定时任务

2026/8/8 22:19:52 拓冰建站 浏览量
【Kubernetes从入门到精通】第24篇:Job和CronJob——一次性任务和定时任务 上一篇【第23篇】DaemonSet——每个节点都要有的“守护者“下一篇【第25篇】HPA——K8s的自动弹性伸缩魔法摘要Deployment、StatefulSet、DaemonSet——它们管的都是活着就别停的长期运行服务。但你有没有碰到过这种需求跑一次数据库迁移跑完就退出每天凌晨2点备份一次数据并行处理100万个计算任务全部完成就收工。这些跑完就停的任务如果丢给Deployment——Pod退出后Deployment会使命般地把它重启陷入完成→重启→完成→重启的死循环。K8s给了两件武器Job管一次性任务跑完就停可以重试但不能无限重启CronJob管定时任务按Cron表达式定期触发Job。这篇文章从最简单的Job讲起拆解三种并发模式、失败重试策略、超时控制最后写一个生产级CronJob——定时备份etcd数据让你睡觉时数据自动备份好。一、Job——“跑完就退休”1.1 Job和Deployment的本质区别【Job vs Deployment——生命目标完全不同】 Deployment Pod 的一生 Job Pod 的一生 ┌─────────────────────┐ ┌─────────────────────┐ │ 活下去不许死 │ │ 完成使命然后退休 │ │ │ │ │ │ Running → 挂了? │ │ Pending → Running │ │ │ │ │ │ │ │ ▼ │ │ ▼ │ │ 自动重启 │ │ Completed │ │ 永远保持Running状态 │ │ (成功退出) │ │ │ │ │ │ 适合Web服务、API │ │ 适合备份、迁移、 │ │ │ │ 批处理、计算 │ └─────────────────────┘ └─────────────────────┘维度DeploymentJobPod退出码0重启restartPolicy: Always完成成功退出Pod退出码非0重启重试backoffLimit控制次数任务跑完N/A永远在跑Pod状态变成Completed删除策略手动删除自动清理ttlSecondsAfterFinished多Pod关系都是对等的可以并行、可以有顺序1.2 最简单的Job——“跑个echo就完事”apiVersion:batch/v1kind:Jobmetadata:name:hello-jobspec:template:spec:containers:-name:helloimage:busyboxcommand:[sh,-c,echo Hello K8s Job!; sleep 3; echo Done!]restartPolicy:Never# ← 关键Never 或 OnFailure不能是 Alwayskubectl apply-fhello-job.yaml# 观察Job生命周期kubectl getjobs-w# NAME COMPLETIONS DURATION AGE# hello-job 0/1 0s 0s# hello-job 1/1 8s 8s ← 完成了kubectl get pods# NAME READY STATUS RESTARTS AGE# hello-job-xxxxx 0/1 Completed 0 15s ← 状态是Completed不是Running# 看日志——任务输出kubectl logs hello-job-xxxxx# Hello K8s Job!# Done!要点Job Pod的restartPolicy只能是Never或OnFailure——不能是Always。因为Job的哲学是完成任务就退休Pod成功退出exit code 0就应该保持Completed状态而不是被重启。如果你设了AlwaysK8s会直接拒绝——这是Job和Deployment最根本的区别。二、Job的三种完成模式——“单人任务到千人团队”2.1 非并行Job——“一个人干完活”默认模式——启动一个Pod完成一次就收工。apiVersion:batch/v1kind:Jobmetadata:name:single-taskspec:completions:1# 需要成功完成1次默认值parallelism:1# 同时运行1个Pod默认值template:spec:containers:-name:workerimage:busyboxcommand:[sh,-c,echo Processing...; sleep 10; echo Done!]restartPolicy:Never【非并行Job——串行执行】 启动 ─┬─ Pod-1 运行 ─┬─ Pod-1 成功退出 ─┬─ Job完成 │ │ │ ─────┴──────────────┴──────────────────┘ 只有一个工人干完活下班2.2 固定完成次数并行Job——“一组人一起干”apiVersion:batch/v1kind:Jobmetadata:name:parallel-taskspec:completions:5# 需要成功完成5次parallelism:2# 同时最多跑2个Podtemplate:spec:containers:-name:workerimage:busyboxcommand:[sh,-c,echo Task $JOB_COMPLETION_INDEX; sleep 5]restartPolicy:Never【固定完成次数并行Job】 时间轴 → ──────────────────────────────────────────────────── Pod-1 ─┬─ Task 0 ─┬─ 完成 ✓ │ │ Pod-2 ─│─ Task 1 ─│─ 完成 ✓ │ │ Pod-3 ─│──────────│── Task 2 ─┬─ 完成 ✓ │ │ │ Pod-4 ─│──────────│── Task 3 ─│─ 完成 ✓ │ │ │ Pod-5 ─│──────────│──────────│── Task 4 ─┬─ 完成 ✓ │ │ │ │ ───────┴──────────┴──────────┴──────────┴─────────── parallelism2同一时间最多2个Pod在跑 completions5总共要跑5个成功的Pod2.3 工作队列并行Job——“抢着干干完为止”apiVersion:batch/v1kind:Jobmetadata:name:work-queuespec:completions:50# 需要完成50个任务parallelism:10# 10个Pod同时抢任务completionMode:Indexed# K8s 1.21 每个Pod有唯一索引template:spec:containers:-name:workerimage:my-worker:latestenv:-name:JOB_COMPLETION_INDEX# 自动注入的索引号valueFrom:fieldRef:fieldPath:metadata.annotations[batch.kubernetes.io/job-completion-index]command:[python,/app/worker.py,--task-id$(JOB_COMPLETION_INDEX)]restartPolicy:Never【工作队列并行Job】 ┌─────────────────────────────────────────────┐ │ Redis / RabbitMQ 任务队列 │ │ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ... │ │ │Q1 │ │Q2 │ │Q3 │ │Q4 │ │Q5 │ │Q6 │ │ │ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ │ └────┬──────┬──────┬──────┬──────┬──────┬──────┘ │ │ │ │ │ │ ▼ ▼ ▼ ▼ ▼ ▼ ┌────────┐┌────────┐┌────────┐ ... (10个Pod同时抢任务) │ Pod-1 ││ Pod-2 ││ Pod-3 │ │ 拿Q1 ││ 拿Q2 ││ 拿Q3 │ │ 处理完 ││ 处理完 ││ 处理完 │ │ 再拿Q7 ││ 再拿Q8 ││ 拿不到 │→ 退出成功 └────────┘└────────┘└────────┘ 50个任务由10个Pod瓜分谁先干完谁多干 最后没新任务了Pod优雅退出三种模式对比模式completionsparallelism适用场景非并行1默认1默认单个一次性任务如数据库迁移固定完成次数N具体数值≤N独立可并行的小任务如图片处理工作队列1设置不正确时Pod判断≥1从消息队列消费任务的场景要点不要混淆completions和parallelism。parallelism是你派多少人去干活completions是总共要成功完成多少次。比如completions: 10, parallelism: 3——你派了3个人干完一份活就去领下一份一共要干完10份才下班。三、Job的保险机制——失败处理、超时和清理3.1 backoffLimit——“再给你几次机会”Pod执行失败了怎么办backoffLimit控制重试次数apiVersion:batch/v1kind:Jobmetadata:name:retry-jobspec:backoffLimit:3# 最多重试3次默认6次template:spec:containers:-name:flaky-taskimage:busyboxcommand:[sh,-c,echo Attempting...; exit 1]# 故意失败restartPolicy:Neverkubectl apply-fretry-job.yaml# 观察重试过程kubectl get pods-w# retry-job-xxxxx Pending 0 0s ← 第1次# retry-job-xxxxx Error 0 3s ← 失败了(exit code 1)# retry-job-yyyyy Pending 0 0s ← 重试第1次# retry-job-yyyyy Error 0 3s ← 又失败# retry-job-zzzzz Pending 0 0s ← 重试第2次# retry-job-zzzzz Error 0 3s ← 还是失败# retry-job-aaaaa Pending 0 0s ← 重试第3次# retry-job-aaaaa Error 0 3s ← 最后一次也失败...# (没有新Pod了——backoffLimit3已经重试3次)kubectl describe job retry-job# Conditions:# Type Status Reason# Failed True BackoffLimitExceeded ← Job标记为失败【backoffLimit 重试策略】 第1次尝试 → 失败(exit code≠0) │ ▼ (等待10秒) 第2次尝试 → 失败 │ ▼ (等待20秒) ← 退避间隔递增 第3次尝试 → 失败 │ ▼ (等待40秒) 第4次尝试(backoffLimit3最后一次) │ ├── 成功 → Job完成 └── 失败 → Job标记Failed不再重试要点backoffLimit是指最多失败几次就放弃不是最多重试几次。backoffLimit: 3意思是如果第1、2、3次都失败了第4次最后一次尝试还失败就放弃。每次失败之间K8s会自动增加等待时间指数退避10s→20s→40s→…。如果你的任务是必须成功型——把backoffLimit设大一点或者干脆不设默认6次。3.2 activeDeadlineSeconds——“超时就别干了”apiVersion:batch/v1kind:Jobmetadata:name:timeout-jobspec:activeDeadlineSeconds:60# Job整个生命周期最多60秒backoffLimit:3template:spec:containers:-name:slow-taskimage:busyboxcommand:[sh,-c,echo Working...; sleep 120; echo Done!]# 60秒一到整个Job被终止restartPolicy:Neverkubectl apply-ftimeout-job.yaml kubectl describe job timeout-job# Conditions:# Type Status Reason# Failed True DeadlineExceeded ← 超时了kubectl get pods# NAME STATUS# timeout-job-xxxxx Terminated (DeadlineExceeded)3.3 ttlSecondsAfterFinished——“自动清理退休老员工”apiVersion:batch/v1kind:Jobmetadata:name:auto-cleanup-jobspec:ttlSecondsAfterFinished:60# 完成后60秒自动删除Pod和Job记录template:spec:containers:-name:taskimage:busyboxcommand:[sh,-c,echo Done!]restartPolicy:Neverkubectl apply-fauto-cleanup-job.yaml# 60秒后Job自动消失kubectl getjobs-w# NAME COMPLETIONS DURATION AGE# auto-cleanup-job 1/1 3s 5s# (60秒后...)# auto-cleanup-job 1/1 3s 65s# (自动删除Job和Pod都消失了)参数作用默认值backoffLimit最多失败几次放弃6activeDeadlineSecondsJob总生命时长上限无限制ttlSecondsAfterFinished完成后多久自动删除不自动删除需手动清理completions需要成功完成多少次1parallelism最多同时运行几个Pod1要点ttlSecondsAfterFinished是1.12版本加入的救命功能——没有它的话Job完成后Pod一直留在那里状态是Completed时间长了集群里几百个Completed Pod看着就烦。设个ttlSecondsAfterFinished: 36001小时完成任务审计窗口过了自动清理。注意TTL Controller在1.21才GA旧版本可能需要手动开Feature Gate。四、CronJob——“让Job定时自动跑”4.1 Cron表达式——“Crontab的五芒星”【Cron 表达式解析】 ┌────────── 分钟 (0-59) │ ┌──────── 小时 (0-23) │ │ ┌────── 日 (1-31) │ │ │ ┌──── 月 (1-12) │ │ │ │ ┌── 星期 (0-6, 0周日) │ │ │ │ │ * * * * * │ │ │ │ │ │ │ │ │ └─── 星期天0或7 │ │ │ └────── 1212月 │ │ └───────── 1515号 │ └─────────── 2凌晨2点 └───────────── 00分整点 例子 0 2 * * * → 每天凌晨2点 */5 * * * * → 每5分钟 0 9 * * 1-5 → 工作日早上9点 0 0 1 * * → 每月1号零点 30 3 15 6 * → 6月15日凌晨3:304.2 一个简单的CronJobapiVersion:batch/v1kind:CronJobmetadata:name:daily-reportspec:schedule:0 8 * * *# 每天早上8点执行concurrencyPolicy:Forbid# 防止并发执行startingDeadlineSeconds:300# 如果到了预定时间调度器没响应300秒内还可以启动successfulJobsHistoryLimit:3# 保留最近3个成功的Job记录failedJobsHistoryLimit:1# 保留最近1个失败的Job记录jobTemplate:spec:template:spec:containers:-name:report-generatorimage:my-report:latestcommand:[python,generate_report.py]env:-name:DATEvalueFrom:fieldRef:fieldPath:metadata.creationTimestamprestartPolicy:Never【CronJob 生命周期】 CronJob Controller 每10秒检查一次 │ ├── 当前时间08:00:00 │ 匹配 schedule: 0 8 * * * → 创建Job │ │ │ ▼ │ ┌──────────────┐ │ │ Job: report- │ │ │ 1627459200 │ ← 后缀是Unix时间戳 │ │ (Pod 执行) │ │ └──────────────┘ │ ├── 第二天 08:00:00 │ 又创建一个新Job │ └── 第三天 08:00:00...4.3 concurrencyPolicy——“上一个还没跑完怎么办”【三种并发策略】 Allow允许并发 ───────────────────────────────────────────── Job-1: ████████████░░░░░░░░░░░░░░░░░░░░░░░░░░ (跑了12秒还没完) Job-2: ░░░░░░░░████████░░░░░░░░░░░░░░░░░░░░░░ ← 照常启动两个同时跑 Forbid禁止并发 ───────────────────────────────────────────── Job-1: ████████████████████████████████████████ (一直在跑) Job-2: ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ ← 被跳过不执行 Replace替换旧任务 ───────────────────────────────────────────── Job-1: ██████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ (跑了10秒还没完) 就被杀死 ──► × Job-2: ░░░░░░░░░░██████████░░░░░░░░░░░░░░░░░░░░ ← 旧任务被杀新任务启动spec:schedule:*/5 * * * *concurrencyPolicy:Forbid# 推荐大多数场景应该禁止并发# concurrencyPolicy: Allow # 只有任务明确支持并发时才用# concurrencyPolicy: Replace # 谨慎使用会杀掉正在跑的任务要点默认的concurrencyPolicy是Allow——这意味着如果你的任务跑了6分钟Cron每分钟触发一次就会出现多个Job同时跑的情况。数据库备份同时跑两个——灾难生产环境的CronJob一律设concurrencyPolicy: Forbid除非你明确知道任务支持并发。五、实战定时备份etcd的CronJob5.1 etcd备份的重要性etcd是K8s的大脑——存着所有集群状态。etcd挂了集群就失忆了【etcd重要性】 ┌─────────────────────────────────────────────┐ │ etcd集群数据库 │ │ ┌─────────────────────────────────────┐ │ │ │ • 所有资源对象Pod/Service/... │ │ │ │ • 所有ConfigMap和Secret │ │ │ │ • 集群状态和配置 │ │ │ │ │ │ │ │ 丢了 集群失忆 灾难 │ │ │ └─────────────────────────────────────┘ │ └─────────────────────────────────────────────┘5.2 完整备份CronJob# 1. ServiceAccount——备份任务需要读etcd证书apiVersion:v1kind:ServiceAccountmetadata:name:etcd-backupnamespace:kube-system---# 2. Secret——存云存储凭证apiVersion:v1kind:Secretmetadata:name:backup-storage-credsnamespace:kube-systemtype:OpaquestringData:s3-access-key:YOUR_ACCESS_KEYs3-secret-key:YOUR_SECRET_KEYs3-bucket:k8s-etcd-backupss3-endpoint:https://s3.amazonaws.com---# 3. ConfigMap——备份脚本apiVersion:v1kind:ConfigMapmetadata:name:etcd-backup-scriptnamespace:kube-systemdata:backup.sh:|#!/bin/bash set -eBACKUP_DIR/backups TIMESTAMP$(date %Y%m%d_%H%M%S) BACKUP_FILE${BACKUP_DIR}/etcd-snapshot-${TIMESTAMP}.db echo echo ETCD Backup Started:$(date) echo # 1. 创建etcd快照echo [1/4]Creating etcd snapshot... ETCDCTL_API3 etcdctl snapshot save ${BACKUP_FILE}\--endpointshttps://127.0.0.1:2379 \--cacert/etc/kubernetes/pki/etcd/ca.crt \--cert/etc/kubernetes/pki/etcd/server.crt \--key/etc/kubernetes/pki/etcd/server.key# 2. 验证快照echo [2/4]Verifying snapshot... ETCDCTL_API3 etcdctl snapshot status ${BACKUP_FILE}# 3. 压缩快照echo [3/4]Compressing snapshot... gzip ${BACKUP_FILE}COMPRESSED_FILE${BACKUP_FILE}.gz# 4. 上传到S3或者你的对象存储echo [4/4]Uploading to S3...# 安装aws cliif[-n ${S3_ACCESS_KEY}]; then aws configure set aws_access_key_id ${S3_ACCESS_KEY}aws configure set aws_secret_access_key ${S3_SECRET_KEY}aws configure set region ${S3_REGION:-us-east-1}aws s3 cp ${COMPRESSED_FILE}\ s3://${S3_BUCKET}/etcd-backups/${CLUSTER_NAME}/$(date %Y/%m/%d)/etcd-snapshot-${TIMESTAMP}.db.gz \--endpoint-url ${S3_ENDPOINT}echo Upload successful! elseecho WARNING:S3 credentials not configured. Backup stored locally only. fi# 5. 清理本地旧备份保留最近7天echo Cleaning up old local backups... find ${BACKUP_DIR}-name etcd-snapshot-*.db.gz-mtime 7-delete echo echo ETCD Backup Completed:$(date)echo Backup file:${COMPRESSED_FILE}echo Size:$(du-h ${COMPRESSED_FILE}|cut-f1) echo ---# 4. CronJob——主角apiVersion:batch/v1kind:CronJobmetadata:name:etcd-backupnamespace:kube-systemspec:schedule:0 2 * * *# 每天凌晨2点concurrencyPolicy:Forbid# 禁止并发successfulJobsHistoryLimit:7# 保留最近7天日志failedJobsHistoryLimit:3startingDeadlineSeconds:600# 10分钟内还可以启动jobTemplate:spec:ttlSecondsAfterFinished:86400# 24小时后自动清理backoffLimit:2# 只重试2次activeDeadlineSeconds:600# 10分钟内必须完成template:spec:serviceAccountName:etcd-backupnodeSelector:node-role.kubernetes.io/control-plane:# 只在Master上跑tolerations:-key:node-role.kubernetes.io/control-planeoperator:Existseffect:NoSchedulecontainers:-name:backupimage:bitnami/etcd:3.5# 带etcdctl的镜像command:[/bin/bash,/scripts/backup.sh]env:-name:CLUSTER_NAMEvalue:prod-cluster-name:S3_REGIONvalue:us-east-1-name:S3_ACCESS_KEYvalueFrom:secretKeyRef:name:backup-storage-credskey:s3-access-key-name:S3_SECRET_KEYvalueFrom:secretKeyRef:name:backup-storage-credskey:s3-secret-key-name:S3_BUCKETvalueFrom:secretKeyRef:name:backup-storage-credskey:s3-bucket-name:S3_ENDPOINTvalueFrom:secretKeyRef:name:backup-storage-credskey:s3-endpointvolumeMounts:-name:backup-datamountPath:/backups-name:etcd-certsmountPath:/etc/kubernetes/pki/etcdreadOnly:true-name:backup-scriptmountPath:/scriptsreadOnly:trueresources:requests:memory:128Micpu:100mlimits:memory:256Micpu:500mrestartPolicy:Nevervolumes:-name:backup-datahostPath:path:/var/backups/etcdtype:DirectoryOrCreate-name:etcd-certshostPath:path:/etc/kubernetes/pki/etcd# 访问Master上的etcd证书type:Directory-name:backup-scriptconfigMap:name:etcd-backup-scriptdefaultMode:0755# 可执行# 部署kubectl apply-fetcd-backup-cronjob.yaml# 手动触发一次测试kubectl create job--fromcronjob/etcd-backup etcd-backup-test-nkube-system# 查看测试结果kubectl logs-nkube-system job/etcd-backup-test# 查看CronJob状态kubectl get cronjob etcd-backup-nkube-system# NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE# etcd-backup 0 2 * * * False 0 none 5m# 暂停CronJob不删配置只是停掉kubectl patch cronjob etcd-backup-nkube-system-p{spec:{suspend:true}}# 恢复kubectl patch cronjob etcd-backup-nkube-system-p{spec:{suspend:false}}# 查看历史Jobkubectl getjobs-nkube-system-lapp.kubernetes.io/managed-bycronjob5.3 恢复测试——备份了还得能恢复# 从S3下载备份aws s3cps3://k8s-etcd-backups/prod-cluster/2026/07/28/etcd-snapshot-20260728_020001.db.gz.# 解压gunzip etcd-snapshot-20260728_020001.db.gz# 验证快照ETCDCTL_API3etcdctl snapshot status etcd-snapshot-20260728_020001.db# ⚠️ 恢复操作——危险只在测试环境做# ETCDCTL_API3 etcdctl snapshot restore etcd-snapshot-20260728_020001.db \# --data-dir/var/lib/etcd-restore \# --namemaster-node \# --initial-clustermaster-nodehttps://127.0.0.1:2380 \# --initial-advertise-peer-urlshttps://127.0.0.1:2380要点备份不验证等于没备份定期做恢复演练——在测试集群里把etcd数据还原出来确认快照有效。否则生产环境真出事了你发现备份文件是坏的——那感觉比没备份还糟糕。六、Job和CronJob常用命令# Job命令kubectl getjobskubectl getjobs-w# 实时观察kubectl describe jobjob-namekubectl logs job/job-name# 查看Job日志所有Podkubectl logs job/job-name-ccontainer# 指定容器# 手动触发CronJobkubectl create jobtest-name--fromcronjob/cronjob-name# 删除Job会删除关联的Podkubectl delete jobjob-name# CronJob命令kubectl get cronjob kubectl get cronjob-wkubectl describe cronjobcronjob-name# 暂停/恢复CronJobkubectl patch cronjobname-p{spec:{suspend:true}}kubectl patch cronjobname-p{spec:{suspend:false}}# 查看CronJob的历史Jobskubectl getjobs--selectorapp.kubernetes.io/managed-bycronjob# 立即触发不等到schedule时间kubectl create job manual-etcd-backup--fromcronjob/etcd-backup# 删除CronJob会删除所有关联的Job和Podkubectl delete cronjobcronjob-name本篇小结Job和CronJob填补了K8s任务调度的最后一块拼图Job 一次性任务Pod成功退出exit 0→ Completed不会像Deployment一样无限重启三种并发模式非并行单兵作战、固定完成次数N个工人干完N份活、工作队列抢任务模式失败处理backoffLimit控制重试次数activeDeadlineSeconds设超时上限指数退避防雪崩自动清理ttlSecondsAfterFinished让完成的Job自动消失不用手动删几百个Completed PodCronJob 定时JobCron表达式精确控制执行时间concurrencyPolicy: Forbid防并发suspend临时暂停实战etcd备份生产级的备份方案——定时快照→压缩→上传S3→审计日志→过期清理Job和CronJob虽然简单但它们是运维自动化的基石。一个凌晨2点自动跑的etcd备份CronJob比运维老王每天手动敲命令可靠一万倍。下一篇咱们聊HPA——K8s的自动弹性伸缩魔法。流量来了自动扩容流量走了自动缩容CPU/内存/自定义指标都能做触发条件——Pod再也不怕撑死或闲死了。上一篇【第23篇】DaemonSet——每个节点都要有的“守护者“下一篇【第25篇】HPA——K8s的自动弹性伸缩魔法