一文看懂 Kubernetes 弹性伸缩

2024-09-06 17:55:04 浏览数 (2)

本文将详细介绍容器弹性伸缩的相关知识,包括K8s有哪些弹性策略?缩容的优先级?什么是HPA?HPA怎样工作?

01、k8s弹性策略有哪些?

在 Kubernetes 集群中,自动化资源管理和伸缩是保持应用高效运行的关键。Kubernetes 提供了几种不同的机制来帮助实现这一目标:水平自动伸缩(HPA, Horizontal Pod Autoscaler)、垂直自动伸缩(VPA, Vertical Pod Autoscaler)和集群自动伸缩(CA, Cluster Autoscaler)。这些伸缩器在功能和使用场景上有所不同:

  • HPA:根据 CPU 使用率或其他自定义指标自动增加或减少 Pod 的副本数,如在业务高峰自动增加Pod副本数,在业务低峰自动减少Pod副本数,通常用于无状态应用;
  • VPA:自动调整 Pod 的 CPU 和内存请求和限制,它的目标是为每个 Pod 分配最合适的资源量,既避免资源浪费,通常用于有状态应用或单实例应用;
  • CA:根据集群的当前负载和资源需求自动增加或减少节点的数量,适用于需要根据应用负载动态调整集群大小的场景,CA 特别适用于云环境

上述所提到的HPA和VPA属于调度层弹性,CA属于资源层弹性,跨层级弹性策略可以结合使用,但不建议HPA和VPA共同使用,避免造成冲突。

02、Pods缩容优先级

在HPA自动扩容的时候,我们似乎不关心Workload中哪个Pod先启动还是后启动,反而我们更关注缩容的时候,哪个Pod被先终止,因为搞不好正在处理业务请求的Pod被它给先终止了,接下来我们就透过源码来解析Kubernetes的缩容规则吧。

kubernetes>pkg>controller>controller_utils.go

代码语言:javascript复制
func (s ActivePods) Less(i, j int) bool {
    // 1. Unassigned < assigned
    // If only one of the pods is unassigned, the unassigned one is smaller
    if s[i].Spec.NodeName != s[j].Spec.NodeName && (len(s[i].Spec.NodeName) == 0 || len(s[j].Spec.NodeName) == 0) {
        return len(s[i].Spec.NodeName) == 0
    }
    // 2. PodPending < PodUnknown < PodRunning
    if podPhaseToOrdinal[s[i].Status.Phase] != podPhaseToOrdinal[s[j].Status.Phase] {
        return podPhaseToOrdinal[s[i].Status.Phase] < podPhaseToOrdinal[s[j].Status.Phase]
    }
    // 3. Not ready < ready
    // If only one of the pods is not ready, the not ready one is smaller
    if podutil.IsPodReady(s[i]) != podutil.IsPodReady(s[j]) {
        return !podutil.IsPodReady(s[i])
    }
    // TODO: take availability into account when we push minReadySeconds information from deployment into pods,
    //       see https://github.com/kubernetes/kubernetes/issues/22065
    // 4. Been ready for empty time < less time < more time
    // If both pods are ready, the latest ready one is smaller
    if podutil.IsPodReady(s[i]) && podutil.IsPodReady(s[j]) {
        readyTime1 := podReadyTime(s[i])
        readyTime2 := podReadyTime(s[j])
        if !readyTime1.Equal(readyTime2) {
            return afterOrZero(readyTime1, readyTime2)
        }
    }
    // 5. Pods with containers with higher restart counts < lower restart counts
    if maxContainerRestarts(s[i]) != maxContainerRestarts(s[j]) {
        return maxContainerRestarts(s[i]) > maxContainerRestarts(s[j])
    }
    // 6. Empty creation time pods < newer pods < older pods
    if !s[i].CreationTimestamp.Equal(&s[j].CreationTimestamp) {
        return afterOrZero(&s[i].CreationTimestamp, &s[j].CreationTimestamp)
    }
    return false
}

总结一下,缩容的顺序如下:

  1. 未调度>已调度:未调度到节点的Pods被终止的优先级更高,因为它们还没有开始运行任何任务,所以优先缩减它们理所应当。
  2. PodPending>PodUnknown>PodRunning:PodPending表示Pod还未开始运行,PodUnknown表示Pod状态未知PodRunning表示Pod正在运行。优先缩减还未运行或者状态未知的Pods,以尽可能保持系统的稳定运行。
  3. Not ready>ready:Not ready的Pods被终止的优先级更高,因为它们可能存在问题,优先缩减它们可以减少系统的错误率。
  4. 较晚Ready>较早Ready:较晚Ready的Pods被终止的优先级更高,因为它们没有接收业务请求的可能性较大,优先缩减它们可以减少对业务的影响。
  5. 容器重启次数较多>较少:重启次数多的Pods被终止的优先级更高,因为它们存在问题的可能性较大,所以优先缩减它们。
  6. 创建时间较短>较长:创建时间短的Pods优先级更高,因为它们没有接收业务请求的可能性较大,优先缩减它们可以减少对业务的影响。

03、HPA实践

我们先创建一个测试用的负载(Workload),命名为nginx

nginx.yaml

代码语言:javascript复制
apiVersion: apps/v1 
kind: Deployment
metadata:
  name: nginx
  namespace: hpa
  labels:
    app: nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx  
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.16.1
        ports:
        - containerPort: 80
        resources:
          requests:       # 必须设置,否则HPA无法运行
            cpu: 100m     # 设置容器运行时所需CPU资源
            memory: 200Mi # 设置容器运行时所需内存资源
          limits:         # 不是必须设置,但是设置后可以避免压测时占用过多资源
            cpu: 200m      # 限制容器CPU资源为200m,m 是毫核(milli-cores)的缩写,因此200m表示0.2核
            memory: 400Mi   # 限制容器内存资源为400Mi 
---
kind: Service
apiVersion: v1
metadata:
  name: nginx
  namespace: hpa
spec:
  type: NodePort
  selector:
    app: nginx
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80

然后我们将其部署到k8s上,运行结果如下

然后我们再创建一个HPA,通过scaleTargetRef设置当前HPA绑定的对象。本例中绑定名为nginx的Deployment,在确保所有Pod中容器的平均CPU使用率或平均内存使用率达到50%时触发扩缩操作(二者满足其一便会触发)。

hpa.yaml

代码语言:javascript复制
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: nginx-hpa
  namespace: hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: nginx
  minReplicas: 1  # Deployment可缩容的容器数量下限,需设置为大于等于1的整数
  maxReplicas: 10  # 该Deployment可扩容的容器数量上限,需大于minReplicas
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 50 # 目标资源的平均使用率,即资源使用量的平均值与其请求量之间的比例
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 50 # 目标资源的平均使用率,即资源使用量的平均值与其请求量之间的比例

我们通过kubectl apply -f hpa.yaml执行HPA创建,并通过kubectl describe看到HPA已经创建完成

在创建完HPA后,我们可以看到nginx的副本数变成了1,是因为我们HPA设置的minReplicas为1,在负载(Workload)平均资源使用率低于50%时,会触发缩容。

好了,接下来,我们将通过压测验证下HPA扩容,在这过程中,我们可以用kubectl get hpa -w -n hpa观察容器的资源使用率和nginx 副本数量的变化,如下图,我们可以看到随着压测,内存的使用率逐渐上涨,在使用率超过50%后REPLICAS副本数会自动增加,至此HPA的伸缩容均已验证完成。

04、HPA相关操作

从v1.18开始,K8s v2beta2 API允许通过HPA的behavior字段配置扩缩行为

稳定窗口,防止扩缩容指标频繁波动 ,如下示例设置为300s作为观察期

代码语言:javascript复制
behavior:
  scaleDown:
    stabilizationWindowSeconds: 300
  scaleUp:
    stabilizationWindowSeconds: 300

禁止扩容

代码语言:javascript复制
behavior:
  scaleUp:
    selectPolicy: Disabled

禁止缩容

代码语言:javascript复制
behavior:
  scaleDown:
    selectPolicy: Disabled

05、总结

综上所述,我们认识了什么是HPA,HPA如何工作,但是在实际的业务场景中,Pods缩容期间是否会对业务带来影响呢?该如何解决?欢迎留言,本期就介绍到这里,谢谢!

欢迎订阅我的公众号「SRE运维手记」

0 人点赞