当企业达到一定规模时,完全依赖于公有云基础设施,IT 成本会很高。
采购物理机器的成本可以摊薄到未来 3~5 年,之后机器并不会报废,而是会继续超期服役。私有云需要配比一定运维人员、购买专线带宽、机房费用等,IT 服务达到一定规模才能有效降低成本。
因此,中大型企业才会采用混合云的方案,将一部分应用部署在公有云,一部分应用部署在私有云。这也促成了托管云服务的发展,云厂商提供纳管私有机器的服务,以便能够快速对接自家的公有云服务。
如上图,私有云的机器用来满足基本的业务资源需求,公有云的机器用来补充业务高峰期的资源需求。而 Kubernetes 作为基础设施,当然也需要适配混合云的场景。
如上图,是一个混合云下的 Kubernetes 集群,私有机房(Master + 部分 Worker)+ 公有云(部分 Worker)。采用 Cluster Autoscaler 组件,对接公有云的弹性伸缩组服务,按需扩容或缩容 Worker 节点。
私有云的机器成本基本是固定的,而公有云的机器成本是按需计费的。在保障业务 SLA 的前提下,尽可能的减少公有云机器的使用,就是我最近在做的事情。
Request 是资源的请求量。如上图,当调度器在给 Pod 选择一个合适的 Node 时,Node 上 Pod 的 Request 总和越少,打分越高,越容易被选中。
如果给每个 Pod 都设置了很低的 Request,你会发现调度非常不均,有些节点负载很高,但调度器还是会选择这些节点。
因为默认的调度器是静态调度,只看 Request 和 Node 上的 Request 总量,而不考虑实际使量。
Request 保障当前应用分配有足够资源,是用来保护自己的;Limit 是用来限制当前应用,是用来保护其他应用的。
你可以不设置 Limit,但一定要设置 Request。
HPA 计算资源使用率的公式是:
currentUtilization = int32((metricsTotal * 100) / requestsTotal)
当使用率超过阈值时,HPA 会增加 Pod 副本数量。而这里的 Total 意味着,HPA 计算的是全部 Pod 的资源消耗。
这里有两个问题需要思考
在 Kubernetes v1.27 中有一个 Beta 特性 ContainerResource,可以指定容器作为计算对象,而忽略 Sidecar 等其他容器。
从研发侧,在开发应用时,就应该考虑多副本的均衡性,避免出现单个副本任务过重的情况。如果是长链接导致的不均衡,应该有再平衡机制。同时,还应该支持优雅重启,避免某个 Pod 负载过高被 Kill 之后,影响服务的 SLO。
从运维侧,可以适当降低 Limit 值,通过 Kill Pod 的方式,让请求分散到其他 Pod 上。
前面说到 Request 是用来保护当前应用的,应该能够满足应用的基本使用。但 Request 太高,又会导致资源浪费。
如上图,Request 应该满足大部分时间段下 CPU 使用。
clamp_min(max(quantile_over_time(0.6, irate(container_cpu_usage_seconds_total{cluster="$cluster", namespace="$namespace", deployment="$deployment"}[2m])[1w:2m])), 0.5)
quantile_over_time
函数统计 60 百分位的使用需求,clamp_min
函数设置最小值为 0.5。
clamp_min(max(quantile_over_time(0.8, max(sum (container_memory_working_set_bytes{cluster="$cluster",image!="",name=~"^k8s_.*", namespace=~"$namespace", deployment=~"$deployment"}) by (pod)))[1w:2m]), 500 * 1024 * 1024)
内存是不可压缩资源,需要设置一个较高的 Request,以保障应用的正常运行。因此,设置的百分位比 CPU 要高。
设置 Limit 是为了保护其他应用,避免当前应用的资源消耗过高时,影响其他应用的正常运行。
如下图,应用经常会碰到,CPU 使用率很低,但是 CPU 限流很严重,需要不断地提高 CPU Limit,而过高的 Limit 又会导致节点不稳定。同时,有些计费系统,是以 Limit 为基础进行计费的,过高的 Limit 会增加业务成本。
这种情况是因为,Prometheus 的采样频率是 15s,监控粒度太粗,采集不到实时的 CPU 使用情况。而如果采集 1s、100ms 监控数据时,很有可能是这样的。
使用率已经超过 400%。有两种办法解决:
5.14 内核新增的 Burst CPU 功能,通过累计算法,可以应对这种瞬时的 CPU 需求。
此时,可以直接使用 99 百分位 CPU 使用核数作为 Limit。
clamp_min(max(quantile_over_time(0.99, irate(container_cpu_usage_seconds_total{cluster="$cluster", namespace="$namespace", deployment="$deployment"}[2m])[1w:2m])), 0.52)
clamp_min(max(quantile_over_time(0.99, irate(container_cpu_usage_seconds_total{cluster="$cluster", namespace="$namespace", deployment="$deployment"}[2m])[1w:2m])) + quantile_over_time(0.99, (sum(irate(container_cpu_cfs_throttled_seconds_total{cluster="$cluster", name=~"^k8s_.*", namespace=~"$namespace", deployment=~"$deployment"}[10s])))[1w]), 0.52)
Limit = 99 百分位 CPU 使用核数 + 99 百分位 CPU 限流核数,同时不小于 0.52 核。
内存超了会被内核 OOM,你会发现内存的监控值始终不会超过 Limit。因此 Limit 应该超过 Request,但又不会触发 OOM 为宜。
quantile_over_time(0.995, container_memory_working_set_bytes{cluster="$cluster", namespace="$namespace", deployment="$deployment"}[1w:5m])
可以以 99.5 百分位的内存作为 Limit 起始值,逐步增加,直到不再触发 OOM。
了解了业务背景、相关的要点,在正式配置之前还需要进行一些预防措施,避免事故。
紧盯业务的关键 SLI 是能够大胆调试的关键,如果不能保障 SLA 一切的优化都是徒劳。
这里选取的是成功率、堆积量 A\B 作为核心指标。
成功率 > 99%
这一成功率要求,也影响了很多参数百分位的设置。
堆积量 A < 1000
堆积量 B < 1000
建议以最能体现使用方体验的指标作为 SLI,而不是以研发、运维的视角来定义 SLI。
由于调试 HPA 会涉及 Pod 的创建,为了避免扩容失败,需要对 Pod 的相关指标进行监控。
sum by (cluster, app, pod)(kube_pod_status_ready{condition='false',exported_namespace='default'})
sum by (cluster, app, pod)(kube_pod_status_phase{phase='Pending',exported_namespace='default'})
sum by (namespace,pod) ((kube_pod_container_status_restarts_total{exported_namespace="default"} - kube_pod_container_status_restarts_total{exported_namespace="default"} offset 10m >= 1) and ignoring (reason) min_over_time(kube_pod_container_status_last_terminated_reason{exported_namespace="default",reason='OOMKilled'}[10m]) == 1)
sum (rate (container_cpu_cfs_throttled_seconds_total{namespace="default",name=~"^k8s_.*"}[5m])) by (pod)
线上的应用面对潮汐流量,在资源使用上总会有波动。一旦这种波动超过了节点的承载能力,就会导致节点驱逐应用,影响业务的稳定性。
而 Kubernetes 驱逐的策略是根据 QoS 类型来决定的,QoS 类型分为三种:
只用区分两种类型就可以,重点业务应用 Request、Limit 相等,非重点业务应用 Request、Limit 不相等。
对于副本较多,普通应用可以开启软亲和性,尽量避免在同一个机器上调度。
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- myapp
topologyKey: kubernetes.io/hostname
对于副本较少,重点应用可以开启硬亲和性,强制分散到不同机器上。
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- myapp
topologyKey: kubernetes.io/hostname
对于副本较多的应用,最好使用软亲和性,避免 Cluster Autoscaler 不断扩容增加额外的成本。
如果有一个 Deployment 部署的应用 example,只需要创建一个 HPA 对象,指定 Deployment 的名称即可。
应用以下 yaml 对象
apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
name: example
namespace: default
spec:
maxReplicas: 5
minReplicas: 2
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: example
targetCPUUtilizationPercentage: 60
或者执行命令
kubectl autoscale deployment example --cpu-percent=60 --min=2 --max=5
创建 HPA 对象,它的目标是 example Deployment,最大副本数 5,最小副本数 2,目标 CPU 使用率 60%。当 Pod 平均 CPU 使用率超过 60% 时,HPA 会增加副本数量。
由于我主要使用的是 HPA v1 cpu 指标,这里只介绍 v1 的几个主要参数。
生产环境最少需要 2 个 Pod,避免单点故障。重点应用,最少需要 3 个 Pod。
Pod 的数量会随着负载的上升,不断增加,按需使用,因此上限应该尽量大一些。如果平时副本 2-3 个,就给上限为 5。如果平时副本数量为 10-20 个,就给上限为 30。
上限最好设置得比平时多一些,同时设置为 5 的倍数为宜,方便识别扩容数量达到 HPA 上限之后,继续增加。
CPU 使用率设置得越低,扩容时就越灵敏;设置得越高,资源的利用率就越低。通常可以根据应用的负载情况,设置为 50%-70%。
设置的策略是,先设置为 50%,等稳定之后,再逐步增加 5%。
本篇主要是记录了在给 60 个应用设置 HPA 的之后,遇到的问题和一些思考。主要内容如下:
给不同应用设置 Request、Limit、HPA 副本上限、HAP 副本下限、HPA CPU 使用率,是一件繁琐的事情,建议先绘制一个 Grafana 计算面板,可以实时计算调试。类似下图:
最终效果如下:
资源的请求量会随着使用量波动,而请求量又直接影响到公有云的机器使用数量。如下图是线上 Usage/ Request 的情况,也是我不断优化 HPA 相关参数的指引指标。
从云厂控制台的账单来看,HPA 相较于没有弹性,成本降低了 50%;在之前的实践中,CronHPA 相较于没有弹性,成本降低了 30%。
另外可以考虑的是,将长期占用的弹性公有云机器转移到私有云,或者采用公有云包年的结算方式,因为云厂的按需付费弹性主机价格比较高。
《Docker中Image、Container与Volume的迁移》
免责声明:本文内容来源于网络,所载内容仅供参考。转载仅为学习和交流之目的,如无意中侵犯您的合法权益,请及时联系Docker中文社区!