一、前言
本篇是Kubernetes第十六篇,大家一定要把环境搭建起来,看是解决不了问题的,必须实战。
- Kubernetes介绍
- Kubernetes环境搭建
- Kubernetes-kubectl介绍
- Kubernetes-Pod介绍(-)
- Kubernetes-Pod介绍(二)-生命周期
- Kubernetes-Pod介绍(三)-Pod调度
- Kubernetes-Pod介绍(四)-Deployment
- Kubernetes-Service介绍(一)-基本概念
- Kubernetes-Service介绍(二)-服务发现
- Kubernetes-Service介绍(三)-Ingress(含最新版安装踩坑实践)
- Kubernetes-网络
- Kubernetes-存储(一)
- Kubernetes-存储(二)
- Kubernetes-API Server
- Kubernetes-Kuboard
二、Kubernetes资源模型
在 Kubernetes 中,有两个基础但是非常重要的概念:Node 和 Pod。Node 翻译成节点,是对集群资源的抽象;Pod 是对容器的封装,是应用运行的实体。Node 提供资源,而 Pod 使用资源,这里的资源分为计算(CPU、Memory、GPU)、存储(Disk、SSD)、网络(Network Bandwidth、IP、Ports)。这些资源提供了应用运行的基础,正确理解这些资源以及集群调度如何使用这些资源,对于大规模的 Kubernetes 集群来说至关重要,不仅能保证应用的稳定性,也可以提高资源的利用率。
img
所有的资源类型,又可以被划分为两大类:可压缩和不可压缩的。在 Kubernetes 中,像CPU这样的资源被称作可压缩资源。它的典型特点是,当可压缩资源不足时,Pod 只会饥饿,但不会退出。而像内存这样的资源,则被称作不可压缩资源。当不可压缩资源不足时,Pod 就会因为 OOM被内核杀掉。
由于 Pod 可以由多个 Container 组成,所以 CPU 和内存资源的限额,是要配置在每个Container的定义上的。这样,Pod 整体的资源配置,就由这些 Container的配置值累加得到。
举个例子:
apiVersion: v1 kind: Pod metadata: name: frontend spec: containers: - name: db image: mysql env: - name: MYSQL_ROOT_PASSWORD value: "password" resources: requests: memory: "64Mi" cpu: "250m" limits: memory: "128Mi" cpu: "500m" - name: wp image: wordpress resources: requests: memory: "64Mi" cpu: "250m" limits: memory: "128Mi" cpu: "500m"
我们可以看到在Pod上资源类型配置主要有四个参数:
spec.container[].resources.requests.cpu spec.container[].resources.limits.cpu spec.container[].resources.requests.memory spec.container[].resources.limits.memory
其中,limits对应资源量的上限,即最多允许使用这个上限的资源量。由于CPU资源是可压缩的,进程无论如何也不可能突破上限,因此设置起来比较容易。对于Memory这种不可压缩资源来说,它的Limit设置就是一个问题了,如果设置得小了,当进程在业务繁忙期试图请求超过Limit限制的Memory时,此进程就会被Kubernetes杀掉。针对这种情况Kubernetes从资源计算、服务质量管理、资源配额等方面,设计了一套完善的机制来管理集群资源。
资源计算
容器的计算资源配额
CPU资源的计量方式
一个核心相当于1000个微核心,及1=1000m,0.5=500m;
内存资源的计量方式
默认单位为字节,也可以使用使用E、P、G、M和K后缀单位,或Ei、Pi、Gi、Mi和Ki后缀单位;
基于Requests和Limits的Pod调度机制
- 调度器在调度时,首先要确保调度后该节点上所有Pod的CPU和内存的Requests总和,不超过该节点能提供给Pod使用的CPU和Memory的最大容量值;
- 即使某节点上的实际资源使用量非常低,但是已运行Pod配置的Requests值的总和非常高,再加上需要调度的Pod的Requests值,会超过该节点提供给Pod的资源容量上限,这时Kubernetes仍然不会将Pod调度到该节点上。如果Kubernetes将Pod调度到该节点上,之后该节点上运行的Pod又面临服务峰值等情况,就可能导致Pod资源短缺;
Requests和Limits的背后机制
img
kubelet在启动Pod的某个容器时,会将容器的Requests和Limits值转化为相应的容器启动参数传递给容器执行器 如果是docker 容器:
kubelet在启动Pod的某个容器时,会将容器的Requests和Limits值转化为相应的容器启动参数传递给容器执行器 如果是docker 容器:
- spec.container[].resources.requests.cpu: 转化为docker 的--cpu-share;
- spec.container[].resources.limits.cpu: 转为docker的--cpu-quota;
- spec.container[].resources.requests.memory: 提供给Kubernetes调度器作为调度和管理的依据,不会作为任何参数传递给Docker;
- spec.container[].resources.limits.memory: 会转为--memory;
三、资源配置范围管理(LimitRange)
在默认情况下,Kubernetes不会对Pod加上CPU和内存限制,这意味着Kubernetes系统中任何Pod都可以使用其所在节点的所有可用的CPU和内存。通过配置容器的计算资源Requests和Limits,我们可以限制Pod的资源使用,但对于Kubernetes集群管理员而言,配置每一个Pod的Requests和Limits是烦琐的,而且很受限制。针对这样的情况Kubernetes提出了LimitRange,对Kubernetes集群内Pod和容器的Requests和Limits进行一份全局的配置,用来限制资源的使用。
使用介绍
- 创建一个Namespace;
kubectl create namespace limit-example
- Namespace设置LimitRange;
apiVersion: v1 kind: LimitRange metadata: name: mylimits spec: limits: #Pod级别 - type: Pod #Pod中所有容器的Requests值的总和最大值 max: cpu: "4" memory: 2Gi #Pod中所有容器的Requests值的总和最小值 min: cpu: 200m memory: 6Mi #Pod中所有容器的Limits值总和与Requests值总和的比例上限 maxLimitRequestRatio: cpu: "3" memory: "2" #容器级别 - type: Container #Pod中所有未指定Request/Limits值的容器的默认Request/Limits值 default: cpu: 300m memory: 200Mi defaultRequest: cpu: 200m memory: 100Mi #单个container的最大值 max: cpu: "2" #单个container的最小值 min: cpu: 100m memory: 3Mi #Max Limit/Requests Ratio的比值 maxLimitRequestRatio: cpu: "5" memory: "4"
- limit-example命名空间创建LimitRange的yaml文件;
kubectl apply -f limits.yaml --namespace=limit-example
- 查看limit-example中的LimitRange;
kubectl describe limits mylimits --namespace=limit-example
image.png
- 创建一个LimitRange对maxLimitRequestRatio比例操过限制的Pod,我们会发现Pod会创建失败;
apiVersion: v1 kind: Pod metadata: name: limit-test-nginx labels: name: limit-test-nginx spec: containers: - name: limit-test-nginx image: nginx resources: limits: cpu: "1" memory: 512Mi requests: cpu: "0.8" memory: 250Mi
kubectl apply -f max-limit-request-ratio.yaml --namespace=limit-example
image.png
特点
- 命名空间中LimitRange只会在Pod创建或者更新时执行检查。如果手动修改LimitRange为一个新的值,那么这个新的值不会去检查或限制之前已经在该命名空间中创建好的Pod;
- 如果在创建Pod时配置的资源值(CPU或者内存)超过了LimitRange的限制,那么该创建过程会报错,在错误信息中会说明详细的错误原因;
四、资源服务质量管理(Resource Qos)
Kubernetes是根据Pod的Requests和Limits配置来实现针对Pod的不同级别的资源服务质量控制(QoS)。Requests是Kubernetes调度时能为容器提供的完全可保障的资源量,而Limits是系统允许容器运行时可能使用的资源量的上限。
Kubernetes中Pod的Requests和Limits资源配置有如下特点。
- 如果Pod配置的Requests值等于Limits值,那么该Pod可以获得的资源是完全可靠的;
- 如果Pod的Requests值小于Limits值,那么该Pod获得的资源可分成两部分;
完全可靠的资源,资源量的大小等于Requests值;
不可靠的资源,资源量最大等于Limits与 Requests的差额,这份不可靠的资源能够申请到多少,取决于当时主机上容器可用资源的余量;
通过这种机制,Kubernetes可以实现节点资源的超售(Over Subscription),超售机制能有效提高资源的利用率,同时不会影响容器申请的完全可靠资源的可靠性。
调度策略的影响
- Kubernetes的kubelet通过计算Pod中所有容器的Requests的总和来决定对Pod的调度;
- 不管是CPU还是内存,Kubernetes调度器和kubelet都会确保节点上所有Pod的Requests的总和不会超过在该节点上可分配给容器使用的资源容量上限;
五、服务质量等级(QoS Classes)
在一个超用(Over Committed,容器Limits总和大于系统容量上限)系统中,由于容器负载的波动可能导致操作系统的资源不足,最终可能导致部分容器被杀掉。针对这种情况Kubernetes为了防止杀掉比较重要的容器,提出了服务质量等级,采用一种优先级的策略。
img
Kubernetes将容器划分成3个QoS等级:Guaranteed(完全可靠的)、Burstable(弹性波动、较可靠的)和BestEffort(尽力而为、不太可靠的),这三种优先级依次递减。
使用介绍
Guaranteed
Pod中的所有容器对所有资源类型都定义了Limits和Requests,并且所有容器的Limits值都和Requests值全部相等,那么该Pod的QoS级别就是Guaranteed。注意:在这种情况下,容器可以不定义Requests,因为Requests值在未定义时默认等于Limits。
apiVersion: v1 kind: Pod metadata: name: myapp labels: name: myapp spec: containers: - name: myapp image: nginx resources: requests: memory: "128Mi" cpu: "500m" limits: memory: "128Mi" cpu: "500m" ports: - containerPort: 80
Burstable
Pod中至少有一个容器设置了Requests,注意:在容器未定义Limits时,Limits值默认等于节点资源容量的上限。
apiVersion: v1 kind: Pod metadata: name: myapp labels: name: myapp spec: containers: - name: myapp image: nginx resources: requests: memory: "128Mi" cpu: "500m" ports: - containerPort: 80
BestEffort
Pod中所有容器都未定义资源配置(Requests和Limits都未定义),那么该Pod的QoS级别就是BestEffort。
apiVersion: v1 kind: Pod metadata: name: myapp labels: name: myapp spec: containers: - name: myapp image: nginx ports: - containerPort: 80
特点
CPU是可以压缩资源,所以在CPU不够的时候会压缩限流。内存是不可压缩资源,所以QoS主要用于内存限制。
- BestEffort Pod的优先级最低,在这类Pod中运行的进程会在系统内存紧缺时被第一优先杀掉。当然,从另外一个角度来看,BestEffort Pod由于没有设置资源Limits,所以在资源充足时,它们可以充分使用所有的闲置资源;
- Burstable Pod的优先级居中,这类Pod初始时会分配较少的可靠资源,但可以按需申请更多的资源。当然,如果整个系统内存紧缺,又没有BestEffort容器可以被杀掉以释放资源,那么这类Pod中的进程可能会被杀掉;
- Guaranteed Pod的优先级最高,而且一般情况下这类Pod只要不超过其资源Limits的限制就不会被杀掉。当然,如果整个系统内存紧缺,又没有其他更低优先级的容器可以被杀掉以释放资源,那么这类Pod中的进程也可能会被杀掉;
OOM计分系统
img
oom_killer首先终止QoS等级最低,且超过请求资源最多的容器。这意味着与Burstable或BestEffort QoS类别的容器相比,具有更好QoS类别Guaranteed的容器被杀死的可能性更低。
但是,并非所有情况都如此。由于oom_killer还考虑了内存使用量与请求的关系,因此,由于内存使用量过多,具有更好QoS类的容器可能具有更高的oom_score,因此可能首先被杀死。
注意
- 对于操作系统来说,内存是支持Swap的,但是对于当前的QoS策略都是假定主机不启用内存Swap。如果主机启用了Swap,那么上面的QoS策略可能会失效,是因为Kubernetes和Docker尚不支持内存Swap空间的隔离机制,所以这一功能暂时还未实现;
- 无法支持自定义QoS策略,当前的QoS策略都是基于Pod的资源配置来定义的,而资源配置本身又承担着对Pod资源管理和限制的功能。两种不同维度的功能使用同一个参数来配置,可能会导致某些复杂需求无法满足,比如当前Kubernetes无法支持弹性的、高优先级的Pod。自定义QoS优先级能提供更大的灵活性,完美地实现各类需求,但同时会引入更高的复杂性,而且过于灵活的设置会给予用户过高的权限,对系统管理也提出了更大的挑战。
六、资源配额管理(Resource Quotas)
资源配额管理主要是解决不同命名空间总体资源限制,如果一个Kubernetes集群被多个用户或者多个团队共享,就需要考虑资源公平使用的问题,因为某个用户可能会使用超过基于公平原则分配给其的资源量。通过ResourceQuota对象,我们可以定义资源配额,这个资源配额可以为每个命名空间都提供一个总体的资源使用的限制:它可以限制命名空间中某种类型的对象的总数目上限,也可以设置命名空间中Pod可以使用的计算资源的总上限。
使用介绍
- 创建一个命名空间,这里我们使用之前创建的limit-example的命名空间;
- 创建ResourceQuota资源,名为compute-resources.yaml;
apiVersion: v1 kind: ResourceQuota metadata: name: compute-resources spec: hard: pods: "4" #资源配额设置Requests和Limits requests.cpu: "1" requests.memory: 1Gi limits.cpu: "2" limits.memory: 2Gi #创建资源 kubectl apply -f compute-resources.yaml --namespace=limit-example
- 查看ResourceQuota的详细信息;
kubectl describe quota compute-resources --namespace=limit-example
image.png
参数介绍
img
Master中开启资源配额选型
资源配额可以通过在kube-apiserver的--admission-control参数值中添加ResourceQuota参数进行开启。如果在某个命名空间的定义中存在ResourceQuota,那么对于该命名空间而言,资源配额就是开启的。一个命名空间可以有多个ResourceQuota配置项。
计算资源配额(Compute Resource Quota)
资源配额可以限制一个命名空间中所有Pod的计算资源的总和;
img
存储资源配额(Volume Count Quota)
给定的命名空间中限制所使用的存储资源的总量;
img
对象数量配额(Object Count Quota)
指定类型的对象数量可以被限制;
img
配额的作用域(Quota Scopes)
img
每项资源配额都可以单独配置一组作用域,配置了作用域的资源配额只会对符合其作用域的资源使用情况进行计量和限制,作用域范围内超过了资源配额的请求都会报验证错误。其中,BestEffort作用域可以限定资源配额来追踪Pod资源的使用,Terminating、NotTerminating和NotBestEffort这三种作用域可以限定资源配额来追踪以下7种类型:
- cpu
- limits.cpu
- limits.memory
- memory
- pods
- requests.cpu
- requests.memory
设置Requests和Limits
资源配额也可以设置Requests和Limits。如果在资源配额中指定了requests.cpu或requests.memory,那么它会强制要求每个容器都配置自己的CPU Requests或CPU Limits(可使用LimitRange提供的默认值)。同理,如果在资源配额中指定了limits.cpu或limits.memory,那么它也会强制要求每个容器都配置自己的内存Requests或内存Limits(可使用LimitRange提供的默认值)。
注意
- 如果集群中总的可用资源小于各命名空间中资源配额的总和,那么可能会导致资源竞争。资源竞争时,Kubernetes系统会遵循先到先得的原则;
- 不管是资源竞争还是配额的修改,都不会影响已经创建的资源使用对象;
七、总结
Kubernetes中资源管理的基础是容器和Pod的资源配置(Requests和Limits)。容器的资源配置指定了容器请求的资源和容器能使用的资源上限,Pod的资源配置则是Pod中所有容器的资源配置总和上限。
通过资源配额机制,我们可以对命名空间中所有Pod使用资源的总量进行限制,也可以对这个命名空间中指定类型的对象的数量进行限制,使用作用域可以让资源配额只对符合特定范围的对象加以限制。对于资源配额机制我觉得可以理解为对特定场景的命名空间进行特殊管理。
通过使用资源配置范围(LimitRange)可以有效地限制Pod和容器的资源配置的最大、最小范围,也可以限制Pod和容器的Limits与Requests的最大比例上限,此外LimitRange还可以为Pod中的容器提供默认的资源配置。对于资源配额范围我觉得可以理解一份保障清单,可以防止忘配置,同时可以防止乱配置。
Kubernetes基于Pod的资源配置实现了资源服务质量(QoS)。不同QoS级别的Pod在系统中拥有不同的优先级:高优先级的Pod有更高的可靠性,可以用于运行可靠性要求较高的服务;低优先级的Pod可以实现集群资源的超售,可以有效地提高集群资源利用率。