定义 Pod 时可以选择性地为每个 容器设定所需要的资源数量。 最常见的可设定资源是 CPU 和内存(RAM)大小;此外还有其他类型的资源。
当你为 Pod 中的 Container 指定了资源 请求 时,调度器就利用该信息决定将 Pod 调度到哪个节点上。 当你还为 Container 指定了资源 约束 时,kubelet 就可以确保运行的容器不会使用超出所设约束的资源。 kubelet 还会为容器预留所 请求 数量的系统资源,供其使用。
Requests and limits
如果 Pod 运行所在的节点具有足够的可用资源,容器可能(且可以)使用超出对应资源 request
属性所设置的资源量。不过,容器不可以使用超出其资源 limit
属性所设置的资源量。
资源类型
CPU 和内存都是资源类型。每种资源类型具有其基本单位。
Pod 中的每个容器都可以指定以下的一个或者多个值:
spec.containers[].resources.limits.cpu
spec.containers[].resources.limits.memory
spec.containers[].resources.limits.hugepages-<size>
spec.containers[].resources.requests.cpu
spec.containers[].resources.requests.memory
spec.containers[].resources.requests.hugepages-<size>
尽管请求和限制值只能在单个容器上指定,我们仍可方便地计算出 Pod 的资源请求和约束。 Pod 对特定资源类型的请求/约束值是 Pod 中各容器对该类型资源的请求/约束值的总和。
spec.containers[].resources.requests.cpu
为 0.5 的 Container 肯定能够获得请求 1 CPU 的容器的一半 CPU 资源。表达式 0.1
等价于表达式 100m,具有小数点(如 0.1
)的请求由 API 转换为 100m
;最大精度是 1m
。
内存的约束和请求以字节为单位。你可以使用以下后缀之一以一般整数或定点数字形式来表示内存: E、P、T、G、M、k。你也可以使用对应的 2 的幂数:Ei、Pi、Ti、Gi、Mi、Ki。 例如,以下表达式所代表的是大致相同的值
1 |
|
带资源约束的 Pod 如何运行
当 kubelet 启动 Pod 中的 Container 时,它会将 CPU 和内存约束信息传递给容器运行时。
当使用 Docker 时:
-
spec.containers[].resources.requests.cpu
先被转换为可能是小数的基础值,再乘以 1024。 这个数值和 2 的较大者用作docker run
命令中的--cpu-shares
标志的值。 -
spec.containers[].resources.limits.cpu
先被转换为 millicore 值,再乘以 100。 其结果就是每 100 毫秒内容器可以使用的 CPU 时间总量。在此期间(100ms),容器所使用的 CPU 时间不会超过它被分配的时间。说明: 默认的配额(Quota)周期为 100 毫秒。CPU 配额的最小精度为 1 毫秒。
-
spec.containers[].resources.limits.memory
被转换为整数值,作为docker run
命令中的--memory
参数值。
如果 Container 超过其内存限制,则可能会被终止。如果容器可重新启动,则与所有其他类型的 运行时失效一样,kubelet 将重新启动容器。
如果一个 Container 内存用量超过其内存请求值,那么当节点内存不足时,容器所处的 Pod 可能被逐出。
每个 Container 可能被允许也可能不被允许使用超过其 CPU 约束的处理时间。 但是,容器不会由于 CPU 使用率过高而被杀死。
QoS pod 服务质量
根据 CPU 对容器内存资源的需求,我们对 pod 的服务质量进行一个分类,分别是 Guaranteed、Burstable 和 BestEffort
- Guaranteed :pod 里面每个容器都必须有内存和 CPU 的 request 以及 limit 的一个声明,且 request 和 limit 必须是一样的,这就是 Guaranteed;
- Burstable:Burstable 至少有一个容器存在内存和 CPU 的一个 request;
- BestEffort:只要不是 Guaranteed 和 Burstable,那就是 BestEffort。必须是所有资源的 request/limit 都不填,才是一种 BestEffort Pod。
那么这个服务质量是什么样的呢?资源配置好后,当这个节点上 pod 容器运行,比如说节点上 memory 配额资源不足,kubelet会把一些低优先级的,或者说服务质量要求不高的(如:BestEffort、Burstable)pod 驱逐掉。它们是按照先去除 BestEffort,再去除 Burstable 的一个顺序来驱逐 pod 的。
cgroup 管理
接下来看不同 cgroup 分别对应到 node 上哪些目录。
kubelet
cgroup root
k8s 的 cgroup 路径都是相对于它的 cgroup root 而言的。 cgroup root 是个 kubelet 配置项,默认为空,表示使用底层 container runtime 的 cgroup root,一般是 /sys/fs/cgroup/
。
/kubepods
(node 级别配置)
cgroup v1 是按 resource controller 类型来组织目录的, 因此,/kubepods
会按 resource controller 对应到 /sys/fs/cgroup/{resource controller}/kubepods/
,例如:
/sys/fs/cgroup/cpu/kubepods/
/sys/fs/cgroup/memory/kubepods/
前面已经介绍了每台 k8s node 的资源切分, 其中 Allocatable 资源量就是写到 kubepods
对应 cgroup 文件中, 例如 allocatable cpu 写到 /sys/fs/cgroup/kubepods/cpu.share
。 这一工作是在 kubelet containerManager Start() 中完成的。
QoS 级别配置
QoS cgroup 是 /kubepods
的 sub-cgroup,因此路径是 /kubepods/{qos}/
,具体来说,
- Burstable: 默认
/sys/fs/cgroup/{controller}/kubepods/burstable/
; - BestEffort: 默认
/sys/fs/cgroup/{controller}/kubepods/besteffort/
; - Guaranteed:这个比较特殊,直接就是
/sys/fs/cgroup/{controller}/kubepods/
, 没有单独的子目录。这是因为这种类型的 pod 都设置了 limits, 就无需再引入一层 wrapper 来防止这种类型的 pods 的资源使用总量超出限额。
Pod 级别配置
Pod 配置在 QoS cgroup 配置的下一级,
- Guaranteed Pod:默认
/sys/fs/cgroup/{controller}/kubepods/{pod_id}/
; - Burstable Pod:默认
/sys/fs/cgroup/{controller}/kubepods/burstable/{pod_id}/
; - BestEffort Pod:默认
/sys/fs/cgroup/{controller}/kubepods/besteffort/{pod_id}/
。
Container 级别配置
Container 级别配置文件在 pod 的下一级:
- Guaranteed container:默认
/sys/fs/cgroup/{controller}/kubepods/{pod_id}/{container_id}/
; - Burstable container:默认
/sys/fs/cgroup/{controller}/kubepods/burstable/{pod_id}/{container_id}/
; - BestEffort container:默认
/sys/fs/cgroup/{controller}/kubepods/besteffort/{pod_id}/{container_id}/
。
requests/limits
对应到具体 cgroup 配置文件
Spec 里的 CPU requests/limits 一般都是以 500m
这样的格式表示的,其中 m
是千分之一个 CPU, kubelet
会将它们转换成 cgroup 支持的单位,然后写入几个 cpu.
开头的配置文件。其中,
- requests 经过转换之后会写入
cpu.share
, 表示这个 cgroup 最少可以使用的 CPU; - limits 经过转换之后会写入
cpu.cfs_quota_us
, 表示这个 cgroup 最多可以使用的 CPU;
内存的单位在 requests/limits 和在 cgroup 配置文件中都是一样的,所以直接写入 cgroup 内存配置文件。 limits
写入的是 memory.limit_in_bytes
。
SecurityContext
SecurityContext 主要是用于限制容器的一个行为,它能保证系统和其他容器的安全。这一块的能力不是 Kubernetes 或者容器 runtime 本身的能力,而是 Kubernetes 和 runtime 通过用户的配置,最后下传到内核里,再通过内核的机制让 SecurityContext 来生效。所以这里讲的内容,会比较简单或者说比较抽象一点。
SecurityContext 主要分为三个级别:
- 容器级别,仅对容器生效;
- pod 级别,对 pod 里所有容器生效;
- 集群级别,就是 PSP,对集群内所有 pod 生效。
权限和访问控制设置项,现在一共列有七项(这个数量后续可能会变化):
- 是通过用户 ID 和组 ID 来控制文件访问权限;
- SELinux,它是通过策略配置来控制用户或者进程对文件的访问控制;
- 特权容器;
- Capabilities,它也是给特定进程来配置一个 privileged 能力;
- AppArmor,它也是通过一些配置文件来控制可执行文件的一个访问控制权限,比如说一些端口的读写;
- 一个对系统调用的控制;
- 对子进程能否获取比父亲更多的权限的一个限制。
最后其实都是落到内核来控制它的一些权限。
InitContainer
InitContainer 和普通 container 的区别,有以下三点内容:
- InitContainer 首先会比普通 container 先启动,并且直到所有的 InitContainer 执行成功后,普通 container 才会被启动;
- InitContainer 之间是按定义的次序去启动执行的,执行成功一个之后再执行第二个,而普通的 container 是并发启动的;
- InitContainer 执行成功后就结束退出,而普通容器可能会一直在执行。它可能是一个 longtime 的,或者说失败了会重启,这个也是 InitContainer 和普通 container 不同的地方。
InitContainer 的一个用途。它其实主要为普通 container 服务,比如说它可以为普通 container 启动之前做一个初始化,或者为它准备一些配置文件, 配置文件可能是一些变化的东西。再比如做一些前置条件的校验,如网络是否联通。