1. 这不是教科书,是我在生产环境里拆了三遍集群后画的“人体解剖图”
Kubernetes 核心组件原理梳理,运维必备——这句话我第一次看到时,心里咯噔一下。不是因为难,而是因为太熟了:etcd、kube-apiserver、kube-scheduler、kube-controller-manager、kubelet、kube-proxy……这些名词在监控告警里刷屏,在日志里反复出现,在故障复盘会上被反复点名。但真要问“它此刻在内存里干了什么”“为什么这个Pod卡在Pending状态超过47秒”“为什么重启一个kube-controller-manager就让所有StatefulSet副本数瞬间翻倍”,很多人会下意识翻文档、查命令、抓包,而不是回到组件本身去想“它设计成这样,到底在防什么、保什么、让什么发生”。
我带过两个中型K8s平台团队,接手过5套从0.9版本升级上来的遗留集群,亲手重建过7次跨AZ高可用控制平面。最深的体会是:运维K8s,不是在操作一堆服务,而是在协调一套精密的分布式状态机。每个组件都不是孤立进程,而是状态流的一个“守门人”或“搬运工”。比如kube-scheduler不决定“谁来跑”,它只决定“谁可以跑”;真正把容器拉起来、挂载卷、配置网络的是kubelet,但它连API Server都不直接连——它只认kube-apiserver签发的Token和etcd里存的PodSpec快照。这种职责切分不是为了炫技,而是为了解耦失败域:API Server挂了,kubelet还能按本地缓存继续工作30分钟;etcd脑裂了,controller-manager会自动降级为只读模式,避免双写污染。
所以这篇梳理,不列官方定义,不抄架构图,不讲“K8s是谷歌开源的容器编排系统”这种废话。我们直接钻进代码调用栈、看etcd watch事件流、分析kubelet sync loop的17个阶段、对比不同版本中kube-proxy iptables规则生成逻辑的演进。你会看到:
- 为什么kube-apiserver的
--max-mutating-requests-inflight=200这个参数,实际影响的是你CI/CD流水线并发部署的吞吐上限; - 为什么在超大规模集群里,把kube-controller-manager的
--concurrent-deployment-syncs从5调到50,反而让Deployment滚动更新变慢; - 为什么你删掉一个Node后,Pod没立刻驱逐,不是bug,而是kube-controller-manager里那个叫
NodeLifecycleController的子控制器,正在执行它设计好的120秒优雅等待窗口。
这不是理论推演,是我把某次凌晨三点的OOM故障,倒着从cgroup内存指标、到kubelet goroutine堆栈、再到etcd key空间膨胀率,一层层剥开后,重新理解的组件协作逻辑。如果你正被“Pod状态飘忽”“Event积压”“节点NotReady却查不到原因”这些问题卡住,这篇就是给你准备的手术刀。
2. 控制平面五大组件:谁在管状态、谁在改状态、谁在传状态
Kubernetes控制平面不是五个并列服务,而是一个有明确数据流向的三层状态处理链:状态存储层 → 状态决策层 → 状态同步层。跳过这个分层视角,所有“组件原理”都是零散知识点。
2.1 etcd:不是数据库,是分布式状态快照中心
etcd常被称作K8s的“数据库”,这是最大误解。它不提供SQL查询、不支持事务回滚、没有索引优化。它的核心能力只有三个:原子性写入(Compare-and-Swap)、强一致性读取(Linearizable Read)、高效Watch通知(Event Stream)。
我们来看一个真实场景:当你执行kubectl scale deploy nginx --replicas=5,发生了什么?
- kubectl把请求发给kube-apiserver;
- apiserver校验RBAC后,向etcd发起一个CAS操作:
如果/deployments/default/nginx的spec.replicas当前值是3,则更新为5; - etcd原子执行,返回成功;
- 关键一步:etcd立即向所有监听
/deployments/...前缀的客户端(主要是controller-manager)推送一个PUT事件,携带新旧value的完整diff; - controller-manager收到事件,启动一个goroutine,根据新spec计算出需要创建2个新Pod,并向etcd写入这两个Pod对象。
注意:整个过程没有“查询当前状态再计算”的步骤。controller-manager不主动查etcd,它只靠watch事件驱动。这就是为什么etcd Watch机制的延迟(通常<100ms)直接决定了K8s状态收敛速度。我见过某集群因etcd磁盘IO过高,Watch延迟飙到2秒,导致HPA扩缩容永远慢半拍——不是算法问题,是状态通知管道堵了。
提示:etcd的
--snapshot-count=10000参数不是“每1万次写就存个快照”,而是“每1万次写就触发一次raft snapshot”。快照本身不耗时,但raft snapshot会阻塞写请求。在写密集型集群(如每秒数百次ConfigMap更新),这个值设太小会导致apiserver间歇性超时。我们线上统一设为50000,并配合--auto-compaction-retention=1h防止历史版本堆积。
2.2 kube-apiserver:不是网关,是状态变更的唯一入口与过滤器
kube-apiserver是控制平面的“海关”,但它不只验护照(Authentication),还查行李(Admission Control),甚至临时扣留可疑物品(Mutating Webhook)。它的核心设计哲学是:所有状态变更必须经过它,且只能经由它。
重点看三个常被忽略的机制:
Request Flow Control(RFC):K8s 1.20+引入的流量整形。它把请求分为
long-running(如watch、exec)和short-running(如get、create)两类,分别限流。默认--max-requests-inflight=400只限制短请求,而--max-mutating-requests-inflight=200专管写操作。为什么写操作限额更低?因为mutating请求(create/update/patch)会触发etcd写入,是真正的瓶颈。我们曾因CI工具并发创建200个Job,触发此限流,后续请求全部返回429,但监控里apiserver CPU和内存都正常——问题不在资源,而在设计阈值。Storage Version Hash:每次apiserver启动时,会计算当前支持的所有API版本(v1, apps/v1, batch/v1)的schema哈希值,并写入etcd
/registry/storageversion。当发现某个API对象(如Deployment)在etcd里存的是extensions/v1beta1格式,而当前apiserver已废弃该版本,它会自动触发转换(Conversion),把对象升级为apps/v1再返回。这个过程对用户透明,但会增加单次GET延迟。线上集群务必确保所有client(包括自研Operator)使用当前主流版本,避免隐式转换拖慢响应。Aggregation Layer:不是插件,是apiserver原生能力。当你访问
/apis/metrics.k8s.io/v1beta1,请求其实被内建的aggregator路由到metrics-server服务。关键点在于:aggregator只代理HTTP路径,不代理TLS证书验证。所以metrics-server必须用apiserver信任的CA签发证书,否则aggregator直接拒绝转发。我们踩过坑:用Let's Encrypt证书配metrics-server,结果kubectl top node永远报x509: certificate signed by unknown authority——不是证书过期,是CA不被apiserver信任。
2.3 kube-scheduler:不是调度器,是满足约束的批量匹配引擎
kube-scheduler的“调度”二字极具误导性。它不决定“哪个Node性能最好”,而是解决一个布尔逻辑题:“对当前Pending Pod,找出所有满足以下条件的Node集合:
① 资源足够(CPU/Mem + 预留量);
② 满足亲和性/反亲和性规则;
③ 满足Taint/Toleration;
④ 满足VolumeBindingMode(如WaitForFirstConsumer);
⑤ 满足自定义调度策略(如Scheduling Framework Plugin)”。
它的工作流程分三步:
- Predicate(过滤):硬性条件筛选,快速排除不满足的Node。例如
CheckNodeCondition检查Node是否Ready,CheckNodeDiskPressure检查磁盘是否满。这一步是“全或无”,任一Predicate失败,Node即出局。 - Priority(打分):对剩余Node打分(0-10分),分数越高越可能被选中。
LeastRequestedPriority倾向资源空闲多的Node,BalancedResourceAllocation倾向CPU/Mem使用率均衡的Node。注意:分数不保证绝对公平,只保证相对排序。 - Bind(绑定):选中最高分Node后,向apiserver发送
POST /api/v1/namespaces/default/pods/nginx-xxx/binding请求,将Pod与Node绑定。
这里有个致命细节:Bind操作不是原子的。Scheduler发出Bind请求后,如果apiserver返回成功,但etcd写入失败(网络抖动),Scheduler不会重试。此时Pod仍处于Pending,但Scheduler已认为它“已调度”。这就是为什么你有时看到Pod卡在Pending,kubectl describe pod显示“0/5 nodes are available”,而kubectl get nodes明明有5个Ready节点——Scheduler的Predicate缓存还没刷新,它还在用旧的Node状态做判断。
实操心得:线上集群必须开启
--leader-elect=true(默认开启),但更要关注--lock-object-name=scheduler这个锁对象。我们曾因误删了这个ConfigMap,导致多个scheduler实例同时运行,出现Pod被重复调度到不同Node的诡异现象。修复后,必须手动删除所有处于Scheduled但未被kubelet拉起的Pod,否则它们会永远卡在“假运行”状态。
2.4 kube-controller-manager:不是控制器集合,是状态差异的永动机
controller-manager是K8s的“免疫系统”,它不创造状态,只负责让实际状态(Actual State)无限逼近期望状态(Desired State)。它的核心循环就一句话:List所有资源 → 对比当前状态与期望状态 → 计算差异 → 执行最小动作集修正差异。
以Deployment控制器为例:
- 它List所有Deployment,对每个Deployment A,再List其下属ReplicaSet(RS);
- 找出RS中
spec.replicas最大的那个作为“新RS”,其余为“旧RS”; - 计算新RS应有Pod数 = Deployment.spec.replicas × (新RS.spec.replicas / 所有RS.spec.replicas之和);
- 如果新RS当前Pod数 < 应有数,则创建Pod;如果旧RS Pod数 > 0,则删除Pod。
关键洞察:controller-manager从不直接操作Pod,它只创建/删除ReplicaSet,由RS控制器再去操作Pod。这种嵌套设计实现了关注点分离:Deployment控制器专注“版本管理”,RS控制器专注“副本数保障”。
我们遇到过最典型的故障:某次升级后,所有Deployment滚动更新停滞。排查发现,kubectl get rs显示新RS的spec.replicas=0,而旧RS的spec.replicas远大于0。根本原因是:新RS的spec.selector与Deployment的spec.selector不匹配(YAML手误),导致RS控制器认为“这个RS管不了任何Pod”,于是拒绝创建Pod。Deployment控制器却认为“我已经创建了新RS,任务完成”,双方都在等对方先动——死锁了。
注意事项:
--concurrent-*系列参数(如--concurrent-deployment-syncs=5)不是并发数,而是每个控制器的goroutine工作池大小。设太高会导致大量goroutine竞争apiserver连接,反而降低吞吐。我们线上集群(500节点)的经验值是:Deployment设为5,Node设为10,EndpointSlice设为20。调整后,Event积压从每分钟2000+降到个位数。
2.5 cloud-controller-manager:不是云厂商适配器,是解耦云依赖的隔离墙
cloud-controller-manager(CCM)的存在,本质是K8s社区与云厂商的“互不信任协议”。它把所有云相关操作(创建LoadBalancer、挂载云盘、同步Node地址)从核心controller-manager中剥离,变成可插拔模块。
它的核心设计是:只在Node加入集群时初始化一次云资源,之后所有操作基于本地缓存。例如:
- 当Node首次注册,CCM调用云API创建对应ECS实例的标签、安全组规则;
- 后续Node状态变化(如NotReady),CCM只更新本地缓存,不再调用云API;
- 只有当Node被删除时,CCM才调用云API清理资源。
这就解释了为什么你删掉一个Node后,云控制台里ECS实例还在:CCM默认不自动销毁云资源,避免误操作。必须显式设置--enable-hostpath-provisioner=false(禁用本地存储)和--configure-cloud-routes=true(启用路由配置),并确保云厂商插件正确实现Instances和Zones接口。
我们曾因CCM配置错误,导致新扩容的Node无法自动加入Service的Endpoint。根因是:CCM的NodeController没正确实现InstanceExistsByProviderID方法,导致它认为“这个Node不存在”,从而跳过Endpoint同步逻辑。修复方案不是重启CCM,而是手动在Node对象上添加providerIDannotation,强制它识别。
3. 数据平面两大组件:状态落地的最后两公里
控制平面决定“应该是什么”,数据平面决定“实际是什么”。kubelet和kube-proxy就是把抽象状态翻译成Linux内核能懂的语言的翻译官。它们不出问题则已,一出就是Pod起不来、网络不通、存储挂不上——直击业务。
3.1 kubelet:不是Agent,是Pod生命周期的本地总控
kubelet是K8s中最复杂的单体进程,它同时扮演:
- Pod管家:管理容器启停、健康检查、日志采集;
- 资源管家:上报Node资源、执行QoS分级、实施cgroup限制;
- 存储管家:挂载Volume、管理Secret/ConfigMap;
- 网络管家:调用CNI插件配置网络、维护Pod IP。
它的核心循环叫syncLoop,每20秒(默认--sync-frequency=1m0s,但内部有更细粒度)执行一次,包含17个阶段。最关键的四个阶段是:
- PodUpdate:从apiserver或本地文件获取最新PodSpec,与本地状态对比;
- SyncPod:对每个需变更的Pod,执行容器创建/删除、Volume挂载/卸载、网络配置;
- StatusUpdate:把Pod当前状态(Running/Pending/Failed)和容器详情(IP、端口、重启次数)回传apiserver;
- Housekeeping:清理已退出容器、回收临时存储、更新cgroup统计。
这里有个反直觉事实:kubelet从不主动向apiserver“拉取”Pod状态,它只通过Watch监听变化。所以当你修改Pod YAML并kubectl apply,kubelet不是立刻收到,而是等apiserver把变更推送给它。Watch通道的稳定性,直接决定Pod状态更新的及时性。
我们遇到过最隐蔽的故障:某批Pod始终卡在ContainerCreating。kubectl describe pod显示Waiting for container runtime to create the pod,但docker ps -a里根本没有对应容器。最终发现是kubelet的--pod-infra-container-image参数指向了一个私有镜像仓库,而该仓库因证书过期返回401,kubelet拉取pause容器失败,却把错误日志埋在journalctl -u kubelet | grep "failed to pull image"里,没在Pod事件中暴露。解决方案不是修证书,而是把pause镜像提前docker load进所有Node。
实操心得:
--eviction-hard参数(如memory.available<500Mi)不是“内存低于500Mi就杀Pod”,而是“当memory.available指标持续10秒(默认--eviction-minimum-reclaim)低于阈值,触发驱逐”。我们线上把--eviction-soft=memory.available<1Gi和--eviction-soft-grace-period=memory.available=2m组合使用,给应用2分钟缓冲时间释放内存,避免瞬时抖动导致误驱逐。
3.2 kube-proxy:不是代理,是Service流量的iptables/ipvs规则生成器
kube-proxy根本不转发流量,它只是个“规则雕刻师”。它监听Service和Endpoint变化,然后在Node上生成iptables或ipvs规则,让Linux内核的Netfilter模块按规则做DNAT/SNAT。
两种模式的本质区别:
- iptables模式:为每个Service生成一条
KUBE-SERVICES链,再为每个Endpoint生成一条KUBE-SEP-XXXX链,规则是纯文本。优点:兼容性好;缺点:规则数O(N×M),N是Service数,M是Endpoint数。当Service超2000个,iptables-save输出超10MB,加载规则耗时超30秒,导致Service不可用。 - ipvs模式:为每个Service创建一个Virtual Server,为每个Endpoint创建Real Server,规则存在内核hash表中。优点:O(1)查找,支持更多负载均衡算法(rr/wlc/sh);缺点:需要
ip_vs内核模块,且--proxy-mode=ipvs必须在kube-proxy启动时指定,无法热切换。
我们线上集群(300+ Service)强制使用ipvs,并额外配置:
# 启用连接跟踪保持,避免会话中断 --ipvs-scheduler=rr \ --ipvs-exclude-cidrs=10.0.0.0/8 \ --feature-gates=SupportIPVSProxyMode=true最关键的是--ipvs-exclude-cidrs:排除集群CIDR,让ClusterIP流量走ipvs,而NodePort和ExternalIP流量走iptables,避免规则冲突。
常见问题:
kubectl get svc看到ClusterIP,但curl http://<cluster-ip>:port不通。90%概率是kube-proxy没生效。检查ipvsadm -ln是否有对应VS,或iptables -t nat -nL KUBE-SERVICES是否有规则。若都没有,看systemctl status kube-proxy是否Active,再查journalctl -u kube-proxy | grep "can't determine system's network configuration"——这表示kube-proxy无法获取Node网络信息,通常因--hostname-override参数与Node对象名称不一致。
4. 组件协同全景:从一个Pod创建看全链路状态流
现在,我们把所有组件串起来,追踪一个Pod从kubectl create到Running的完整旅程。这不是理想流程图,而是包含所有真实延迟、重试、降级的“血肉版”链路。
4.1 第一阶段:声明提交(0~2秒)
kubectl读取YAML,序列化为JSON,用Bearer Token签名;- 请求发往kube-apiserver,经历:
- Authentication(验证Token有效性)→
- Authorization(RBAC检查
pods/create权限)→ - Admission Control(执行Validating/Mutating Webhook,如注入sidecar)→
- Storage(写入etcd
/registry/pods/default/nginx-xxx);
- apiserver返回201 Created,
kubectl打印pod/nginx-xxx created。
关键延迟点:
- Mutating Webhook平均增加300ms延迟(我们实测Istio注入Webhook);
- etcd写入延迟受磁盘IO影响,P99应<100ms,超200ms需告警。
4.2 第二阶段:调度决策(2~5秒)
- kube-scheduler的Watch机制捕获etcd的
PUT /pods/...事件; - 启动Predicate过滤:检查Node资源、Taint、亲和性;
- 对剩余Node执行Priority打分;
- 选中Node A,向apiserver发送Bind请求;
- apiserver验证Node A存在且Ready,写入
/pods/nginx-xxx/spec.nodeName=NodeA。
关键故障点:
- 若Predicate缓存过期(默认
--node-cache-ttl=5s),Scheduler可能用旧Node状态计算,导致绑定失败; - Bind请求失败时,Scheduler会重试3次(默认
--retry-backoff-limit=3),每次间隔1秒。
4.3 第三阶段:状态同步(5~15秒)
- kubelet的Watch机制捕获
/pods/nginx-xxx的nodeName变更事件; - kubelet启动
syncLoop,进入SyncPod阶段:- 拉取pause容器(
--pod-infra-container-image); - 调用CNI插件分配IP、配置网络命名空间;
- 挂载Volume(emptyDir、hostPath、PV);
- 拉取应用镜像(
imagePullPolicy=Always时); - 启动容器,执行
livenessProbe初始探测;
- 拉取pause容器(
- kubelet调用
StatusUpdate,把Pod状态设为Running,IP设为10.244.1.100,回传apiserver。
关键耗时点:
- CNI插件执行(Calico约200ms,Cilium约150ms);
- 镜像拉取(私有仓库+大镜像可达10秒);
livenessProbe初始延迟(initialDelaySeconds)。
4.4 第四阶段:服务暴露(15~30秒)
- endpoints-controller监听Pod状态变更,发现新Pod Ready,更新Service的Endpoints对象;
- kube-proxy监听Endpoints变更,生成新iptables/ipvs规则;
- Node上
iptables -t nat -A KUBE-SVC-XXXX规则生效; - 其他Pod访问
http://my-service:80时,Netfilter按规则DNAT到10.244.1.100:80。
关键验证点:
kubectl get endpoints my-service应显示10.244.1.100:80;- 在Node上执行
curl -v http://10.244.1.100:80应通; - 在其他Pod内执行
curl -v http://my-service:80应通(验证DNS和Service规则)。
实操记录:某次Pod启动耗时47秒,远超预期。我们按链路逐段排查:
- apiserver日志:
"request completed" latency=123ms→ 正常;- scheduler日志:
"attempting to bind pod to node" node="node-01"→ 正常;- kubelet日志:
"pulling image" image="my-app:v2.1"→ 发现镜像拉取耗时32秒;- 进入Node执行
time docker pull my-registry.com/my-app:v2.1→ 31.8秒;- 最终定位:私有仓库网络策略限制了单连接带宽。解决方案:在kubelet配置中添加
--image-pull-progress-deadline=5m,并优化仓库网络。
5. 故障排查实战:从现象反推组件失联点
运维K8s,80%的故障不是组件崩溃,而是组件间“失语”。下面整理我们高频遇到的6类现象,每类给出精准定位路径和独家修复技巧。
5.1 现象:Pod状态长期Pending,describe显示“No nodes available”
| 排查层级 | 检查命令 | 关键线索 | 修复技巧 |
|---|---|---|---|
| Scheduler层面 | kubectl logs -n kube-system $(kubectl get pods -n kube-system | grep scheduler | awk '{print $1}') | grep "FailedScheduling" | 出现0/5 nodes fit但没列具体原因 → Predicate缓存过期 | 重启scheduler:kubectl delete pod -n kube-system -l component=kube-scheduler |
| Node层面 | kubectl describe node <node-name> | Conditions中Ready=False或DiskPressure=True | 查kubectl get events --field-selector involvedObject.name=<node-name>找根本原因 |
| Taint层面 | kubectl describe node <node-name> | grep Taints | 显示node-role.kubernetes.io/master:NoSchedule→ Pod没加toleration | 为Pod添加toleration:tolerations:<br>- key: "node-role.kubernetes.io/master"<br> operator: "Exists"<br> effect: "NoSchedule" |
独家技巧:当
kubectl describe pod只显示模糊提示,立即执行kubectl get events --sort-by=.lastTimestamp。Events是组件间的“聊天记录”,比Pod状态更早暴露问题。我们曾靠Events里一行"Node node-03 is not ready",5分钟定位到是Node上kubelet进程OOM被系统kill。
5.2 现象:Pod Running但无法访问Service,curl ClusterIP超时
| 排查层级 | 检查命令 | 关键线索 | 修复技巧 |
|---|---|---|---|
| Endpoints层面 | kubectl get endpoints <service-name> | Endpoints为空 → endpoints-controller没工作 | 检查kubectl get pods -n kube-system | grep controller-manager是否Running |
| kube-proxy层面 | kubectl exec -n kube-system <kube-proxy-pod> -- ipvsadm -ln | 无对应VS → kube-proxy没同步 | 检查kubectl logs -n kube-system <kube-proxy-pod> | grep "SyncLoop"是否报错 |
| Node网络层面 | kubectl debug node/<node-name> -it --image=nicolaka/netshoot | 进入Node后curl http://10.244.1.100:80通,但curl http://<cluster-ip>:80不通 → iptables规则缺失 | 手动触发kube-proxy重同步:kubectl exec -n kube-system <kube-proxy-pod> -- touch /var/lib/kube-proxy/config.conf |
注意事项:
kubectl debug需要EphemeralContainers特性开启(K8s 1.23+默认开启)。若不可用,用nsenter:nsenter -t $(pidof kubelet) -n curl http://10.244.1.100:80。
5.3 现象:Node状态NotReady,但kubelet进程正常
| 排查层级 | 检查命令 | 关键线索 | 修复技巧 |
|---|---|---|---|
| kubelet健康检查 | systemctl status kubelet | 显示active (running)但Last login时间异常 → kubelet僵死 | 强制重启:systemctl restart kubelet && journalctl -u kubelet -n 100 |
| Node心跳 | kubectl get nodes -o wide | AGE列显示<invalid>→ kubelet没上报心跳 | 检查/var/lib/kubelet/config.yaml中serverTLSBootstrap: true是否启用,未启用则需手动批准CSR:kubectl get csr | grep Pending | awk '{print $1}' | xargs kubectl certificate approve |
| 云厂商集成 | kubectl get nodes -o wide | INTERNAL-IP为空 → cloud-controller-manager未同步 | 检查CCM日志:kubectl logs -n kube-system -l component=cloud-controller-manager | grep "updateNodeAddress" |
实操心得:Node NotReady时,永远先看
journalctl -u kubelet -n 200,而不是kubectl describe node。后者是kubelet上报的状态摘要,前者是kubelet的真实心跳和错误。我们曾靠journalctl里一行"failed to run Kubelet: unable to load bootstrap kubeconfig",10分钟修复了因--bootstrap-kubeconfig路径错误导致的集群雪崩。
5.4 现象:etcd集群健康,但apiserver频繁503
| 排查层级 | 检查命令 | 关键线索 | 修复技巧 |
|---|---|---|---|
| etcd连接 | ETCDCTL_API=3 etcdctl --endpoints=https://127.0.0.1:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=/etc/kubernetes/pki/etcd/server.crt --key=/etc/kubernetes/pki/etcd/server.key endpoint health | 所有endpoint显示healthy→ etcd正常 | 检查apiserver日志:kubectl logs -n kube-system <apiserver-pod> | grep "etcd server failed" |
| apiserver资源 | kubectl top pods -n kube-system | grep apiserver | CPU/MEM持续>80% → 资源不足 | 垂直扩容:kubectl edit deployment -n kube-system kube-apiserver,增加resources.limits.cpu |
| 客户端连接 | ss -tnp | grep :6443 | wc -l | 连接数>5000 → 连接泄漏 | 检查客户端是否未正确关闭连接(如Python requests未用session);临时限流:kubectl edit deployment -n kube-system kube-apiserver,添加--max-requests-inflight=300 |
独家技巧:当apiserver 503且etcd健康,立即检查
/proc/<apiserver-pid>/fd文件描述符数:ls -l /proc/$(pgrep kube-apiserver)/fd \| wc -l。超过65535(ulimit默认值)必然503。解决方案:在apiserver启动参数中添加--max-open-files=100000,并修改/etc/security/limits.conf。
5.5 现象:Pod日志显示“Connection refused”,但容器进程正常
| 排查层级 | 检查命令 | 关键线索 | 修复技巧 |
|---|---|---|---|
| 容器端口 | kubectl exec <pod-name> -- netstat -tuln | 未监听0.0.0.0:8080,只监听127.0.0.1:8080→ 应用绑定localhost | 修改应用配置,监听0.0.0.0;或用kubectl port-forward调试 |
| Service端口 | kubectl get svc <service-name> -o yaml | targetPort与容器实际端口不一致 → 流量被丢弃 | 确保targetPort等于容器EXPOSE端口或containerPort |
| NetworkPolicy | kubectl get networkpolicy --all-namespaces | 存在deny-all策略 → 流量被拦截 | 临时禁用:kubectl delete networkpolicy <np-name> -n <namespace> |
注意事项:
netstat -tuln在Alpine镜像中需安装net-tools。更通用的方法是kubectl exec <pod-name> -- cat /proc/1/exe看主进程,再kubectl exec <pod-name> -- ps auxf看子进程监听端口。
5.6 现象:HPA显示Target 50%,Current 200%,但Pod副本数不增加
| 排查层级 | 检查命令 | 关键线索 | 修复技巧 |
|---|---|---|---|
| Metrics Server | kubectl get apiservice v1beta1.metrics.k8s.io | STATUS为False→ metrics-server未就绪 | 检查kubectl get pods -n kube-system | grep metrics-server,重启失败Pod |
| HPA配置 | kubectl get hpa <hpa-name> -o yaml | minReplicas和maxReplicas相同 → 无法扩缩 | 修改minReplicas为1,maxReplicas为10 |
| Pod指标 | kubectl top pods | 无输出或显示<unknown>→ 指标采集失败 | 检查kubectl get pods -n kube-system | grep metrics-server日志,常见错误"unable to fetch metrics from Kubelet",需检查kubelet--read-only-port是否关闭(K8s 1.12+默认关闭,需用--authentication-token-webhook) |
实操记录:某次HPA失效,
kubectl top pods返回error: Metrics API not available。我们发现kubectl get apiservice中v1beta1.metrics.k8s.io的Conditions显示Reason: FailedDiscoveryCheck。深入查metrics-server日志,发现"failed to get node metrics: no metrics known for node"。根因是kubelet未开启--enable-server=true(默认开启),但--authentication-token-webhook配置错误。修复后,HPA在30秒内恢复正常。
6. 运维加固清单:让集群在故障中多活30分钟
最后,分享我们沉淀的12条生产环境加固项。每一条都来自真实故障后的痛定思痛,不是教科书建议,而是血泪经验。
6.1 控制平面高可用:别只盯着etcd
- etcd集群必须奇数节点(3/5/7),且跨AZ部署:我们曾用2节点etcd,AZ故障后脑裂,数据不一致。3节点是底线,5节点可容忍2节点故障。
- apiserver必须多副本,且用DNS轮询或VIP:不要用kube-proxy做apiserver负载均衡,它自身依赖apiserver。我们用HAProxy+Keepalived实现VIP,故障切换<3秒。
- controller-manager和scheduler必须开启Leader选举:参数
--leader-elect=true(默认true),但更要检查--leader-elect-resource-lock=configmaps是否指向正确的Namespace。
6.2 监控告警:只看CPU/MEM是运维自杀
- **必