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 --namespace=kube-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 wide
kubectl get是最基础的查询命令,但它的输出默认非常"吝啬"。比如kubectl get pods只显示 NAME、READY、STATUS、RESTARTS、AGE 这几列。要看到更多信息,一般会加-o wide,能显示出 Pod 的 IP、所在的 Node,这对排查网络问题非常关键。
除了-o wide,我实际工作中经常用的还有几种:
# 用标签筛选 Pod,比如只看有 app=nginx 标签的 kubectl get pods -l app=nginx # 以 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 资源种类非常多,CustomResourceDefinition(CRD)还会引入各种自定义资源。遇到一个不认识的资源类型,先别急着百度,用官方自带的两条命令就能查明白:
# 查看集群支持的所有资源类型 kubectl api-resources # 查看某个资源的字段定义,比如 pod 的 spec 支持哪些字段 kubectl explain pod.speckubectl explain适合在写 yaml 的时候用,比如你记不住securityContext下面有哪些字段,直接kubectl explain pod.spec.securityContext,K8s 会把每个字段的含义、类型、默认值列得清清楚楚,比翻文档还方便。
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 依赖。如果容器里没有 tar,kubectl 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。
- NotReady:kubelet 和 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,就说明节点处于某方面的压力中,会影响调度决策。
- Resources:CPU、内存、存储的资源总量和已分配量。这里的已分配量统计的是所有 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或者用户自定义的dedicated=special:NoSchedule。
Pod 必须声明匹配的 toleration 才能容忍这个污点。查看 Pod 是否声明了 toleration:
kubectl 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、--previous
kubectl logs是用来查看容器标准输出日志的命令。但直接kubectl logs <pod>会输出全部日志,量大不说,也很费资源。我常用的几个参数组合:
# 只看最后 200 行 kubectl logs <pod> --tail=200 # 实时跟踪日志输出 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 app=xxx -o wide # 2. 看日志里有没有异常堆栈,并实时跟踪 kubectl logs -f <pod> --tail=100 # 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-configuration
kubectl 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-period=0但--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 k='kubectl' alias kgp='kubectl get pods' alias kgd='kubectl get deploy' alias kd='kubectl describe' alias kl='kubectl logs' alias kexec='kubectl exec -it' # 给补全功能加上,zsh/bash 都支持 source <(kubectl completion bash) # bash 用户 source <(kubectl completion zsh) # zsh 用户我个人的使用节奏是:日常查看用 alias,操作类命令还是用全名,因为涉及生产变更时,打全命令反而能强迫自己多想几秒,降低误操作概率。另外,所有涉及变更的命令,我都习惯先加个--dry-run=client -o yaml看看效果,确认无误后再真正执行。这个习惯帮我避免了好几次"手滑删错资源"的惨剧。
经验:
kubectl apply --dry-run=client -o yaml -f xxx.yaml能在下发请求前展示这次变更的效果,但注意它只是客户端本地模拟,不保证集群侧校验结果完全一致。更严格的校验可以用--dry-run=server,需要集群支持。
按这套习惯操作下来,kubectl 从"背命令"变成"表达意图",对集群的理解也会慢慢深入。多摸、多敲、多看 describe 和 yaml 里的字段,比翻十篇教程都管用。