
1. 先想清楚一件事kubectl 到底在操作什么我见过不少刚接触 Kubernetes 的同学拿着kubectl get pods能背得滚瓜烂熟但一遇到执行了命令却什么都没发生就彻底抓瞎。问题往往不是命令记错了而是没理解 kubectl 的工作方式。kubectl 本质上是一个 API Server 的客户端。你敲下去的每一条命令都不是直接操作容器或节点而是向 API Server 提交一个请求。比如kubectl delete pod xxx它做的事情是告诉 API Server我希望这个 Pod 被删除。至于 Pod 是不是真的消失、什么时候消失由 kubelet 和控制器去处理。这种声明式的工作模式是理解 kubectl 一切行为的底层逻辑。这也是为什么我建议新人在学习具体命令之前先做两件事第一搞清楚当前 kubectl 连接的是哪个集群、哪个命名空间第二养成先查再改的习惯——不确定状态的时候先get、describe而不是直接delete或者apply。很多生产事故都是因为没看清当前上下文把命令敲到了错误的集群上。1.1 kubeconfig 与 context多集群混用的护身符日常工作中大部分人手里都不止一个集群开发环境、预发环境、生产环境甚至还有自建的、托管的。kubectl 默认读取~/.kube/config这个文件里面可以配置多个集群、多个用户、多个 context然后通过kubectl config use-context来切换。我自己的习惯是给每个环境设置一个简短好记的 context 名并且永远不要只在一条命令里附带--context而是先切过去再看一眼当前 context 是什么。推荐一条命令kubectl config get-contexts这个命令会列出所有 context并标出当前正在使用的那一个。每次操作前跑一下几秒钟的事能避免绝大多数在机房敲错机器的惨剧。提示某些托管的 Kubernetes 服务比如云厂商的托管集群会在 kubeconfig 里自动帮你配好 context命名通常是集群名集群ID用户这种很长的一串。建议用kubectl config rename-context或者直接手动改 kubeconfig把名字改短比如prod、staging、dev操作起来会顺手很多。1.2 Namespace 决定了你看到的世界kubectl 很多命令默认操作的是default命名空间。这意味着你执行kubectl get pods的时候看不到其他命名空间里的 Pod。新手经常困惑为什么我 get 不到资源十有八九是命名空间对不上。这里分享一个我长期使用的组合# 查看所有命名空间的 Pod kubectl get pods -A # 设置默认命名空间省得每次敲 -n kubectl config set-context --current --namespacekube-system第二条命令会把当前 context 的默认命名空间改掉之后所有不带-n的命令都会跑到 kube-system 下去执行。这在只用一两个命名空间的场景下特别好用但在跨团队共享集群时要小心别把别人的 Namespace 的东西误删了。建议设置之前先kubectl config view --minify | grep namespace确认一下当前状态。理解了kubectl 只是 API 客户端 一切有上下文集群、命名空间这两个前提之后后面所有命令学起来都会顺很多。下面进入正题从最常用的查询类命令说起。2. 状态查询三板斧get、describe、explain如果有人让我推荐 kubelet 排查问题最常用的三条命令我会毫不犹豫地说kubectl get、kubectl describe、kubectl explain。这三条命令覆盖了看到什么、现在怎么样、这个字段是什么意思三个层面的需求组合起来可以解决绝大多数状态类问题。2.1 get 的几种实用形态别只会-o widekubectl get是最基础的查询命令但它的输出默认非常吝啬。比如kubectl get pods只显示 NAME、READY、STATUS、RESTARTS、AGE 这几列。要看到更多信息一般会加-o wide能显示出 Pod 的 IP、所在的 Node这对排查网络问题非常关键。除了-o wide我实际工作中经常用的还有几种# 用标签筛选 Pod比如只看有 appnginx 标签的 kubectl get pods -l appnginx # 以 yaml 格式输出适合看完整配置 kubectl get pod nginx-xxx -o yaml # 用 jsonpath 提取单个字段适合脚本里取 IP kubectl get pod nginx-xxx -o jsonpath{.status.podIP}-o jsonpath这个用法值得多说一句。很多人写脚本需要从资源里提取某个值不会用 jsonpath 就只能先-o yaml再 grep麻烦且容易出错。其实 K8s 的 API 对象就是一个结构化数据用 jsonpath 可以精确取到任意字段。比如查看某个 Deployment 的副本数kubectl get deployment nginx -o jsonpath{.spec.replicas}输出就是一个数字非常干净可以直接赋值给 shell 变量。2.2 describe 才是排障的第一工具不是 get很多人一上来就kubectl get pods看到状态是CrashLoopBackOff就开始慌然后到处问。其实真相藏在kubectl describe pod里。describe会输出一个资源对象的详细事件流包括最近的事件Events、容器启动命令、挂载的卷、节点调度结果、镜像拉取情况等。排障时最需要关注的是最后面那一段 Events它会直接告诉你为什么这个 Pod 没有被调度、为什么镜像拉取失败、为什么探针失败了。举一个真实场景某天线上 Pod 一直处于 Pending 状态get看不出任何端倪但describe里一行字就暴露了问题——0/3 nodes are available: 3 Insufficient cpu。这说明集群节点 CPU 资源不足Pod 无法被调度。如果没有 describe光靠 get 你永远不知道卡在哪一步。另一个必须用 describe 的场景是查看 Node 的状态详情kubectl describe node node-name这里面会展示节点的资源总量、已分配量、剩余量以及节点上的所有 Pod 列表。判断节点是不是被打满了看这个比看监控面板还快。注意describe展示的是资源对象的状态和事件不是完整的配置。如果你想看完整的、可导出的配置内容应该用-o yaml。两者各司其职排障时建议先 describe再按需看 yaml。2.3 explain 与 api-resources遇到不认识的资源怎么办K8s 的 API 资源种类非常多CustomResourceDefinitionCRD还会引入各种自定义资源。遇到一个不认识的资源类型先别急着百度用官方自带的两条命令就能查明白# 查看集群支持的所有资源类型 kubectl api-resources # 查看某个资源的字段定义比如 pod 的 spec 支持哪些字段 kubectl explain pod.speckubectl explain适合在写 yaml 的时候用比如你记不住securityContext下面有哪些字段直接kubectl explain pod.spec.securityContextK8s 会把每个字段的含义、类型、默认值列得清清楚楚比翻文档还方便。3. 文件进出集群kubectl cp 的细节与坑kubectl cp是热搜词之一也是日常使用频率很高但坑点密集的命令。作用是在本地和 Pod 之间拷贝文件用法类似scp和cp的组合。3.1 基本语法与双向拷贝# 从本地拷到 Pod 内 kubectl cp ./local-file.txt namespace/pod-name:/remote/path/ # 从 Pod 内拷到本地 kubectl cp namespace/pod-name:/remote/path/ ./local-path/如果 Pod 所在的 Namespace 就是当前默认 Namespace可以省略前缀直接写pod-name:。一个很容易忽略的地方kubectl cp其实是在本地执行 tar 命令打包文件然后在容器内解包来实现的。所以容器内必须存在tar这个二进制如果镜像比较精简比如基于 scratch 或者 distroless 的镜像cp会报错这是第一个大坑。3.2 三个高频坑tar 依赖、符号链接、路径分隔符先说 tar 依赖。如果容器里没有 tarkubectl cp 会报类似 tar: not found 之类的错误。解法有两种一是换一个有 tar 的临时容器比如kubectl debug二是用kubectl exec配合重定向来实现拷贝比如# 将本地文件内容写入 Pod 内文件 cat local-file.txt | kubectl exec -i pod -- sh -c cat /tmp/remote-file.txt # 从 Pod 内把文件内容读出来 kubectl exec pod -- cat /tmp/remote-file.txt local-file.txt这种方式不依赖 tar但要小心只适用于文本文件如果是二进制文件编码转换可能会出问题。再说符号链接。默认情况下kubectl cp 会保留符号链接本身而不是解引用。这会导致你拷出来的文件是一个断链内容却是空的。如果你希望拷贝实际指向的文件内容需要先手动解引用或者在容器里先用cp -L处理一下。最后一个坑是路径分隔符。Windows 用户尤其容易踩kubectl cp的目标路径如果有反斜杠\有时会被转义出问题。建议统一使用绝对路径并且用正斜杠写远程路径。3.3 大文件拷贝特别慢先想想走的是什么网络kubectl cp的性能取决于 API Server 到容器之间的链路。如果 APIServer 和节点之间网络受限大文件拷贝会非常慢有时甚至会超时中断。遇到这种情况我更推荐用下面这种方案先kubectl exec进入容器用cat把文件内容打印出来配合本地重定向写入或者更直接一点如果容器内有sftp或scp直接走节点 IP绕开 API Server。但考虑到很多容器里根本没有这些工具最靠谱的大文件方案其实是把文件先做成 ConfigMap 或挂载到共享存储然后让 Pod 挂载读取。不过这属于架构层面的调整临时救急还是用kubectl exec配合重定向最稳。经验小文件几十 KB 以内用kubectl cp没问题大文件几十 MB 以上优先考虑共享存储工具链不齐的容器用kubectl exec重定向方案兜底。这套思路应对 90% 的文件拷贝场景足够了。4. 看清节点全貌kubectl get nodes 的详细输出与故障定位kubectl get nodes 显示详细信息在热搜词里出现得很自然——因为节点状态直接决定了你的 Pod 能不能跑起来而get nodes默认输出确实太简单了就 NAME、STATUS、ROLES、AGE、VERSION 五列。4.1 节点状态列里的信息量Ready、NotReady、SchedulingDisabled先用kubectl get nodes看一下状态可能出现的几种值Ready节点一切正常kubelet 心跳正常可以调度 Pod。NotReadykubelet 和 API Server 失联了通常意味着节点负载过高、kubelet 挂了或者网络隔离。SchedulingDisabled节点被 cordon封锁了已运行的 Pod 不受影响但新的 Pod 不会调度上去一般用于节点维护前主动摘流量。我见过不少新手在 NotReady 节点上重启 Pod结果越重启越多因为所有 Pod 都会被调度到其他空闲节点上。正确做法是先看节点为什么 NotReady而不是先折腾 Pod。4.2 用-o wide和-o json挖出更深层的节点信息kubectl get nodes -o wide会多显示几列节点的内部 IP、外部 IP、操作系统、内核版本、容器运行时版本。这些信息在排查跨节点网络、版本兼容问题时非常有用。如果想要更结构化的数据用-o json或者-o jsonpath。比如我想快速看所有节点的内存总量和可分配量kubectl get nodes -o jsonpath{range .items[*]}{.metadata.name}{\t}{.status.capacity.memory}{\t}{.status.allocatable.memory}{\n}{end}这条命令会输出一个表格节点名、内存总量、可分配内存。类似的思路可以扩展到 CPU、存储等维度写个 shell 脚本就能做成一个简易的集群资源盘点工具。4.3 describe node 到底在看什么很多人对kubectl describe node不太重视其实它是排查节点问题的最强工具没有之一。输出大致分几块Conditions节点状态清单包括 MemoryPressure、DiskPressure、PIDPressure、Ready 等。只要有一项为 True就说明节点处于某方面的压力中会影响调度决策。ResourcesCPU、内存、存储的资源总量和已分配量。这里的已分配量统计的是所有 Pod 的 requests 值你能直接看出节点是不是超卖了。Non-terminated Pods当前节点上所有还在运行的 Pod 列表包括每个 Pod 占用的资源。Events节点生命周期里的关键事件比如 kubelet 重启、磁盘空间不足、镜像清理等。排障的思路一般是先看 Conditions 里有没有 True再看 Resources 是不是快满了对照 Events 时间线判断问题是从什么时候开始的。4.4 taint 与 toleration为什么 Pod 就是不上某个节点有时候节点明明 Ready但某些 Pod 就是不会调度上去这大概率是 taint污点在起作用。kubectl describe node的 Taints 字段会显示节点上的污点信息比如常见的node.kubernetes.io/not-ready或者用户自定义的dedicatedspecial:NoSchedule。Pod 必须声明匹配的 toleration 才能容忍这个污点。查看 Pod 是否声明了 tolerationkubectl get pod pod-name -o yaml | grep -A5 tolerations如果 Pod 的调度需求是必须运行在某些特定节点上除了 taint/toleration还可能用了 nodeSelector 或 nodeAffinity。遇到调度相关的问题先看这三个地方再结合 describe 里的 Events基本就能定位原因。5. 日志、交互与端口打通logs、exec、port-forward这一组命令是日常排查和应用调试的法宝。如果说前面那些命令是查看状态那么这几个就是钻进现场直接和容器里的进程对话。5.1 logs 常用参数--tail、-f、--previouskubectl logs是用来查看容器标准输出日志的命令。但直接kubectl logs pod会输出全部日志量大不说也很费资源。我常用的几个参数组合# 只看最后 200 行 kubectl logs pod --tail200 # 实时跟踪日志输出 kubectl logs -f pod # 查看上一个崩溃容器的日志这是排查 CrashLoopBackOff 的关键 kubectl logs pod --previous--previous这个参数极其关键。当容器崩溃重启之后当前容器的日志是空的但崩溃前那个容器的日志还在用--previous就能看到崩溃前的完整堆栈。排查容器反复重启类问题第一件事就是跑这条命令。如果 Pod 里有两个容器需要加-c container-name指定容器名否则 kubectl 会报错提示你指定。5.2 exec 进入容器交互式会话与单条命令执行kubectl exec的使用非常频繁。进入容器后可以查看进程、检查配置、手动发请求等。# 交互式进入容器 kubectl exec -it pod -- /bin/sh # 单条命令执行适合脚本里用 kubectl exec pod -- env有一个细节如果容器里没有/bin/sh而是只有 bash那就换成-- /bin/bash有些精简镜像两个都没有那就没办法用 exec 交互进入了。这时候可以借助kubectl debug为容器注入一个临时工具容器但涉及镜像和配置生产环境要谨慎使用。5.3 port-forward把集群里的服务拉到本地调试kubectl port-forward可以把 Pod 或 Service 的端口映射到本地端口非常适合不经过 Ingress 和负载均衡直接调试服务接口。# 把本机 8080 端口映射到 Pod 的 80 端口 kubectl port-forward pod 8080:80然后用curl localhost:8080就能访问到集群内部的服务。这个命令对访问一些只开了 ClusterIP、没有暴露到外部的服务特别有用。注意port-forward 走的是 API Server 转发只适合单机调试、请求量小的场景。压测或者长期联调不要用 port-forward否则 API Server 会顶不住应该用 Service Ingress 或 LoadBalancer 暴露。5.4 一个真实的组合排查场景假设线上报了一个某个接口偶尔超时的问题。我的排查流程一般是# 1. 找到出问题的 Pod kubectl get pods -l appxxx -o wide # 2. 看日志里有没有异常堆栈并实时跟踪 kubectl logs -f pod --tail100 # 3. 进入容器检查本地网络和进程 kubectl exec -it pod -- /bin/sh # 在容器内执行 netstat、curl 等命令定位 # 4. 如果要在本地复现用 port-forward 把服务拉出来 kubectl port-forward pod 8080:8080整个过程不用切换任何工具全靠 kubectl 一条命令一条命令地推进非常顺滑。6. 发布与回收apply、delete、rollout 的日常节奏前面讲的是看和进现在讲改。日常发布、回滚、清理资源基本离不开这三组命令。6.1 apply 的声明式逻辑与 last-applied-configurationkubectl apply -f deployment.yaml是目前主流的资源变更方式。它的底层逻辑是把 yaml 里的配置作为期望状态与集群里的当前状态做一次三方合并然后只变更差异部分。这个三方合并用的是资源对象上的一个注解kubectl.kubernetes.io/last-applied-configuration。每次 apply 时K8s 会把这个注解更新成你这次的 yaml 内容后续再 apply 时它会以此为基础计算需要删除或修改哪些字段。这个机制有个坑如果你用kubectl edit或者kubectl patch改了资源内容下次再apply旧 yaml 时K8s 可能会把你手动改的部分覆盖掉因为它认为上次 apply 时没有这些字段应该删除。要避免这种问题关键原则是所有变更尽量走同一个入口要么全部用 apply要么全部用 edit/patch不要混着来。6.2 delete 的优雅销毁与 finalizer 陷阱kubectl delete默认会下发一个优雅删除请求给 Pod 一个期限默认为 30 秒来处理退出前逻辑。如果超过期限还没退出kubelet 会强制杀掉容器。# 强制删除不等待优雅退出 kubectl delete pod pod --force --grace-period0但--force只适用于 Pod。对于删除 Namespace、PVC、CRD 这类资源可能遇到一直删不掉的情况元凶通常是 finalizer。finalizer 是资源对象上的一组预删除钩子只要 finalizer 没清空资源就会一直处于 Terminating 状态。遇到这种情况可以先kubectl get resource name -o json查看metadata.finalizers字段确认是哪个 finalizer 卡住了。查找相关控制器让它清理。如果只是想快速销毁一个资源可以手动编辑 yaml 把 finalizers 字段置空后再删但务必搞清楚 finalizer 对应的清理逻辑是什么否则会造成孤儿资源。6.3 rollout 系列发布与回滚的正确姿势Deployment 是日常发布最常用的工作负载类型。kubectl rollout是专门管理发布过程的命令。# 查看发布进度 kubectl rollout status deployment/nginx # 暂停发布比如做金丝雀验证时 kubectl rollout pause deployment/nginx # 恢复发布 kubectl rollout resume deployment/nginx # 回滚到上一个版本 kubectl rollout undo deployment/nginx # 查看发布历史 kubectl rollout history deployment/nginxrollout status会阻塞当前终端直到发布完成或者超时非常适合写进 CI/CD 脚本里做发布校验。rollout undo回滚时可以通过--to-revision指定回滚到某个历史版本。这里也提醒一句kubectl rollout restart deployment/nginx可以无变化强制滚动重启在更新 ConfigMap 后需要重启 Pod 加载新配置的场景非常实用。记住这个命令能省掉手工删一堆 Pod 的功夫。6.4 效率技巧alias 和常用组合最后分享几个我自己长期在用的效率技巧。# alias 日常命令大幅减少敲击量 alias kkubectl alias kgpkubectl get pods alias kgdkubectl get deploy alias kdkubectl describe alias klkubectl logs alias kexeckubectl exec -it # 给补全功能加上zsh/bash 都支持 source (kubectl completion bash) # bash 用户 source (kubectl completion zsh) # zsh 用户我个人的使用节奏是日常查看用 alias操作类命令还是用全名因为涉及生产变更时打全命令反而能强迫自己多想几秒降低误操作概率。另外所有涉及变更的命令我都习惯先加个--dry-runclient -o yaml看看效果确认无误后再真正执行。这个习惯帮我避免了好几次手滑删错资源的惨剧。经验kubectl apply --dry-runclient -o yaml -f xxx.yaml能在下发请求前展示这次变更的效果但注意它只是客户端本地模拟不保证集群侧校验结果完全一致。更严格的校验可以用--dry-runserver需要集群支持。按这套习惯操作下来kubectl 从背命令变成表达意图对集群的理解也会慢慢深入。多摸、多敲、多看 describe 和 yaml 里的字段比翻十篇教程都管用。