背景
下面我将以这个思维导图来总结一下如何对pod生命周期进行管理。
pod完整生命周期
- 用户发起创建pod请求到apiserver,apiserver存储pod信息到etcd中。pod还没有经过调度,pod的nodeName是空的,此时pod处于Pending状态。
- scheduler监听到 pending状态的pod,经过调度策略(资源请求,tolerations,亲和度等)选择一个节点,此时pod 处于ContainerCreating状态。
- node上的kublet调用CRI,CNI,CSI接口启动容器,启动后pod处于Running状态。
- 用户删除pod时,pod立即处于Terminating状态。停止容器进程,删除网络,卸载目录挂载,删除finalizer。即删除所有和pod有关的对象(gone)。
- 假如pod中的所有容器进程都异常退出了,或者至少有一个容器进程exit非0,且重启策略为Never,那么pod处于Failed状态。
- 假如node由于网络原因,无法上报状态,那么pod处于Unknown状态。
- 假如pod中的所有容器进程都正常退出了,且重启策略为Never,那么pod处于Successd状态。
- 假如pod被kubelet驱逐了,那么pod处于Evicted状态。
pod状态机
下图就是上面pod状态流转步骤的简化。
pod phase
kubectl get pod中的STATUS是由pod中status的conditions和phase计算得来的。
计算细节
常见的比如CrashLoopBackOff:phase状态为running,但是容器进程退出了。
最下面的Outofcpu/OutofMemory:由于kubelet有准入插件,当node的cpu或者内存不足时,就会出现这个状态。
pod高可用
-
应用要想平稳的运行在k8s上,owner就必须知道应用的资源需求。
- 设置合理的 resources.memory limits 防止容器进程被 OOMKill;
- 设置合理的 emptydir.sizeLimit 并且确保数据写入不超过 emptyDir 的限制,防止 Pod 被驱逐。
-
需要部署多少实例
-
更新策略:二者不能同时为0。
- maxSurge:和期望ready的副本数比,超过期望副本数最大比例(或最大值),这个值调的越大,副本更新速度越快。
- maxUnavailable:和期望ready的副本数比,不可用副本数最大比例(或最大值),这个值越小,越能保证服务稳定,更新越平滑;
容器可能遇到的进程中断
pod数据如何存储
qosClass
-
Guaranteed
- Pod 的每个容器都设置了资源 CPU 和内存需求
- requests 和 limits 是一致的
-
Burstable
- 至少一个容器制定了 CPU 或内存需求
- requests 和 limits 可以不一致
-
BestEffort
- Pod 中的所有容器都未指定 CPU 或内存需求
- Pod 中的所有容器都未指定 CPU 或内存需求
-
当计算节点检测到内存压力时,Kubernetes 会按 BestEffort-> Burstable->Guaranteed的顺序驱逐pod。
-
对于重要应用使用 Guaranteed 类型。
-
认真考量 Pod 需要的真实需求并设置 Limit 和 resource,这有利于将集群资源利用率控制在合理范围并减少 Pod 被驱逐的现象。
-
尽量避免将生产 Pod 设置为 BestEffort,但是对测试环境来讲,BestEffort Pod 能确保大多数应用不会因为资源不足而处于 Pending 状态。
-
Burstable 适用于大多数场景。因为它既支持超卖,又锁定一定的资源,可以平衡资源利用率和应用的性能。
基于 taint 的 Evictions
当node状态异常时,nodeLifeCycleController会监听这种node和node上的pod。当node状态为notReady或unreachable时,这个node就会被打上如下的taint。
- NoExecute:node上的pod被驱逐
- NoSchedule:不往这个node上调度pod
taints:
- effect: NoSchedule
key: node.kubernetes.io/unreachable
timeAdded: "2022-03-09T11:25:10Z"
- effect: NoExecute
key: node.kubernetes.io/unreachable
timeAdded: "2022-03-09T11:25:21Z"
- effect: NoSchedule
key: node.kubernetes.io/not-ready
timeAdded: "2022-03-09T11:24:28Z"
- effect: NoExecute
key: node.kubernetes.io /not-ready
timeAdded: "2022-03-09T11:24:32z"
pod也有对应的tolerations。由于网络可能会抖动,所以需要tolerationSeconds,默认是300s。如果是依赖于本地存储状态的有状态应用应增大 toleration Seconds 比如900s以避免被驱逐。
Probe
健康探针类型
- livenessProbe
- 探活,当检查失败时,意味着该应用进程已经无法正常提供服务,kubelet会终止该容器进程并按照 restartPolicy 决定是否重启。
- readinessProbe
- 就绪状态检查,当检查失败时,意味着应用进程正在运行,但因为某些原因不能提供服务,Pod 状态会被标记为NotReady。
- startupProbe
- 应用没有起来,探活和就绪探测是失败的,由于不停的重试会导致应用压力过大。
- 在初始化阶段(Ready 之前)进行的健康检查,startupPorbe运行后才运行liveness和readness。通常用来避免过于频繁的监测影响应用启动。
探测方法
- Exec:在容器内部运行指定命令,当返回码为◎时,探测结果为成功。
- TCPSocket:由 kubelet 发起,通过TCP 协议检查容器 1P 和端口,当端口可达时,探测结果为成功。
- HTTPGet:由 kubelet 发起,对Pod 的IP 和指定端口以及路径进行HTTPGet 操作,当返回码为 200-400 之间时,探测结果为成功。
探针属性
- initialDelaySeconds 表示延迟5s开始第一次探测,默认值是0,最小值是0。
- timeoutSeconds 表示每次探测的超时时间,5s后如果没返回结果就认为超时失败,默认值是1,最小值是1。
- successThreshold 表示在探测失败后,最小的连续成功被认为是成功的,默认值是1,最小值是1。
- failureThreshold 表示当探测失败时,Kubernetes将在认为失败前尝试次数。默认值是3,最小值是1。
- periodSeconds 表示多久进行一次探测,默认是10S,最小值是1。
重启策略
- Always: 当容器终止退出后,总是重启容器,默认策略
- Onfailure: 当容器异常退出后(退出码非0)时,才重启容器
- Never: 当容器终止退出时,不重启容器
ReadinessGates
- 使用了ReadinessGates,即使readiness就绪返回成功了,pod也不算就绪。
- ReadinessGates condition status需要为 True 状态后,加上内置的 Conditions,Pod 才可以为就绪状态。
- 该状态应该由某控制器修改。
比如pod需要配置额外的dns,那么就需要有一个额外的controller,配置好dns后,将condition中的status由false改为true,pod才ready。
健康检查注意
- 所有提供服务的容器都应该加上readinessProbe,不通过就视为 Pod 不健康,然后会自动将不健康的 Pod 踢出去,避免将业务流量转发给异常 Pod。
- 探测结果一定要真实反映业务健康状态,通常是业务程序提供 HTTP 探测接口比如/health。
- 不要轻易使用livenessProbe,因为探测失败就可能会导致pod重启。
- 即使要使用livenessProbe,也要注意:
- 将failureThreshold 设置得更大一点,避免因为太敏感导致pod重启;
- 而且要在应用完全启动后再开始探测,如果版本是1.18以下,可以将initialDelaySeconds 加大一点;版本是1.18以上,那么可以用startupProbe。
- 探测逻辑里不要有外部依赖,比如依赖外部的db,由于网络抖动,探测失败,容易造成级联故障。
- 避免使用 TCP 探测,因为TCP 探测结果并不能真实反映业务真实情况:
- 程序 hang 死时, TCP 探测仍然能通过 (TCP 的 SYN 包探测端口是否存活在内核态完成,应用层不感知)。
- 当程序正在优雅退出的过程中,端口监听还在,TCP 探测会成功,但业务层面已不再处理新请求了。
Post-start 和 Pre-Stop Hook
- Post-start:容器启动后还需要做一些额外的操作,可以执行Post-start script。无法保证script和容器的 Entrypoint谁先运行。
- Pre-Stop:用户删除容器时,会先运行Pre-Stop script,这里面可以定义优雅终止。然后kubelet会发kill -SIGTERM不再接受新的连接然后再发kill -SIGKILL。
terminationGracePeriodSeconds
- 当删除pod到kubelet强制kill容器进程的时间是terminationGracePeriodSeconds。默认是30s。可以防止应用突然被kill掉。
- 如果30s内还没有执行完成,那么pod会被强制删除。
Terminating Pod 的误用
bash/sh 会忽略 SIGTERM 信号量,因此 kilL -SIGTERM 会永远超时,若应用使用 bash/sh 作为Entrypoint,则应避免过长的 grace period。
如果不关心 Pod 的终止时长,那么无需采取特殊措施;
如果希望快速终止应用进程,那么可采取如下方案:
- 在preStop script 中主动退出进程;
- 在主容器进程中使用特定的初始化进程。
优雅启动和终止
优雅的初始化进程应该:
- 正确处理系统信号量,将信号量转发给子进程;
- 在主进程退出之前,需要先等待并确保所有子进程退出;
- 监控并清理孤儿子进程。
可以使用tini作为容器镜像的entrypoint。
参考链接:https://qithub.com/kralin/tini
赞,请问 kubectl get pod 返回的 STATUS 字段的计算逻辑是在源码的哪一块?我没找到