7

Kubernetes Pod驱逐和迁移

 3 years ago
source link: https://www.wencst.com/archives/1050
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Kubernetes Pod驱逐和迁移

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

网上查了一下,出现这种情况不在少数。用户的应用我们没办法控制,好在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”

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

• 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不被迁移。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK