Kubernetes如何管理pod更高可用

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Kubernetes如何管理pod更高可用相关的知识,希望对你有一定的参考价值。

参考技术A 如果容器直接启动失败(如业务进程异常退出),那么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,则认为探测成功。

在一个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。

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优先级和抢占机制,这保证了高优先级的任务首先被调度,并且在整个集群资源不足的情况下,终止低优先级任务,从而保障高优先任务的稳定执行。

在驱赶后,主机上面的资源可能仍然无法满足要求,此时便会触发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才能调度这个节点。

Kubernetes如何管理暴露服务

一、Pod概念

在一个集群中,K8S会为每个Pod都分配一个集群内唯一的IP地址。因为K8S要求底层网络支持集群内的任意节点之间的两个Pod能够直接通信。这些容器共享当前Pod的文件系统和网络,而这些容器之所以能够共享,是因为Pod中有一个叫Pause的根容器,其余的用户业务容器都是共享这个根容器的IP和Volume。所以这些容器之间都可以通过localhost进行通信。

1、为什么要引入根容器的概念?

那是因为如果没有根容器的话,当一个Pod中引入了多个容器的时候,我们应该用哪一个容器的状态来判断Pod的状态呢?所以才要引入与业务无关且不容易挂掉的Pause容器作为根容器,用根容器的状态来代表整个容器的状态

2、Pod被分配固定的IP如何负载均衡?

熟悉微服务的都知道,微服务中最忌讳的就是出现单点的情况。

所以针对同一个服务我们一般会部署2个或者更多个实例。在K8S中,则是会部署多个Pod副本,组成一个Pod集群来对外提供服务。

而我们前面提过,K8S会为每一个Pod提供一个唯一的IP地址,客户端就需要通过每个Pod的唯一IP+容器端口来访问到具体的Pod,这样一来,如果客户端把调用地址写死,服务器就没有办法做负载均衡,而且,Pod重启之后IP地址是会变的,难道每次重启都要通知客户端IP变更吗?

为了解决这个问题,就要引出Service的概念了。

二、Service

Service是K8S中最核心的资源对象之一,Service一旦被创建,K8S会为其分配一个集群内唯一的IP,叫做ClusterIP,而且在Service的整个生命周期中,ClusterIP不会发生变更,这样一来,就可以建立一个ClusterIP到服务名的DNS域名映射即可,即可通过服务名或IP两种方式进行寻址。

值得注意的是,ClusterIP是一个虚拟的IP地址,无法被Ping,仅仅只限于在K8S的集群内使用。

而Service对客户端,屏蔽了底层Pod的寻址的过程。并且由kube-proxy进程将对Service的请求转发到具体的Pod上,具体到哪一个,由具体的调度算法决定。这样以来,就实现了负载均衡。

而Service是怎么找到Pod的呢?这就需要继续引入另外一个核心概念Label了。

三、Label

Lable本质上是一个键值对,具体的值由用户决定。Lable就是标签,可以打在Pod上,也可以打到Service上。总结来说,Label与被标记的资源是一个一对多的关系。

例如,我们给上面所描述的Pod打上了role=serviceA的标签,那么只需要在Service中的Label Selector中加入刚刚那个标签,这样一来,Service就可以通过Label Selector找到打了同一Label的Pod副本集了。

四、Replica Set

K8S最开始有一个概念叫Replication Controller,不过现在已经慢慢的被Replica Set所替代,RS也叫下一代的RC。简单来说Replica Set定义了一种期望的场景,即让任何时候集群内的Pod副本数量都符合预期的值。

**RS一旦被创建,集群就会定期的检测当前存活的Pod数量,如果多了,集群就会停掉一些Pod。相反,如果少了就会创建一些Pod。**假设某个服务有两个实例在运行,其中一个意外挂掉了,如果我们设置了副本数量是2,那么集群就会自动创建一个Pod,以保证集群内始终有两个Pod在运行。

以上是关于Kubernetes如何管理pod更高可用的主要内容,如果未能解决你的问题,请参考以下文章

「容器云架构」设置高可用性Kubernetes Master

IPVS从入门到精通kube-proxy实现原理

「首席看容器云架构」设置高可用性Kubernetes Master

IPVS从入门到精通kube-proxy实现原理(转)

深入kubernetes之Pod——一pod多容器

kubernetes基本概念 pod, service