【K8S 运维实战】06-kubectl精通

📅 2026/7/22 6:48:41 👁️ 阅读次数 📝 编程学习
【K8S 运维实战】06-kubectl精通

kubectl 精通:从常用命令到排错套路

一句话定位:把 kubectl 从"会用"变成"肌肉记忆",排错效率翻十倍。

写在前面

凌晨两点,告警炸了。你 SSH 跳板机,手指下意识敲出kubectl get pods——然后呢?盯着 Pending 的 Pod 发呆,翻历史命令找上次的describe,复制 Pod 名拼到kubectl logs后面,漏了 namespace 又重来一遍。五分钟过去了,业务还在告警。

这是我带新人的时候最常看到的场景。很多人用 kubectl 三年了,还是停留在getdescribelogs三板斧,遇到问题靠"多试几次"。其实 kubectl 的设计哲学很统一:声明式 API + 强大的输出格式化 + 可扩展的插件生态。一旦你把这套心智模型建立起来,排错就不再是"猜",而是"查"。

这篇文章不是 man page 翻译,而是把我八年生产环境里真正高频用到的命令、组合拳、alias 配置沉淀出来,附上一套能直接source用的配置文件。你读完应该能做到:看到一个故障现象,手比脑子快地把定位命令敲出来。

核心问题

  • 怎么用 kubectl 快速定位 Pod/Service/节点问题?
  • -o jsonpath/-o go-template到底怎么写,有哪些高频模板?
  • krew 插件生态有哪些必装插件?
  • label / selector / field-selector 怎么组合查询?
  • 怎么配置一套自己的排错 alias,让效率起飞?

一、原理剖析

1.1 kubectl 的通信模型

kubectl 本身不"做"任何事,它只是 kube-apiserver 的一个 HTTP 客户端。理解这一点很关键——所有 kubectl 命令最终都会翻译成一个对 apiserver 的 REST 调用。

┌──────────┐ HTTPS ┌────────────┐ watch/cache ┌─────────┐ │ kubectl │ ──────────> │ apiserver │ <──────────────> │ etcd │ │ (client)│ <────────── │ (control │ │ (store) │ └──────────┘ JSON/YAML │ plane) │ └─────────┘ └──────────┘ └────────────┘

这意味着:

  • kubectl get拿到的是 apiserver 缓存里的"期望状态 + 当前状态",不是实时去节点上拉;
  • kubectl describe额外聚合了 Event、Condition、相关资源,信息更多但也是 apiserver 视角;
  • 真正容器运行时的状态要kubectl execkubectl debug进节点上看。

排错第一原则:apiserver 看到的状态 ≠ 节点上的真实状态。两者不一致时,十有八九是 kubelet、容器运行时或网络的问题。

1.2 输出格式化:-o的威力

kubectl 的-o参数是它区别于其他 CLI 工具的核心设计。资源在 apiserver 里是结构化对象(JSON),-o决定怎么把它"投影"出来:

┌─────────────────────────────────────────────────────────────┐ │ Resource Object (full JSON) │ │ { │ │ "metadata": {...}, │ │ "spec": {...}, │ │ "status": { │ │ "conditions": [...], │ │ "containerStatuses": [ │ │ {"name":"app","ready":true,"restartCount":3,...} │ │ ] │ │ } │ │ } │ └─────────────────────────────────────────────────────────────┘ │ ├── -o wide → 表格 + 关键扩展字段(IP/Node) ├── -o yaml → 完整 YAML(可重新 apply) ├── -o json → 完整 JSON(jq 友好) ├── -o jsonpath=... → 按路径提取,适合脚本 ├── -o go-template=...→ 支持循环/条件,更灵活 ├── -o custom-columns → 自定义列 ├── -o name → 只输出 kind/name └── -o jsonpath-as-json → 结构化输出,给 jq -c 处理

jsonpathgo-template是排错脚本化的关键。jsonpath 语法轻量,go-template 功能更强(支持range/if/index)。实际工作中 80% 场景 jsonpath 够用。

1.3 selector:label 与 field-selector

K8s 里所有"按条件查资源"的能力都建立在 selector 上。分两种:

  • label selector:kubectl get pods -l app=nginx,tier=frontend。这是声明式的、用户自定义的标签查询,支持=!=innotinexists。几乎所有资源都支持。
  • field-selector:kubectl get pods --field-selector status.phase=Pending,nodeName=node-1。这是按 apiserver 内部字段过滤,字段集有限(支持 phase、nodeName、namespace、status.phase 等)。

按标签

按字段

按命名空间

kubectl get pods

过滤维度

-l app=nginx,env!=prod

--field-selector
status.phase=Pending

-n kube-system / -A

结果集

注意:field-selector 不支持自定义字段,只能用 K8s 内置的那几个。想在所有资源里找"所有有app=nginx标签的 Service 和 Ingress"?用-l配合-A

二、实战操作

2.1 环境准备

# 版本基线:K8s 1.30, kubectl 1.30kubectl version--client--output=yaml# 期望:clientVersion.gitVersion: v1.30.x# 开启 kubectl 自动补全(bash)echo'source <(kubectl completion bash)'>>~/.bashrcecho'alias k=kubectl'>>~/.bashrcecho'complete -o default -F __start_kubectl k'>>~/.bashrcsource~/.bashrc# zsh 用户source<(kubectl completionzsh)

2.2 排错组合拳:get → describe → logs → exec

这是最经典的三段式定位法。以一个 CrashLoopBackOff 的 Pod 为例:

# 第一步:广角扫描,定位异常 Podkubectl get pods-A--field-selector=status.phase!=Running kubectl get pods-A-owide|grep-vE'Running|Completed'# 第二步:聚焦单个 Pod,看 Event 和容器状态kubectl describe pod<pod-name>-n<ns># 重点看:# - Status / Reason 字段# - Containers 段的 State / Last State# - Events 段(按时间倒序,最后发生的在最下面)# 第三步:看日志,优先看上一个崩溃的容器kubectl logs<pod-name>-n<ns>--previous--tail=200# --previous 看上一次容器退出前的日志,崩溃排查必备# --tail=200 只看最后 200 行,避免刷屏# 第四步:进容器看运行时状态(如果 Pod 还活着)kubectlexec-it<pod-name>-n<ns>-- /bin/sh# 容器没有 sh 时用 debugkubectl debug-it<pod-name>--image=nicolaka/netshoot--target=<container>

2.3 jsonpath / go-template 高频模板

这是我生产里真正每天用的模板,直接抄走:

# 1. 列出所有 Pod 及其所在节点 + 状态(快速全局视图)kubectl get pods-A-ocustom-columns=\NS:.metadata.namespace,\POD:.metadata.name,\STATUS:.status.phase,\NODE:.spec.nodeName,\IP:.status.podIP# 2. 找所有非 Running 的 Pod(脚本化告警)kubectl get pods-A--field-selector=status.phase!=Running\-ojsonpath='{range .items[*]}{.metadata.namespace}/{.metadata.name} -> {.status.phase}{"\n"}{end}'# 3. 找所有重启过的 Pod(restartCount > 0)kubectl get pods-A-ojsonpath=\'{range .items[*]}{range .status.containerStatuses[*]}{.name}{"\t"}{.restartCount}{"\n"}{end}{end}'\|awk'$2>0'# 4. 列出所有 ImagePullBackOff(镜像拉取失败的)kubectl get pods-A-ojsonpath=\'{range .items[*]}{range .status.containerStatuses[?(@.state.waiting.reason=="ImagePullBackOff")]}\ {.name}{"\t"}{.state.waiting.message}{"\n"}{end}{end}'# 5. 看 HPA 当前副本数和目标(快速判断扩容是否生效)kubectl get hpa-A-ocustom-columns=\NS:.metadata.namespace,\HPA:.metadata.name,\TARGET:.spec.scaleTargetRef.name,\CURRENT:.status.currentReplicas,\DESIRED:.status.desiredReplicas# 6. 列出所有节点的资源分配(调度看节点压力时用)kubectl get nodes-ojsonpath=\'{range .items[*]}{.metadata.name}{"\t"}{.status.allocatable.cpu}{"\t"}{.status.allocatable.memory}{"\n"}{end}'# 7. 看所有 PVC 的挂载状态kubectl get pvc-A-ocustom-columns=\NS:.metadata.namespace,\PVC:.metadata.name,\STATUS:.status.phase,\CAPACITY:.status.capacity.storage,\BOUND:.spec.volumeName# 8. 提取 Service 的 Endpoints(看后端 Pod 是否就绪)kubectl get endpoints<svc>-n<ns>-ojsonpath=\'{range .subsets[*]}{range .addresses[*]}{.ip}{"\n"}{end}{end}'

go-template 适合需要循环+条件的场景:

# 列出所有节点及其 Ready 状态 + 资源压力 conditionkubectl get nodes-ogo-template='{{range .items}}{{.metadata.name}}{{"\t"}}{{range .status.conditions}}{{if eq .type "Ready"}}{{.status}}{{end}}{{end}}{{"\n"}}{{end}}'# 找所有不是 Running 的 Pod,输出命名空间/Pod名/原因kubectl get pods-A-ogo-template='{{range .items}}{{if ne .status.phase "Running"}}{{.metadata.namespace}}/{{.metadata.name}}{{"\t"}}{{.status.phase}}{{"\n"}}{{end}}{{end}}'

2.4 krew 插件生态

krew 是 kubectl 的插件管理器,类似 apt/brew。装一次终身受益:

# 安装 krew(set-x;cd"$(mktemp-d)"&&OS="$(uname|tr'[:upper:]''[:lower:]')"&&ARCH="$(uname-m|sed-e's/x86_64/amd64/'-e's/\(arm64\|aarch64\)/arm64/')"&&curl-fsSLokrew.tar.gz"https://github.com/kubernetes-sigs/krew/releases/latest/download/krew-${OS}_${ARCH}.tar.gz"&&tarzxvf krew.tar.gz&&./"krew-${OS}_${ARCH}"installkrew)# 加入 PATHecho'export PATH="${KREW_ROOT:-$HOME/.krew}/bin:$PATH"'>>~/.bashrcsource~/.bashrc# 验证kubectl krew version

我推荐的生产必装插件清单:

# 1. ns —— 快速切换 namespace,告别 -n 一长串kubectl krewinstallns kubectl ns kube-system# 切到 kube-systemkubectl ns# 不带参数显示当前 ns# 2. ctx —— 快速切换 context(多集群必备)kubectl krewinstallctx kubectl ctx prod-shanghai# 3. debug 树状展示资源关系(Deployment→ReplicaSet→Pod)kubectl krewinstalltree kubectl tree deployment nginx-ndefault# 4. iexec —— 交互式选择 Pod 再 exec(不用记 Pod 名)kubectl krewinstalliexec kubectl iexec-ndefault# 弹列表选# 5. tail —— 多 Pod 同时看日志,按 label 聚合kubectl krewinstalltailkubectltail-lapp=nginx-ndefault# 6. neat —— 输出 yaml 时去掉 kubectl 自动加的字段(status/creationTimestamp)kubectl krewinstallneat kubectl get pod nginx-oyaml|kubectl neat|kubectl apply-f-# 7. df-pv —— 看 PV 使用率(节点上 df 的全局版)kubectl krewinstalldf-pv kubectl df-pv# 8. resource-capacity —— 节点/Pod 资源请求与限制汇总kubectl krewinstallresource-capacity kubectl resource-capacity--sortcpu.request--util# 9. blame —— 谁动过这个资源?显示资源的创建者和最近修改kubectl krewinstallblame# 10. view-secret —— 直接看 Secret 内容(生产慎用,合规场景)kubectl krewinstallview-secret kubectl view-secret db-password-napp

2.5 排错 alias 配置文件

这是我这几年沉淀下来的~/.kubectl-alias.sh,生产环境直接source用:

#!/bin/bash# ~/.kubectl-alias.sh —— kubectl 排错 alias 集合# 用法: source ~/.kubectl-alias.sh# 基础简写aliask='kubectl'aliaskg='kubectl get'aliaskgp='kubectl get pods'aliaskgpa='kubectl get pods -A'aliaskgs='kubectl get svc'aliaskgn='kubectl get nodes'aliaskgd='kubectl get deploy'aliaskgss='kubectl get sts'aliaskgds='kubectl get ds'aliaskgi='kubectl get ingress'aliaskgcm='kubectl get cm'aliaskgsec='kubectl get secret'aliaskga='kubectl get all'aliaskgaa='kubectl get all -A'# describe / logs / exec 简写aliaskd='kubectl describe'aliaskdp='kubectl describe pod'aliaskds='kubectl describe svc'aliaskdn='kubectl describe node'aliaskl='kubectl logs'aliasklf='kubectl logs -f'aliasklp='kubectl logs --previous --tail=200'aliaske='kubectl exec -it'# 高频组合:排错专用# 所有非 Running 的 Podaliaskbad='kubectl get pods -A --field-selector=status.phase!=Running -o wide'# 所有有重启的 Podaliaskrestart='kubectl get pods -A -o jsonpath="{range .items[*]}{range .status.containerStatuses[*]}{.name}{\"\t\"}{.restartCount}{\"\n\"}{end}{end}" | awk "\$2>0 {print}" | column -t'# 节点资源分配aliasknode='kubectl get nodes -o custom-columns=NAME:.metadata.name,CPU:.status.allocatable.cpu,MEM:.status.allocatable.memory,TAINTS:.spec.taints'# 所有 Event(按时间倒序看最近的问题)aliaskev='kubectl get events -A --sort-by=.lastTimestamp'# 看 HPAaliaskhpa='kubectl get hpa -A'# 看 PVCaliaskpvc='kubectl get pvc -A -o wide'# 切 namespace 快捷kns(){kubectl config set-context--current--namespace="$1";kubectl get pods;}# 用法: kns kube-system# 进入 Pod 的快捷(交互式选择)kpod(){localpod=$(kubectl get pods-oname|fzf|sed's|pod/||')[-n"$pod"]&&kubectlexec-it"$pod"--"${@:-/bin/sh}"}# 看某 Pod 所有容器的日志(多容器 Pod)kalllogs(){localpod=$1forcin$(kubectl get pod"$pod"-ojsonpath='{.spec.containers[*].name}');doecho"===$c==="kubectl logs"$pod"-c"$c"--tail=100done}# 一键看 Pod 的 Event + 上一次日志(排错三合一)ktrouble(){localpod=$1ns=${2:-default}echo"===== STATUS ====="kubectl get pod"$pod"-n"$ns"-owideecho"===== EVENTS ====="kubectl get events-n"$ns"--field-selectorinvolvedObject.name="$pod"--sort-by=.lastTimestampecho"===== PREVIOUS LOGS ====="kubectl logs"$pod"-n"$ns"--previous--tail=1002>/dev/null||echo"(no previous container)"}# 用法: ktrouble my-pod app-namespace

把上面内容存到~/.kubectl-alias.sh,然后在~/.bashrc里加一行source ~/.kubectl-alias.sh即可。

2.6 常用排错一键脚本

遇到特定现象时,这些脚本可以直接用:

# 脚本 1:节点 NotReady 定位#!/bin/bashNODE=$1echo"=== Node$NODEConditions ==="kubectl getnode"$NODE"-ojsonpath='{range .status.conditions[*]}{.type}={.status} reason={.reason} msg={.message}{"\n"}{end}'echo"=== Kubelet 状态(需 SSH 到节点) ==="ssh"$NODE"'systemctl status kubelet --no-pager | head -20'ssh"$NODE"'journalctl -u kubelet --since "10 min ago" --no-pager | tail -50'# 脚本 2:某 Pod 一直 Pending,定位原因#!/bin/bashPOD=$1NS=${2:-default}kubectl describe pod"$POD"-n"$NS"|grep-A20Events# 重点看 FailedScheduling 事件的 message:# - 0/3 nodes are available: 3 Insufficient cpu —— 资源不够# - 3 node(s) had untolerated taint —— 被 taint 挡住# - 3 node(s) didn't match Pod's node affinity —— 亲和性不匹配# 脚本 3:Pod 一直 Terminating,强制清理#!/bin/bashPOD=$1NS=${2:-default}kubectl delete pod"$POD"-n"$NS"--grace-period=0--force# 如果还不行,删掉 finalizer(谨慎!确认不是有 sidecar 在清理)kubectl patch pod"$POD"-n"$NS"-p'{"metadata":{"finalizers":[]}}'--type=merge

三、踩坑与排查

踩坑 1:kubectl logs没输出,但 Pod 明明在跑

现象:kubectl logs <pod>返回空,但kubectl exec进去看应用日志在正常写。

原因:多容器 Pod。kubectl logs <pod>不加-c时,只看第一个容器(或报错要求指定)。多容器 Pod 必须指定-c

解决:

# 看所有容器kubectl logs<pod>--all-containers=true--tail=100# 指定容器kubectl logs<pod>-c<container-name># 跟随kubectl logs<pod>-c<container-name>-f

踩坑 2:-A以为查所有 namespace,结果资源类型对不上

现象:kubectl get deployment -A报错error: a resource cannot be retrieved by name across all namespaces

原因:不是所有资源都是 namespace 级别的。Node、PV、StorageClass、ClusterRole 是集群级资源,没有 namespace 概念,不能用-A。判断方法:

# 看资源是 namespace 级还是集群级kubectl api-resources--namespaced=true# 仅 namespace 级kubectl api-resources--namespaced=false# 集群级

解决:查集群级资源时去掉-A:kubectl get nodeskubectl get pv

踩坑 3:kubectl get -o wide看不到 Pod IP / Node,以为调度失败

现象:kubectl get pods -o wideNODE列是<none>,以为没调度上。

原因:这个 Pod 可能是Pending状态,根本没被调度,所以没分配 node。还有一种情况:刚创建还在调度中,几秒后就有了。判断要用status.phase而不是 NODE 列

解决:

# 看真实调度状态kubectl get pod<pod>-ojsonpath='phase={.status.phase} node={.spec.nodeName}{"\n"}'# Pending 阶段 spec.nodeName 是空的,正常# Running 但 NODE 为空 才是真异常

踩坑 4:kubectl execerror: unable to upgrade connection: container not found

现象:刚创建的 Pod,kubectl exec立刻进去就报这个。

原因:Pod 还没真正 Ready。即使status.phase=Running,容器可能还在启动(比如 init container 跑着,或者主容器刚拉起还没监听)。Ready列要为1/1才表示就绪。

解决:kubectl wait等就绪再 exec:

kubectlwait--for=condition=Ready pod/<pod>--timeout=120s kubectlexec-itpod/<pod>-- /bin/sh

踩坑 5:jsonpath 写法报错error: error parsing jsonpath

现象:kubectl get pods -o jsonpath='{.items[*].metadata.name}'正常,但加循环就报错。

原因:jsonpath 的range语法对引号和换行敏感,\n要写成{"\n"},而且整个表达式要在单引号里(避免 shell 解析)。

解决:记住模板:

# 通用模板kubectl get<resource>-ojsonpath='{range .items[*]}<field1>{"\t"}<field2>{"\n"}{end}'# 单个资源kubectl get<resource><name>-ojsonpath='{.metadata.name}{"\n"}'# 测试 jsonpath 写法时,先 -o json | jq 看结构,再转 jsonpathkubectl get pod<pod>-ojson|jq .status.containerStatuses

四、最佳实践

命令使用层面

  1. 永远先-A再聚焦:全局视图 → 单 namespace → 单 Pod,避免漏掉跨 namespace 的关联问题。
  2. describe 比 get 信息全:看问题先 describe,看 Event 段是排错金矿。
  3. --previous是崩溃排查的命门:容器一崩一拉,当前 logs 看不到崩溃前的现场,必须--previous
  4. 生产别用--force删 Pod:除非真的卡 Terminating,否则让 kubelet 走优雅终止。--force --grace-period=0会跳过 preStop hook,可能丢数据。
  5. jsonpath 调试先jq:-o json | jq看清楚结构,再写 jsonpath,比直接试错快十倍。

配置层面

  1. 多集群用 context +kubectl ctx:不要在一个 kubeconfig 里混淆,用kubectxkubectl ctx明确切换。
  2. 生产禁用--all-namespaces删除:kubectl delete pod -A这种命令绝对不能敲,误删成本极高。
  3. alias 要团队共享:把.kubectl-alias.sh放进 git,新人 onboarding 时source即用,效率对齐。
  4. krew 插件别装太多:装多了kubectl启动变慢(每次都要扫插件目录)。装 5-8 个高频的就够。
  5. CI/CD 里别用 alias:alias 是交互式的,脚本里用全名 +--request-timeout等显式参数。

安全层面

  1. kubectl view-secret慎用:合规环境里,看 Secret 内容要审计。建议用 RBAC 限制get secret的权限,需要看时用专用 service account。
  2. kubectl debug默认是 privileged:生产里 debug 容器要限制权限,避免拿到节点 root。
  3. kubeconfig 别提交 git:~/.kube/config里是集群凭证,泄露即失守。用 vault 或 sealed-secrets 管理。

五、小结

kubectl 的本质是 apiserver 的 HTTP 客户端,它的强大来自三件事:声明式 API(资源即对象)、结构化输出(-o全家桶)、插件生态(krew)。掌握这三件事,排错就从"撞运气"变成"有路径"。

日常排错记住这条主线:广角get -A→ 聚焦describe→ 现场logs --previous→ 验证exec。配合 jsonpath 模板做脚本化告警,配合 alias 做肌肉记忆,配合 krew 插件做能力扩展。这套组合拳练熟了,你的排错速度会和只会get/describe/logs的同事拉开一个数量级。

最后一句:工具是放大器,不是替代品。kubectl 再熟,也要懂背后的原理——apiserver 看到的状态和节点真实状态的差异,才是大部分诡异问题的根源。下一篇讲 Pod 生命周期,我们把这块"状态差异"彻底讲透。

思考题

  1. kubectl get pod显示RunningReady列是0/1,可能的原因有哪些?至少列 3 种。
  2. 写一个 jsonpath,输出所有命名空间里所有restartCount > 0的 Pod,格式为ns/podname restartCount
  3. kubectl describe里的 Event 段最多保留多少条?满了之后老的 Event 会怎样?这对排错有什么影响?

延伸阅读

  • kubectl 官方 cheat sheet
  • JSONPath 语法参考
  • krew 插件索引
  • kubectl 源码与架构
  • 《Kubernetes Operators》——理解声明式 API 设计哲学