我们可以通过调高ReplicationController、ReplicaSet、Deployment等可伸缩资源的replicas字段手动实现pod中应用的横向扩容。我们也可以通过增加容器资源请求和限制来纵向扩容(目前该操作只能在pod创建时,而非运行时进行)。如果事先知道负载何时会发生,手动扩容时可以的。但是对于突发情况,手动扩容明显是不现实的。
幸运的是,kubernetes可用监控你的pod,并检测到CPU使用率或其他度量增长时自动对它们扩容。如果kubernetes是部署在云基础架构之上,它甚至能在现有节点无法承载更多pod之时,自动新建更多节点。本章会介绍如何让kubernetes进行pod与节点级别的自动伸缩。
kubernetes自动伸缩特性在1.6与1.7版本之间经历了一次重写,所以有可能网上一些介绍已经过时了。

一、pod的横向自动伸缩

横向pod自动伸缩是指由控制器管理的pod副本数量自动伸缩。它由Horizontal控制器执行,通过创建一个HorizontalpodAutoscaler(HPA)资源来启用和配置Horizontal控制器。该控制器周期性检查pod度量,计算满足HPA资源所配置的目标数值所需的副本数量,进而调整目标资源(如Deployment、ReplicationController、ReplicaSet、StatefulSet等)的replicas字段。

1、了解字段伸缩的过程

自动伸缩分为三个步骤:

  1. 获取伸缩资源对象所管理的所有pod度量
  2. 计算使度量数值到达(或接近)所指定目标数值所需要的pod数量
  3. 更新被伸缩资源的replicas字段

下面我们来看看这三个步骤

获取pod度量
Autoscaler本身并不负责采集pod度量数据,而是从另外资源来获取。正如上一章节提到的,pod与节点度量数据是由运行在每个节点的kubelet之上,名为cAdvisor的agent采集的。这些数据将由集群级别的组件Heapster聚合。HPA控制器向Heapster发起REST调用来获取所有pod的度量数据。
pod(s) --> cAdvisor(s) --> Heapster --> Horizontal Pod Autoscler(s)
这样的数据流意味着在集群中必须运行Heapster才能实现自动伸缩。可以在集群的kube-system命名空间中找到Heapster的pod和service。

关于Autoscaler采集度量数据按时的改变
在kubernetes1.6版本之前,HPA直接从Heapster采集度量。在1.8版本中,如果用 --horizontal-pod-autoscaler-use-rest-clients=true参数启动ControllerManger,Autoscaler就能通过聚合版的资源度量API拉取度量了。该行为将在1.9版本开始将变为默认。
核心API服务器本身并不会向外界暴露度量数据。从1.7版本开始kubernetes运行注册多个API服务器并使用他们对外呈现为一个API服务器。这运行kubernetes通过这些底层的API服务器之一对外暴露度量数据。我们将在后面讲述API服务器聚合的内容。
集群管理员负责选择集群中使用何种度量采集器。我们需要一层简单的转换组件将度量数据以正确的格式暴露在正确的API路径下

计算所需要的pod数量
一旦Autoscaler获得了它所调整的资源(Deployment、ReplicationController、ReplicaSet、StatefulSet)所辖pod的全部度量,就可以利用这些度量算出所需的副本数,以使所有副本上度量的平均值尽量接近配置的目标值。该计算的输入是一组pod度量(每个pod可能有多个),输出一个整数值(pod副本数量)。
当Autoscaler配置只考虑单个度量时,计算就比较简单。只要将所有pod度量求和后除以HPA资源配置的目标值,再向上取整即可。实际计算稍微复杂些,Autoscaler还保证了度量数值不稳定、迅速抖动时不会导致系统抖动(thrash)。
基于多个pod度量的自动伸缩(CPU使用率和每秒查询率[QPS])的计算也不复杂。Autoscaler单独计算每个度量的副本数,然后取最大值。

更新被伸缩资源的副本数
自动伸缩操作的最后一步是更新被伸缩资源对象(如ReplicaSet)的副本数字段,然后让ReplicaSet控制器复杂启动更多或删除多余的pod。
Autoscaler控制器通过Scale子资源来修改被伸缩资源的replicas字段,这样Autoscaler就不用了了解它管理资源的细节,而只要通过Scale子资源暴露的界面,就可以完成工作。
这意味着只要API服务器为某个可伸缩资源暴露了Scale子资源。Autoscaler即可操作资源。目前暴露了Scale子资源的资源有:

  • Deployment

  • ReplicaSet

  • ReplicationController

  • StatefulSet

    目前只有这些对象附着Autoscaler

了解整个自动伸缩的过程
cAdvisor从pod获取数据,Heapster从cAdvisor获取数据,HPA从Heapster获取数据,HPA计算出副本数,通过资源对象暴露的Scale去修改资源对象的replicas字段,资源本身的控制器根据replicas字段进行伸缩。需要注意的是前三个过程,每个组件去拉取另一个组件数据的动作是周期性的。这也意味着度量数据的传播与相应动作的触发都需要相当一段时间,不是立即触发的。

2、基于CPU使用率进行自动伸缩

假设你用几个pod来提供服务,如果他们的CPU使用率达到了100%,显然他们已经扛不住压力了,要么进行纵向扩容(scale up),增加他们可用的CPU时间,要么横向扩容(scale out),增加pod数量。因为这里讨论的HPA,所以仅仅关注横向扩容。这样一来,平均CPU使用率就降下来了。
因为CPU的使用通常是不稳定的,比较靠谱的做法是CPU被压垮之前就横向扩容。一定要把目标CPU使用率设置的远低于100%(一定不要超过90%),可能平均负责达到了80%就可以横向扩容了,以预留充分的时间给突发的流量洪峰。但是有一个问题,到底是谁的80%?
在前一章节提到容器中的进程被保证能够使用该容器资源请求中所请求的CPU资源量。但当没有其他进程需要CPU时,进程就能使用节点上所有可用的CPU资源。如果有人说“这和pod使用了80%的CPU”,我们并不知道是指占节点CPU的80%,还是指资源请求量的80%,还是指资源限额硬上限的80%。
就Autoscaler而言,只有pod的请求量彩玉确认pod的CPU有关。Autoscaler对比的是pod实际CPU使用与它的请求,这意味着你需要为被伸缩的pod设置CPU请求,不管是直接设置还是通过LimitRange对象简洁设置的,这样Autoscaler才能确定CPU的使用率。

基于CPU使用率创建HPA
我们现在来创建一个HPA,并让它基于CPU使用率来伸缩pod。先将创建一个设置有CPU请求量的deployment对象,这样才能自动伸缩,你需要为deployment的pod模板添加一个CPU资源请求。

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: kubia
spec:
  replicas: 3    // 初始副本数为 3
  template:
    metadata:
      name: kubia
      labels:
        app: kubia
    spec:
      containers:
      - image: luksa/kubia:v1
        name: nodejs
        resources:
          requests:
            cpu: 100m     // 每个pod请求100毫核
该deployment对象创建时,还没启用自动伸缩,将运行三个实例的 kubia NodeJS应用,每个实例请求100毫核。
创建了Deployment之后,为给它启用横向自动伸缩,需要再创建一个HorizontalpodAutoscaler(HPA)对象,并把它指向前面的deployment。可以通过YAML manifest,但可以简单的通过命令:

kubectl autoscale deployment kubia --cpu-percent=30 --min=1 --max==5
这会帮你创建HPA对象,并指向叫做 kubia 的deployment,还设置了目标CPU使用率为 30% ,但副本数至少为1,最多为5。
提示: 一定要确保目标是deployment而是不底层的 ReplicaSet。这样才能确保预期的副本数在更新后继续生效。手动伸缩也是同样的道理。

来看下HorizontalpodAutoscaler资源的定义,加深理解
$ kubectl  get  hpa.v2beta1.autoscaling kubia  -o yaml
apiVersion: autoscaling/v2beta1
kind: HorizontPodAutoscaler
metadata:
  name: kubia
  ....
spec:
  maxReplicas: 5   // 最大副本数
  metrics:  // 以下表示,Autoscaler调整pod数量,以保持每个pod都使用所请求CPU的30%
  - resource:
      cpu
      targetAverageUtilization: 30
    type: Resource
  minReplicas: 1  // 最小副本数
  scaleTargetRef:  // 该Autoscaler 作用的目标
    apiVersion: extensions/v1beta1
    kind: Deployment
    name: kubia
  status:  // Autoscaler当前的状态
    currentMetrics: [] 
    currentReplicas: 3
    desiredReplicas: 0

注意: HPA资源存在多个版本,新的 autoscaling/v2beta1 个旧的 autoscaling/v1

观察一个自动伸缩事件
cAdvisor获取CPU度量需要一段时间,之后Autoscaler才会采取行动。这段时间里,使用kubectl get hpa 获取HPA资源,TARGETS列会显示

$ kubectl get hpa                                                         
NAME        REFERENCE        TARGET         MINPODS   MAXPODS   REFLICAS
kubia       Deployment/kubia  <unknown>/30%  1         5         0

因为三个pod都无一请求,它们的CPU使用率应该接近0,应该预期 Autoscaler会将它们收缩到一个pod,因为即使只有一个pod,CPU使用率任然会低于30%的目标值。

$ kubectl get deployment hpa-nginx-deploy
NAME        DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
kubia       1          1         1           1           30m

需要注意的是,Autoscaler只会调节Deployment副本数,然后由Deployment控制器负责更新ReplicaSet对象的副本数,从而使ReplicaSet控制器删除多余的两个pod。
可以通过kubectl describe hpa 查看HorizontalpodAutoscaler的事件信息,其中就包含了扩缩容的信息。

触发一次扩容
现在要往pod发生请求,增加它的CPU使用率。随后就可以看到Autoscaler检测到这些,启动更多的pod。
通过一个service 来暴露pod,以便使用单一的URL访问到所有的pod。比较简单的方法就是kubectl expose命令了

kubectl expose deployment kubia --port=80 --target-port=8080

在向pod发送请求之前,运行命令来观察HPA和deployment。

watch -n 1 kubectl get hpa,deployment   // 以逗号分隔资源类型,可以一次列举多个资源类型

如果是macOS系统需要换成一个循环,手动的调用kubectl get 命令来获取,或者使用 kubectl 的–watch选项。不过使用了–watch 选项之后就不能同时列出多个资源了,需要开启多个终端来观察。

同时在另一个终端进行访问

kubectl run  -it  --rm  --restart=Never  loadgenerator  --image=busybox  --sh -c "while true; do wget -O - -q http://kubia.default; done"

run 也可以使用 -it 参数,它可以将控制台附加到观察的进程,可以直接查看输出,当按下CTRL+C组合键时,还会终止进程。–rm 选项表示pod退出后会自动删除。–restart=Never 选项则表示 kubectl run 直接创建一个非托管的pod,而不是通过一个deployment对象间接的创建。对于需要在集群中执行命令,又不想在已有的pod上运行,这组选项很实用。

观察Autoscaler扩容Deployment
随着负载生成的pod运行,可以看到一开始的请求都在目前唯一的pod。与此前一样,度量更新需要一段时间,最后笔者的环境中,pod的CPU使用率在110%,使得Autoscaler增加pod数量为4,于是单个pod的CPU使用率在下降,最终小于30%。(如果负载一直达不到30%,可以生成多个负载pod)
可以使用kubectl describe 检查Autoscaler事件。
一开始CPU使用率超过100%,那是因为当其他进程没有使用CPU时,容器可以使用更多的CPU时间,而我们这里没有设置CPU使用时间上限,所以可以超过100%。
简单的计算一下,最开始只有一个pod,CPU使用率达到了110%,除以目标的30%,得到3.6,再向上取整为4,所以Autoscaler会扩容到4个副本。

了解伸缩操作的最大速率
Autoscaler在单词扩容的速率会受到限制,如果当前副本数大于2,则Autoscaler单次操作最多只能翻倍扩容,如果副本数只有1或2,Autoscaler最多只能扩容到4个副本。
另外,Autoscaler两次之间的扩容之间的时间间隔也是有限制的,目前,只有当三分钟内没有扩缩容操作时,才会触发扩容。缩容操作的周期为五分钟。所以当你看到没有及时扩缩容时不要奇怪。

修改一个已有HPA对象的目标度量值
使用kubectl edit 将前面30修改成60。此时Autoscaler会检测到这一变化,并执行相应的伸缩动作。也可以先删除HPA资源再创建一个,删除HPA只会禁止目标对象的自动伸缩,目标对象的副本会保持删除HPA时的副本数。当再创建一个HPA后,自动伸缩就又会继续。

3、基于内存使用进行伸缩

基于内存的自动伸缩臂基于CPU的困难很多。主要是扩容之后原有的pod需要有办法释放内存。这只能由应用本身来完成,系统无法做到。系统能做的就是杀死重启,希望它少占一些内存。如果应用使用了跟之前一样多的内存,Autoscaler就只能扩容、再扩容,直到达到HPA资源配置的最大pod数量。显然这样不是很好。基于内存的自动伸缩在kubernetes1.8中得到支持,方法与基与CPU的自动伸缩扩容一样。

4、基于其他自定义度量进行伸缩

最开始kubernetes只能基于CPU度量的自动伸缩,后面kubernetes自动伸缩特别小组(SIG),重新设计了Autoscaler。
在HPA中,metrics字段允许你定义多个度量供使用。每个条目都制定相应度量的类型(前面使用的resource)目前在HPA中可以使用的三种度量:

  • 定义metric类型
  • 使用情况会被监控的资源
  • 资源的目标使用量

了解Resource度量类型
Resource类型使Autoscaler基于一个资源度量做出自动伸缩决策,在容器的资源请求中指定那些度量。这一类型的我们已经了解了,来看下其他两种

了解pods度量类型
Pods类型用来引用任何其他种类的(包括自定义的)与pod直接相关的度量。如上面提到的每秒查询次数QPS,或者消息队列中消息的数量(当消息队列服务运行在pod之中)都属于这种度量。要配置Autoscaler使用pod的QPS度量,HPA对象的metrics字段就要包含以下代码清单

...
spec:
  metrics:
  - type: Pods   // 定义一个pod度量
    resource:
      metricName: qps    // 度量名称
      targetAverageValue: 100    // 所有被涵盖pod内的目标平均值    
...    

代码清单配置的Autoscaler,使用该HPA控制的资源对象(deployment、rs等)控制器下所辖pod的平均QPS维持在100的水平。

了解object度量类型
object度量类型被用来让Autoscaler基于非直接与pod相关的度量来进行伸缩。比如,希望基于另一个集群对象,例如ingress对象,来伸缩你的pod。该度量可能是QPS,也可以是平均请求延迟,或者完全不相干的其他东西。
与此前例子不同,使用Object度量类型时,Autoscaler只会从这单个对象中获取度量数据。之前Autoscaler是从所有下属pod中获取度量,并使用平均值。需要在HPA对象的定义中指定目标对象与目标值。

...
spec:
  metrics:
  - type: Object   // 使用某个特定对象的度量
    resource:
      metricName: latencyMillis  // 度量名称
      target:
            apiVersion: extensions/v1beta1
            kind: Ingress
            name: frontend  // Autoscaler需要从中获取度量的特定对象
          targetValue: 20  // 目标值
      scaleTargetRef:   // autoscaler 需要管理的对象
        apiVersion: extensions/v1beta1
        kind: Deployment
        name: kubia    
...       

该实例中HPA被配置为使用Ingress对象frontend 的 latencyMillis度量,目标值为20。HPA会监控Ingress对象的度量,如果该度量超过了目标值较多,Autoscaler便会对kubia Deployment资源进行扩容。

5、确定哪些度量适合用于自动伸缩

不是所有的度量都适合作为自动伸缩的基础,只有当度量的平均值会随着副本数量的伸缩,而线性变化,nameAutoscaler就不能正常工作了。

6、伸缩到0个副本

HPA目前不支持minReplicas字段为0,所有Autoscaler不会缩容到 0 个,即使pod什么都不做。允许pod数量缩容到0个,可以大幅提升硬件利用率。如果你服务器几小时甚至几天都才会收到一条请求,就没必要一直运行,可以把占用的资源释放出来。如果一旦客户端请求进来了,你任然还想让这些服务马上可用。
这叫空载(idling)与解除空载(un-idling),即允许提供特定服务的pod被缩容到0副本。在新请求到来时,请求会被先阻塞,直到pod启动,从而被转发到新pod为止。
kubernetes目前不支持该特性,后续可能会实现。
.

二、集群节点的横向伸缩

当有新pod要部署时,不管是HPA扩容还是手动创建,此时要么对已有pod纵向缩容,要么增加新的节点。如果是自建的集群,那要新增一台物理机并注册到集群。如果是部署在云端基础架构上,就比较方便了。kubernetes支持在需要时立即从云服务提供者请求更多的节点,该特性有 Cluster Autoscaler执行。

1、Cluster Autoscaler 介绍

Cluster Autoscaler负责在由于节点资源不足,而无法调度某个pod到已有节点时,自动部署新节点,也会在节点长时间使用率低的情况下下线节点。

从云端基础架构请求新节点
如果在一个pod创建后,scheduler无法将其调度到任何一个已有节点,一个新节点就会被创建。Cluster Autoscaler会注意此类pod,并请求云服务提供者启动有个新节点。但在这个之前,它会先检查新节点是否能够容纳这些pod,如果不行,就没必要启用这么一个节点了。
云服务提供者一般把同规格(或者相同特性)的节点聚合成组,因此Cluster Autoscaler需要指明节点类型。
Cluster Autoscaler通过检查可用的节点分组来确定是否可以容纳这些pod,如果只有一种节点类型满足,Cluster Autoscaler就可以增加该节点分组的大小,让云服务提供商给分组增加一个节点。如果存在多种类型的节点分组满足条件,Cluster Autoscaler就必须挑选一个最合适的。这里“最合适”的精确含义显然是可以配置的。
新节点启动后,其上运行的kubelet会联系API服务器,创建一个可以Node资源以注册该节点,该节点即成为集群的一部分,可以调度pod到其上了。

归还节点
当节点利用率不足时,Cluster Autoscaler也需要能够减少节点的数目。Cluster Autoscaler通过监控所有节点上请求CPU与内存来实现这一点。如果某一个节点上所有pod请求的CPU、内存都不足50%,该节点及被认为不需要。
这并不是决定是否要归还某一个节点的的唯一因素。Cluster Autoscaler 会检查是否由系统pod(仅仅)运行在该节点上(不包括每个节点上运行的服务,比如DaemonSet所部署的服务)。如果有系统pod运行在该节点上,那它不会被归还。对非托管pod,以及有本地存储的pod也是如此,否则就会造成这些pod提供的服务中断。
当一个节点被选中下线,首先会标记为不可调度,随后运行在其上的pod将被疏散至其他节点。因为所有这些pod都属于ReplicaSet或其他控制器,它们代替pod会被创建或调度到其他剩下的节点(这也是为何要先标记节点不可调度)

手动标记节点为不可调度、排空节点
节点也可以手动被标记为不可用调度并排空,可以直接通过命令完成
kubectl cordon 标记不可调用,但不排空节点上的pod,对在其上的pod不做任何事情
kubectl drain 标记不可调用,并疏散其上所有的pod
两种情况下,在kubectl uncordon 解除节点不可调用之前,不会有新的pod调度到该节点

2、启用Cluster Autoscaler

不同的云厂商有不同的方法,可以查询云厂商文档
需要注意的是,Cluster Autoscaler 将它的状态发布到kube-system命名空间的 cluster-autoscaler-status ConfigMap上。

3、限制集群缩容是的服务干扰

如果一个节点发生非预期故障,不可能阻止其上的pod不可用。但如果是Cluster Autoscaler或认为的操作下线,可以用一个新特性来确保下线操作不会干扰到这个节点上pod所提供的服务。一些服务要求至少保持一定数量的pod持续运行,kubernetes可以使用 podDisruptionBudget资源来确保这一点。
它包含了一个pod标签选择器和一个数字,指定要至少维持运行的pod数量,在1.7版本之后还支持最大不可用pod数量。
可以使用命令来创建它
kubectl create pdb kubia-pod --selector=app=kubia --min-available=3
查看它的yaml文件

$ kubectl  get  pdb kubia-pod -o yaml
apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
  name: kubia-pod
spec:
  minAvailable: 3   // 至少有3个pod始终可用
  selector:
    matchLabels:
      app: kubia   // 用来指定哪些pod
status:
....    

也可以用百分比而非绝对的数字来写 minAvailable字段。maxUnavailable 表示最多有几个pod不可用

(本文章是学习《kubernetes in action》 一书时做的笔记,由于本人是刚学习k8s,且自学方式就是把它敲一遍,然后加一些自己的理解。所以会发现很多内容是跟书中内容一模一样,如果本文是对本书的侵权,敬请谅解,告知后会删除。如果引用或转载,请注明出处——谢谢)

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐