通常,总是需要为应用程序提供专用的资源池,以使其正确、稳定地运行。 但是,如果多个应用程序在同一电源上运行怎么办? 如何为他们每个人提供最少的必要资源? 如何限制资源消耗? 如何在节点之间正确分配负载? 当应用负载增加时,如何保证水平扩展机制发挥作用?
您需要从系统中存在哪些主要资源类型开始 - 这当然是处理器时间和 RAM。 在 k8s 清单中,这些资源类型以以下单位进行测量:
- CPU - 核心数
- RAM - 以字节为单位
此外,对于每种资源,可以设置两种类型的要求 - 要求 и 范围。 请求 - 描述运行容器(以及整个 Pod)的节点可用资源的最低要求,而限制则对容器可用的资源设置硬性限制。
重要的是要理解清单不必显式定义这两种类型,但行为如下:
- 如果仅显式指定资源的限制,则对该资源的请求将自动采用等于限制的值(您可以通过调用描述实体来验证这一点)。 那些。 事实上,容器将被限制为其运行所需的相同数量的资源。
- 如果仅对资源显式指定请求,则不会对该资源设置上限 - 即。 容器仅受节点本身资源的限制。
不仅可以在特定容器级别配置资源管理,还可以使用以下实体在命名空间级别配置资源管理:
- 限制范围 — 描述 ns 中容器/pod 级别的限制策略,为了描述容器/pod 的默认限制,以及防止创建明显胖的容器/pod(反之亦然),限制其数量,需要使用该限制策略并确定限制和请求中的值可能存在的差异
- 资源配额 — 描述 ns 中所有容器的一般限制策略,通常用于分隔环境之间的资源(当环境在节点级别没有严格划分时有用)
以下是设置资源限制的清单示例:
-
在特定容器级别:
containers: - name: app-nginx image: nginx resources: requests: memory: 1Gi limits: cpu: 200m
那些。 在这种情况下,要运行带有 nginx 的容器,节点上至少需要 1G 的可用 RAM 和 0.2 个 CPU,而容器最多可以消耗 0.2 个 CPU 和节点上的所有可用 RAM。
-
在整数级别 ns:
apiVersion: v1 kind: ResourceQuota metadata: name: nxs-test spec: hard: requests.cpu: 300m requests.memory: 1Gi limits.cpu: 700m limits.memory: 2Gi
那些。 默认ns中所有请求容器的总和不能超过CPU 300m,OP 1G,所有限制总和为CPU 700m,OP 2G。
-
ns 中容器的默认限制:
apiVersion: v1 kind: LimitRange metadata: name: nxs-limit-per-container spec: limits: - type: Container defaultRequest: cpu: 100m memory: 1Gi default: cpu: 1 memory: 2Gi min: cpu: 50m memory: 500Mi max: cpu: 2 memory: 4Gi
那些。 在所有容器的默认命名空间中,CPU 请求设置为 100m,OP 请求设置为 1G,限制为 1 个 CPU 和 2G。 同时,还对CPU(50m < x < 2)和RAM(500M < x < 4G)的request/limit中可能的值设置了限制。
-
Pod 级别限制 ns:
apiVersion: v1 kind: LimitRange metadata: name: nxs-limit-pod spec: limits: - type: Pod max: cpu: 4 memory: 1Gi
那些。 对于默认 ns 中的每个 pod,将有 4 个 vCPU 和 1G 的限制。
现在我想告诉你设置这些限制可以给我们带来什么好处。
节点间负载均衡机制
大家知道,k8s组件负责pod在节点之间的分发,比如 调度,它根据特定的算法工作。 该算法在选择要启动的最佳节点时经历两个阶段:
- 过滤
- 测距
那些。 根据所描述的策略,最初选择可以基于一组启动 Pod 的节点 谓词 (包括检查节点是否有足够的资源来运行 pod - PodFitsResources),然后对于每个节点,根据 优先级 奖励积分(包括,节点拥有的空闲资源越多,分配的积分就越多 - LeastResourceAllocation/LeastRequestedPriority/BalancedResourceAllocation),并在积分最多的节点上启动 pod(如果多个节点同时满足此条件,则随机选择一个)。
同时,您需要了解调度程序在评估节点的可用资源时,是由存储在 etcd 中的数据指导的 - 即该节点上运行的每个 Pod 所请求/限制的资源量,而不是实际的资源消耗量。 该信息可以从命令输出中获取 kubectl describe node $NODE
例如:
# kubectl describe nodes nxs-k8s-s1
..
Non-terminated Pods: (9 in total)
Namespace Name CPU Requests CPU Limits Memory Requests Memory Limits AGE
--------- ---- ------------ ---------- --------------- ------------- ---
ingress-nginx nginx-ingress-controller-754b85bf44-qkt2t 0 (0%) 0 (0%) 0 (0%) 0 (0%) 233d
kube-system kube-flannel-26bl4 150m (0%) 300m (1%) 64M (0%) 500M (1%) 233d
kube-system kube-proxy-exporter-cb629 0 (0%) 0 (0%) 0 (0%) 0 (0%) 233d
kube-system kube-proxy-x9fsc 0 (0%) 0 (0%) 0 (0%) 0 (0%) 233d
kube-system nginx-proxy-k8s-worker-s1 25m (0%) 300m (1%) 32M (0%) 512M (1%) 233d
nxs-monitoring alertmanager-main-1 100m (0%) 100m (0%) 425Mi (1%) 25Mi (0%) 233d
nxs-logging filebeat-lmsmp 100m (0%) 0 (0%) 100Mi (0%) 200Mi (0%) 233d
nxs-monitoring node-exporter-v4gdq 112m (0%) 122m (0%) 200Mi (0%) 220Mi (0%) 233d
Allocated resources:
(Total limits may be over 100 percent, i.e., overcommitted.)
Resource Requests Limits
-------- -------- ------
cpu 487m (3%) 822m (5%)
memory 15856217600 (2%) 749976320 (3%)
ephemeral-storage 0 (0%) 0 (0%)
在这里,我们可以看到特定节点上运行的所有 Pod,以及每个 Pod 请求的资源。 这是启动 cronjob-cron-events-1573793820-xt6q9 pod 时调度程序日志的样子(当您在启动命令参数中设置第 10 个日志记录级别 -v=10 时,此信息将出现在调度程序日志中):
日志文件
I1115 07:57:21.637791 1 scheduling_queue.go:908] About to try and schedule pod nxs-stage/cronjob-cron-events-1573793820-xt6q9
I1115 07:57:21.637804 1 scheduler.go:453] Attempting to schedule pod: nxs-stage/cronjob-cron-events-1573793820-xt6q9
I1115 07:57:21.638285 1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s5 is allowed, Node is running only 16 out of 110 Pods.
I1115 07:57:21.638300 1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s6 is allowed, Node is running only 20 out of 110 Pods.
I1115 07:57:21.638322 1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s3 is allowed, Node is running only 20 out of 110 Pods.
I1115 07:57:21.638322 1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s4 is allowed, Node is running only 17 out of 110 Pods.
I1115 07:57:21.638334 1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s10 is allowed, Node is running only 16 out of 110 Pods.
I1115 07:57:21.638365 1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s12 is allowed, Node is running only 9 out of 110 Pods.
I1115 07:57:21.638334 1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s11 is allowed, Node is running only 11 out of 110 Pods.
I1115 07:57:21.638385 1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s1 is allowed, Node is running only 19 out of 110 Pods.
I1115 07:57:21.638402 1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s2 is allowed, Node is running only 21 out of 110 Pods.
I1115 07:57:21.638383 1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s9 is allowed, Node is running only 16 out of 110 Pods.
I1115 07:57:21.638335 1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s8 is allowed, Node is running only 18 out of 110 Pods.
I1115 07:57:21.638408 1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s13 is allowed, Node is running only 8 out of 110 Pods.
I1115 07:57:21.638478 1 predicates.go:1369] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s10 is allowed, existing pods anti-affinity terms satisfied.
I1115 07:57:21.638505 1 predicates.go:1369] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s8 is allowed, existing pods anti-affinity terms satisfied.
I1115 07:57:21.638577 1 predicates.go:1369] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s9 is allowed, existing pods anti-affinity terms satisfied.
I1115 07:57:21.638583 1 predicates.go:829] Schedule Pod nxs-stage/cronjob-cron-events-1573793820-xt6q9 on Node nxs-k8s-s7 is allowed, Node is running only 25 out of 110 Pods.
I1115 07:57:21.638932 1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s10: BalancedResourceAllocation, capacity 39900 millicores 66620178432 memory bytes, total request 2343 millicores 9640186880 memory bytes, score 9
I1115 07:57:21.638946 1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s10: LeastResourceAllocation, capacity 39900 millicores 66620178432 memory bytes, total request 2343 millicores 9640186880 memory bytes, score 8
I1115 07:57:21.638961 1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s9: BalancedResourceAllocation, capacity 39900 millicores 66620170240 memory bytes, total request 4107 millicores 11307422720 memory bytes, score 9
I1115 07:57:21.638971 1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s8: BalancedResourceAllocation, capacity 39900 millicores 66620178432 memory bytes, total request 5847 millicores 24333637120 memory bytes, score 7
I1115 07:57:21.638975 1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s9: LeastResourceAllocation, capacity 39900 millicores 66620170240 memory bytes, total request 4107 millicores 11307422720 memory bytes, score 8
I1115 07:57:21.638990 1 resource_allocation.go:78] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s8: LeastResourceAllocation, capacity 39900 millicores 66620178432 memory bytes, total request 5847 millicores 24333637120 memory bytes, score 7
I1115 07:57:21.639022 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s10: TaintTolerationPriority, Score: (10)
I1115 07:57:21.639030 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s8: TaintTolerationPriority, Score: (10)
I1115 07:57:21.639034 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s9: TaintTolerationPriority, Score: (10)
I1115 07:57:21.639041 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s10: NodeAffinityPriority, Score: (0)
I1115 07:57:21.639053 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s8: NodeAffinityPriority, Score: (0)
I1115 07:57:21.639059 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s9: NodeAffinityPriority, Score: (0)
I1115 07:57:21.639061 1 interpod_affinity.go:237] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s10: InterPodAffinityPriority, Score: (0)
I1115 07:57:21.639063 1 selector_spreading.go:146] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s10: SelectorSpreadPriority, Score: (10)
I1115 07:57:21.639073 1 interpod_affinity.go:237] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s8: InterPodAffinityPriority, Score: (0)
I1115 07:57:21.639077 1 selector_spreading.go:146] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s8: SelectorSpreadPriority, Score: (10)
I1115 07:57:21.639085 1 interpod_affinity.go:237] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s9: InterPodAffinityPriority, Score: (0)
I1115 07:57:21.639088 1 selector_spreading.go:146] cronjob-cron-events-1573793820-xt6q9 -> nxs-k8s-s9: SelectorSpreadPriority, Score: (10)
I1115 07:57:21.639103 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s10: SelectorSpreadPriority, Score: (10)
I1115 07:57:21.639109 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s8: SelectorSpreadPriority, Score: (10)
I1115 07:57:21.639114 1 generic_scheduler.go:726] cronjob-cron-events-1573793820-xt6q9_nxs-stage -> nxs-k8s-s9: SelectorSpreadPriority, Score: (10)
I1115 07:57:21.639127 1 generic_scheduler.go:781] Host nxs-k8s-s10 => Score 100037
I1115 07:57:21.639150 1 generic_scheduler.go:781] Host nxs-k8s-s8 => Score 100034
I1115 07:57:21.639154 1 generic_scheduler.go:781] Host nxs-k8s-s9 => Score 100037
I1115 07:57:21.639267 1 scheduler_binder.go:269] AssumePodVolumes for pod "nxs-stage/cronjob-cron-events-1573793820-xt6q9", node "nxs-k8s-s10"
I1115 07:57:21.639286 1 scheduler_binder.go:279] AssumePodVolumes for pod "nxs-stage/cronjob-cron-events-1573793820-xt6q9", node "nxs-k8s-s10": all PVCs bound and nothing to do
I1115 07:57:21.639333 1 factory.go:733] Attempting to bind cronjob-cron-events-1573793820-xt6q9 to nxs-k8s-s10
在这里我们看到,调度程序最初会过滤并生成可以启动它的 3 个节点的列表(nxs-k8s-s8、nxs-k8s-s9、nxs-k8s-s10)。 然后,它根据每个节点的多个参数(包括 BalancedResourceAllocation、LeastResourceAllocation)计算分数,以确定最合适的节点。 最终,Pod 被调度到点数最多的节点上(这里两个节点的点数相同 100037,所以随机选择一个 - nxs-k8s-s10)。
结论:如果一个节点运行没有设置任何限制的 pod,那么对于 k8s(从资源消耗的角度来看)这将相当于该节点上根本没有这样的 pod。 因此,如果你有条件有一个 pod 有贪吃进程(比如 wowza),并且没有对其设置任何限制,那么可能会出现这样一种情况:这个 pod 实际上吃掉了该节点的所有资源,但是对于 k8s 来说这个节点被视为已卸载,并且在排名时将获得与没有工作 Pod 的节点相同数量的分数(准确地说是评估可用资源的分数),这最终可能导致节点之间的负载分布不均匀。
Pod 的驱逐
如您所知,每个 pod 都被分配了 3 个 QoS 类别之一:
- 有保证的 — 当为 pod 中的每个容器指定内存和 cpu 的请求和限制时分配,并且这些值必须匹配
- 可爆裂的 — Pod 中至少有一个容器有请求和限制,且请求 < 限制
- 尽力而为 — 当 pod 中没有一个容器资源受限时
同时,当节点遇到资源(磁盘、内存)不足时,kubelet 开始根据考虑 pod 优先级及其 QoS 类别的特定算法对 pod 进行排名和驱逐。 例如,如果我们谈论 RAM,则根据 QoS 等级,根据以下原则奖励积分:
- 保证:-998
- 最大的努力:1000
- 爆裂: min(max(2, 1000 - (1000 * 内存请求字节) / machineMemoryCapacityBytes), 999)
那些。 具有相同的优先级,kubelet 将首先从节点中驱逐具有尽力服务 QoS 级别的 pod。
结论:如果您想减少所需 pod 在缺少资源时被从节点驱逐的可能性,那么除了优先级之外,您还需要注意为其设置请求/限制。
应用程序 Pod 水平自动缩放机制 (HPA)
当任务是根据资源的使用情况(系统 - CPU/RAM 或用户 - rps)自动增加和减少 pod 数量时,例如 k8s 实体 HPA (水平 Pod 自动缩放器)。 其算法如下:
- 确定观察到的资源的当前读数(currentMetricValue)
- 确定资源的期望值(desiredMetricValue),系统资源的期望值使用 request 设置
- 确定当前副本数量(currentReplicas)
- 以下公式计算所需的副本数量(desiredReplicas)
desiredReplicas = [ currentReplicas * ( currentMetricValue /desiredMetricValue )]
在这种情况下,当系数(currentMetricValue /desiredMetricValue)接近1时,不会发生缩放(这种情况下,我们可以自己设置允许误差,默认为0.1)。
让我们使用 app-test 应用程序(描述为部署)的示例来看看 hpa 是如何工作的,其中需要根据 CPU 消耗来更改副本数量:
-
应用清单
kind: Deployment apiVersion: apps/v1beta2 metadata: name: app-test spec: selector: matchLabels: app: app-test replicas: 2 template: metadata: labels: app: app-test spec: containers: - name: nginx image: registry.nixys.ru/generic-images/nginx imagePullPolicy: Always resources: requests: cpu: 60m ports: - name: http containerPort: 80 - name: nginx-exporter image: nginx/nginx-prometheus-exporter resources: requests: cpu: 30m ports: - name: nginx-exporter containerPort: 9113 args: - -nginx.scrape-uri - http://127.0.0.1:80/nginx-status
那些。 我们看到应用程序 pod 最初在两个实例中启动,每个实例包含两个 nginx 和 nginx-exporter 容器,每个容器都有一个指定的 要求 对于CPU。
-
HPA 宣言
apiVersion: autoscaling/v2beta2 kind: HorizontalPodAutoscaler metadata: name: app-test-hpa spec: maxReplicas: 10 minReplicas: 2 scaleTargetRef: apiVersion: extensions/v1beta1 kind: Deployment name: app-test metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 30
那些。 我们创建了一个 hpa,它将监视 Deployment app-test 并根据 cpu 指标调节应用程序的 pod 数量(我们预计 pod 应消耗其请求的 CPU 的 30%),副本数量为在2-10的范围内。
现在,让我们看看当我们向其中一个炉床施加负载时 hpa 运行的机制:
# kubectl top pod NAME CPU(cores) MEMORY(bytes) app-test-78559f8f44-pgs58 101m 243Mi app-test-78559f8f44-cj4jz 4m 240Mi
我们总共有以下内容:
- 期望值(desiredMetricValue)——根据hpa设置,我们有30%
- 当前值(currentMetricValue) - 用于计算,controller-manager计算资源消耗的平均值,以%为单位,即有条件地执行以下操作:
- 从metric server接收pod指标的绝对值,即101m 和 4m
- 计算平均绝对值,即(101m + 4m) / 2 = 53m
- 获取所需资源消耗的绝对值(为此,将所有容器的请求相加) 60m + 30m = 90m
- 计算相对于请求 pod 的 CPU 消耗的平均百分比,即53m / 90m * 100% = 59%
现在我们已经拥有了确定是否需要更改副本数量所需的一切;为此,我们计算系数:
ratio = 59% / 30% = 1.96
那些。 副本数量应增加约 2 倍,达到 [2 * 1.96] = 4。
结论: 正如您所看到的,为了使该机制发挥作用,必要条件是观察到的 pod 中存在对所有容器的请求。
节点水平自动缩放机制(Cluster Autoscaler)
为了消除负载激增期间对系统的负面影响,仅配置 hpa 是不够的。 例如,根据hpa控制器管理器中的设置,它决定需要将副本数量增加2倍,但节点没有空闲资源来运行如此数量的pod(即节点无法提供向 requests pod 请求资源),并且这些 pod 切换到 Pending 状态。
在这种情况下,如果提供商有相应的IaaS/PaaS(例如GKE/GCE、AKS、EKS等),则可以使用类似 节点自动缩放器。 它允许您设置集群中的最大和最小节点数,并在集群和 Pod 资源不足时自动调整当前节点数(通过调用云提供商 API 来订购/删除节点)无法安排(处于 Pending 状态)。
结论: 为了能够自动缩放节点,需要在 pod 容器中设置请求,以便 k8s 能够正确评估节点上的负载,并相应地报告集群中没有资源来启动下一个 pod。
结论
需要注意的是,设置容器资源限制并不是应用程序成功运行的必要条件,但最好还是这样做,原因如下:
- 为了让调度器在k8s节点之间的负载均衡方面更加精准的运行
- 减少发生“pod 驱逐”事件的可能性
- 用于应用程序 Pod (HPA) 的水平自动缩放工作
- 用于云提供商的节点水平自动缩放(集群自动缩放)
另请阅读我们博客上的其他文章:
Tekton Pipeline - Kubernetes 原生管道 为 Nginx 构建动态模块 从无授权的ClickHouse迁移到授权的ClickHouse导致了什么? 理解Golang中的Context包 缩小 Docker 镜像的三个简单技巧 大量异构Web项目的备份
来源: habr.com