Kubernetes in Action笔记 - (16) 计算资源管理
文章目录
为pod中的容器申请资源
创建一个pod时,可以指定容器对 CPU 和内存的资源请求量(即requests),以及资源限制量(即limits)。它是针对每个容器单独指定,pod对资源的请求量和限制量是所包含的所有容器的请求量和限制量之和。
1apiVersion: v1
2kind: Pod
3metadata:
4 name: requests-pod
5spec:
6 containers:
7 - image: busybox
8 command: ["dd", "if=/dev/zero", "of=/dev/null"]
9 name: main
10 resources:
11 requests:
12 cpu: 200m # 申请200毫核,即一个CPU核心的1/5时间
13 memory: 10Mi # 申请50MB内存
资源requests对调度的影响
资源requests指定了pod对资源需求的最小值。调度器在调度时只考虑那些未分配资源量满足pod需求量的节点。如果节点的未分配资源量小于pod需求量,这时节点没有能力提供pod对资源需求的最小量,因此k8s不会将该pod调度到这个节点。
调度器在调度时并不关注各类资源在当前时刻的实际使用量,而只关心节点上部署的所有pod的资源申请量之和。尽管现有所有pod的资源实际使用量可能小于它的申请量,但如果使用基于实际资源消耗量的调度算法将打破系统为这些已部署成功的pod提供足够资源的保证。
调度器如何利用pod requests为其选择最佳节点
调度器首先会对节点列表进行过滤,排除那些不满足需求的节点, 然后根据预先配置的优先级函数对其余节点进行排序。其中有两个基于资源请求量的优先级排序函数:
- LeastRequestedPriority:优先将pod调度到请求量少的节点上,也就是拥有更多未分配资源的节点
- MostRequestedPriority:优先调度到请求量多的节点,拥有更少未分配资源的节点。
它们都只考虑资源请求量, 而不关注实际使用资源量。
调度器只能配置一种优先级函数。
配置调度器使用MostRequestedPriority函数, 可以在为每个pod提供足量CPU与内存资源的同时, 确保k8s使用尽可能少的节点。通过使pod紧凑地编排,一些节点可以保持空闲并可随时从集群中移除。
查看节点资源总量
查看节点资源总量
1$ kubectl describe nodes
2Name: minikube
3...
4# 节点的资源总量
5Capacity:
6 cpu: 2
7 memory: 2048484Ki
8 pods: 110
9# 可分配给pod的资源量
10Allocatable:
11 cpu: 2
12 memory: 1946084Ki
13 pods: 110
14...
通过kubectl describe node命令的输出结果,可以检查节点已经分配的资源,找出为什么pod没有成功调度
1kubectl describe node
CPU requests对CPU时间分配的影响
CPU requests不仅仅在调度时起作用,它还决定着剩余(未使用) 的CPU时间如何在pod之间分配。假设第一个pod请求了200毫核,另一个请求了1000毫核,那么未使用的CPU将按照1:5的比例来划分给这两个pod。
另一方面,如果一个容器能够跑满CPU, 而另一个容器在该时段处于空闲状态,那么前者将可以使用整个CPU时间
限制容器的可用资源
设置容器可使用资源量的硬限制
CPU是一种可压缩资源,意味着我们可以在不对容器内运行的进程产生不利影响的同时,对其使用量进行限制
内存是一种不可压缩资源。一旦系统为进程分配了一块内存,这块内存在进程主动释放之前将无法被回收。这就是为什么需要限制容器的最大内存分配量的根本原因。
1containers:
2- image: busybox
3 ...
4 resources:
5 limits:
6 cpu: 1
7 memory: 10Mi
8
9# 注意:因为这里没有指定资源requests,它将被设置为与资源limits相同的值
与资源requests不同的是,资源 limits 不受节点可分配资源量的约束。所有limits的总和允许超过节点资源总量的100%。换句话说,资源limits可以超卖。如果节点资源使用量超过100%,一些容器将被杀掉。
当为一个容器设置 CPU 限额时,该进程只会分不到比限额更多的 CPU 而己。而内存却有所不同,当进程尝试申请分配比限额更多的内存时会被杀掉。如果 pod 的重启策略为 Always 或 OnFailure ,进程将会立即重启,因此用户可能根本察觉不到它被杀掉。但是如果它继续超限并被杀死, k8s会再次尝试重启,并开始增加下次重启的间隔时间。这种情况下用户会看到 pod 处于CrashLoopBackOff状态。
CrashLoopBackOff 状态表示 Kubelet 还没有放弃,它意味着在每次崩溃之后,Kubelet 就会增加下次重启之前的间隔时间。 第一次崩渍之后, Kubelet立即重启容器, 如果容器再次崩溃, 会等待10秒钟后再重启。随着不断崩溃,延迟时间也会按照20、40、80、160秒以几何倍数增长,最终收敛在300秒。一旦间隔时间达到300秒, Kubelet将以5分钟为间隔时间对容器进行无限重启,直到容器正常运行或被删除。
要定位容器crash的原因,可以通过查看pod日志以及kubectl describe pod
命令。
容器中的应用如何看待limits
在容器内看到的始终是节点的内存, 而不是容器本身的内存
在容器内看到的始终是节点的内存, 而不是容器本身的内存。即使你为容器设置了最大可用内存的限额, top 命令显示的是运行该容器的节点的内存数量, 而容器无法感知到此限制。
对于Java程序来说这是个很大的问题,简单的设置 -Xmx 选项并不能解决问题,因为仅仅限制了堆大小,并不管其他 off-heap 内存。在新版本的Java 会考虑到容器 limits 以 缓解这个 问题
容器内同样可以看到节点所有的 CPU 核
与内存完全一样,无论有没有配置 CPU limits, 容器内也会看到节点所有的CPU
CPU limits 做的只是限制容器使用的 CPU 时间。
一些程序通过查询系统 CPU 核数来决定启动工作线程的数量。同样在开发环境的笔记本电脑上运行良好,但是部署在拥有更多数量 CPU 的节点上,程序将快速启动大量线程,所有线程都会争夺(可能极其)有限的 CPU 时间。同时每个线程通常都需要额外的内存资源,导致应用的内存用量急剧增加。不要依赖应用程序从系统获取的 CPU 数量, 可能需要使用 Downward API 将 CPU 限额传递至容器并使用这个值。也可以通过 cgroup 系统直接获取配置的 CPU限制,请查看下面的文件:
- /sys/fs/cgroup/cpu/cpu.cfs_quota_us
- /sys/fs/cgroup/cpu/cpu.cfs_period_us
pod QoS等级
当节点无法提供足量内存,哪个容器将被杀掉呢?k8s可能无法做出正确的决策。因此就需要一种方式, 通过这种方式可以指定哪种 pod 在该场景中优先级更高。k8s将pod 划分为3种QoS等级:
- BestEffort (优先级最低)
- Burstable
- Guaranteed (优先级最高)
定义pod的QoS等级
QoS等级不是通过独立的字段来分配,它来源于pod所包含的容器的资源requests和limits的配置。
为pod分配BestEffort等级
最低优先级的QoS等级是BestEffort。会分配给那些没有(为任何容器)设置任何requests和limits的pod。
为pod分配Guaranteed等级
与 Burstable 相对的是 Guaranteed 等级,会分配给那些所有资源request和limits 相等的 pod 。对于一个 Guaranteed 级别的pod,有以下几个条件:
- CPU和内存都要设置 requests 和 limits
- 每个容器都需要设置资源量
- 它们必须相等(每个容器的每种资源的requests和limits必须相等)
因为如果容器的资源 requests 没有显式设置,默认与limits相同,所以只设置所有资源(pod内每个容器的每种资源)的限制量就可以使pod的QoS等级为Guaranteed。这些 pod 的容器可以使用它所申请的等额资源,但是无法消耗更多的资源(因为它们的limits和requests相等)。
为pod分配Burstable等级
Burstable QoS等级介于BestEffort和Guaranteed之间。其他所有的pod都属于这个等级。包括:
- 容器的requests和limits不相同的单容器 pod
- 至少有一个容器只定义了requests 但没有定义limits的pod
- 一个容器的request limits 相等,但是另一个容器不指定requests或limits的pod。
Burstable pod可以获得它们所申请的等额资源,并可以使用额外的资源(不超过 limits)。
单容器Pod的QOS等级
下面的表显示了基于资源requests和limits如何为单个容器Pod定义QoS等级。对于单容器pod,容器的QoS等级也适用于 pod 。
CPU requests vs. limits | 内 存的 requests vs. limits | 容器 的 QoS 等级 |
---|---|---|
未设置 | 未设置 | BestEffort |
未设置 | Requests < Limits | Burstable |
未设置 | Requests = Limits | Burstable |
Requests < Limits | 未设置 | Burstable |
Requests < Limits | Requests < Limits | Burstable |
Requests< Limits | Requests = Limits | Burstable |
Requests = Limits | Requests = Limits | Guaranteed |
注意:如果设置了requests而没有设置limits,参考表中requests小于limits那一行。如果设置了limits,requests默认与 limits相等,因此参考request等于limits那一行
多容器Pod的QOS等级
对千多容器pod, 如果所有的容器的QoS等级相同, 那么这个等级就是pod的QoS等级。如果至少有一个容器的QoS等级与其他不同,无论这个容器是什么等级, 这个pod的QoS等级都是Burstable等级。
注意:运行kubectl describe pod以及通过pod的YAML/JSON描述的Status.qosClass字段都可以查看pod的QoS等级。
内存不足时哪个进程会被杀死
等级不同的情况
BestEffort等级的pod首先被杀掉, 其次是Burstable pod, 最后是Guaranteed pod。Guaranteedpod只有在系统进程 需要内存时才会被杀掉。
等级相同的情况
每个运行中的进程都有一个称为OutOfMemory (OOM)分数的值。系统通过比较所有运行进程的OOM分数来选择要杀掉的进程。当需要释放内存时, 分数最高的进程将被杀死。
OOM分数由两个参数计算得出:进程已消耗内存占可用内存的百分比,与一个基于pod QoS等级和容器内存申请量固定的OOM分数调节因子。对于两个属于Burstable等级的单容器的pod, 系统会杀掉内存实际使用量占内存申请量比例更高的pod。
为命名空间中的pod设置默认的requests和limits
用户可以通过创建一个LimitRange资源来避免给每个容器分别配置。它不仅允许用户 (为每个命名空间)指定能给容器配置的每种资源的最小和最大限额, 还支持在没有显式指定资源 requests 时为容器设置默认值。
LimitRange资源中的limits应用千同一个命名空间中每个独立的pod、容器或者其他类型的对象。它并不会限制这个命名空间中所有pod可用资源的总量, 总量是通过ResourceQuota对象指定的
1apiVersion: v1
2kind: LimitRange
3metadata:
4 name: example
5spec:
6 limits:
7 # 指定整个pod资源的limits
8 - type: Pod
9 # 所有容器的CPU与内存请求量之和的最小值
10 min:
11 cpu: 50m
12 memory: 5Mi
13 # 所有容器的CPU与内存请求量之和的最大值
14 max:
15 cpu: 1
16 memory: 1Gi
17 - type: Container
18 # 容器没有指定CPU与内存请求量时设置的默认值
19 defaultRequest:
20 cpu: 100m
21 memory: 10Mi
22 # 没有指定limits时设置的默认值
23 default:
24 cpu: 200m
25 memory: 100Mi
26 # 容器CPU和内存的资源request和limits的最小值
27 min:
28 cpu: 50m
29 memory: 5Mi
30 # 容器CPU和内存的资源request和limits的最大值
31 max:
32 cpu: 1
33 memory: 1Gi
34 # 每种资源requests与limits的最大比值
35 maxLimitRequestRatio:
36 cpu: 4
37 memory: 10
38 # 请求PVC存储容量的最大值与最小值
39 - type: persistentVolumeClaim
40 min:
41 storage: 1Gi
42 max:
43 storage: 10Gi
限制命名空间中的可用资源总量
ResourceQuota的接纳控制插件会检查将要创建的pod是否会引起总资源量超出ResourceQuota。如果那样,创建请求会被拒绝。资源配额只是在pod创建时进行检查,所以ResourceQuota对象仅仅作用于在其后创建的pod,并不影响已经存在的pod。
为CPU和内存创建ResourceQuota
1apiVersion: v1
2kind: ResourceQuota
3metadata:
4 name: cpu-and-memory
5spec:
6 hard:
7 requests.cpu: 400m
8 requests.memory: 200Mi
9 limits.cpu: 600m
10 limits.memory: 500Mi
与LimitRange一样,ResourceQuota对象应用于它所创建的那个命名空间。
查看配额和配额使用情况:
1kubectl describe quota
为持久化存储指定配额
它也可以限制某个命名空间中最多可以声明的持久化存储总量
1apiVersion: v1
2kind: ResourceQuota
3metadata:
4 name: storage
5spec:
6 hard:
7 # 可声明总存储量
8 requests.storage: 500Gi
9 # SSD可声明的存储量
10 ssd.storageclass.storage.k8s.io/requests.storage: 300Gi
11 standard.storageclass.storage.k8s.io/requests.storage: 1Ti
限制可创建对象的个数
资源配额同样可以限制单个命名空间中的pod、ReplicationController、Service以及其他对象的个数。
1apiVersion: v1
2kind: ResourceQuota
3metadata:
4 name: objects
5spec:
6 hard:
7 pods: 10
8 replicationcontrollers: 5
9 secrets: 10
10 configmaps: 10
11 persistentvolumeclaims: 4
12 services: 5
13 services.loadbalancers: 5
14 services.nodeports: 5
15 ssd.storageclass.storage.k8s.io/persistentvolumeclaims: 2
为特定的pod状态或者QoS等级指定配额
Quota 可以被一组quota scopes限制。目前配额作用范围共有4种:
- BestEffort:应用于 BestEffort QoS 等级
- NotBestEffort: 应用于除了BestEffort之外的 QoS等级,即Burstable和Guaranteed
- Termination:应用于配置了 activeDeadlineSeconds 的 pod
- NotTerminating:应用于那些没有指定activeDeadlineSeconds配置的 pod
activeDeadlineSeconds属性定义了一个 pod 从开始尝试停止的时间到其被标记为 Failed 然后真正停止 之前,允许其在节点上继续运行的秒数。
配额的范围也决定着配额可以限制的内容。BestEffort范围只允许限制pod个数,而其他3种范围除了pod个数,还可以限制 CPU和内存的requests和limits。
监控 pod 的资源使用量
设置合适的资源requests和limits对充分利用k8s节点资源来说非常重要。如果requests设置得太高,集群节点利用率就会比较低;如果设置得太低,应用就会处于CPU饥饿状态,甚至很容易被OOM Killer杀死。
可以通过对容器在期望负载下的资源实际使用率进行监控来找到这个最佳配置
收集、获取实际资源使用情况
Kubelet自身就包含了一个名为cAdvisor的agent,它会收集整个节点和节点上运行的所有单独容器的资源消耗情况。集中统计整个集群的监控信息需要运行一个叫作 Heapster的附加组件。
Heapster以pod的方式运行在某个节点上,它通过普通的Kubernetes Service暴露服务,使外部可以通过一个稳定的IP地址访问。它从集群中所有的cAdvisor收集数据,然后通过一个单独的地址暴露。
显示集群节点的CPU和内存使用量
显示节点的CPU和内存的实际使用量
1kubectl top node
显示单独pod的CPU和内存的实际使用量
1kubectl top node --all-namespaces
显示容器的CPU和内存的实际使用量
1kubectl top node --container
保存并分析历史资源的使用统计信息
一种方案是,使用 InfluxDB 来存储统计数据, 然后使用Grafana 对数据进行可视化和分析
图书资料: