Kubernetes Job 修改镜像时报 spec.template field is immutable
已有 Job 的容器镜像属于不可原地更新的模板。核对报错路径和旧执行状态,为新版本创建独立 Job,并保留日志、重复执行与回退边界。
把已有 Kubernetes Job 的镜像标签改成新版本,再执行 kubectl apply -f job.yaml,API 返回 spec.template: ... field is immutable。即使旧 Job 已经完成,重复提交仍被拒绝。
这里要区分“修改一次执行的配置”和“用新版本再执行一次”。Job 的容器镜像属于 Pod 模板,不能像 Deployment 的镜像那样原地更新并触发滚动发布。下面针对 batch/v1 Job 更新 spec.template.spec.containers[].image 的场景;以 Kubernetes 1.34 的校验规则为明确版本依据。部分版本允许在特定条件下修改模板中的调度字段,不能据此推断镜像也可修改。
确认改的是已有 Job 的模板
先检查当前上下文、命名空间、旧模板和执行状态。以下 batch-demo、report-v1 都是占位值:
kubectl config current-context
kubectl -n batch-demo get job report-v1 -o yaml
kubectl -n batch-demo get pods -l batch.kubernetes.io/job-name=report-v1
比较线上 Job 与本地清单中的容器名称和 image。如果报错路径落在 spec.template,而本次只改了镜像,失败发生在 API 接受更新之前。重试拉镜像、修改 imagePullPolicy 或删除一个 Pod,都不能让这次模板更新通过;旧 Job 仍保存旧模板。
如果本地清单是从 kubectl get -o yaml 导出的,还要检查是否把服务端生成的字段一起带回来了。为新执行准备清单时,优先使用版本库中的原始声明,不复制 status、uid、resourceVersion、自动生成的 selector 和 controller-uid 标签。
用独立 Job 表示新版本的执行
如果目的确实是重新生成一次报表,给新执行使用不同名称,比删除旧 Job 再抢占同名对象更容易保留状态和日志。先确认旧任务已结束,或应用明确支持两个版本并行;报表写入、发信、迁移等副作用必须有相应的去重或补偿措施。
下面是一份结构完整的示例,镜像地址、标签与程序参数需替换为真实值。镜像中的 /app/report 必须存在:
apiVersion: batch/v1
kind: Job
metadata:
name: report-v2
namespace: batch-demo
spec:
backoffLimit: 0
template:
spec:
restartPolicy: Never
containers:
- name: report
image: registry.example.com/team/report:2.0.0
command: ["/app/report"]
args: ["--period", "2026-09"]
保存为 report-v2.yaml,先让服务端检查创建请求,但不保存对象:
kubectl create --dry-run=server -f report-v2.yaml -o yaml
这个检查能发现权限、字段和准入规则问题,不能证明镜像能拉取或程序能正确处理数据。检查通过、输入数据已备份且确认可以启动新执行后,再创建:
kubectl create -f report-v2.yaml
使用 create 也使同名对象已存在时直接报错,避免把“重试发布”误当成又一次更新已有执行。遇到已存在或客户端请求超时,先读取该 Job 的真实状态,不要立即改一个新名称再次创建。
示例的 backoffLimit: 0 用于避免普通 Pod 失败后的自动重试,但 Job 机制不提供业务上的“恰好一次”保证。即便只要求一个 Pod,也应让任务能识别已经处理过的输入。
完成状态不会解除镜像的更新限制
Job 已完成,只表示这一轮执行达到成功条件;它不会因此变成可以重新填入镜像的空壳。暂停也不是通用的模板解锁开关。在 Kubernetes 1.34 中,尚未解除过暂停的 Job 可以按条件修改节点亲和性、节点选择器、容忍、标签、注解和调度门等字段,这项例外不包含容器镜像。后续版本的暂停调度或资源调整能力,也应按具体字段核对,不能把它们当作任意修改模板的许可。
如果任务由 CronJob、GitOps 或其他控制器管理,应同时修改真正的声明来源,避免下一轮仍按旧镜像创建任务。不要仅为了更新镜像就把有限批任务改成 Deployment;常驻服务与运行到结束的批任务有不同的生命周期。
验收新执行并保留可追踪的回退路径
kubectl -n batch-demo get job report-v2 -o yaml
kubectl -n batch-demo get pods -l batch.kubernetes.io/job-name=report-v2 -o wide
kubectl -n batch-demo logs job/report-v2
确认新 Job 的模板记录正确镜像、Pod 已实际启动、最终出现 Complete=True,并核对业务产物。若有多个 Pod,应分别核对它们的状态和日志;一条日志命令不能代替所有执行结果。进入 Failed 或出现镜像拉取错误时,按实际失败原因处理,不能把创建成功当作任务成功。
旧 Job、日志、原清单与旧镜像引用先保留到验收结束。撤销代码版本不会撤销已经发出的邮件或已经写入的数据;新执行失败后,需要先判断它是否产生了部分结果,再决定补偿或用旧版本另建执行。删除并重建同名 Job 会丢失旧对象身份,普通删除还会清理其 Pod,应在日志和结果已保存、旧执行已妥善停止后才考虑。不要把 replace --force 当作无损更新镜像的方法。


