1. 从“只会敲命令”到“理解集群”:我的K8s运维思维转变

刚接触Kubernetes那会儿,我和很多新手一样,沉迷于收集各种命令。笔记本里记满了kubectl get podskubectl describe node,以为把这些命令背熟了就是合格的运维。直到有一次线上服务突然出现大面积访问超时,我手忙脚乱地敲了一堆查看命令,看着满屏的Pod状态和事件列表,却完全理不出头绪,不知道问题到底出在哪里。那次狼狈的经历让我明白,在K8s的世界里,孤立地记忆命令是远远不够的,真正的能力在于建立一套体系化的命令使用思维,能够像侦探一样,根据现象快速组合命令,定位到问题的根源。

这篇文章,我想和你分享的,就是我这几年从“命令收集者”成长为“集群管理者”的实战心得。我不会仅仅给你罗列命令手册,那样网上到处都是。我会带你深入几个最核心的运维场景:日常巡检、故障排查、资源优化和变更管理。在每个场景里,我会告诉你我通常按什么顺序、用什么命令组合来解决问题,以及命令输出里哪些关键信息是绝对不能放过的“破案线索”。你会发现,当你理解了命令背后的集群运作逻辑,那些看似枯燥的命令会变得异常强大和有趣。

举个例子,很多人知道kubectl get pods能看Pod列表,但高手会加上-o wide看Pod被调度到了哪个节点,会加上--show-labels看它的标签是否符合预期,会用-w参数实时观察Pod状态的变化。这一套组合拳打下来,你对工作负载的分布和健康度就有了立体的认识。这就是思维的不同:从“点”到“线”再到“面”。我们接下来要做的,就是构建这个“面”。

2. 高效运维的基石:日常巡检与状态洞察

日常巡检就像给集群做“体检”,目的是在问题爆发前发现亚健康状态。我习惯把巡检分成几个层次,从宏观到微观,用一套固定的命令流来执行,效率非常高。

2.1 集群健康度全景扫描

每天上班第一件事,我通常会打开终端,执行下面这个简单的命令组合,快速给集群做个“全身检查”:

# 1. 看一眼集群核心组件状态(虽然kubeadm默认部署的etcd可能不在这里显示,但这是个好习惯)
kubectl get componentstatuses
# 或者用简写
kubectl get cs

# 2. 检查所有Node节点是否就绪(Ready)
kubectl get nodes

这里kubectl get nodes的输出里,STATUS 列必须是 Ready。如果看到 NotReadySchedulingDisabled,那就需要立刻警惕了。我还会顺手加上 -o wide 看看每个节点的内部IP和操作系统版本,心里有个底。

接下来,我会关注资源水位,这是预防性运维的关键:

# 3. 查看节点资源使用情况(需要已部署metrics-server)
kubectl top nodes

# 4. 查看Pod资源使用情况,按CPU或内存排序,快速找出“资源大户”
kubectl top pods -A --sort-by=cpu
kubectl top pods -A --sort-by=memory

通过 top 命令,我能一眼看出哪个节点的CPU或内存压力大,哪个命名空间里的Pod吃掉了过多资源。如果某个Pod的CPU使用率持续超过其请求(Request)的80%,我就知道可能需要给它调整资源限制了,或者检查是否有代码层面的性能问题。

2.2 工作负载与网络状态深潜

集群节点没问题,接下来就要看跑在上面的业务了。我不用一个个命名空间去查,而是用一些带过滤和格式化选项的命令,一次性获取结构化信息。

# 5. 查看所有命名空间下的关键工作负载状态
kubectl get pods -A --field-selector=status.phase!=Running

这个命令非常实用,它直接过滤出所有非Running状态的Pod(比如Pending、Error、CrashLoopBackOff)。日常巡检时,我们只关心异常,这个命令能帮我们快速聚焦问题点。

# 6. 查看服务(Service)和端点(Endpoints)的对应关系
kubectl get svc -A
kubectl get endpoints -A

把这两个命令的输出对照着看,可以确保每个Service后面都有健康的Pod在支撑。如果一个Service对应的Endpoints列表是空的,那说明没有Pod匹配它的标签选择器,网络访问肯定会失败。

对于使用了Ingress暴露服务的应用,我还会检查Ingress控制器和规则:

# 7. 查看Ingress状态和后端
kubectl get ingress -A
kubectl describe ingress <ingress-name> -n <namespace>

describe 命令在这里能展示出Ingress规则具体将流量转发到了哪个Service,以及Service的端口,是排查外部访问问题的必备步骤。

3. 故障排查实战:像侦探一样定位问题

当监控告警响起,或者用户反馈服务异常时,就是考验我们故障排查能力的时候了。我的经验是,遵循一个清晰的排查路径,从外到内,从现象到根源。

3.1 排查流程与命令组合拳

假设我们收到告警:“用户服务API响应超时”。我的排查思路通常是这样的:

第一步:确定问题边界。 是所有用户都超时,还是部分?是某个API接口超时,还是全部?这个通常需要和业务侧确认。在运维侧,我们先从集群层面看。

# 快速检查疑似服务的Pod状态
kubectl get pods -n <user-service-namespace> -l app=user-service
# 如果Pod数量不对(比如期望3个,实际只有2个),立刻用describe查看事件
kubectl describe deployment user-service-deployment -n <namespace>

describe deployment 的输出中,Events 部分是宝藏。这里会记录Pod创建失败、镜像拉取失败、调度失败等关键历史事件。我遇到过很多次因为节点资源不足导致Pod一直处于Pending状态,就是在这里发现的。

第二步:检查Pod内部状态。 如果Pod是Running,但不代表它健康。我们需要深入容器内部。

# 查看Pod内容器的状态
kubectl describe pod <problem-pod-name> -n <namespace>

仔细看 describe pod 输出中的 Containers 部分,每个容器的 StateLast State。如果看到 Restart Count 很高,比如几十上百次,那说明这个容器在频繁崩溃重启,应用肯定不正常。

第三步:查看应用日志。 这是定位程序错误最直接的方法。

# 查看指定Pod最近100行日志
kubectl logs <pod-name> -n <namespace> --tail=100
# 如果Pod内有多个容器,需要指定容器名
kubectl logs <pod-name> -n <namespace> -c <container-name>
# 实时跟踪日志输出(类似tail -f)
kubectl logs <pod-name> -n <namespace> -f

看日志要有技巧,不要从头开始看。我通常先用 --tail=100 看最近的错误,如果发现某个时间点有大量异常,再用 --since 参数查看特定时间段的日志,例如 --since=10m 查看过去10分钟的日志。

第四步:进入容器现场诊断。 当日志信息不够时,需要进入容器内部看看。

# 进入Pod的某个容器执行命令
kubectl exec -it <pod-name> -n <namespace> -c <container-name> -- /bin/sh
# 进入后,可以检查进程、网络连接、文件系统等
ps aux
netstat -tlnp
df -h

我曾经通过 exec 进入容器,发现是磁盘空间被日志写满了,导致应用无法启动。也遇到过容器内DNS解析失败,导致服务无法调用下游依赖。

3.2 高级调试技巧:事件与资源监控

有些问题比较隐蔽,比如网络间歇性抖动、节点内核问题。这时候需要看更全局的信息。

# 查看集群级别的事件,按时间倒序排列,关注Warning类型
kubectl get events -A --sort-by='.lastTimestamp' | grep -i warning

集群事件是一个总览,可以看到节点失联、镜像拉取失败、卷挂载失败等影响面较大的问题。

对于性能问题,kubectl top 命令可以实时观察:

# 动态观察某个Pod的资源使用率变化
watch -n 2 'kubectl top pod <pod-name> -n <namespace>'

这个命令每2秒刷新一次,能帮你捕捉到CPU或内存的瞬时尖峰,比如是否在某个时间点发生了内存泄漏,或者CPU被某个定时任务打满。

4. 资源管理与成本优化:让集群更“经济”

运维不仅要保证稳定,还要控制成本。在K8s里,资源管理不当很容易造成浪费,或者反过来导致应用因资源不足而运行不稳定。

4.1 为Pod设置合理的Requests和Limits

这是资源管理的黄金法则。Requests(请求) 是调度依据,Limits(限制) 是运行上限。很多新手要么不设,要么拍脑袋乱设。

# 一个规范的Pod资源请求与限制示例 (在Deployment的template.spec.containers下)
resources:
  requests:
    memory: "256Mi"
    cpu: "250m"  # 250 milliCPU,即0.25个CPU核心
  limits:
    memory: "512Mi"
    cpu: "500m"

怎么确定该设多少?我的经验是:

  1. 观察历史数据:用 kubectl top pod 观察应用在平稳运行时的实际使用量。将日常平均使用量的120%-150%设为 requests
  2. 设定安全上限limits 可以设为 requests 的1.5到2倍,给突发流量留有余地,同时防止单个Pod故障拖垮整个节点。
  3. 使用工具分析:对于Java等有堆内存的应用,要结合JVM参数来设定,limits 内存应大于JVM堆内存最大值(Xmx)。

4.2 识别并清理资源“僵尸”

集群运行久了,难免会有一些不再使用但未被删除的资源,比如失败的Job、未绑定的PVC、残留的ConfigMap等。

# 查找所有命名空间下状态为Completed或Failed的Job
kubectl get jobs -A --field-selector=status.successful!=1
# 查找未被任何Pod使用的PVC(孤立卷)
kubectl get pvc -A | awk '$2=="0" {print $0}'
# 查找可能未被引用的ConfigMap和Secret(需要谨慎,结合标签和命名空间判断)
kubectl get cm -A --show-labels
kubectl get secret -A --show-labels

定期清理这些资源,不仅能释放存储空间,还能让集群视图更清晰。不过删除前一定要确认,比如一个PVC显示未被使用,但可能对应着重要的数据备份卷。

4.3 节点调度与亲和性优化

通过合理设置节点标签和Pod的亲和性/反亲和性规则,可以优化工作负载分布,提高资源利用率或满足业务需求。

# 给节点打上自定义标签,如区分机型、机房、磁盘类型
kubectl label node <node-name> disktype=ssd
kubectl label node <node-name> zone=beijing

# 然后,在Pod的spec中,可以使用nodeSelector或更高级的affinity来调度
# nodeSelector示例:
spec:
  nodeSelector:
    disktype: ssd

我经常用这个功能把数据库Pod调度到带SSD盘的节点上,或者把同一个服务的多个Pod分散到不同的可用区(zone),实现高可用。反过来,也可以用 Pod反亲和性 避免同一个服务的多个Pod挤在同一节点,防止节点宕机导致服务全挂。

5. 变更管理:平滑发布与快速回滚

在K8s里做应用更新,再也不用半夜三更手动替换文件了。用好Deployment的滚动更新策略,可以实现真正的零停机部署。但这里面也有很多门道,搞不好就会翻车。

5.1 掌握滚动更新的节奏

默认的滚动更新策略是“杀一个,起一个”,但我们可以控制得更精细。

# 在Deployment的strategy部分配置
spec:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1        # 更新过程中,最多可以比期望Pod数多出1个(用于启动新版本)
      maxUnavailable: 0   # 更新过程中,最多允许0个Pod不可用(保证全时服务能力)

maxSurgemaxUnavailable 的配合是关键。maxUnavailable: 0 意味着在更新时,必须始终有100%的旧Pod或新Pod在提供服务,服务能力不会下降。maxSurge: 1 意味着可以额外多启动1个Pod来加速更新过程。对于核心服务,我通常采用这种保守策略。

更新操作本身很简单:

# 方法1:通过修改镜像版本触发更新(最常用)
kubectl set image deployment/<deployment-name> <container-name>=<new-image:tag> -n <namespace>

# 方法2:通过apply新的yaml文件触发更新
kubectl apply -f updated-deployment.yaml

执行后,千万不要立刻离开!一定要用 rollout status 命令盯着:

# 实时监控更新状态,直到完成或失败
kubectl rollout status deployment/<deployment-name> -n <namespace> -w

这个命令会阻塞并持续输出状态,直到所有新Pod就绪,或者更新超时失败。这是保证发布过程受控的关键一步。

5.2 必须掌握的“后悔药”:回滚操作

再完善的测试也挡不住线上环境的复杂性,准备好回滚方案是运维的底线。K8s的Deployment天然支持版本回滚,而且非常方便。

# 1. 查看发布历史
kubectl rollout history deployment/<deployment-name> -n <namespace>
# 输出会显示版本编号和变更原因(如果发布时用--record记录了命令)

# 2. 查看某个历史版本的详细配置
kubectl rollout history deployment/<deployment-name> --revision=2 -n <namespace>

# 3. 回滚到上一个版本
kubectl rollout undo deployment/<deployment-name> -n <namespace>

# 4. 回滚到指定的历史版本
kubectl rollout undo deployment/<deployment-name> --to-revision=2 -n <namespace>

我强烈建议在每次执行 kubectl set imagekubectl apply 时,都加上 --record 参数,这样在 history 里就能清晰地看到每次变更是因为执行了哪条命令,回滚时心里更有谱。

5.3 手动干预:Pod重启与节点维护

有时候,我们可能需要手动重启某个Pod(比如清除内存状态),或者对某个节点进行维护。

优雅重启Pod: 直接 kubectl delete pod 是最粗暴的方式,如果Pod副本数大于1,Deployment会立刻创建一个新的,但中间仍有短暂的服务中断风险。更优雅的方式是通过修改Pod模板来触发滚动更新,比如给Pod添加一个无关紧要的注解:

kubectl patch deployment <deployment-name> -n <namespace> -p '{"spec":{"template":{"metadata":{"annotations":{"restart-time":"'$(date +%s)'"}}}}}'

这条命令给Pod模板添加了一个基于当前时间的注解,Deployment会认为模板发生了变更,从而触发一次平滑的滚动更新,所有Pod会被逐个替换,服务不间断。

安全排空节点: 当需要给节点打补丁或下线时,不能直接关机,要用 drain 命令:

# 1. 先将节点标记为不可调度,防止新Pod调度上来
kubectl cordon <node-name>

# 2. 安全驱逐节点上的所有Pod(DaemonSet管理的Pod除外)
kubectl drain <node-name> --ignore-daemonsets --delete-emptydir-data

drain 命令会优雅地终止节点上的Pod(发送SIGTERM信号,等待优雅关闭),并在其他可用节点上重新创建它们。完成后,你就可以放心地对节点进行维护了。维护完成后,记得取消不可调度标记:

kubectl uncordon <node-name>

6. 构建你的命令工具箱:效率提升技巧

最后,分享几个让我效率倍增的小技巧,它们不是某个具体命令,而是使用命令的方式。

第一,善用别名和函数。 把常用的长命令封装起来。在你的 ~/.bashrc~/.zshrc 里添加:

alias kgp="kubectl get pods"
alias kgpa="kubectl get pods -A"
alias kdp="kubectl describe pod"
alias kl="kubectl logs"
alias kaf="kubectl apply -f"
alias kdf="kubectl delete -f"
# 一个快速进入Pod Shell的函数
function kbash() {
    kubectl exec -it $1 -- /bin/bash
}
function ksh() {
    kubectl exec -it $1 -- /bin/sh
}

第二,活用输出格式化 -o 和自定义列 -o custom-columns 默认的 get 命令输出信息有限。

# 查看Pod时,同时显示节点和IP
kubectl get pods -n production -o wide

# 自定义输出列,只显示最关心的信息
kubectl get pods -n production -o custom-columns=NAME:.metadata.name,STATUS:.status.phase,NODE:.spec.nodeName,IP:.status.podIP

第三,掌握标签选择器 -l 的强大威力。 这是精准操作一批资源的关键。

# 操作所有带 app=user-service 标签的Pod
kubectl get pods -l app=user-service
kubectl delete pods -l app=user-service
# 组合条件选择
kubectl get pods -l 'app in (user-service, order-service),env=prod'

第四,使用 kubectl explain 学习资源结构。 当你忘记某个资源(如Deployment)的yaml字段该怎么写时,这是最好的老师。

# 查看Deployment资源的规范定义
kubectl explain deployment
# 查看Deployment.spec字段下的子字段
kubectl explain deployment.spec
# 一直往下钻
kubectl explain deployment.spec.template.spec.containers

这个命令会返回对应字段的详细说明、类型和是否必填,比去网上查文档快得多。

说到底,K8s运维的核心不是背命令,而是理解其声明式API和控制器模型背后的思想。命令只是我们与这个庞大系统对话的语言。当你真正理解了Pod、Deployment、Service这些抽象概念是如何协作的,那些命令自然就变成了你手中得心应手的工具。每次排查问题,都是一次对集群运行机理的再认识。这个过程充满挑战,但也正是运维工作的乐趣所在。希望我分享的这些场景和命令组合,能帮你少走些弯路,更快地享受驾驭K8s集群的成就感。

更多推荐