pod 计算资源管理
简述
为一个 pod 配置资源的预期使用量和最大使用量是 pod 定义中的重要组成部分。 通过设置这两组参数, 可以确保 pod 公平地使用 Kubemetes集群资源, 同时也影响着整个集群pod的调度方式。
为Pod中的容器申请资源
我们创建 一 个pod 时,可以指定容器对CPU 和内存的资源请求量(即requests), 以及资源限制量(即Limt)。它们并不在 pod 里定义, 而是针对每个容器单独指定。pod 对资源的请求量和限制量是它所包含的所有容器的请求量和限制量之和。
创建包含资源 requests 的 pod
apiVersion: vl kind: Pod metadata: name: requests-pod spec: containers: - image: busybox command: ["dd", "if=/dev/zero", "of=/dev/null"] name: main resources: #我们为主容器指定了资源请求 requests: cpu: 200m #容器申请200毫核(即一个CPU核心的1/5) memory: lOMi #容器申请了10M 内存
当我们不指定CPU requests时,表示我们并不关心系统为容器内的进程分配了多少CPU时间。在最坏情况下进程可能根本分不到CPU时间(当其他进程对CPU需求量很大时会发生)。这对一些时间不敏感、低优先级的batch jobs没有问题,但对于处理用户请求的容器这样配置显然不太合适。
资源 requests 影响调度
通过设置资源requests我们指定了pod对资源需求的最小值。调度器在将pod调度到节点的过程中会用到该信息。调度器在调度时只考虑那些未分配资源量满足pod 需求量的节点。如果节点的未分配资源量小于pod 需求量,这时节点没有能力提供pod对资源需求的最小量,因此Kubemetes不会将该pod调度到这个节点。
调度器在调度时并不关注各类资源在当前时刻的实际使用量,而只关心节点上部署的所有pod 的资源申请(request)量之和。尽管现有pods 的资源实际使用量 可能小于它 的 申请量,但如果使用基于 实际资源消耗量的调度算法将打破系统为这些已部署成功的pods提供足够资源的保证。
查看资源分配情况
kubectl describe node node-name #整个节点总体资源 Capacity: cpu: 4 ephemeral-storage: 515922800Ki hugepages-1Gi: 0 hugepages-2Mi: 0 memory: 15732704Ki pods: 23 #整个节点可以对外分配的资源 Allocatable: cpu: 4 ephemeral-storage: 475474451693 hugepages-1Gi: 0 hugepages-2Mi: 0 memory: 14708704Ki pods: 23 ... #各个命名空间实际requests分配的情况,会有kube-system 下的系统级别的Pod申请了资源。 Non-terminated Pods: (15 in total) Namespace Name CPU Requests CPU Limits Memory Requests Memory Limits AGE --------- ---- ------------ ---------- --------------- ------------- --- default jianwei-k8s-3afe2134-3b6f-43a6-a473-ce4 0 (0%) 0 (0%) 1Gi (7%) 0 (0%) 58d filebeat filebeat-fdasd 250m (6%) 500m (12%) 500Mi (3%) 1Gi (7%) 21d kube-system flexvolume-dfhjj 100m (2%) 1 (25%) 100Mi (0%) 1000Mi (6%) 58d kube-system kube-proxy-worker-bqcmk 0 (0%) 0 (0%) 0 (0%) 0 (0%) 58d kube-system terway-eniip-fffvg 250m (6%) 0 (0%) 0 (0%) 0 (0%) 58d monitoring ack-prometheus-operator-adapter-7c 0 (0%) 0 (0%) 0 (0%) 0 (0%) 23d monitoring ack-prometheus-operator-grafana-5b 0 (0%) 0 (0%) 0 (0%) 0 (0%) 23d monitoring ack-prometheus-operator-kube-state 0 (0%) 0 (0%) 0 (0%) 0 (0%) 23d monitoring ack-prometheus-operator-operator-8 0 (0%) 0 (0%) 0 (0%) 0 (0%) 23d monitoring ack-prometheus-operator-prometheus-node 0 (0%) 0 (0%) 0 (0%) 0 (0%) 23d monitoring alertmanager-ack-prometheus-operator 100m (2%) 100m (2%) 225Mi (1%) 25Mi (0%) 23d monitoring prometheus-ack-prometheus-operator- 150m (3%) 150m (3%) 75Mi (0%) 75Mi (0%) 23d ad1 payshop-85dasdbcd4749-h625m 500m (12%) 2 (50%) 1Gi (7%) 4Gi (28%) 24d ad3 fdaf-supply-chain-go-64988c99d8-ppv2v 500m (12%) 1 (25%) 1Gi (7%) 2Gi (14%) 16d ad6 k8s-admin-soul-85d5945974-lxjh4 500m (12%) 2 (50%) 1Gi (7%) 4Gi (28%) 24d #整个节点汇总后实际requests分配的情况,k8s根据分配后的剩余量来调度pod Allocated resources: (Total limits may be over 100 percent, i.e., overcommitted.) Resource Requests Limits -------- -------- ------ cpu 2350m (58%) 6750m (168%) memory 4996Mi (34%) 12364Mi (86%) ephemeral-storage 0 (0%) 0 (0%) ...
CPU requests影响CPU时间分配
现在有两个pod运行在集群中(暂且不理会那些系统pod 因为它们大部分时间都是空闲的)。 一个请求了200毫核,另一个是前者的5倍。我们还没有定义任何limts, 因此每个pod 分别可以消耗多少CPU并没有做任何限制。那么假设每个pod内的进程都尽情消耗CPU时间,每个pod最终能分到多少CPU时间呢?CPU requests不仅仅在调度时起作用,它还决定着剩余(未使用) 的CPU时间如何在pod之间分配。第一个pod 请求了200毫核,另 一个请求了1000毫核,所以未使用的CPU将按照1:5的比例来划分给这两个
pod。如果两个pod 都全力使用CPU, 第一个pod 将获得16.7%的CPU时间,另 一个将获得83.3%的CPU时间。
另一方面,如果一个容器能够跑满CPU,而另 一个容器在该时段处于空闲状态,那么前者将可以使用整个CPU时间(当然会减掉第二个容器消耗的少量时间)。毕竟当没有其他人使用时提高整个CPU的利用率也是有意义的,对吧?当然 ,第二个容器需要CPU时间的时候就会获取到,同时第一个容器会被限制回来。
定义和申请自定义资源
Kubemetes允许用户为节点添加属于自己的自定义资源,同时支持在pod资源 requests里申请这种资源。因为目前是一个alpha特性,所以不打算描述其细节,而只会简短地介绍一下。首先, 需要通过将自定义资源加入节点API对象的capacity属性让Kubemetes知道它的存在。这可以通过执行HTTP的PATCH请求来完成。资源名称可以是不以 kubernetes.io 域名开头的任 意值,例如 example.org/myresource ,数量必须是整数(例如不能设为 lOOm ,因为 0.1 不是整数;但是可以设置为 l000m、 2000m ,或者简单地设为1和2 。这个值将自动从 capacity 复制到 allocatable字段。
创建 pod 时只要简单地在容器 spec resources requests 宇段下,或者像之前例子那样使用带 requests 参数的 kubectl run 命令来指定自定义资源名称和申请 ,调度器就可以确保这个 pod 只能部署到满足自定义资源申的节点,同时每个己部署的 pod 会减少节点的这类可分配资源数量。
一个自定义资源的例子就是节点上可用的 GPU 单元数量。如果pod 要使用GPU ,只 要简单指定其 requests 调度器就会保证这个 pod 只能调度到至少拥有一个未分配 GPU 单元的节点上。
限制容器的可用资源
设置 pod 的容器资源申请量保证了每个容器能够获得它所需要资源的最小量。现在我们再看看硬币的另一面一一容器可以消耗资源的最大量。
CPU与内存的区别
CPU 是一种可压缩资源,意味着我们可 在不对容器内运行的进程产生不利影响的同时,对其使用量进行限制而内存明显不同一一是一种不可压缩资源。一旦系统为进程分配了 块内存,这块内存在进程主动释放之前将无法被回收。这就是为什么需要限制容器的最大内存分配的根本原因。
如果不对内存进行限制, 工作节点上的容器(或者 pod )可能会吃掉所有可用内存,会对该节点上所有其他 po 任何新调度上来的pod (记住新调度的 pod基于内存的申请量而不是实际使用量的)造成影响。单个故障 pod 或恶意 pod 几乎可以导致整个节点不可用。
创建一个带有资源 limits 的pod
apiVersion: vl kind: Pod metadata: name: limited-pod spec: containers: - image: busybox command: [ "dd”,”if=/dev/zero”,”of=/dev/null"] name: main resources: limits: cpu: 1 memory : 20Mi
可超卖的 limits
与资源 requests 不同的是,资源 limits 不受节点可分配资源量的约束所有limits 总和允许超过节点资源总量的 100%。换句话说,资源 limits以超卖。 如果节点资源使用超过 100% ,一些容器将被杀掉, 这是一个很重要的结果 。
超过 limits
当容器内运行的进程尝试使用比限额更多的资源时会发生什么呢?
我们己经了解了 CPU 是可压缩资源,当进程不等待 IO 操作时消耗所有的 CPU 时间是非常常见的。 正如我们所知道的,对一个进程的 CPU 使用率可以进行限制,因此当为一个容器设置 CPU 限额时,该进程只会分不到比限额更多的 CPU 而己。
而内存却有所不同 当进程尝试申请分配比限额更多的内存时会被杀掉(我们会说这个容器被 OOMKilled 了, OMM是Out Of Memory 的缩写〉 如果 pod 的重启策略为 Always 或 OnFailure ,进程将会立即重启,因此用户可能根本察觉不到它被杀掉。但是如果它继续超限并被杀死, kubernetes 会再次尝试重启,并开始增加下次重启的间隔时间。这种情况下用户会看到 pod CrashLoopBackOff 状态。
关于 CrashLoopBackOf
CrashLoopBackOf 状态表示 Kubelet 还没有放弃,它 味着在每次崩溃之后,Kubelet 会增加下次重启之前的间隔时间 次崩渍之后, kubelet 立即重启容器,如果容器再次崩溃, kubelet 会等待 10 秒钟后再重启,随着不断崩溃,延迟时间也会按照 20 40 80 160 秒以几何倍数增长,最终收敛在 300s 。一旦间隔时间达300 秒, kubelet 将以5分钟为间隔时间对容器进行无限重启,直到容器正常运行或被删除。 要定位容器 crash 原因,可以通过查看 pod 日志以及 kubectl describe pod 命令
在容器内看到的始终是节点的内存, 而不是容器本身的内存
kubectl exec -it limited-pod top #此命令查看到的是整个节点的资源
即使你为容器设置了最大可用内存的限额, top 命令显示的是运行该容器的节点的内存数 ,而容器无法感知到此限制。这对任何通过查看系统剩余可用内存数量,并用这些信息来决定自己该使用多少内存的应用来说具有非常不利的影响。
对于 Java 程序来说这是个很大的问题,尤其是不使用一Xmx 选项指定虚拟机的最大堆大小时, JVM 会将其设置为主机总物理内存的百分值。在 kubemetes 开发集群运行 Java 容器化应用(比如在笔记本电脑上运行)时,因为内存 limit 和笔记本电脑总内存差距不是很大,这个问题还不太明显但是如果 pod 部署在拥有更大物理内存的生产系统中, JVM 将迅速超过预先配置的内存限额,然后被 OOM 杀死也许你觉得可以简单地设 Xmx 选项就可以解决这个问题,那么你就错了,很遗憾。 Xmx 选项仅仅限制了堆大小,并不管其他 off heap 内存,好在新版本的Java 会考虑到容器 limits 缓解这个问题
容器内同样可感知的是节点所有的 CPU
与内存完全一样,无论有没有配置 CPU limits 容器内也会看到节点所有的CPU 。将 CPU 限额配置为1 ,并不会神奇地只为容器暴露一个核。 CPU limit 做的只是限制容器使用的 CPU 时间。因此如果拥有1个 CPU 限额的容器运行在 64 CPU 上,只能获得 1/64 的全部 CPU 时间 而且即使限额设置为1核, 容器进程也不会只运行在一个核上,不同时刻,代码还是会在多个核上执行。
上面的描述没什么问题,对吧?虽然一般情况下如此,但在一些情况下却是灾难。
一些程序通过查询系统 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等级
前面己经提到资源 limits 可以超卖, 换句话说, 一个节点不一定能提供所有pod 所指定的资源 limits 之和那么多的资源量。如果两个pod 争抢内存资源,内存资源已经不足,那么哪个进程先被杀死?
Kubemetes 无法自己做出正确决策,因此就需要一种方式,我们通过这种方式可 以指定哪 pod 在该场景中优先级更高。 Kubernetes 将pod 划分为3种 QoS 等级
• BestEffort (优先级最低)
最低优先级的 QoS 等级是 BestEffort 会分配给那些没有(为任何容器)设置任何 requests limits pod 。在这个等级运行的容器没有任何资源保证。在最坏情况下,它们分不到任 何CPU 时间,同时在需要为其他 pod 释放内存时这些容器会第一批被杀死。不过因为 BestEffort pod 没有配置内存 limits 当有充足的可用内存时,这些容器可以使用任意多的内存
• Burstable
Burstable QoS 级介于 BestEffort Guaranteed 之间。其他所有 pod 属于这个等级 包括容器的 requests limits 不相同的单容器 pod ,至少有一个容器只定义了 reques ts 但没有定义 limits 的pod ,以及一个容器的 request limt 相等,但是另一个容器不指定 requests或limits 的pod。 Burstable pod 可以获得它们所申请的等额资源, 并可以使用额外的资源(不超过 limits)。
• Guaranteed (优先级最高)
Guaranteed 等级会分配给那些所有资源 request limits 相等的 pod。 对于 Guaranteed 级别的 pod ,有以下几个条件:
CPU 和内存都要设置 requests limits
每个容器都需要设置资源量
它们必须相等(每个容器的每种资源的 requests limits 必须相等)
了解多容器 pod 的 QoS 等级
对千多容器pod, 如果所有的容器的QoS等级相同, 那么这个等级就是pod的QoS等级。 如果至少有一个容器的QoS等级与其他不同,无论这个容器是什么等级,这个pod的QoS等级都是Burstable等级。
注意 运行kubectl describe pod以及通过pod的YAML/JSON描述的Status.qosClass字段都可以查看pod的QoS等级。
内存不足时哪个进程会被杀死
在一个超卖的系统,QoS等级决定着哪个容器第一个被杀掉, 这样释放出的资源可以提供给高优先级的pod使用。 BestEffort等级的pod首先被杀掉, 其次是 Burstable pod, 最后是Guaranteed pod。 Guaranteedpod只有在系统进程需要内存时才会被杀掉。
如何处理相同QoS等级的容器
每个运行中的进程都有一个称为OutOfMemory (OOM)分数的值。系统通过比较所有运行进程的OOM分数来选择要杀掉的进程。当需要释放内存时分数最高的进程将被杀死。
OOM分数由两个参数计算得出:进程已消耗内存占可用内存的百分比, 与 一个基于 pod QoS等级和容器内存申请量固定的OOM分数调节因子。 对于两个属于Burstable等级的单容器的pod, 系统会杀掉 内存 实际使用量占内存申请量比例更高的pod。这说明我们不仅要注意requets和limits之间的关系,还要留心requests和预期实际消耗内存之间的关系
LimitRange 资源
用户可以通过创建一个LimitRange资源来避免必须配置每个容器。 LimitRange资源不仅允许用户 (为每个命名空间)指定能给容器配置的每种资源的最小和最大限额, 还支持在没有显式指定资源 request 时为容器设置默认值
LimitRange资源被LimitRanger准入控制插件。API服务器接收到带有pod描述信息的POST请求时, LimitRanger插件对pod spec进行校验。 如果校验失败, 将直接拒绝。 因此, LimitRange对象的一个广泛应用场景就是阻止用户创建大于单个节点资源量的pod。 如果没有LimitRange, API服务器将欣然接收pod创建请求, 但永远无法调度成功。Lmi itRange资源中的limit s应用千同一 个命名空间中每个独立的pod、 容器,或者其他类型的对象。 它并不会限制这个命名空间中所有pod可用资源的总量, 总量是通过 Resource Quota对象指定的
LimitRange 对象的创建
apiVersion: v1 kind: LimitRange metadata: name: example sepc: limits: - type: pod min: cpu: 50m memory: 5Mi max: cpu: 1 memory: 1Gi - type: container defaultRequest: cpu: 100m memory: 10Mi default: cpu: 200m memory: 100Mi min: cpu: 50m memory: 50Mi max: cpu: 1 memory: 1Gi maxLimitRequestRatio: cpu: 4 memory: 10 - type: PersistentVolumeClaim min: storage: 1Gi max: storage: 1Gi 正如在上面例子中看到的,整个pod资源限制的最小值和最大值是可以配置的。它应用于pod内所有容器的requests和limits之和。 在更低一层的容器级别,用户不仅可以设置最小值和最大值,还可以为没有显式指定的容器设置资源requests(defaultRequest)和limits(default)的默认值。 除了最小值、最大值和默认值,用户甚至可以设置limits和requests的最大比例。上面示例中设置了maxLimitRequestRatio为4, 表示容器的CPU limits不能超过CPU requests的4倍。 因此,对于一个申请了200毫核的容器,如果它的CPU限额设置为801毫核或者更大就无法创建。而对于内存,这个比例设为了10。 这个例子只使用一个LimitRange对象, 其中包含了对所有资源的限制,而如果你希望按照类型进行组织,也可以将其分割为多个对象(例如一个用于pod限制, 一个用于容器限制, 一个用于PVC 限制)。多个LimitRange对象的限制会在校验pod或PVC 合法性时进行合并。 由于L血itRange对象中配置的校验(和默认值)信息在API服务器接收到新的pod或PVC创建请求时执行,如果之后 修改了限制,已经存在 的pod和PVC 将不会再次进行校验, 新的限制只会应用于之后创建的pod和PVC 。
ResourceQuota 资源(限制命名空间中的可用资源总量 )
ResourceQuota的接纳控制插件会检查将要创建的pod是否会引起总资源量超出ResourceQuota。如果那样,创建请求会被拒绝。因为资源配额在pod创建时进行检查,所以ResourceQuota对象仅仅作用于在其后创建的pod ,并不会影响已经存在的pod。
资源 配额 限制了一个命名空间中pod和PVC存储最多可以使用的资源总址。同时也可以限制用户允许在该命名空间中创建pod、PVC,以及其他API对象的数量,因为到目前为止我们处理最多的资源是CPU和内存
限制可创建对象个数
对象个数配额目前可以为以下对象配置
• pod
• ReplicationController
• Secret
• ConfigMap
• Persistent Volume Claim
• Service (通用)及两种特定类型的 Service ,比如 LoadBalancer Service (services.loadbalancers )和 Node Port Service (services.nodeports)
为特定的 pod 状态或者 QoS 等级指定配额
目前为止我们创建的 Quota 应用于所有的 pod 不管 pod 的当前状态和 QoS 级如何。但是 Quota 可以被 quota scopes 限制, 目前配额作用范围共有4种:BestEffort NotBestEffort Termination NotTerminating。 BestEffort和NotBestEffort 范围决定配额是否应用于 BestEffort QoS 级或者其他两种等级(Burstable和Guaranteed)的pod
创建 ResourceQuota 时,可以为其指定作用范围。目标 pod 必须与配额中配置的所有范围相匹配 另外,配额的范围也决定着配额可以限制的内容BestEffort 范围只允许限制 pod 数,而其他3种范围除了 pod 个数,还可以限 CPU 内存的 requests 和 limits
Terminating 和 NotTerminating
可以为每个 pod 指定被标记为 Failed ,然后真正停止之前还可以运行多长时间 这是通过在 pod pec 中配置 activeDeadlineSeconds 来实现的该属性定义了一个 pod 从开始尝试停止的时间到其被标记为 Failed 然后真正停止之前,允许其在节点上继续运行的秒数 Terminating 配额作用范围应用于这些配置了activeDeadlineSeconds 的pod 而 Not Terminating 应用于那些没有指定该配置的 pod
创建的 ResourceQuota 对象
apiVersion: vl kind : ResourceQuota metadata: name: besteffort-notterminating-pods spec scopes - BestEffort - NotTerminating hard : pods: 4 这个配额允许最多4个属于BestEffort QoS等级,并没有设置activedeadline的pod。如果配额针对的是 NotBestEffort pod ,我们便可以指定requests.cpu,requests.memory,limit.cpu limits.memory
监控pod 的资源使用量
设置合适的 资源 request 和 limits 对充分利用 kubernetes十分重要,如果 requests 设置得太高,集群节点利用率就会比较低,这样就白白浪费了资源,如果设置得太低,应用就会处于 CPU 饥饿状态,甚至很容易被 OOM Killer 杀死所以如何才能找到 requests limi ts 的最佳配置呢?
可以通过对容器在期望负载下的资源实际使用率进行监控来找到这个最佳配置。当然 旦应用暴露于公网,都应该保持监控并且在需要时对其资源的 request limits 进行调节
kubelet 自身就包含了一个名为 cAdvisor的agent ,它会收集整个节点和节点上运行的所有单独容器的资源消耗情况。集中统计整个集群的监控信息需要运行一个叫作 Heapster 的附加组件。Heapste 以pod 的方式运行在某个节点上,它通过普通的 Kubernetes Service 暴露服务,使外部可以通过一个稳定的 IP 地址访问 它从集群中所有的 cAdvisor收集数据,然后通过一个单独的地址暴露。
显示集群节点的CPU和内存使用量
在集群中运行Heapster可以通过kubectl top命令获得节点和单个pod的资源用量。要看节点使用了多少CPU和内存,可以执行以下代码清单所示的命令。
kubect1 top node NAME CPU(cores) CPU% MEMORY(bytes) MEMORY% minikube 170m 8% 516Mi 21% 它显示了节点上运行的所有pod当前的CPU和内存实际使用量,与kubectl describe node命令不同的是,后者仅仅显示节点CPU和内存的requests和limits,而不是实际运行时的使用数据。
显示单独pod的CPU和内存使用量
$kubectl top pod --all-namespaces NAMESPACE NAME CPU(cores) MEMORY(bytes) kube-system influxdb-grafana-2r2w9 lm 32Mi kube-system heapster-40j6d 0m 18Mi default kubia-3773182134-63bmb 0m 9Mi kube-system kube-dns-v20-z0hq6 lm llMi kube-system kubernetes-dashboard-r53mc 0m 14Mi kube-system kube-addon-manager-minikube 7m 33Mi 提示要查看容器而不是 pod 的资源使用情况, 可以使用 --container 选项。 有一点需要提醒的是, 有时toppod命令会拒绝输出任何指标而输出以下错误: $ kubect1 top pod W0312 22:12:58.021885 15126 top_pod.go:186] Metrics not available for default/kubia-3773182134-63bmb, age: lh24ml9.021873823s error: Metrics not available for pod default/kubia-3773182134-63bmb, age 2h24ml9.D25674823s 如果看到这个错误, 先不要急于寻找出错的原因,然后等待一会儿重新执行一可能需要好几分钟, 不过最终会看到指标的。 因为 kubectl top 命令从 Heapster 中获取指标,它将几分钟的数据汇总起来,所以通常不会立即暴露。
保存并分析历史资源的使用统计信息
top 命令仅仅展示了当前的资源使用量-它并不会显示比如从一小时、一天或者一周前到现在 pod 的 CPU 和内存使用了多少。 事实上 cAdvisor 和 Heapster都只保存一个很短时间窗的资源使用量数据。如果需要分析一段时间的 pod 的资源使用情况, 必须使用额外的工具。如果使用 Google Container Engine, 可以通过Google Cloud Monitoring 来对集群进行监控,但是如果是本地Kubemetes 集群(通过 Minik:ube 或其他方式创建),人们往往使用 Influx.DB 来存储统计数据,然后使用Grafana 对数据进行可视化和分析