1. 动手之前的准备:安装方式与kubeconfig配置文件解析
每次聊到kubectl,我都习惯先提醒一句:别急着背命令,先搞清楚你手里的这个客户端到底是怎么连上集群的。kubectl本质上是Kubernetes的官方命令行工具,它做的事情可以用一句话概括——把你在终端里敲的指令翻译成对API Server的HTTP请求,然后由API Server去调度集群里的各种资源。所以你会发现,kubectl本身并不直接操作容器,它只是一个面向Kubernetes API的客户端。
作为运维或平台工程师,我和kubectl打交道的频率高过绝大多数工具。从看Pod状态、查日志,到发布应用、排查故障,几乎每天都在用。这些年用下来,最大的体会是:kubectl的命令再多,真正高频、核心的就那么几十条,只要把底层逻辑和常用场景吃透,完全可以覆盖日常工作。这篇文章就把我平时最常用的命令和踩过的坑整理出来,顺便聊聊很多人关心的配置文件问题。
1.1 安装kubectl:别小看这一步
安装kubectl本身不复杂,但你会遇到第一个问题:版本。kubectl和集群的版本不建议相差太大,官方建议是允许kubectl版本比集群的minor version大一或小一。实际操作中,我一般直接装跟集群相同或者更新一点点的版本,避免出现API资源列表识别不了的情况。
Linux下最常见的安装方式就是下载官方二进制并放到PATH里,然后chmod加执行权限。macOS可以用brew install kubectl。如果本地装了Docker Desktop,它自己会带一个kubectl,但那个版本往往偏老,我建议还是单独装一份,避免混淆。
装完后先验证一下版本,观察kubectl version输出的Client Version和Server Version。如果Server Version那一栏显示Unable to connect,那说明kubectl本身没问题,问题出在连不上集群,这就要去看kubeconfig配置文件了。
1.2 kubeconfig配置文件:kubectl连接集群的钥匙
这也是为什么会有人在问“gitlab怎么设置kubectl配置文件”的根本原因——kubeconfig(通常默认路径是~/.kube/config)就是kubectl用来确定“连接哪个集群、用什么身份、切换哪个命名空间”的唯一依据。
一个标准的kubeconfig文件由三块核心内容组成:
- clusters:集群信息。里面保存的是API Server的访问地址(server字段)和证书信息(certificate-authority-data字段)。
- users:用户凭证。包括客户端证书、私钥,或者token等身份凭据。
- contexts:上下文。它把cluster和user组合到一起,同时指定默认的namespace。你可以理解为“用哪个身份、连哪个集群、进哪个命名空间”的三元组。
打个比方,clusters是门牌号,users是你手里的钥匙,contexts则是“你拿着这把钥匙去开这个门牌号对应的门,然后直接走进某个房间”的完整路线。kubectl实际工作时会读config文件里的current-context字段,找到当前上下文,再根据上下文里指定的cluster和user去建立连接。
这里有个实际场景值得多说一句:很多人会不小心把集群的admin证书塞进kubeconfig,然后到处分发。从安全角度来说,这是我不太建议的,最小权限原则在任何地方都适用。给开发或CI/CD环境用的kubeconfig,最好单独创建只读或限定命名空间的ServiceAccount,然后把对应的token分发出去。
1.3 多集群与KUBECONFIG环境变量:一份配置走天下
如果你手上同时有好几个集群,比如一个测试环境、一个预发环境,还有一个生产环境,那靠kubectl config view来回翻context就会效率很低。我常用的配置方式是利用KUBECONFIG环境变量,把多个config文件合并起来。
做法是这样的:
export KUBECONFIG=/root/.kube/config:/opt/k8s/test.conf:/opt/k8s/prod.conf kubectl config get-contextsget-contexts会把所有config文件里的context都列出来,然后你可以用一句话完成切换:
kubectl config use-context prod切完上下文后,再执行kubectl get pods,它就会去连接生产集群。用这个方案,你再也不用把好多个config文件来回覆盖到~/.kube/config里了。我现在是把所有context名称约定成“环境名-集群名”的格式,比如test-cluster、prod-cluster,一眼就能认出当前在哪个环境。
还有一个小技巧:为了避免在某台机器上误操作生产环境,我习惯给生产环境的context单独配置一个特殊的KUBECONFIG变量,并且只在需要操作生产时才export它。配合kubectl config current-context确认当前位置,基本不会出现“明明想操作测试集群,结果把生产环境搞挂”的悲剧。
2. 日常使用频率最高的kubectl命令
命令不用背太多,但常用的这几个你得形成肌肉记忆。我把它们分成“查看类”“变更类”“运维类”三组,这样记忆起来更有条理。
2.1 查看类三件套:get、describe、logs
kubectl get是最基础的查询命令,负责以列表形式展示资源状态。平时我用得最多的对象是pod、node、deployment、service、event。
kubectl get pods kubectl get pods -n kube-system kubectl get nodes -o wide kubectl get deploy,svc -l app=nginx-o wide会多显示一些字段,比如Pod所在的节点IP、Pod的IP,排查网络问题时几乎必用。至于-n参数,没什么好说的——不指定命名空间的话,只看default命名空间,很多资源你会“找不到”,但其实它就在别的namespace里待着。
kubectl describe适合看一个资源的详细信息,比如事件、标签、状态变化过程。我很少对着get的输出猜问题,因为get只显示当前状态,不显示历史事件。真正排查Pod为什么起不来,我会先describe一下:
kubectl describe pod nginx-xxxxx重点看Events这一段。常见的情况是镜像拉取失败(ImagePullBackOff)、健康检查失败(Liveness probe failed)、资源不足(FailedScheduling)等,原因基本在Events里都写得明明白白。
kubectl logs用来查看容器日志,你要是看过Pod里多个容器的情况,得用-c指定容器名:
kubectl logs -f pod-name -n namespace kubectl logs pod-name -c container-name --tail=50-f是实时跟踪,--tail是只显示最后多少行,这两个参数组合起来用,排查问题的时候效率很高。如果容器之前崩溃过,想查上一次进程的日志,记住加-p参数:
kubectl logs pod-name -p这个-p参数很多人容易漏,导致查不到崩溃原因。
2.2 变更类:apply、edit、patch、delete
再接着说变更操作。我以前习惯用kubectl create来创建资源,但后来发现,如果要做“有则更新,无则创建”这种幂等操作,kubectl apply才是正道。特别是持续集成环境里,反复执行同一个YAML,用apply完全不会报错。
kubectl apply -f deployment.yaml kubectl apply -f ./manifests/线上临时改镜像版本,我不太喜欢打开编辑器去edit,直接用set命令最快:
kubectl set image deployment/nginx nginx=nginx:1.25 -n default不过如果你还是习惯像vim那样改,kubectl edit也行,它会把当前资源的定义拉下来变成YAML,保存后自动apply。注意一点,edit是直接改live object,如果资源和当前声明的配置有冲突,保存时会因为冲突报错,这时候可以用patch去做局部字段的修改。
kubectl patch适合精准修改某个字段,尤其是改一些嵌套很深、用set很难表达的参数。示例:
kubectl patch deployment nginx -p '{"spec":{"replicas":3}}'这段命令的意思是把nginx这个Deployment的副本数改成3。patch处理JSON格式,注意引号转义,习惯了也就不觉得麻烦。
删除资源的命令看起来简单,但坑不少。kubectl delete会先把资源标记为删除状态,然后看资源是否有finalizer,如果finalizer没处理完,资源会一直处于Terminating状态。遇到Terminating的Pod卡住,可以用强制删除:
kubectl delete pod xxx --force --grace-period=0但这里要提醒一句:强制删除Pod会导致Pod里正在处理的任务被硬生生掐断,不到万不得已,别乱用。
2.3 手动运维:exec、port-forward、cp
进到容器内部去调试,在Docker时代是docker exec,在Kubernetes时代就是kubectl exec:
kubectl exec -it pod-name -- /bin/sh kubectl exec -it pod-name -c sidecar-container -- /bin/sh-c指定容器这点在Pod里有多个容器时非常重要。我在Pod里查完问题之后,通常先敲exit再退出,而不是直接关闭终端,否则偶尔会留下异常会话。
端口转发是另一个高频命令。比如你本地想访问集群里的某个服务,但服务没有对外的NodePort或LoadBalancer,可以直接用port-forward把远端端口映射到本地:
kubectl port-forward svc/nginx-service 8080:80执行完这条,访问本地的8080端口,就等于访问集群里nginx-service的80端口。调试阶段特别好用,但别把它当成生产环境的流量入口——port-forward走的是kubectl建立的隧道,稳定性跟网络直接相关,扛不住生产流量。
文件拷贝我平时用得也很多,比如把Pod里的日志拉出来或者把本地配置传进去:
kubectl cp pod-name:/var/log/app.log ./app.log kubectl cp ./config.yaml pod-name:/etc/app/config.yamlkubectl cp的路径规则跟scp很像,但有个细节:如果Pod里有多容器,同样用-c指定容器,否则容易拷错容器,这事我踩过一次,传文件传到sidecar里,找了半天才发现。
3. 把kubectl用出效率的进阶技巧
命令本身不难,真正拉开效率差距的是组合使用和输出处理。下面这几个技巧是我日常使用频率最高、最能节省时间的。
3.1 自定义输出列:只看你想看的信息
kubectl get默认会有一列输出,但有时候字段太多,有时候字段又不够。没有捷径,用-o custom-columns就好:
kubectl get pods -o custom-columns=NAME:.metadata.name,IP:.status.podIP,NODE:.spec.nodeName这个表达方式简洁地指定了要输出的列。开发环境中我常用这个组合来看所有Pod分布在哪些节点上,一轮肉眼扫描就能发现问题。
另一个极其强大但很多人没用起来的是JSONPath。配合-o jsonpath可以取出某个字段的值。比如要获取某个Deployment的副本数:
kubectl get deploy nginx -o jsonpath='{.spec.replicas}'再配合shell的循环,你就可以写一些很小但很实用的自动化脚本。比如批量将所有不符合规则的Deployment副本数统一调整。这种能力在日常巡检时很有价值。
如果要沉淀成表格或报告,可以用-o wide或-o yaml导出完整定义,再用yq或jq去解析,比人眼盯着终端输出稳得多。我自己比较常用的习惯是:先kubectl get xxx -o yaml > xxx.yaml,再用编辑器打开,做信息提取或备份。
3.2 标签选择器与字段选择器:从全量中筛出目标
集群里Pod一多,kubectl get pods不带过滤条件输出几百行,基本等于没查。我习惯用标签选择器(-l)来缩小范围:
kubectl get pods -l app=nginx,env=prod kubectl get pods -l 'tier in (frontend,backend)'标签是Kubernetes里推荐的资源关联方式。平时创建Deployment、Service时,我会提前约定好环境、应用、版本这组标签规范,后续用标签做筛选就非常顺手。
除了标签,还可以用--field-selector按字段过滤。比如查看某个节点上的所有Pod:
kubectl get pods --field-selector spec.nodeName=node01或者查所有处于Failed阶段的Pod:
kubectl get pods --field-selector status.phase=Failed标签选择器和字段选择器可以一起用,我自己做故障演练时经常这样组合,能很快圈定一台节点上的异常Pod范围,然后逐一排查。
3.3 命名空间批处理与上下文操作
想要“一键查询所有命名空间里的某个资源”,可以加上--all-namespaces(简写是-A):
kubectl get pods -A kubectl get events -A --sort-by=.lastTimestamp-A这个参数太重了要慎用,但排查那种“全局范围找不到哪个命名空间的Pod在报错”时特别有效。配合--sort-by排序后,最新发生的异常事件会排在最上面,我一般先看Events,再看报错时间点前后的Pod日志,基本就能定位。
如果你经常在某几个命名空间之间切换,kubectl config set-context可以直接修改当前context的默认namespace:
kubectl config set-context --current --namespace=dev这比每次敲命名空间省事多了。但小心,这个设置是持久的,下次在这个context里执行命令还是会落到dev命名空间里,如果临时切到别的环境忘了改回来,可能就把资源建错地方了。
3.4 自动补全与别名:终端效率翻倍
kubectl的命令参数又多又长,手敲很费劲。我强烈建议开启自动补全。以bash为例:
source <(kubectl completion bash) echo "source <(kubectl completion bash)" >> ~/.bashrczsh用户就换成kubectl completion zsh。补全之后,敲kubectl get po再加两下Tab键,namespace、资源名都会自动提示,节省的时间相当可观。
别名这件事,我的建议是只给那些你自己确实高频使用的命令配别名,不要贪多。我自己的几个常用别名供参考:
alias k='kubectl' alias kgp='kubectl get pods' alias kgd='kubectl get deploy' alias ksys='kubectl -n kube-system' alias kdesc='kubectl describe'注意一点:如果用了类似kubectx和kubens这类切换工具,别名系统会跟它们有交互,自己按照习惯调整即可。
4. 生产环境排障:从Pod事件到核心命令的串联使用
命令单个拿出来都好理解,难的是在出问题时把这些命令串成一条排障流水线。下面分享一下我的排查思路和几个高频问题的处理方案。
4.1 一次Pod故障的排查路径
假设现在有个Pod一直处于CrashLoopBackOff状态,我会按下面的次序来排查:
第一步,先get pods看看整体状态,确认是哪个命名空间下的哪个Pod出了问题。
kubectl get pods -A | grep CrashLoopBackOff第二步,describe这个Pod,重点看Events和Status里的信息。Events里如果有Back-off restarting failed container,说明容器启动后不断退出;如果看到Failed to pull image,那基本是镜像仓库认证或镜像名写错的问题。
第三步,看日志。如果容器还在运行或者刚崩溃,用kubectl logs调试失败原因:
kubectl logs -f pod-name --previous--previous参数能看到上一次容器退出前的日志,这一步往往能直接看到业务报错。如果是应用启动时报配置错误,就在这里暴露无遗。
第四步,如果日志没输出,可能是健康检查失败导致容器被杀。手动进入容器看一眼进程情况:
kubectl exec -it pod-name -- /bin/sh进去后检查进程是否存在、监听端口是否正常。有时候应用起来比较慢,而readinessProbe的超时时间设得太短,也会导致Pod被反复重启。这种问题在describe里也能看到线索。
第五步,结合事件中的调度信息确认资源是否充足。如果Events里有FailedScheduling且提示Insufficient cpu或Insufficient memory,那就是节点资源不足,要么扩容节点,要么调低资源请求。
这套流程走完,90%以上的Pod异常都能找到原因。剩下的那10%,大多是集群层面的问题,这时就需要去查节点状态、网络组件、存储卷之类的内容,那是另一个话题了。
4.2 常见报错速查:问题现象与原因对照
我把工作中高频见过的kubectl报错做了一个整理,方便对照参考。
| 报错信息 | 常见原因 | 排查方向 |
|---|---|---|
| Unable to connect to the server: dial tcp ... i/o timeout | 集群APIServer网络不通或安全组拦截 | 检查本机到APIServer地址的连通性,telnet或nc测试对应端口 |
| server returned 401 Unauthorized | kubeconfig里的token过期或证书不匹配 | 重新生成ServiceAccount的token,或更新config里的client-certificate |
| server returned 403 Forbidden | 当前用户的RBAC权限不足 | 检查RoleBinding或ClusterRoleBinding,确认用户是否有相应命名空间的权限 |
| No resources found | 命名空间写错,或该命名空间下确实没有这个资源 | 确认-n参数,或改用-A全命名空间查看 |
| error: You must be logged in to the server | 未设置KUBECONFIG或config文件为空 | 检查KUBECONFIG环境变量和~/.kube/config是否存在 |
| The connection to the server ... was refused | APIServer端口拒绝连接或服务未监听 | 在集群节点上检查kube-apiserver进程与443端口状态 |
| pod has unbound PersistentVolumeClaims | PVC未绑定PV | 检查StorageClass和PV状态,确认动态供给是否正常 |
| Error from server: etcdserver: request timed out | 集群存储层出现性能问题或etcd不稳定 | 检查etcd集群健康状态、磁盘IO和节点负载 |
上面这些都是实际工作中比较高频遇到的。我的经验是,报错本身不可怕,关键是先判断是client端问题还是server端问题。凡是出现Unable to connect、Unauthorized,先查client侧的网络和配置;凡是出现FailedScheduling、ImagePullBackOff,再去查集群内部。
4.3 在GitLab CI/CD中设置kubectl配置文件的实操
最后来回答一下很多人关心的“gitlab怎么设置kubectl配置文件”这个问题。实际上GitLab本身不会直接操作Kubernetes集群,它需要在CI/CD的job里让kubectl具备访问集群的能力。最稳妥的办法是在GitLab项目的CI/CD Variables里保存KUBECONFIG的内容,然后在job里写入指定路径。
第一步,把kubeconfig文件内容作为变量存到GitLab里。打开项目的Settings -> CI/CD -> Variables,添加一个变量,类型选择File,变量名建议叫KUBECONFIG,然后粘贴kubeconfig文件的内容。选File类型的好处是,GitLab会在runner上把它生成一个临时文件,并把文件路径传给CI,这样比直接把内容写到代码仓库安全得多。
第二步,在.gitlab-ci.yml里用这个变量。示例配置:
deploy-job: stage: deploy before_script: - mkdir -p $HOME/.kube - cp $KUBECONFIG $HOME/.kube/config script: - kubectl get namespaces - kubectl apply -f manifests/第三步,是安全性设置。如果你的runner是共享的,我建议不要在Runner上长期保留任何kubeconfig文件。更好的做法是基于短期token来动态生成kubeconfig。具体思路是在job里先使用一个只拥有临时权限的token创建config,然后执行完任务后立即删除。这部分逻辑可以写成一个脚本作为公共模板复用:
cat <<EOF > $HOME/.kube/config apiVersion: v1 kind: Config clusters: - cluster: server: ${K8S_APISERVER} certificate-authority-data: ${K8S_CA_DATA} name: gitlab-cluster users: - name: gitlab-user user: token: ${K8S_TOKEN} contexts: - context: cluster: gitlab-cluster user: gitlab-user namespace: ${CI_ENVIRONMENT_NAME} name: gitlab-context current-context: gitlab-context EOF这里把APIServer地址、CA证书、token全部通过CI/CD变量注入,安全性和灵活性都得到了保障。而且注意,我将namespace设置为当前环境名,这样同一份模板可以同时用在test、preview、production多个环境上,互不干扰。
这种做法的好处是显而易见的:不用把集群管理员凭证长时间暴露在CI系统里,每个环境仅用最小权限的token,出现安全问题时回收凭证也很方便。从我接触过的项目来看,很多团队早期图省事直接拿admin kubeconfig塞进GitLab,后面权限一旦泄露,整个集群都暴露在风险之中,所以这个成本值得花。
最后再分享一个实际工作里的细节:CI里执行kubectl apply之后,建议加一步检查部署状态,而不是apply完就算结束。可以在job里循环等待Rollout完成:
kubectl rollout status deployment/my-service -n my-namespace --timeout=2m这条命令会阻塞到Deployment滚动更新完成或超时。如果超时,CI job会直接失败,这比发布完才发现服务没起来要舒服得多。很多真实事故的核心根源,就是发布脚本只做了apply,没有做rollout status校验,结果上线报错半天后才被监控发现。这个习惯我现在一直保留着,也建议读到这里的各位写进团队模板里。