1. K8s基础回顾:集群架构、核心组件与Pod生命周期
说实话,每次接手一个新的Kubernetes集群,我第一件事不是看业务部署,而是先摸清楚集群的架构和组件状态。为什么?因为很多故障的根因,其实都藏在基础组件里。这一章,我们就从最核心的东西开始聊。
1.1 集群架构:控制面与数据面
Kubernetes集群说白了就两大块:控制面(Control Plane)和数据面(Data Plane)。控制面负责决策,数据面负责干活。
- 控制面节点(Master):运行etcd、API Server、Scheduler、Controller Manager。一般部署3个节点做高可用。
- 数据面节点(Worker):运行Kubelet、Kube-Proxy、容器运行时(Docker/containerd)。数量可以很多,几十到几千台都行。
我遇到过不少新手,以为Master挂了集群还能正常跑。其实不然——Pod还在运行,但你没法做任何变更操作了。说白了,控制面是大脑,大脑宕机了,身体虽然能动,但已经失去指挥能力。
核心要点:控制面组件必须高可用。我建议至少3个Master节点,etcd也建议奇数节点(3、5、7)。
1.2 核心组件详解
1.2.1 etcd:集群的“数据库”
etcd存储了集群的所有状态数据。Pod在哪里、Service有哪些、ConfigMap内容是什么……全在etcd里。它是整个集群的“真相来源”。
我曾经遇到过一次etcd磁盘写满导致集群瘫痪的事故。当时所有API请求都超时,Pod调度也停了。排查了半天才发现是etcd的WAL日志把磁盘撑爆了。嗯,从那以后,我监控etcd磁盘使用率成了习惯。
避坑指南:etcd对磁盘IO非常敏感。不要用机械硬盘,不要跟其他高IO应用共用磁盘。etcd的磁盘延迟超过10ms,集群性能就会明显下降。
1.2.2 API Server:集群的“大门”
所有组件之间的通信,都必须经过API Server。kubectl、Scheduler、Controller Manager、Kubelet……谁想读写集群状态,都得走API Server。
API Server本身是无状态的,它只负责认证、授权、准入控制和数据转发。真正的数据存在etcd里。所以API Server可以水平扩展,你部署多个实例也没问题。
我个人习惯把API Server的审计日志打开。虽然会多占点磁盘,但排查问题时太有用了。谁在什么时候改了什么东西,一查便知。
1.2.3 Scheduler:Pod的“分配员”
Scheduler负责把新创建的Pod分配到合适的Node上。它怎么选?看资源够不够、有没有污点容忍、亲和性策略等等。
你想想看,如果集群里有100个Node,Scheduler得一个个过滤、打分,最后选出最优的那个。这个过程叫“调度周期”。
我记得有一次,集群里突然有大量Pod处于Pending状态。查了半天,发现是Scheduler的Leader选举出了问题,导致调度器实际上没有在工作。重启一下Scheduler Pod就好了。这种问题,说白了就是高可用机制偶尔会抽风。
1.2.4 Controller Manager:集群的“管家”
Controller Manager里跑着一堆控制器:Node Controller、Replication Controller、Endpoint Controller……它们各自盯着集群状态,确保实际状态跟期望状态一致。
比如你声明了3个副本,Replication Controller发现只有2个在跑,它会立刻创建一个新的Pod。这就是Kubernetes的“声明式”管理。
我建议你重点关注Node Controller。它负责检测节点健康状态。如果Node失联超过一定时间(默认40秒),它会标记为NotReady,再过一段时间(默认5分钟)就会驱逐Pod。这个时间参数在生产环境可能需要调优。
1.2.5 Kubelet:Node上的“代理人”
Kubelet是运行在每个Worker节点上的代理。它负责跟API Server通信,接收Pod定义,然后调用容器运行时启动容器。
Kubelet还会定期向API Server上报节点状态(心跳)。如果Kubelet挂了,API Server会认为这个节点也挂了。
我曾经遇到过Kubelet证书过期导致节点NotReady的情况。当时排查了很久,最后发现是证书没自动续期。嗯,证书管理是个容易忽略的点。
小技巧:排查节点问题时,先看Kubelet日志。通常日志在 /var/log/messages 或 journalctl -u kubelet。Kubelet的日志非常详细,很多问题都能从这里找到线索。
1.3 Pod生命周期
Pod是Kubernetes里最小的调度单元。一个Pod可以包含一个或多个容器,这些容器共享网络和存储。
Pod的生命周期,说白了就是它从创建到销毁的全过程。我把它分成几个阶段:
| 阶段 | 状态 | 说明 |
|---|---|---|
| 1 | Pending | Pod已被API Server接受,但还没调度到Node上,或者镜像正在拉取 |
| 2 | Running | Pod已调度到Node,所有容器都已启动,至少有一个容器在运行 |
| 3 | Succeeded | Pod中的所有容器都已成功终止,且不会再重启(Job场景) |
| 4 | Failed | Pod中的至少一个容器以非零状态退出 |
| 5 | Unknown | API Server无法获取Pod状态,通常是因为节点失联 |
除了这些阶段,Pod内部还有更细粒度的生命周期钩子:
- Init容器:在应用容器启动前运行,用于初始化工作。比如等待数据库就绪、下载依赖等。
- PostStart钩子:容器启动后立即执行。我习惯用它来做一些注册操作。
- PreStop钩子:容器停止前执行。用于优雅关闭,比如通知注册中心下线、等待请求处理完。
- 存活探针(Liveness Probe):检测容器是否还活着。如果挂了,Kubelet会重启容器。
- 就绪探针(Readiness Probe):检测容器是否准备好接收流量。如果没准备好,Service不会把流量转发过来。
- 启动探针(Startup Probe):用于启动慢的容器。在启动探针成功之前,存活探针不会工作。
实战建议:我建议每个Pod都配置就绪探针和存活探针。就绪探针用于流量管理,存活探针用于自动恢复。启动探针则用在那些启动需要10秒以上的应用上。
举个例子,一个典型的Pod YAML长这样:
apiVersion: v1
kind: Pod
metadata:
name: my-app
spec:
containers:
- name: app
image: nginx:1.21
ports:
- containerPort: 80
livenessProbe:
httpGet:
path: /healthz
port: 80
initialDelaySeconds: 3
periodSeconds: 5
readinessProbe:
httpGet:
path: /ready
port: 80
initialDelaySeconds: 5
periodSeconds: 10
startupProbe:
httpGet:
path: /startup
port: 80
failureThreshold: 30
periodSeconds: 10
这里要注意,startupProbe的failureThreshold设成了30,periodSeconds是10秒。也就是说,Kubelet会给容器最多300秒(5分钟)来启动。对于启动慢的Java应用,这个配置很实用。
Pod的终止过程也值得一说。当你执行kubectl delete pod时,流程是这样的:
- API Server记录Pod的删除时间,并设置宽限期(默认30秒)。
- Kubelet收到通知,执行PreStop钩子(如果有)。
- Kubelet向容器发送SIGTERM信号。
- 等待宽限期结束,或者容器自行退出。
- 如果宽限期到了容器还没退出,Kubelet发送SIGKILL强制杀死。
我曾经踩过一个坑:有个应用没有处理SIGTERM信号,导致每次滚动更新都有请求中断。后来加了PreStop钩子,在收到SIGTERM前先sleep 5秒,让负载均衡器有时间把流量切走。问题就解决了。
小技巧:生产环境中,我建议把terminationGracePeriodSeconds设长一点(比如60秒),给应用足够的时间做优雅关闭。同时,PreStop钩子里不要做太耗时的事情,否则会影响滚动更新的速度。
好了,这一章的基础回顾就到这里。下一章我们会深入etcd的运维和故障排查,那可是个硬骨头。到时候见。