4.8.2 资源限制
容器的资源需求仅能达到为其保证可用的最少资源量的目的,它并不会限制容器的可用资源上限,因此对因应用本身存在 Bug 等多种原因而导致的系统资源被长时间占用的情况则无计可施,这就需要通过 limits 属性为容器定义资源的最大可用量。资源分配时,可压缩型资源 CPU 的控制阀门可自由调节,容器进程无法获得超过其 CPU 配额的可用时间。不过,如果进程申请分配超出其 limits 属性定义的硬限制的内存资源时,它将被 OOM Killer 杀死,不过,随后可能会被其控制进程所重启,例如,容器进程的 Pod 对象会被杀死并重启(重启策略为 Always 或 OnFailure 时),或者时容器进程的子进程被其父进程所重启。
下面的配置清单文件(memleak-pod.yaml)中定义了如何使用 saadali/simmemleak 镜像运行一个 Pod 对象,它模拟内存泄露操作不断地申请使用内存资源,直到超出 limits 属性中 memory 字段设定的值而导致 “OMMKilled” 为止:
apiVersion: v1
kind: Pod
metadata:
name: memleak-pod
labels:
app: memleak
spec:
containers:
- name: simmemleak
image: saadali/simmemleak
resources:
requests:
memory: "64Mi"
cpu: "1"
limits:
memory: "64Mi"
cpu: "1"
下面测试其运行效果,首先将配置清单中定义的资源使用下面的命令创建于集群中:
**[terminal]
**[delimiter $ ]**[command kubectl apply -f memleak-pod.yaml]
pod/memleak-pod created
pod 资源的默认重启策略为 Always,于是在 memleak 因内存资源达到硬限制而被终止后会立即重启,因为用户很难观察到其因 OOM 而被杀死的相关信息。不过,多次重复的因为内存资源消耗尽而重启会出发 Kubernetes 系统的重启延迟机制,即每次重启的时间间隔会不断地拉长。于是,用户看到的 Pod 资源的相关状态通常为 “CrashLoopBackOff”:
**[terminal]
**[delimiter $ ]**[command kubectl apply -f memleak-pod.yaml]
NAME READY STATUS RESTARTS AGE
memleak-pod 0/1 CrashLoopBackOff 5 4m11s
Pod 资源首次的重启将在 crash 后立即完成,若随后再次 crash,那么其重启操作会延迟10秒进行,随后的延迟时长会逐渐增加,依次为20秒,40秒,80秒,160秒和300秒,随后的延迟将固定在5分钟的时长之上而不再增加,直到其不再 crash 或者 delete 为止。describe 命令可以显示其状态的详细信息,其部分内容如下所示:
**[terminal]
**[delimiter $ ]**[command kubectl describe pods memleak-pod]
Name: memleak-pod
Namespace: default
Priority: 0
PriorityClassName: <none>
Node: kube-node-1.localdomain/192.168.0.134
Start Time: Sat, 07 Dec 2019 10:56:17 +0800
Labels: app=memleak
Annotations: kubectl.kubernetes.io/last-applied-configuration:
{"apiVersion":"v1","kind":"Pod","metadata":{"annotations":{},"labels":{"app":"memleak"},"name":"memleak-pod","namespace":"default"},"spec"...
Status: Running
IP: 10.244.2.9
Containers:
simmemleak:
Container ID: docker://465caf91fd04e71f16e413401f5b3ebb43eae6c2942e8a4002adb921501ce1a4
Image: saadali/simmemleak
Image ID: docker-pullable://saadali/simmemleak@sha256:5cf58299a7698b0c9779acfed15c8e488314fcb80944550eab5992cdf3193054
Port: <none>
Host Port: <none>
State: Waiting
Reason: CrashLoopBackOff
Last State: Terminated
Reason: OOMKilled
Exit Code: 137
Started: Sat, 07 Dec 2019 11:02:01 +0800
Finished: Sat, 07 Dec 2019 11:02:02 +0800
Ready: False
Restart Count: 6
Limits:
cpu: 1
memory: 64Mi
Requests:
cpu: 1
memory: 64Mi
Environment: <none>
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from default-token-fwfzr (ro)
Conditions:
Type Status
Initialized True
Ready False
ContainersReady False
PodScheduled True
Volumes:
default-token-fwfzr:
Type: Secret (a volume populated by a Secret)
SecretName: default-token-fwfzr
Optional: false
QoS Class: Guaranteed
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute for 300s
node.kubernetes.io/unreachable:NoExecute for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 8m20s default-scheduler Successfully assigned default/memleak-pod to kube-node-1.localdomain
Normal Pulling 7m32s (x4 over 8m19s) kubelet, kube-node-1.localdomain Pulling image "saadali/simmemleak"
Normal Pulled 7m32s (x4 over 8m17s) kubelet, kube-node-1.localdomain Successfully pulled image "saadali/simmemleak"
Normal Created 7m31s (x4 over 8m17s) kubelet, kube-node-1.localdomain Created container simmemleak
Normal Started 7m31s (x4 over 8m17s) kubelet, kube-node-1.localdomain Started container simmemleak
Warning BackOff 3m12s (x28 over 8m14s) kubelet, kube-node-1.localdomain Back-off restarting failed container
如上述命令结果所显示的,OOMKilled 表示容器因内存耗尽而被终止,因此,为 limits 属性中的 memory 设置一个合理值至关重要。与 requests 不同的是,limits 并不影响 Pod 的调度结果,也就是说,一个节点上的所有 Pod 对象的 limits 数量之和可以大于节点所拥有的资源量,即支持资源的过载使用(overcommitted)。不过,这么一来一旦资源耗尽,尤其是内存资源耗尽,则必然会有容器因 OOMKilled 而终止。
另外需要说明的是,Kubernetes 仅会确保 Pod 能够获得它们请求(requests)的 CPU 时间额度,它们能否获得额外(throttled)的 CPU 时间,则取决于其他正在运行的作业对 CPU 资源的占用情况。例如,对于总数为 1000m 的 CPU 资源来说,容器A请求使用 200m,容器B请求使用 500m,在不超出它们各自的最大限额的前提下,余下的300m在双方都需要时会以 2:5(200m:500m) 的方式进行分配。