Kubernetes Pod驱逐和迁移

作者: wencst 分类:docker,微服务,架构设计 发布时间: 2018-12-25 11:29 阅读: 8,737 次

网上查了一下,出现这种情况不在少数。用户的应用我们没办法控制,好在K8S本身提供了完善的Pod迁移和驱逐机制,能够在异常情况下保证集群和大多数应用正常运行,下面来详细看一下。

用户配置

尽管Kubernetes中的Pod驱逐和迁移机制非常复杂,但是实际用户在使用的时候需要在Pod内配置的东西非常简单,只需要配置spec.containers.resources,下面包含两个字段limits和requests,再下面就是各项资源的数量。

下面是一个比较完整的配置示例:

spec:

  1. containers:
  2. – name:qos-demo-ctr
  3. image:nginx
  4. resources:
  5. limits:
  6. memory:”200Mi”
  7. cpu:”700m”
  8. requests:
  9. memory:”200Mi”
  10. cpu:”700m”

可以配置的资源类型包括:

• cpu

• memory

• storage

• ephemeral-storage

我们本次要讲的驱逐和迁移逻辑主要与cpu和memory相关,与另外两个关系不大。

那么如果你是用户的话,关于这个字段的配置,有两条建议:

1. 不要偷懒,limits和requests都填好。

2. 不要贪心,吃多少拿多少。

不要误会,这不是吃自助餐,配多了也没有用不完会收费的问题,这两条建议完全是从资源紧缺的场景下应用存活的角度考虑。

系统层面

Kubernetes会使用Linux的OOM killer能力,OOM killer是linux系统中控制内存不足时内核行为的插件。简单说明下Kubernetes用到的OOM killer的工作逻辑:

1. 每个应用有一个分值,即OOMScoreAdj得分越高,越先被杀掉。(小时候老师不是这么说的呀)

2. 当分值一样时,申请资源越多越先被杀掉。(杀个最胖的,少拉仇恨)

下面看一下OOMScoreAdj的计算方式

在Kubernetes里,关于OOM分值的计算,会将Pod分为三类,分别是Guaranteed/Burstable/BestEffort。分类依据如下表所示:

条件 分类 分值
pod中每一个容器都有cpu和memory的limit和request Guaranteed -998
pod中所有容器没有任何资源限制 BestEffort 1000
其他 Burstable 2~999

其中Burstable的Pod分值计算公式如下:

1000 – (1000*memoryRequest)/memoryCapacity

另外程序里加上了两个限制,保证这个分值的范围在2~999之间。

我们知道OOM的分值越高越容易被杀掉,所以如果没有配置任何资源限制的话,分值就是1000,肯定最先被杀掉,假设pod1的容器只在资源限制里随便配了一个字段,而pod2什么都没配,那么肯定pod2会比pod1先被杀掉,因为程序保证了pod1最大分值为999,会比pod2低一分。

当然最好还是都配置好,这样OOM的分值就会是-998,这是一个特别高的保证优先级,基本上如果这个优先级的Pod被杀掉,那就表示这个节点马上要挂掉了。

除了用户自己的Pod之外,系统进程也都会有一个自己的分值。具体如下表所示:

组件 分值
kubelet -999
kube-proxy -999
docker -999
pod-infra-container -998

注:详细信息可以查看源码

kubeGenericRuntimeManager.generateLinuxContainerConfig()

Scheduler根据资源使用情况的调度策略

根据上面的描述,当节点资源紧缺的时候,一些Pod会被系统清掉。这个能够保证集群能在最大程度上正常运行。但是这需要一个前提,就是调度器能够根据资源使用情况调度Pod。否则就可能出现类似手机上一核工作七核围观的情况。或者一个Pod刚被清掉,又被调度器调度到这个节点上,一个Pod杀来杀去,还是有点残忍的。

好在Scheduler有根据Node资源的调度策略,可以平衡各个节点的资源。调度时Scheduler会根据节点的资源使用情况进行排序,并计算得到一个分值,算入最后节点的总得分中。这种策略当然不会保证pod必然调度到资源使用最少的节点中(实际也没有那种必要),但肯定会把节点资源使用情况作为一个考量因素。

相关策略及分值计算如下表所示:

策略 说明
LeastResourceAllocation 计算pod调度到节点之后,节点剩余cpu和内存占总cpu和内存的比例,比例越高优先级越高
BalancedResourceAllocation 计算节点上cpu/内存/存储使用比例三者的方差,方差越小越优先
MostResourceAllocation 计算pod调度到节点之后,节点所用cpu和内存占总cpu和内存的比例,比例越高优先级越高。(自动扩所容用到)

上面是计算方式的简单描述,我们需要注意到的点有:

•BalancedResourceAllocation策略目的是让节点资源使用更均衡,防止某一种资源占满了,另外两种资源还完全没用的情况,这样的话资源利用率也不高。但是它必须与LeastResourceAllocation结合使用。

•三种资源相关的策略都会假设待调度的pod已经调度到节点上来计算节点已用资源。计算资源的时候只用到pod各个容器的resource.request,不考虑limit。如果没有配置request则使用默认值,cpu 0.1核,内存200M。

•LeastResourceAllocation中以资源使用量占节点可用资源的比值为准,而非资源的绝对量。

注:更多关于调度策略的细节,可以查看源码:

pkg\scheduler\algorithm\priorities\most_requested.go

pkg\scheduler\algorithm\priorities\resource_allocation.go

pkg\scheduler\algorithm\priorities\balanced_resource_allocation.go

pkg\scheduler\algorithm\priorities\least_requested.go

Kubelet驱逐策略

调度策略能保证在布置pod的时候不给节点太大压力,但是如果已经调度到节点上的Pod不好好跑,占了太多资源怎么办呢?

虽然有前面介绍的OOM killer,但是这相当于我们的最后一条防线,但是玩过植物大战僵尸的人都知道,肯定不能靠后面的小车档僵尸,毕竟等到了这一步的时候,节点已经在崩溃的边缘了。

所以有了Kubelet驱逐机制,当节点资源较少时,Kubelet为了保证节点稳定运行,会驱逐一些Pod。

资源类型

kubelet支持通过下面几种资源类型触发驱逐,包括:

驱逐信号 描述
memory.available memory.available :=  node.status.capacity[memory] – node.stats.memory.workingSet
nodefs.available nodefs.available :=  node.stats.fs.available
nodefs.inodesFree nodefs.inodesFree :=  node.stats.fs.inodesFree
imagefs.available imagefs.available :=  node.stats.runtime.imagefs.available
imagefs.inodesFree imagefs.inodesFree :=  node.stats.runtime.imagefs.inodesFree

可以根据上面5中资源类型配置驱逐触发条件,触发条件的形式如下:

<eviction-signal><operator><quantity>

eviction-signal即上表列出的驱逐信号,operator是判断方法,目前只支持<,quantity可以是资源的数值和百分比,比如,配置memory的驱逐策略,可以是memory.available<10%,也可以是memory.available<1Gi。

两种驱逐形式

Kubelet有软驱逐和硬驱逐两种形式,主要区别如下:

硬驱逐:可用资源小于规定的数值或百分比时立即出发驱逐。

软驱逐:可用资源小于规定数值或百分比并持续一定时间后触发驱逐,并且被驱逐的Pod有优雅删除时间。

可以通过Kubelet的flag配置软驱逐或硬驱逐,以及相关参数,示例如下:

第一种情况

# 当可用存储<100Mi,或可用nodefs<100Mi触发驱逐

–eviction-hard=memory.available<100Mi[,][nodefs.available<100Mi]

另一种情况

# 当可用存储<100Mi,持续时间超过1分钟后触发驱逐,被驱逐pod的优雅删除时间为30s.

–eviction-hard=memory.available<100Mi  –eviction-soft-grace-period=1m  –eviction-max-pod-grace-period=30s

系统资源分配

前文描述的逻辑,无论是OOM killer,还是根据资源的调度,或者是Kubelet的驱逐,默认情况下都认为Pod能够使用节点全部可用容量。这肯定有其它问题,因为节点内还有很多系统和Kubernetes进程,这些进程使用的系统资源应该预留出来。

Kubelet的Node Allocatable特性就是为了这一目的。在Node Allocatable的规划中,Node资源可以分成四份,这样实际pod可能使用的资源就可以如下图所示.

Kube Reserved:为Kubelet/node problm detector等系统守护进程预留的资源。

System Reserved:为sshd/udev等系统守护进程预留的资源.

Eviction Thresholds:驱逐触发条件,当pod使用资源数量到达这一条线时触发驱逐。这样从驱逐到触发OOM killer会有一个缓冲。

具体使用过程中,需要通过配置Kubelet的flag允许系统资源分配,举个例子,为kube和system进场预留,具体配置如下所示:

# 为kube和system进程预留cpu,内存,硬盘资源.

# 为kube和system创建对应的cgroup

# node资源分配分为pods,kube-reserved,system-reserved三种

# 当可用内存<500Mi时触发驱逐

–enforce-node-allocatable=pods,kube-reserved,system-reserved

–kube-reserved-cgroup=/kubelet.service

–system-reserved-cgroup=/system.slice

–kube-reserved=cpu=1,memory=2Gi,ephemeral-storage=1Gi

–system-reserved=cpu=500m,memory=1Gi,ephemeral-storage=1Gi

–eviction-hard=memory.available<500Mi,nodefs.available<10%

其他相关配置

•–eviction-minimum-reclaim=memory.available=100Mi:最小资源回收量,防止每次驱逐只回收一点点资源,造成资源波动的情况。

•–eviction-pressure-transition-period=5m:node异常状态恢复时间,防止资源波动的时候node状态频繁切换。

注:具体逻辑可查看代码

// 执行驱逐

pkg\kubelet\eviction\eviction_manager.go(managerImpl.synchronize)

// 根据参数创建驱逐触发条件

pkg\kubelet\eviction\helpers.go(ParseThresholdConfig)

// 获取节点状态

pkg\kubelet\server\stats\summary.go(summaryProviderImpl.Get)

// 配置node资源分配cgroup

pkg\kubelet\cm\node_container_manager.go(containerManagerImpl.enforceNodeAllocatableCgroups)

Pod迁移机制

以上工作理论上可以保证在整个系统负载不高的情况下,单个Node不会因为资源不足而挂掉。

但是实际应用中,Node难免会因为这样那样的原因处于异常状态,这时候就需要node-controller的Pod迁移机制,把异常Node上的Pod迁移到其他地方。

当Node Ready状态处于unknown或false ,且持续时间超过–pod-eviction-timeout规定时间后使用驱逐或者taint-toleration的形式将Node上的Pod迁移到其他节点。

实际执行过程中会将有问题的节点添加到迁移队列,并且按照一定算法严格控制节点上Pod的迁移速率。在执行Pod迁移时,会进行如下判定:

1.大集群判定:根据large-cluster-size-threshold参数判定,当集群Node数量大于这一数值是判定为大集群,默认是50.

2.集群被严重破坏判定:当集群内异常node数量大于2,且比例大于unhealthy-zone-threshold规定比例时,认为集群被严重破坏,默认这一数值是0.55。

根据上面两个判定,将集群分为三种状态,针对这三种状态执行不同的迁移速率:

•正常状态,按照node-eviction-rate规定的速率迁移,默认是0.1qps,即每10s取出一个异常节点,迁移上面的pod。

•大集群,单个zone被严重破坏场景,执行secondary-node-eviction-rate规定的第二迁移速率,默认为0.01,即100s执行一次迁移。

•大集群,所有zone被严重破坏场景,正常迁移.

•小集群严重破坏场景,不执行迁移。

另外,在1.9及以后版本可以打开Feature-gate TaintBasedEvictions后可以使用基于taint的迁移,逻辑与正常的迁移机制相同,不过可以通过在Pod中配置toleration控制Pod迁移时间,或者控制节点异常状态时Pod不被迁移。

如果文章对您有用,扫一下支付宝的红包,不胜感激!

欢迎加入QQ群进行技术交流:656897351(各种技术、招聘、兼职、培训欢迎加入)



Leave a Reply