容器启动异常后重启
如果容器直接启动失败(如业务进程异常退出),那么Kubelet会尝试在本机重启容器,尝试间隔时间按照指数递增(10s,20s,40s...),最长不超过5分钟。
引入探针机制判断业务是否正常
当业务容器启动成功后,Kubelet只能知道容器已经处于运行状态,但容器里面运行应用的真实状态就不得而知了。为此,Kubelet引入了探针机制,Kubelet支持两种探针LivenessProbe和ReadinessProbe。
LivenessProbe(存活探针):探测容器中的应用是否正在运行。如果探测失败,Kubelet会终止容器,并根据“重启策略”重建容器。如果设置存活探针,默认状态为Success,LivenessProbe的SuccessThreshold为1。
ReadinessProbe(绪探探针):探测容器中,应用是否准备好服务请求。如果探测失败,端点控制器将从与Pod匹配的所有Service端点中删除该Pod的IP地址。初始延迟之前的就绪状态默认为Failure。如果容器不提供就绪探针,则默认状态为Success。
那么在什么条件下使用存活(liveness)或就绪(readiness)探针呢?
如果容器中的应用能够在遇到问题或不健康的情况下自行崩溃,则不一定需要存活探针,Kubelet根据Pod的restartPolicy自动执行正确的操作。
如果应用在故障时仍然保持运行状态不退出,则需要借助存活探针,探测服务真实状态,并指定restartPolicy为Always或OnFailure。
应用启动是需要时间的。为了避免容器刚启动(此时应用还未启动)时,kube-proxy就直接将流量导入容器引发的错误,需要引入就绪探针。探测成功后,才开始将业务流量导入Pod。(如果是多个副本,根据业务量进行弹缩副本,引入就绪探针,外部访问时就可以避免访问到还没有启动完成的pod。如果是单个副本或者固定副本数,个人觉得并不需要就绪探针)
通常存活探针和就绪探针使用相同的探测方式来检测应用状态:
1)ExecAction:在容器内执行指定命令。如果命令退出时,返回码为0,则认为探测成功。
2)TCPSocketAction:对指定端口上的容器IP地址进行TCP检查。如果端口打开,则诊断为成功。这个可以理解为telnet端口,查看是否被监听。
3)HTTPGetAction:对指定的端口和路径上的容器IP地址发送HTTP Get请求。如果响应的状态码大于等于200,并且小于400,则认为探测成功。
PostStart和PreStop
在一个Pod的生命周期中,Kubernetes提供了两个钩子函数,一个是容器启动之后PostStart,另一个是容器关闭前PreStop。
由于PostStart和容器Entrypoint是异步执行的,Kubernetes并不能保证PostStart一定是在Entrypoint之后执行的,但如果PostStart一直无法结束,那么Pod将无法进入Running状态,所以PostStart通常可以在短时间内完成任务,不会驻留太长时间。
PreStop一定是在容器关闭之前执行,执行完成后,才会继续执行关闭操作。如果PreStop在关闭时间(默认30s)之内无法完成时,将强制停止,并将关闭时间设置成2s,继续执行之后的关闭操作。
PostStart和PreStop的配置方式和探针配置非常相似,都支持Exec(命令行)和HTTP请求两种方式。
以上官网提供的一个PostStart和PreStop的案例,目的是在容器启动后输出一段内容到message文件中,以及在容器关闭前停止nginx服务。
另一个案例:SpringCloud应用都是通过统一的服务注册发现中心eureka,在应用的服务端添加preStop的hook可以完成容器关闭前,通知eureka摘除服务节点,从而避免客户端报错500。
pod驱赶
Kubernetes通过资源调度算法将资源公平合理地分配给每一个Pod,从而能充分利用资源,达到资源的最优分配。集群资源有可能不足,为了保障高优先级任务的稳定运行,引入了QoS的概念。QoS通过释放低优先级任务占用资源,转移给高优先级任务,从而保障高优先级任务的执行,那么Kubernetes如何做到Pod分优先级的呢?Pod的QoS分级从高到低分为三个级别:Guaranteed、Burstable和Best-Effort。
Guaranteed级别是指Pod中所有容器都必须设置limit。如果有一个容器设置了request,那么所有的容器都要设置,并且针对每个容器的request和limit必须相等。如果一个容器只设置了limit,而未设定request,则request值等于limit值。
Burstable级别是指Pod中只要有一个容器的request和limit的设置不相同,该Pod的QoS即为Burstable。
Best-Effort是指Pod中所有容器的request和limit均未设置。
疑问:容器的request和limit是人为设置的,那么QoS是Kubernetes根据人为设置的request和limit来自动判断是某个级别还是人为设置?
为了保证高优先级Pod(如果pod和容器是一对一,这个地方说是容器也可以)的稳定运行,Kubelet引入了Pod驱赶策略,将优先级较低的Pod驱赶到其他节点运行。那么何时会触发驱赶呢?Kubelet设置Pod的驱赶条件主要包括memory.available(节点内存可用值)、nodefs.available (文件系统存储可用值)、nodefs.inodesFree(inode可用值)、imagefs.available(镜像文件系统存储可用值)和imagefs.inodesFree(镜像文件系统inode可用值)这五个指标。
备注:QoS三个级别是针对Pod所有资源的,包括Pod的内存和存储。此处的驱赶条件是某个节点上所有Pod的资源占用之和是否达到该节点上因资源的使用情况设置的驱赶条件(奇怪的是,没啥没有CPU资源?)。
如果Kubelet启动参数设置memory.available<10%或者memory.available<1Gi后,当Kubelet所在主机剩余可用内存不足10%或者1GB后,将会触发驱赶动作。
Kubelet驱赶的阈值分为软硬两个值,软阈值是指当节点满足该阈值后,Kubelet允许业务Pod优雅停止,硬阈值则是会触发Kubelet强制杀死Pod并且会标注节点处于不健康状态(内存不足MemoryPressure、磁盘不足DiskPressure),从而阻止新Pod在调度时候分配到该节点。
除了上面介绍的QoS优先级以外,在Kubernetes 1.8版本引入了PriorityClass(优先级)和Preemption(抢占)功能,但直到Kubernetes 1.14,该功能才进入稳定版本。之前版本的Scheduler在出现资源不足时,新的容器将会一直处于等待调度状态(Pending),直到运维人员手动添加新资源,或者删除其他容器后,这些容器才有可能被重新调度。为了保障高优先级任务的执行,引入了Pod优先级和抢占机制,这保证了高优先级的任务首先被调度,并且在整个集群资源不足的情况下,终止低优先级任务,从而保障高优先任务的稳定执行。
OMM killer
在驱赶后,主机上面的资源可能仍然无法满足要求,此时便会触发Linux内核的OOM killer,它会终止优先级较低的进程,保障系统继续运行。
在Kubelet启动容器时,不同优先级的容器会设置对应的OOM分数(分数越大,优先级越低,在资源不足时,将首先被回收),默认的OOM分数如下。
Burstable级别Pod的OOM分数则是通过下面公式计算得出的,可见Pod申请的内存越多,OOM分数越低,优先级越高。
为了保障Pod不被OOM,请将优先高的任务设置成guaranteed。
亲和与反亲和调度策略和排除策略
之后的Kubernetes又引入了亲和(Affinity)和反亲和(Anti-affinity)调度策略,可以更加灵活地设置容器和容器,以及容器和节点之间的调度关系。亲和和反亲和策略主要分为3个场景:节点亲和、Pod亲和及Pod反亲和,每种场景下都有两种策略:RequiredDuringSchedulingIgnoredDuringExecution (调度时必须满足规则)和PreferredDuringScheduling IgnoredDuringExecution(调度时可选满足规则)。
通过上面的亲和/反亲和策略能够很好地控制容器调度到某些节点。除此外,Kubernetes还提供一种排除策略,控制容器不调度到某些节点上,通过为主机添加Taint (污点)和为Pod添加Tolerations(容忍)的方式完成。
节点支持设置3种污点策略:NoSchedule (严格不调度)、PreferNoSchedule(最好不调度)及NoExecute(不允许运行)。当节点被设置为污点后,只有设置容忍测量Pod才能调度这个节点。
网友评论