kubectl apply vs set:为资源更新选择正确的命令

了解何时使用 kubectl apply、set 和 edit,避免 Kubernetes 实时对象与 Git 之间产生配置漂移。

kubectl apply vs set:为资源更新选择正确的命令

kubectl applykubectl set 的区别不仅仅是语法。它代表了两种管理 Kubernetes 的方式:一种是从声明的可信源管理,另一种是直接修改实时对象。两者都很有用,但如果使用不当,都可能带来问题。

当变更应该成为系统的期望状态时,使用 kubectl apply。当你需要一个有针对性的、通常是临时或紧急的实时变更时,使用 kubectl set。当你需要交互式地检查和修补实时对象时,使用 kubectl edit,但要知道这是最容易导致与 Git 产生漂移的方式。

Kubernetes 对象在 API 服务器中存储了一个期望状态。一个 Deployment 指定了应该存在多少个副本、运行哪个镜像、哪些标签标识 Pod、请求哪些资源等等。控制器努力使现实匹配那个期望状态。你的更新命令改变了期望状态;控制器完成其余的工作。

使用 kubectl apply,你将期望状态保存在 YAML 或 JSON 文件中。这个文件是你审查、提交、升级和回滚的对象。一个典型的命令很简单:

kubectl apply -f deployment.yaml

如果对象不存在,Kubernetes 会创建它。如果存在,Kubernetes 会更新它以匹配清单。再次应用同一个文件不应导致新的行为变化。这种幂等性是 apply 在 CI/CD 和 GitOps 工作流中表现良好的原因之一。

客户端 kubectl apply 历来使用一个 last-applied 注解来计算变更。服务端 apply(通过 --server-side 启用)通过 API 服务器中的 managed fields 跟踪字段所有权。细节有所不同,但操作理念是相同的:声明的配置拥有期望状态。

这是一个小的 Deployment 清单:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
  labels:
    app: web
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
      - name: nginx
        image: nginx:1.25
        ports:
        - containerPort: 80

如果你在文件中更改了镜像并运行 kubectl apply -f deployment.yaml,Deployment 会更新。如果文件在 Git 中,变更可以被审查。如果发布失败,你可以回滚提交或根据你的部署过程记录修订的方式使用 Kubernetes 的 rollout history。

kubectl set 的工作方式不同。它更改实时对象上的特定字段。常见的例子是更改容器镜像:

kubectl set image deployment/web nginx=nginx:1.26

这个命令快速且可读。在事故期间,速度很重要。如果当前镜像有问题,你需要将 Deployment 移动到一个已知良好的镜像,kubectl set image 可能是最快的路径。危险在于接下来会发生什么。如果 deployment.yaml 仍然写着 nginx:1.25,下一次 kubectl apply -f deployment.yaml 可能会将工作负载移回 1.25

这种不匹配就是配置漂移。实时集群说一件事。源文件说另一件事。漂移并不总是灾难性的,但它会使调试更加困难,因为人们不再信任仓库。有人问:“生产环境中运行的是什么?”诚实的回答变成了:“让我检查一下集群。”

kubectl set 有几个有用的子命令:

kubectl set image deployment/web nginx=nginx:1.26
kubectl set env deployment/web FEATURE_FLAG=true
kubectl set resources deployment/web -c=nginx --requests=cpu=200m,memory=256Mi --limits=cpu=500m,memory=512Mi

这些对于开发集群、演示、短期测试以及需要后续提交的紧急生产变更非常实用。它们不能替代维护良好的清单。

kubectl edit 获取实时对象,在你的编辑器中打开它,并在你保存时将更改后的对象发送回 API 服务器:

kubectl edit deployment/web

它很方便,因为你可以看到完整的实时 YAML,包括控制器添加的字段。它也有风险,因为实时对象包含许多你不应该手动管理的字段,例如 status、生成的元数据、资源版本和 managed fields。Kubernetes 会忽略或拒绝一些无效的编辑,但并非每个错误的编辑在语法上都是无效的。

kubectl edit 一个常见的安全用途是快速的非生产实验:增加副本数、更改注解或测试探针值。一个常见的不安全用途是每周手动编辑生产 Deployment 但从不更新清单。这会导致一个没有人能自信重建的集群。

实用的规则是:如果你希望变更在下一次部署后仍然存在,将其放入清单并使用 apply。如果你只需要临时修改实时对象,使用 setedit,然后要么恢复变更,要么将其反向移植到 Git。

在某些情况下,命令式命令不仅可接受而且有帮助。在调试期间,你可能会临时添加一个环境变量来增加日志详细程度:

kubectl set env deployment/api LOG_LEVEL=debug

收集日志后,移除它:

kubectl set env deployment/api LOG_LEVEL-

如果调试设置应该成为永久性的,请将其提交到清单中。不要依赖记忆。

另一种情况是紧急回滚。如果你的部署管道卡住并且客户受到影响,直接设置镜像可能是合理的:

kubectl set image deployment/api api=registry.example.com/api:2026-05-23-good
kubectl rollout status deployment/api

后续行动应立即进行:打开一个拉取请求或提交,使声明的清单与紧急状态匹配,或者在管道恢复健康后运行正常的回滚流程。实时修复争取了时间;它不应成为新的未记录的部署方法。

kubectl apply 也有陷阱。如果你混合使用多个管理相同字段的工具,可能会遇到冲突或令人惊讶的覆盖。例如,一个 GitOps 控制器、Helm 和一个手动运行 kubectl apply 的人可能都认为他们拥有同一个 Deployment 的部分所有权。选择明确的所有权。如果 Helm 管理资源,更新 Helm values 并运行 Helm 发布流程。如果 Argo CD 或 Flux 管理它,更改 Git 并让控制器协调。

对于 Secrets 和 Config,要特别小心。kubectl set env 可以快速将变更放入 Deployment,但它可能会在 shell 历史或审计日志中暴露值。对于敏感值,通过你正常的 Secret 管理流程更新 Secret。除非你的团队明确接受了该工作流,否则不要将生产凭据粘贴到临时命令中。

在更改实时对象之前,检查它:

kubectl get deployment web -o yaml
kubectl diff -f deployment.yaml

kubectl diff 使用不足。它会在你进行更改之前显示 apply 会改变什么。在生产环境中,这种预览可以捕捉错误,例如意外删除标签选择器、删除资源限制或应用错误环境的清单。

对于服务端 apply,命令如下所示:

kubectl apply --server-side -f deployment.yaml

当多个参与者管理不同字段时,服务端 apply 可能很有用,但它并不能消除所有权纪律的必要性。如果两个管理器试图拥有同一个字段,Kubernetes 可能会报告冲突。这是一个特性;它告诉你工作流是模糊的。

这是我在真实集群中使用的一个简单决策指南。新的应用程序发布?更改清单并通过管道使用 apply。在暂存环境中为负载测试增加副本数?如果之后恢复,kubectl scalekubectl set 是可以的。在生产环境中热修复一个损坏的镜像?kubectl set image 可以接受,但要立即创建可信源的变更。永久调整 CPU 请求?更新清单。探索资源上存在哪些字段?在尝试 edit 之前使用 kubectl get -o yaml

当团队正确处理这一点时,Kubernetes 变得更容易推理。仓库讲述了故事。集群大部分时间与仓库匹配。临时的实时变更被标记为临时并清理干净。事故仍然有压力,但配置不会成为第二个谜团。

命令本身不是重点。重点是你明天能否从可信文件重建集群状态。kubectl apply 支持这种习惯。kubectl setkubectl edit 是在直接操作有用时的锋利工具。保持这个界限清晰,你将避免很多可以避免的 Kubernetes 混乱。

还有另一个命令人们放在同一个心理类别中:kubectl patch。它也是命令式的,但它更适合精确的脚本化变更,而不是 edit。例如,你可以修补一个 Deployment 注解来触发重启或在自动化中更新一个小字段。同样的漂移规则适用。如果修补的字段代表长期期望状态,也要更新源清单。

kubectl patch deployment web   -p '{"spec":{"template":{"metadata":{"annotations":{"restartedAt":"2026-05-24T10:00:00Z"}}}}}'

对于重启,首选专用命令:

kubectl rollout restart deployment/web

该命令仍然更改实时对象,但其意图很明确:从当前的 Pod 模板开始新的发布。它不能替代在 Git 中更改配置。

在 GitOps 环境中,界限更加严格。如果 Argo CD 或 Flux 拥有一个对象,手动 kubectl set image 可能会被自动恢复,因为控制器检测到漂移并协调回 Git。在事故期间,这可能会令人惊讶。在进行手动生产变更之前,要知道 GitOps 控制器是否会与你对抗。有时正确的紧急操作是暂停该应用程序的协调,进行修复,然后提交匹配的 Git 变更并恢复协调。

你还应该知道如何捕获实时差异,而不会盲目提交生成的字段。kubectl get deployment web -o yaml 包括 status、资源版本、managed fields 和其他不应放入干净清单的数据。如果你需要反向移植一个热修复,手动编辑源清单或使用 Kustomize 或 Helm values 等工具,然后运行 kubectl diff 来验证预期的变更。除非你仔细清理,否则不要用原始的实时 YAML 替换你的源文件。

对于团队来说,最健康的策略通常是简短而明确的。生产变更通过 Git 进行。紧急实时变更在需要时允许,但需要后续的源变更或回滚。开发集群更宽松,但任何超出开发范围的内容都必须声明。这个策略比一长串禁止的命令更容易遵循。

这也有一个人性的一面。在中断期间,拥有集群访问权限的人可能会进行最快的修复。当团队将其视为紧急例外时,这是可以的。当这些例外成为正常操作时,它就变得危险了。如果生产环境经常通过手动修复,那么部署过程要么太慢、太脆弱,要么不被信任。修复那个过程,而不是教每个人更多的实时编辑技巧。

RBAC 可以强化工作流。许多团队允许在开发环境中广泛的 kubectl 访问,但将生产写入限制为 CI/CD 系统、GitOps 控制器或一个小型值班组。这不是为了官僚主义而官僚主义。它减少了可以改变期望状态的路径数量。当某些东西发生变化时,审计日志和 Git 历史更容易跟踪。

为了学习,仍然值得在一个可丢弃的命名空间中练习所有三个命令。使用 apply 创建一个 Deployment,使用 set 更改其镜像,使用 kubectl diff 检查漂移,然后更新清单并再次应用。在安全环境中亲眼看到一次漂移,会使生产规则更容易记住。