kubectl
周五下午,开发者准备在测试环境重启一个卡住的 Pod。kubectl delete pod 返回 deleted,几秒后告警却来自生产环境。命令本身没有写错,错的是命令背后那组隐含状态:当前 context 指向生产集群,默认 namespace 又恰好存在同名工作负载。
这类事故揭示了 kubectl 的真实身份。它不是“远程执行 Kubernetes 命令的 shell”,而是 Kubernetes API 客户端:先从 kubeconfig 解析 API Server、用户和 context,再通过 discovery 确认资源与版本,把命令转换成 HTTP 请求,经过认证、授权和准入,最后由 API Server 持久化对象。终端里的一行字可能对应一次读取、一次补丁,也可能对应一批删除请求。高效使用它的关键,不是背更多子命令,而是让目标、意图、结果和审计证据都能被复核。
先安装与集群匹配的客户端
kubectl 是 Kubernetes 项目随版本发布的 Apache-2.0 开源客户端,本身没有订阅费用;云集群、网络出口、审计存储和第三方插件可能产生费用。安装包、校验值与各平台命令应从 Kubernetes 官方安装入口 获取,不要从网盘或聊天附件接收二进制。
Linux 上,下面的方式会先读取官方当前稳定版本,再下载对应架构的二进制。示例使用 amd64;ARM 机器要把路径中的 amd64 改为 arm64。
KUBECTL_VERSION="$(curl -L -s https://dl.k8s.io/release/stable.txt)"
curl -LO "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl"
curl -LO "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl.sha256"
echo "$(cat kubectl.sha256) kubectl" | sha256sum --check
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
kubectl version --client --output=yaml校验成功时会看到 kubectl: OK;sha256sum: WARNING 或 FAILED 表示文件不完整、下载源被替换或架构路径写错,此时不要继续安装。企业内网可以把二进制和 .sha256 一起进入制品库,但同步任务仍需验证上游签名链与摘要,不能只镜像文件名 stable。
macOS 更适合使用 Homebrew 的 brew install kubectl,Windows 可使用 winget install -e --id Kubernetes.kubectl 或 choco install kubernetes-cli。包管理器简化了升级,却可能滞后于集群版本;安装后仍要执行:
Get-Command kubectl | Format-List Source
kubectl version --client --output=yaml第一条命令用来发现 PATH 中真正被执行的文件。若版本升级后仍显示旧版本,常见证据是 Get-Command -All kubectl 返回多个路径,排在前面的旧二进制遮蔽了新安装。
不要把“安装最新”当作兼容策略。Kubernetes version skew policy 规定,kubectl 只支持比 kube-apiserver 新或旧一个 minor 版本。例如 Server 为 1.36 时,受支持的客户端是 1.35、1.36、1.37;HA 控制面若同时存在 1.35 和 1.36,必须取两个允许窗口的交集,因此只支持 1.35 和 1.36,1.34 与 1.37 都不在交集中。连接集群后执行:
kubectl version --output=yaml输出中的 clientVersion.gitVersion 与 serverVersion.gitVersion 才是比较对象。若 API Server 过旧、客户端生成了服务端不认识的字段,可能看到 unknown field、no matches for kind 或资源发现失败;若只是输出列或客户端命令行为变化,则会表现为脚本解析失败。团队应按目标集群 minor 版本固定开发机与 CI 中的 kubectl,并跟进该 minor 的最新 patch,而不是依赖个人电脑自动升级。集群升级窗口内可以并存带版本号的二进制,例如 kubectl-1.35 与 kubectl-1.36,脚本再明确选择。
不连集群也能验证本地命令链
先用客户端生成一个 Deployment,能把“二进制可执行、参数能解析、YAML 序列化正常”与“集群连接正常”分开验证:
kubectl create deployment hello \
--image=nginx:stable \
--replicas=2 \
--dry-run=client \
--output=yaml > hello-deployment.yaml
kubectl create deployment hello \
--image=nginx:stable \
--replicas=2 \
--dry-run=client \
--output=jsonpath='{.spec.replicas}{"\n"}'--dry-run=client 不向 API Server 发请求;--output=yaml 把将要创建的对象输出为清单。第二条命令预期输出 2,证明 JSONPath 选取的是 spec.replicas。把路径故意改成 {.spec.replica} 通常只得到空输出而不是报错,这正是脚本里的危险:空值可能继续流入后续命令。因此自动化脚本要检查输出非空和退出码,关键字段更适合用 JSON 解析器做类型校验。
生成的清单只是客户端当前认知下的对象。它没有经过目标集群的 OpenAPI schema、准入策略、默认值和 RBAC 校验,不能替代服务端预演。
先问集群“它实际支持什么”
接手陌生集群时,不要从记忆猜资源缩写和 API 版本。先固定目标,再读取 discovery:
CTX=kind-tooling-dev
NS=tooling-dev
kubectl --context "$CTX" cluster-info
kubectl --context "$CTX" api-versions
kubectl --context "$CTX" api-resources --namespaced=true
kubectl --context "$CTX" api-resources --api-group=apps
kubectl --context "$CTX" explain deployment.spec.replicasapi-versions 展示 Server 提供的 group/version,api-resources 展示资源名、简称、是否 namespaced、Kind 和可用 verbs,explain 根据 Server 发布的 schema 解释字段。这些信息来自 API Server 的 /api、/apis 和 OpenAPI/discovery 端点,而非一张随 kubectl 固化的资源表;CRD、聚合 API 和集群升级都可能改变结果。Kubernetes 对 API discovery 的说明见 The Kubernetes API。
若看到 the server doesn't have a resource type,先用 api-resources 确认资源是否存在,再确认 API group 和 CRD;若看到 couldn't get current server API group list、TLS 超时或 401,则问题发生在发现之前,应检查 Server 地址、CA、代理和凭证。缓存也可能让排障变得混乱,必要时用 kubectl --cache-dir <临时目录> 重做发现,不要先删除整个用户目录下的缓存。
API 版本弃用时,客户端能否发送请求只是第一关。清单中的 apiVersion 必须被目标 Server 提供,相关字段还要通过 schema 与 admission。升级前可用 api-resources 与服务端 dry-run 扫描清单;kubectl convert 已不再内置于 kubectl,官方将其作为插件分发,因此旧脚本若依赖内置命令,应显式安装并锁定插件来源与版本。
读取对象时保留“原始事实”和“解释结果”
连接测试集群后,先创建独立 namespace,再部署前面生成的对象。所有写操作都显式携带 context 和 namespace:
kubectl --context "$CTX" create namespace "$NS"
kubectl --context "$CTX" -n "$NS" apply -f hello-deployment.yaml
kubectl --context "$CTX" -n "$NS" rollout status deployment/hello --timeout=120sapply 成功时输出 deployment.apps/hello created,rollout status 最终应输出 successfully rolled out。若卡在超时,继续读取 Pod 和事件,不要重复 apply:
kubectl --context "$CTX" -n "$NS" get deployment hello -o wide
kubectl --context "$CTX" -n "$NS" get pods \
-l app=hello \
-o custom-columns='NAME:.metadata.name,READY:.status.containerStatuses[*].ready,IMAGE:.spec.containers[*].image,NODE:.spec.nodeName'
kubectl --context "$CTX" -n "$NS" get events --sort-by=.metadata.creationTimestamp
kubectl --context "$CTX" -n "$NS" describe deployment helloget -o yaml/json 是对象事实,适合保存证据和机器解析;describe 会组合对象、关联资源和事件,适合人读,但格式不承诺稳定,不应作为脚本接口。wide 只是额外列,不同资源的列含义不同。JSONPath、custom-columns 和 Go template 适合提取字段,但需要对缺失值、数组和转义做测试。
读取 Secret 时要格外克制。kubectl get secret -o yaml 得到的 data 只是 Base64 编码,不是加密;kubectl diff --show-secrets 会把值暴露到终端、CI 日志和审查记录。日常排障优先读取 Secret 名、类型、键名或资源版本,不打印值:
kubectl --context "$CTX" -n "$NS" get secret \
-o custom-columns='NAME:.metadata.name,TYPE:.type,KEYS:.data'即使 RBAC 允许,也不代表应该把敏感内容带到本机。命令历史、滚屏录屏、终端协作和 CI artifact 都属于数据外泄面。
把变更拆成渲染、预演、差异和提交
对仓库里的 YAML,安全链从本地可读性开始,再进入目标 Server 验证:
kubectl --context "$CTX" -n "$NS" apply \
--dry-run=server \
--validate=strict \
-f hello-deployment.yaml \
-o yaml > /tmp/hello-admitted.yaml
kubectl --context "$CTX" -n "$NS" diff -f hello-deployment.yaml
echo $?
kubectl --context "$CTX" -n "$NS" apply -f hello-deployment.yaml
kubectl --context "$CTX" -n "$NS" rollout status deployment/hello --timeout=120s--dry-run=server 会经过认证、授权、schema、默认值和支持 dry-run 的准入链,但不持久化对象;它会暴露 Forbidden、未知字段、缺失 CRD 和准入拒绝。--validate=strict 要求字段校验严格,避免拼错字段被静默丢弃。并非所有 admission webhook 都正确声明或实现 dry-run,出现 does not support dry run 时需要平台 owner 修复 webhook 配置,不能改用真实 apply 试运气。
kubectl diff 比较线上对象与“如果 apply 后”的对象。它的退出码非常重要:0 表示无差异,1 表示存在差异,>1 才是命令或外部 diff 失败。CI 若把任何非零都当成失败,会误把“发现正常差异”当成故障;若完全忽略退出码,又会放过连接失败。官方行为与并发、field manager 等参数见 kubectl diff reference。
差异审查后才执行真实 apply,并等待控制器把期望状态推进到可用状态。apply 返回成功只代表 API 请求已被接受,不代表镜像可拉取、探针通过或副本就绪。rollout status、Deployment conditions、Pod events 与业务探针共同组成结果证据。
Server-Side Apply 让字段责任可见
传统 client-side apply 依赖 kubectl.kubernetes.io/last-applied-configuration annotation 做三方合并;大对象会放大 annotation,多个工具同时编辑时也很难解释“谁拥有这个字段”。Server-Side Apply(SSA)把字段管理交给 API Server,并在 metadata.managedFields 记录 manager、operation、API version 和字段集合。它解决的是协作与冲突可见性,不是自动判定谁的配置更正确。
下面的实验在测试 namespace 创建一个 ConfigMap,并让两个 manager 争用同一个字段:
cat > owner-a.yaml <<'YAML'
apiVersion: v1
kind: ConfigMap
metadata:
name: field-owner-demo
data:
color: blue
owner: platform
YAML
kubectl --context "$CTX" -n "$NS" apply \
--server-side \
--field-manager=platform-reconciler \
-f owner-a.yaml
cat > owner-b.yaml <<'YAML'
apiVersion: v1
kind: ConfigMap
metadata:
name: field-owner-demo
data:
color: green
YAML
kubectl --context "$CTX" -n "$NS" apply \
--server-side \
--field-manager=release-pipeline \
-f owner-b.yaml第一次预期输出 serverside-applied;第二次应失败,并指出 .data.color 与 platform-reconciler 冲突。这是可利用的保护信号:release pipeline 试图修改平台控制的字段。查看责任证据:
kubectl --context "$CTX" -n "$NS" get configmap field-owner-demo \
--show-managed-fields \
-o yaml--force-conflicts 会夺取冲突字段,而不是“更强地重试”。只有完成责任转移审查后才使用:
kubectl --context "$CTX" -n "$NS" apply \
--server-side \
--field-manager=release-pipeline \
--force-conflicts \
-f owner-b.yaml此后 release-pipeline 成为 data.color 的 owner。若 manager 从自己的清单中删除一个字段,且没有其他 manager 共同拥有它,Server 会删除该字段或恢复默认值。控制器、GitOps、Helm 和人工 kubectl 混用时,应先设计 field manager 名称和责任边界;否则 managedFields 只会记录混乱。SSA 的冲突与所有权转移机制见 Server-Side Apply。
managedFields 会增加对象体积与 etcd、API 响应和审计存储开销。不要为了日常列表默认展示它;发生冲突、迁移 manager 或审计责任时再读取。批量 SSA 的并发还会增加 API Server CPU、admission 与 etcd 写压力,流水线应限制并发并遵守集群的 API Priority and Fairness,而不是不断缩短重试间隔。
删除之前先证明选择器只命中目标
删除是 API 操作,不是本地撤销。一个稳妥链路先冻结目标,再验证权限和服务端解释:
kubectl --context "$CTX" -n "$NS" get configmap field-owner-demo \
-o custom-columns='NAME:.metadata.name,UID:.metadata.uid,CREATED:.metadata.creationTimestamp'
kubectl --context "$CTX" -n "$NS" auth can-i delete configmaps
kubectl --context "$CTX" -n "$NS" delete configmap field-owner-demo \
--dry-run=server \
-o yaml
kubectl --context "$CTX" -n "$NS" delete configmap field-owner-demo \
--wait=true \
--timeout=60sget 证明名称、UID 和创建时间,auth can-i 证明授权层判断,dry-run 证明 Server 接受删除请求,真实删除再等待对象消失。若按 label 删除,必须先用同一个 selector 执行 get 并保存命中数;空 selector、过宽 selector 与 --all 不应出现在无人值守脚本中。
Kubernetes 删除通常先写入 deletionTimestamp,再等待 finalizer 与优雅终止完成。命令长时间等待不是简单的客户端卡死:
kubectl --context "$CTX" -n "$NS" get pod <pod-name> \
-o jsonpath='{.metadata.deletionTimestamp}{"\n"}{.metadata.finalizers}{"\n"}'--force --grace-period=0 会让 API Server 不再等待 kubelet 确认进程已经停止,可能造成工作负载仍运行、同一身份对象被重建、存储写入并发等风险。kubectl delete reference 明确说明强制删除不会等待资源确认终止。它是故障处置工具,不是“删除太慢”的常规加速参数。finalizer 卡住时应先定位负责的 controller 及其外部资源,再决定是否人工移除 finalizer。
Deployment 的回退也不是 kubectl apply 的自动能力。可以在测试 namespace 故意发布不存在的镜像,先取得失败证据,再预演和执行回退:
kubectl --context "$CTX" -n "$NS" set image \
deployment/hello nginx=nginx:this-tag-does-not-exist
kubectl --context "$CTX" -n "$NS" rollout status \
deployment/hello --timeout=30s
kubectl --context "$CTX" -n "$NS" get pods -l app=hello
kubectl --context "$CTX" -n "$NS" get events \
--sort-by=.metadata.creationTimestamp
kubectl --context "$CTX" -n "$NS" rollout history deployment/hello
kubectl --context "$CTX" -n "$NS" rollout undo deployment/hello \
--dry-run=server -o yaml > /tmp/hello-rollback.yaml
kubectl --context "$CTX" -n "$NS" rollout undo deployment/hello
kubectl --context "$CTX" -n "$NS" rollout status \
deployment/hello --timeout=120s
kubectl --context "$CTX" -n "$NS" get deployment hello \
-o jsonpath='{.spec.template.spec.containers[0].image}{"\n"}'第一次 rollout status 预期超时并返回非零;Pod 状态和事件应出现 ImagePullBackOff、ErrImagePull 或仓库返回的拉取失败信息。rollout undo --dry-run=server 需要与真实回退相同的 patch 权限,但不会保存新 revision;真实 undo 后再次等待 rollout,最后一条应恢复为此前的 nginx:stable。若 history 中没有可用旧 revision,undo 会失败;Deployment 的 revisionHistoryLimit 过小或旧 ReplicaSet 已被清理时,不能把 undo 当作备份。
rollout undo 只恢复 Deployment 或 DaemonSet 等控制器保存的 Pod template revision,不会一起恢复 ConfigMap、Secret、RBAC、CRD、数据库 schema 和外部服务。跨对象发布最可靠的回退入口仍是 Git 中上一份已验证清单,再走同一套 server dry-run、diff、apply 和结果验证;有状态变更还要单独验证数据向后兼容与恢复点。
插件是本机程序,不继承 kubectl 的可信度
kubectl 插件只是 PATH 中名为 kubectl-* 的可执行文件。kubectl foo 会寻找 kubectl-foo 并把参数与环境变量交给它;插件可以读 kubeconfig、云凭证、工作目录和终端环境,也可以绕过团队在 shell alias 中添加的保护。官方 kubectl plugin guide 还明确提醒,Krew 索引里的第三方插件并未经过安全审计。
安装插件后至少记录来源仓库、版本、摘要、许可证、owner 和升级周期,并检查 PATH 冲突:
kubectl plugin list
command -v kubectl-foo
kubectl foo --helpkubectl plugin list 出现 overshadowed 时,实际执行的是 PATH 中靠前的文件。CI 不应临时拉取 main 分支脚本后直接执行;应固定 release 与摘要,放入受控工具镜像。插件退出时也必须透传非零退出码,否则流水线会把部分失败误判为成功。
把一次变更还原成可审计证据
客户端 verbose 日志适合确认“请求发往哪里、使用哪个 verb、耗时多久”,例如:
kubectl --context "$CTX" -n "$NS" get deployment hello -v=6日志会显示请求 URL、HTTP 状态与延迟。更高 verbosity 可能暴露请求头、对象内容和凭证相关信息,只应在受控终端短时使用,完成后清理日志。--request-timeout 控制单次请求等待时间;设置过短会把 API Server 排队、慢 admission 或大列表误判为网络故障,设置为 0 又可能让自动化永久挂起。
真正的责任追溯在 API Server audit。Kubernetes 审计事件可以记录请求用户、verb、资源、namespace、对象名、来源地址、user agent、响应码与阶段;记录到 Metadata、Request 还是 RequestResponse 由集群审计策略决定。Request/Response 级别可能包含 Secret、ConfigMap 与业务配置,因此审计后端也需要访问控制、脱敏、保留期和容量预算。官方的 Auditing 说明了策略顺序、事件阶段以及日志和 webhook 后端。
团队脚本应设置稳定、可识别的 field manager,并让 CI 使用独立身份;不要让几十个人共享一个 admin kubeconfig。这样审计记录才能回答“谁通过哪条流水线改了哪个字段”。命令日志不能代替服务端审计,因为本机日志可以缺失或被修改;审计也不能代替 Git 审查,因为 audit 记录的是发生了什么,不一定保存变更动机。
项目接入要消除终端隐式状态
仓库中的脚本不应依赖执行者当前 context、namespace 或 PATH 偶然状态。一个可维护的部署入口至少把目标作为参数,并在写入前输出身份与差异:
#!/usr/bin/env bash
set -euo pipefail
: "${KUBE_CONTEXT:?set KUBE_CONTEXT explicitly}"
: "${KUBE_NAMESPACE:?set KUBE_NAMESPACE explicitly}"
kubectl version --client --output=yaml
printf 'target context=%s namespace=%s\n' "$KUBE_CONTEXT" "$KUBE_NAMESPACE"
kubectl config get-contexts "$KUBE_CONTEXT"
kubectl --context "$KUBE_CONTEXT" -n "$KUBE_NAMESPACE" auth can-i patch deployments
kubectl --context "$KUBE_CONTEXT" -n "$KUBE_NAMESPACE" apply \
--server-side \
--field-manager=your-project-release \
--dry-run=server \
-f k8s/
kubectl --context "$KUBE_CONTEXT" -n "$KUBE_NAMESPACE" diff \
--server-side \
--field-manager=your-project-release \
-f k8s/ || test "$?" -eq 1
kubectl --context "$KUBE_CONTEXT" -n "$KUBE_NAMESPACE" apply \
--server-side \
--field-manager=your-project-release \
-f k8s/
kubectl --context "$KUBE_CONTEXT" -n "$KUBE_NAMESPACE" rollout status \
deployment/hello --timeout=120s这里 set -euo pipefail 阻止未定义变量和管道中间失败被吞掉;两个 :? 强制调用者给目标;diff 只把退出码 1 当作“有差异”,其他错误继续失败。生产流水线还应在 apply 前加入审批、制品签名与变更窗口,并使用短期工作负载身份,避免把长期 kubeconfig 保存成普通 CI 变量。
完成实验后的清理与复核
先删除文章创建的对象,再删除 namespace;如果 namespace 中混入了其他人的资源,应停止,不要整段删除:
kubectl --context "$CTX" -n "$NS" delete deployment hello --wait=true
kubectl --context "$CTX" -n "$NS" delete configmap field-owner-demo \
--ignore-not-found=true --wait=true
kubectl --context "$CTX" get all -n "$NS"
kubectl --context "$CTX" delete namespace "$NS" --wait=true --timeout=120s
rm -f hello-deployment.yaml owner-a.yaml owner-b.yaml \
/tmp/hello-admitted.yaml /tmp/hello-rollback.yaml
kubectl --context "$CTX" get namespace "$NS"最后一条预期返回 NotFound。若 namespace 长期停在 Terminating,查看其中仍存资源、namespace finalizers 与 API discovery 是否健康;某个已经删除的 APIService 或 CRD 也可能让 namespace controller 无法完成发现。不要直接清空 namespace finalizer,除非已经确认所有 namespaced 资源及外部依赖的回收结果。
长期治理的判断可以落在几条不变量上:每个自动化身份只拥有所需 verbs 和资源;每次变更都能从 Git 提交、field manager 和 audit event 串回 owner;客户端版本始终处于所有目标 API Server 的支持窗口;批量 list/watch/apply 不持续压高 API Server 延迟与审计存储;插件、凭证和二进制都有可追踪来源、轮换与退出路径。做到这些,kubectl 才从个人快捷工具变成团队可以信任的 Kubernetes 变更入口。
