背景
目前正在使用kubectl scale命令可以来实现Pod的扩缩容功能,但是这个毕竟是安全手动操作的,要应对线上的各种复杂情况,我们需要能够做到自动化去感知业务,来自动进行扩缩容。为此,Kubernetes 也为我们提供了这样的一个资源对象:Horizontal Pod Autoscaling(Pod 水平自动伸缩),简称HPA,HPA 通过监控分析一些控制器控制的所有 Pod 的负载变化情况来确定是否需要调整 Pod 的副本数量;
监控指标如何获取
1.Resource Metrics API
通过Metrics API,可与获取指定node或pod当前使用的资源量,这个 API 不存储指标值, 因此想要获取某个指定 node 10分钟前的资源使用量是不可能的。
Metrics API 和其他的 API 没有什么不同,它可以通过与 /apis/metrics.k8s.io/ 路径下的其他 Kubernetes API 相同的端点来发现,并且提供了相同的安全性、可扩展性和可靠性保证,Metrics API 在 k8s.io/metrics 仓库中定义,你可以在这里找到关于 Metrics API 的更多信息。
注意 : Metrics API 需要在集群中部署 Metrics Server。否则它将不可用。
2.Metrics Server
Metrics Server 实现了Resource Metrics API。
Metrics Server 是集群范围资源使用数据的聚合器。 从 Kubernetes 1.8 开始,它作为一个 Deployment 对象默认部署在由 kube-up.sh 脚本创建的集群中。 如果你使用了其他的 Kubernetes 安装方法,您可以使用 Kubernetes 1.7+ (请参阅下面的详细信息) 中引入的 deployment yamls 文件来部署。
Metrics Server 从每个节点上的 Kubelet 公开的 Summary API 中采集指标信息。
通过在主 API server 中注册的 Metrics Server Kubernetes 聚合器 来采集指标信息, 这是在 Kubernetes 1.7 中引入的。在 设计文档 中可以了解到有关 Metrics Server 的更多信息。
HPA 是什么
Pod水平自动扩容(Horizontal Pod Autoscaler) 可以基于 CPU 利用率自动扩缩 ReplicationController、Deployment、ReplicaSet 和 StatefulSet 中的 Pod 数量。 除了 CPU 利用率,也可以基于其他应程序提供的 自定义度量指标 来执行自动扩缩。 Pod 自动扩缩不适用于无法扩缩的对象,比如 DaemonSet。
Pod 水平自动扩缩特性由 Kubernetes API 资源和控制器实现。资源决定了控制器的行为。 控制器会周期性地调整副本控制器或 Deployment 中的副本数量,以使得类似 Pod 平均 CPU 利用率、平均内存利用率这类观测到的度量值与用户所设定的目标值匹配。
HPA 工作机制
Pod 水平自动扩容器的实现是一个控制回路,由控制管理器的--horizontal-pod-autoscaler-sync-period参数指定周期(默认值为 15 秒)。
每个周期内,控制器管理器根据每个HorizontalPodAutoscaler 定义中指定的指标查询资源利用率。控制器管理器可与从资源度量指标API(按 Pod 统计的资源用量)和自定义度量指标 API(其他指标)获取度量值。
-
对于按 Pod 统计的资源指标(如 CPU),控制器从资源指标 API 中获取每一个 HorizontalPodAutoscaler 指定的 Pod 的度量值,如果设置了目标使用率, 控制器获取每个 Pod 中的容器资源使用情况,并计算资源使用率。 如果设置了 target 值,将直接使用原始数据(不再计算百分比)。 接下来,控制器根据平均的资源使用率或原始值计算出扩缩的比例,进而计算出目标副本数。
-
需要注意的是,如果 Pod 某些容器不支持资源采集,那么控制器将不会使用该 Pod 的 CPU 使用率。
-
如果 Pod 使用自定义指示,控制器机制与资源指标类似,区别在于自定义指标只使用 原始值,而不是使用率。
-
如果 Pod 使用对象指标和外部指标(每个指标描述一个对象信息)。 这个指标将直接根据目标设定值相比较,并生成一个上面提到的扩缩比例。
HPA 算法细节
Pod水平自动扩缩容控制器根据当前指标和期望指标来计算扩缩比例。
期望副本数 = ceil[当前副本数 * (当前指标 / 期望指标)]
例如:当前度量值为200m,目前设定值为100m,那么由于200.0/100.0 == 2.0, 副本数量将会翻倍。 如果当前指标为 50m,副本数量将会减半,因为50.0/100.0 == 0.5。 如果计算出的扩缩比例接近 1.0 (根据--horizontal-pod-autoscaler-tolerance 参数全局配置的容忍值,默认为 0.1), 将会放弃本次扩缩。
如果 HorizontalPodAutoscaler 指定的是 targetAverageValue 或 targetAverageUtilization, 那么将会把指定 Pod 度量值的平均值做为 currentMetricValue。 然而,在检查容忍度和决定最终扩缩值前,我们仍然会把那些无法获取指标的 Pod 统计进去。
所有被标记了删除时间戳(Pod 正在关闭过程中)的 Pod 和失败的 Pod 都会被忽略。
如果某个 Pod 缺失度量值,它将会被搁置,只在最终确定扩缩数量时再考虑。
当使用 CPU 指标来扩缩时,任何还未就绪(例如还在初始化)状态的 Pod 或 最近的指标 度量值采集于就绪状态前的 Pod,该 Pod 也会被搁置。
由于受技术限制,Pod 水平扩缩控制器无法准确的知道 Pod 什么时候就绪, 也就无法决定是否暂时搁置该 Pod。 --horizontal-pod-autoscaler-initial-readiness-delay 参数(默认为 30s)用于设置 Pod 准备时间, 在此时间内的 Pod 统统被认为未就绪。 --horizontal-pod-autoscaler-cpu-initialization-period 参数(默认为5分钟) 用于设置 Pod 的初始化时间, 在此时间内的 Pod,CPU 资源度量值将不会被采纳。
在排除掉被搁置的 Pod 后,扩缩比例就会根据 currentMetricValue/desiredMetricValue 计算出来。
如果缺失任何的度量值,我们会更保守地重新计算平均值, 在需要缩小时假设这些 Pod 消耗了目标值的 100%, 在需要放大时假设这些 Pod 消耗了 0% 目标值。 这可以在一定程度上抑制扩缩的幅度。
此外,如果存在任何尚未就绪的 Pod,我们可以在不考虑遗漏指标或尚未就绪的 Pod 的情况下进行扩缩, 我们保守地假设尚未就绪的 Pod 消耗了期望指标的 0%,从而进一步降低了扩缩的幅度。
在扩缩方向(缩小或放大)确定后,我们会把未就绪的 Pod 和缺少指标的 Pod 考虑进来再次计算使用率。 如果新的比率与扩缩方向相反,或者在容忍范围内,则跳过扩缩。 否则,我们使用新的扩缩比例。
注意,平均利用率的原始值会通过 HorizontalPodAutoscaler 的状态体现( 即使使用了新的使用率,也不考虑未就绪 Pod 和 缺少指标的 Pod)。
如果创建 HorizontalPodAutoscaler 时指定了多个指标, 那么会按照每个指标分别计算扩缩副本数,取最大值进行扩缩。 如果任何一个指标无法顺利地计算出扩缩副本数(比如,通过 API 获取指标时出错), 并且可获取的指标建议缩容,那么本次扩缩会被跳过。 这表示,如果一个或多个指标给出的 desiredReplicas 值大于当前值,HPA 仍然能实现扩容。
最后,在 HPA 控制器执行扩缩操作之前,会记录扩缩建议信息。 控制器会在操作时间窗口中考虑所有的建议信息,并从中选择得分最高的建议。 这个值可通过 kube-controller-manager 服务的启动参数 --horizontal-pod-autoscaler-downscale-stabilization 进行配置, 默认值为 5 分钟。 这个配置可以让系统更为平滑地进行缩容操作,从而消除短时间内指标值快速波动产生的影响。
HPA 案例
现在应用是固定2个副本,但是往往在生产环境流量是不可控,很有可能一次活动就会有大量得流量。2个副本很有可能抗不住大量的用户请求,这个时候我们就希望能够自动对 Pod 进行伸缩
部署 Metrics Server
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
k8s-app: metrics-server
name: metrics-server
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
k8s-app: metrics-server
rbac.authorization.k8s.io/aggregate-to-admin: "true"
rbac.authorization.k8s.io/aggregate-to-edit: "true"
rbac.authorization.k8s.io/aggregate-to-view: "true"
name: system:aggregated-metrics-reader
rules:
- apiGroups:
- metrics.k8s.io
resources:
- pods
- nodes
verbs:
- get
- list
- watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
k8s-app: metrics-server
name: system:metrics-server
rules:
- apiGroups:
- ""
resources:
- pods
- nodes
- nodes/stats
- namespaces
- configmaps
verbs:
- get
- list
- watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
labels:
k8s-app: metrics-server
name: metrics-server-auth-reader
namespace: kube-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: extension-apiserver-authentication-reader
subjects:
- kind: ServiceAccount
name: metrics-server
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
labels:
k8s-app: metrics-server
name: metrics-server:system:auth-delegator
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:auth-delegator
subjects:
- kind: ServiceAccount
name: metrics-server
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
labels:
k8s-app: metrics-server
name: system:metrics-server
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:metrics-server
subjects:
- kind: ServiceAccount
name: metrics-server
namespace: kube-system
---
apiVersion: v1
kind: Service
metadata:
labels:
k8s-app: metrics-server
name: metrics-server
namespace: kube-system
spec:
ports:
- name: https
port: 443
protocol: TCP
targetPort: https
selector:
k8s-app: metrics-server
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
k8s-app: metrics-server
name: metrics-server
namespace: kube-system
spec:
selector:
matchLabels:
k8s-app: metrics-server
strategy:
rollingUpdate:
maxUnavailable: 0
template:
metadata:
labels:
k8s-app: metrics-server
spec:
containers:
- args:
- --cert-dir=/tmp
- --secure-port=4443
- --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
- --kubelet-use-node-status-port
- --metric-resolution=15s
- --kubelet-insecure-tls
image: bitnami/metrics-server:0.5.1
imagePullPolicy: IfNotPresent
livenessProbe:
failureThreshold: 3
httpGet:
path: /livez
port: https
scheme: HTTPS
periodSeconds: 10
name: metrics-server
ports:
- containerPort: 4443
name: https
protocol: TCP
readinessProbe:
failureThreshold: 3
httpGet:
path: /readyz
port: https
scheme: HTTPS
initialDelaySeconds: 20
periodSeconds: 10
resources:
requests:
cpu: 100m
memory: 200Mi
securityContext:
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 1000
volumeMounts:
- mountPath: /tmp
name: tmp-dir
nodeSelector:
kubernetes.io/os: linux
priorityClassName: system-cluster-critical
serviceAccountName: metrics-server
volumes:
- emptyDir: {}
name: tmp-dir
---
apiVersion: apiregistration.k8s.io/v1
kind: APIService
metadata:
labels:
k8s-app: metrics-server
name: v1beta1.metrics.k8s.io
spec:
group: metrics.k8s.io
groupPriorityMinimum: 100
insecureSkipTLSVerify: true
service:
name: metrics-server
namespace: kube-system
version: v1beta1
versionPriority: 100
部署应用
apiVersion: v1
kind: Service
metadata:
name: wordpress
namespace: kube-example
labels:
app: wordpress
spec:
selector:
app: wordpress
tier: frontend
type: NodePort
ports:
- name: web
port: 80
targetPort: wdport
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: wordpress
namespace: kube-example
labels:
app: wordpress
tier: frontend
spec:
replicas: 3
selector:
matchLabels:
app: wordpress
tier: frontend
template:
metadata:
labels:
app: wordpress
tier: frontend
spec:
containers:
- name: wordpress
image: wordpress
ports:
- containerPort: 80
name: wdport
resources:
limits:
cpu: 100m
memory: 100Mi
requests:
cpu: 100m
memory: 100Mi
直接使用kubectl autoscale命令来创建一个 HPA 对象
[root@k8s-master ~]# kubectl autoscale deployment wordpress --namespace kube-example --cpu-percent=20 --min=2 --max=6
horizontalpodautoscaler.autoscaling/wordpress autoscaled
[root@k8s-master ~]# kubectl get hpa -n kube-example
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
wordpress Deployment/wordpress <unknown>/20% 2 6 0 8s
此命令创建了一个关联资源 wordpress 的 HPA,最小的 Pod 副本数为2,最大为6。HPA 会根据设定的 cpu 使用率(20%)动态的增加或者减少 Pod 数量。同样,使用上面的 Fortio 工具来进行压测一次,看下能否进行自动的扩缩容:
fortio load -a -c 8 -qps 1000 -t 60s http://192.168.1.103:30413
[root@k8s-master ~]# kubectl get hpa -n kube-example
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
wordpress Deployment/wordpress 99%/20% 2 6 6 38m
[root@k8s-master ~]# kubectl get pods -n kube-example
NAME READY STATUS RESTARTS AGE
wordpress-6c4d767f55-62rd5 1/1 Running 0 42s
wordpress-6c4d767f55-8rxrg 1/1 Running 0 27s
wordpress-6c4d767f55-99btd 1/1 Running 0 42m
wordpress-6c4d767f55-d6j9n 1/1 Running 0 27s
wordpress-6c4d767f55-s6f6m 1/1 Running 0 42s
wordpress-6c4d767f55-tw2mg 1/1 Running 0 42m
wordpress-mysql-5d664b6f54-c5m7m 1/1 Running 0 7h43m
小结
滚动升级时扩缩
目前 在Kubernetes中,可以针对ReplicationController 或 Deployment 执行 滚动更新,它们会为你管理底层副本数。 Pod 水平扩缩只支持后一种:HPA 会被绑定到 Deployment 对象, HPA 设置副本数量时,Deployment 会设置底层副本数。
通过直接操控副本控制器执行滚动升级时,HPA 不能工作, 也就是说你不能将 HPA 绑定到某个 RC 再执行滚动升级。 HPA 不能工作的原因是它无法绑定到滚动更新时所新创建的副本控制器。
冷却/延迟支持
当使用 Horizontal Pod Autoscaler 管理一组副本扩缩时, 有可能因为指标动态的变化造成副本数量频繁的变化,有时这被称为 抖动(Thrashing)。
从 v1.6 版本起,集群操作员可以调节某些 kube-controller-manager 的全局参数来 缓解这个问题。
从 v1.12 开始,算法调整后,扩容操作时的延迟就不必设置了。
--horizontal-pod-autoscaler-downscale-stabilization: 设置缩容冷却时间窗口长度。 水平 Pod 扩缩器能够记住过去建议的负载规模,并仅对此时间窗口内的最大规模执行操作。 默认值是 5 分钟(5m0s)。
说明: 当调整这些参数时,集群操作员需要明白其可能的影响。 如果延迟(冷却)时间设置的太长,Horizontal Pod Autoscaler 可能会不能很好的改变负载。 如果延迟(冷却)时间设置的太短,那么副本数量有可能跟以前一样出现抖动。
评论区