VPA

Kubernetes VPA(Vertical Pod Autoscaler)可以理解为对单个服务资源进行扩容,如CPU、内存之类。它一般应用于一些中心化的单体应用,且无法对其进行部

署多份副本的场景,如 Prometheus 或 Jenkins 这类垂直 Pod 应用自动扩缩容。

VPA 会基于 Pod 的 资源使用情况自动为集群设置资源占用的限制,从而让集群将 Pod 调度到有足够资源的最佳节点上。VPA 也会保持最初容器定义中资源

request 和 limit 的占比。

当控制器检测到一个Pod负载过高时,这时会对当前Pod进行终止,接着对Pod的CPU或内存进行扩容,最后重建Pod,此时Pod可能在任何一个节点进行重建。

它与HPA的扩容机制是完全不一样的,VPA扩容过程中将无法正常提供服务,也因此它使用的场景相比HPA来说,要少的多。

安装vpa

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# k8s集群节点上导入镜像
[root@node1 data]# docker load -i vpa-admission-controller_0_8_0.tar.gz
Loaded image: scofield/vpa-admission-controller:0.8.0
[root@node1 data]# docker load -i vpa-recommender_0_8_0.tar.gz
b8cda3fb4e0a: Loading layer [==================================================>] 45.95MB/45.95MB
Loaded image: scofield/vpa-recommender:0.8.0
[root@node1 data]# docker load -i vpa-updater_0_8_0.tar.gz
9fe5b61fc509: Loading layer [==================================================>] 47.39MB/47.39MB
Loaded image: scofield/vpa-updater:0.8.0

# 控制节点
[root@master vpa]# unzip autoscaler-vertical-pod-autoscaler-0.8.0.zip
[root@master vpa]# cd /root/vpa/autoscaler-vertical-pod-autoscaler-0.8.0/vertical-pod-autoscaler/deploy
修改 admission-controller-deployment.yaml 里的 image:
image: scofield/vpa-admission-controller:0.8.0
imagePullPolicy: IfNotPresent

# 修改 recommender-deployment.yaml 里的 image:
image: scofield/vpa-recommender:0.8.0
imagePullPolicy: IfNotPresent

# 修改 updater-deployment.yaml 文件里的 image:
image: scofield/vpa-updater:0.8.0
imagePullPolicy: IfNotPresent

cd /root/vpa/autoscaler-vertical-pod-autoscaler-0.8.0/vertical-pod-autoscaler/hack

[root@master hack]# ./vpa-up.sh
Warning: apiextensions.k8s.io/v1beta1 CustomResourceDefinition is deprecated in v1.16+, unavailable in v1.22+; use apiextensions.k8s.io/v1 CustomResourceDefinition
customresourcedefinition.apiextensions.k8s.io/verticalpodautoscalers.autoscaling.k8s.io created
customresourcedefinition.apiextensions.k8s.io/verticalpodautoscalercheckpoints.autoscaling.k8s.io created
clusterrole.rbac.authorization.k8s.io/system:metrics-reader created
clusterrole.rbac.authorization.k8s.io/system:vpa-actor created
clusterrole.rbac.authorization.k8s.io/system:vpa-checkpoint-actor created
clusterrole.rbac.authorization.k8s.io/system:evictioner created
clusterrolebinding.rbac.authorization.k8s.io/system:metrics-reader created
clusterrolebinding.rbac.authorization.k8s.io/system:vpa-actor created
clusterrolebinding.rbac.authorization.k8s.io/system:vpa-checkpoint-actor created
clusterrole.rbac.authorization.k8s.io/system:vpa-target-reader created
clusterrolebinding.rbac.authorization.k8s.io/system:vpa-target-reader-binding created
clusterrolebinding.rbac.authorization.k8s.io/system:vpa-evictionter-binding created
serviceaccount/vpa-admission-controller created
clusterrole.rbac.authorization.k8s.io/system:vpa-admission-controller created
clusterrolebinding.rbac.authorization.k8s.io/system:vpa-admission-controller created
clusterrole.rbac.authorization.k8s.io/system:vpa-status-reader created
clusterrolebinding.rbac.authorization.k8s.io/system:vpa-status-reader-binding created
serviceaccount/vpa-updater created
deployment.apps/vpa-updater created
serviceaccount/vpa-recommender created
deployment.apps/vpa-recommender created
Generating certs for the VPA Admission Controller in /tmp/vpa-certs.
Generating RSA private key, 2048 bit long modulus
.........................................................+++
.............+++
e is 65537 (0x10001)
Generating RSA private key, 2048 bit long modulus
..+++
............+++
e is 65537 (0x10001)
Signature ok
subject=/CN=vpa-webhook.kube-system.svc
Getting CA Private Key
Uploading certs to the cluster.
secret/vpa-tls-certs created
Deleting /tmp/vpa-certs.
deployment.apps/vpa-admission-controller created
service/vpa-webhook created

[root@master manifests]# kubectl get pods -n kube-system | grep vpa
vpa-admission-controller-777694497b-bqpb2 1/1 Running 0 7m9s
vpa-recommender-64f6765bd9-twxbw 1/1 Running 0 7m9s
vpa-updater-c5474f4c7-h78xt 1/1 Running 0 7m9s

[root@master manifests]# kubectl get svc -n kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP 18d
metrics-server ClusterIP 10.109.69.92 <none> 443/TCP 18h
vpa-webhook ClusterIP 10.99.181.122 <none> 443/TCP 12m

测试VPA实现Pod自动扩缩容

updateMode:"Off"

  • 部署一个 nginx 服务,部署到 namespace: vpa 名称空间下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
[root@master vpa]# kubectl create ns vpa
namespace/vpa created

[root@master vpa]# cat vpa-1.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: nginx
namespace: vpa
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx
imagePullPolicy: IfNotPresent
name: nginx
resources:
requests:
cpu: 200m
memory: 300Mi

[root@master vpa]# kubectl apply -f vpa-1.yaml
deployment.apps/nginx created
  • 在 nginx 管理的 pod 前端创建四层代理 Service
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[root@master vpa]# cat vpa-service-1.yaml 
apiVersion: v1
kind: Service
metadata:
name: nginx
namespace: vpa
spec:
type: NodePort
ports:
- port: 80
targetPort: 80
selector:
app: nginx

[root@master vpa]# kubectl apply -f vpa-service-1.yaml
service/nginx created
[root@master vpa]# kubectl get svc -n vpa
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx NodePort 10.97.228.179 <none> 80:30127/TCP 9s
[root@master vpa]# curl -I 192.168.10.10:30127
HTTP/1.1 200 OK
  • 创建VPA,先使用 updateMode: "Off"模式,这种模式仅获取资源推荐值,但是不更新 Pod
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
[root@master vpa]# cat vpa-nginx.yaml 
apiVersion: autoscaling.k8s.io/v1beta2
kind: VerticalPodAutoscaler
metadata:
name: nginx-vpa
namespace: vpa
spec:
targetRef:
apiVersion: "apps/v1"
kind: Deployment
name: nginx
updatePolicy:
updateMode: "Off"
resourcePolicy:
containerPolicies:
- containerName: "nginx"
minAllowed:
cpu: "500m"
memory: "100Mi"
maxAllowed:
cpu: "2000m"
memory: "2600Mi"

[root@master vpa]# kubectl apply -f vpa-nginx.yaml
verticalpodautoscaler.autoscaling.k8s.io/nginx-vpa created
[root@master vpa]# kubectl get vpa -n vpa
NAME AGE
nginx-vpa 31s
  • 查看详细信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[root@master vpa]# kubectl describe vpa nginx-vpa -n vpa

# 使用上面命令这次没看到
Recommendation:
Container Recommendations:
Container Name: nginx
Lower Bound:
Cpu: 500m
Memory: 262144k
Target:
Cpu: 500m
Memory: 262144k
Uncapped Target:
Cpu: 25m
Memory: 262144k
Upper Bound:
Cpu: 1349m
Memory: 1410936619
Events: <none>

# Lower Bound: 下限值
# Target: 推荐值
# Upper Bound: 上限值
# Uncapped Target: 如果没有为 VPA 提供最小或最大边界,则表示目标利用率
# 上面结果表示,推荐的 Pod 的 CPU 请求为 500m,推荐的内存请求为 262144k 字节。
  • 压测nginx
1
2
3
4
5
6
7
8
9
10
11
12
[root@master vpa]# yum install -y httpd-tools ab

[root@master manifests]# kubectl get svc -n vpa
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx NodePort 10.97.228.179 <none> 80:30127/TCP 55m

[root@master vpa]# ab -c 100 -n 10000000 http://192.168.10.10:30127/

[root@master vpa]# kubectl describe vpa nginx-vpa -n vpa

# VPA 对 Pod 给出了推荐值:Cpu: 763m,因为我们这里设置了
# updateMode: "Off",所以不会更新 Pod

updateMode: "On"

  • 改成 updateMode: "Auto"

  • 把 resources 改为:memory: 50Mi,cpu: 100m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: nginx
namespace: vpa
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx
imagePullPolicy: IfNotPresent
name: nginx
resources:
requests:
cpu: 100m
memory: 50Mi

[root@master vpa]# kubectl apply -f vpa-nginx-1.yaml
deployment.apps/nginx created
[root@master vpa]# kubectl get pods -n vpa
NAME READY STATUS RESTARTS AGE
nginx-7d946f55c4-p52gj 1/1 Running 0 10s
nginx-7d946f55c4-s8ggc 1/1 Running 0 10s
  • 再次部署vpa
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
[root@master vpa]# cat vpa-nginx.yaml 
apiVersion: autoscaling.k8s.io/v1beta2
kind: VerticalPodAutoscaler
metadata:
name: nginx-vpa-2
namespace: vpa
spec:
targetRef:
apiVersion: "apps/v1"
kind: Deployment
name: nginx
updatePolicy:
updateMode: "Auto"
resourcePolicy:
containerPolicies:
- containerName: "nginx"
minAllowed:
cpu: "500m"
memory: "100Mi"
maxAllowed:
cpu: "2000m"
memory: "2600Mi"

[root@master vpa]# kubectl apply -f vpa-nginx.yaml
verticalpodautoscaler.autoscaling.k8s.io/nginx-vpa-2 created
[root@master vpa]# kubectl get vpa -n vpa
NAME AGE
nginx-vpa-2 9s
  • 再次压测
1
2
3
4
[root@master vpa]# ab -c 100 -n 1000000 http://192.168.10.10:30127/
# 几分钟后,使用 describe 查看 vpa 详情,只关注 Container Recommendations
kubectl describe vpa nginx-vpa -n vpa |tail -n 20
# Target 变成了 Cpu: 656m ,Memory: 262144k

查看event事件

1
2
3
4
5
6
7
[root@master vpa]# kubectl get event -n vpa
# vpa 执行了 EvictedByVPA,自动停掉了 nginx,然后使用 VPA 推荐的
# 资源启动了新的 nginx

[root@master vpa]# kubectl describe pods nginx-7d946f55c4-p52gj -n vpa
# 查看Requests的cpu和memory是多少和部署的文件中作比较
# 随着服务的负载的变化,VPA 的推荐值也会不断变化。当目前运行的 pod 的资源达不到 VPA 的推荐值,就会执行 pod 驱逐,重新部署新的足够资源的服务。

VPA 使用限制

不能与 HPA(Horizontal Pod Autoscaler )一起使用

Pod 比如使用副本控制器,例如属于 Deployment 或者 StatefulSet

VPA扩容过程中将无法正常提供服务,也因此它使用的场景相比HPA来说,要少的多

VPA优点

Pod 资源用其所需,所以集群节点使用效率高。

Pod 会被安排到具有适当可用资源的节点上。

不必运行基准测试任务来确定 CPU 和内存请求的合适值。

VPA 可以随时调整 CPU 和内存请求,无需人为操作,因此可以减少维护时间。

CA(kubernetes cluster-autoscaler)

什么是cluster-autoscaler?

Cluster Autoscaler (CA)是一个独立程序,是用来弹性伸缩 kubernetes 集群的。它可以自动根据部署应用所请求的资源量来动态的伸缩集群。当集群容量不足时,它会自动去 Cloud Provider (支持 GCE、GKE 、Ali、Tencent、AWS等)创建新的 Node,而在 Node 长时间资源 利用率很低时自动将其删除以节省开支。

项目地址:https://github.com/kubernetes/autoscaler

Cluster Autoscaler什么时候伸缩集群?

在以下情况下,集群自动扩容或者缩放:

扩容:由于资源不足,某些 Pod 无法在任何当前节点上进行调度

缩容: Node 节点资源利用率较低时,且此 node 节点上存在的 pod 都能被重新调度到 其他 node 节点上运行

什么时候集群节点不会被CA删除?

1)节点上有 pod 被 PodDisruptionBudget 控制器限制。

2)节点上有命名空间是 kube-system 的 pods。

3)节点上的 pod 不是被控制器创建,例如不是被 deployment, replica set, job, stateful set 创建。

4)节点上有 pod 使用了本地存储

5)节点上 pod 驱逐后无处可去,即没有其他 node 能调度这个 pod

6)节点有注解:"cluster-autoscaler.kubernetes.io/scale-down-disabled": "true"(在 CA 1.0.3 或更高版本中受支持)

扩展:什么是 PodDisruptionBudget?

通过 PodDisruptionBudget 控制器可以设置应用 POD 集群处于运行状态最低个数,也可以设置应用 POD 集群处于运行状态的最低百分比,这样可以保证在主动销毁应用 POD 的时候,不会一次性销毁太多的应用 POD,从而保证业务不中断

Horizontal Pod Autoscaler 如何与 Cluster Autoscaler 一起使用?

Horizontal Pod Autoscaler 会根据当前 CPU 负载更改部署或副本集的副本数。如果负 载增加,则 HPA 将创建新的副本,集群中可能有足够的空间,也可能没有足够的空间。 如果没有足够的资源,CA 将尝试启动一些节点,以便 HPA 创建的 Pod 可以运行。如果 负载减少,则 HPA 将停止某些副本。结果,某些节点可能变得利用率过低或完全为空, 然后 CA 将终止这些不需要的节点。

扩展:如何防止节点被 CA 删除

节点可以打上以下标签: "cluster-autoscaler.kubernetes.io/scale-down-disabled": "true"

可以使用 kubectl 将其添加到节点(或从节点删除): kubectl annotate node cluster-autoscaler.kubernetes.io/scale-down-disabled=true

部署

Cluster AutoScaler v1.0+ 可以基于 Docker 镜像 gcr.io/google_containers/cluster-autoscaler:v1.3.0 来部署

针对各个云厂商可能也需要各自的运行环境,如腾讯云需要运行在自家TKE上才能使用

Rancher中使用CA:https://ranchermanager.docs.rancher.com/zh/how-to-guides/new-user-guides/manage-clusters/install-cluster-autoscaler

Cluster Autoscaler 支持云厂商:https://github.com/kubernetes/autoscaler/tree/master/cluster-autoscaler#deployment

注意,在开启 RBAC 的集群中创建 cluster-autoscaler ClusterRole

工作原理

Cluster AutoScaler 定期(默认间隔 10s)检测是否有充足的资源来调度新创建的 Pod,当资源不足时会调用 Cloud Provider 创建新的 Node。

为了自动创建和初始化 Node,Cluster Autoscaler 要求 Node 必须属于某个 Node Group,比如

  • GCE/GKE 中的 Managed instance groups(MIG)
  • AWS 中的 Autoscaling Groups
  • Azure 中的 Scale Sets 和 Availability Sets

当集群中有多个 Node Group 时,可以通过 --expander=<option> 选项配置选择 Node Group 的策咯,支持如下四种方式

  • random:随机选择
  • most-pods:选择容量最大(可以创建最多 Pod)的 Node Group
  • least-waste:以最小浪费原则选择,即选择有最少可用资源的 Node Group
  • price:选择最便宜的 Node Group(仅支持 GCE 和 GKE)

目前,Cluster Autoscaler 可以保证

  • 小集群(小于 100 个 Node)可以在不超过 30 秒内完成扩展(平均 5 秒)
  • 大集群(100-1000 个 Node)可以在不超过 60 秒内完成扩展(平均 15 秒)

Cluster AutoScaler 也会定期(默认间隔 10s)自动监测 Node 的资源使用情况,当一个 Node 长时间(超过 10 分钟其期间没有执行任何扩展操作)资源利用率都很低时(低于 50%)自动将其所在虚拟机从云服务商中删除(注意删除时会有 1 分钟的 graceful termination 时间)。此时,原来的 Pod 会自动调度到其他 Node 上面(通过 Deployment、StatefulSet 等控制器)。

注意,Cluster Autoscaler 仅根据 Pod 的调度情况和 Node 的整体资源使用清空来增删 Node,跟 Pod 或 Node 的资源度量(metrics)没有直接关系。

用户在启动 Cluster AutoScaler 时可以配置 Node 数量的范围(包括最大 Node 数和最小 Node 数)。

在使用 Cluster AutoScaler 时需要注意:

  • 由于在删除 Node 时会发生 Pod 重新调度的情况,所以应用必须可以容忍重新调度和短时的中断(比如使用多副本的 Deployment)
  • 当 Node 上面的 Pods 满足下面的条件之一 时,Node 不会删除
    • Pod 配置了 PodDisruptionBudget (PDB)
    • kube-system Pod 默认不在 Node 上运行或者未配置 PDB
    • Pod 不是通过 deployment, replica set, job, stateful set 等控制器创建的
    • Pod 使用了本地存储
    • 其他原因导致的 Pod 无法重新调度,如资源不足,其他 Node 无法满足 NodeSelector 或 Affinity 等

最佳实践

  • Cluster AutoScaler 可以和 Horizontal Pod Autoscaler(HPA)配合使用
  • 不要手动修改 Node 配置,保证集群内的所有 Node 有相同的配置并属于同一个 Node 组
  • 运行 Pod 时指定资源请求
  • 必要时使用 PodDisruptionBudgets 阻止 Pod 被误删除
  • 确保云服务商的配额充足
  • Cluster AutoScaler 与云服务商提供的 Node 自动扩展功能以及基于 CPU 利用率的 Node 自动扩展机制冲突,不要同时启用

https://github.com/kubernetes/kubernetes/blob/master/cluster/addons/rbac/cluster-autoscaler/cluster-autoscaler-rbac.yaml)