【Kubernetes系列四】pod探测及资源限制
1.pod存活性探测
pod spec为容器列表中的相应容器定义其专用的探针即可启用存活性探测,目前,k8s的容器支持存活性探测的方法包含:ExecAction、TCPSocketActon和HTTPGetAction。
(1) 设置exec探针
exec类型的探针通过在目标容器中执行由用户自定义的命令来判定容器的健康状态,若命令状态返回值为0则表示成功通过探测。spec.containers.livenessProbe.exec字段用于定义此类检测,它只有一个属性“command”,用于定义要执行的命令。
示列:
~]# cat liveness-exec.yaml
apiVersion: v1
kind: Pod
metadata:
name: lifecycle-exec
spec:
containers:
- name: lifecycle-echo-demo
image: busybox
args: ["/bin/sh","-c","touch /tmp/healthy; sleep 60; rm -rf /tmp/healthy; sleep 600"]
livenessProbe:
exec:
command: ["test", "-e", "/tmp/healthy"] #文件存在则返回状态码0,表示成功通过测试
~]# kubectl apply -f liveness-exec.yaml
~]# kubectl describe pods/liveness-exec #查看观察,超过60s,存活性探测失败,进行重启pod
exec指定的命令运行于容器中,会消耗容器的可用资源配额,另外,考虑到探测操作的效率本身等因素,探测操作的命令应该尽可能简单和轻量。
(2) 设置HTTP探针
基于HTTP的探测向目标容器发起一个HTTP请求,根据其响应码进行结果判定,响应码形如2或3时表示检测通过。spec.containers.livenessProbe.httpGet字段用于定义此类检测,它的可用配置字段包括如下:
host:请求的主机地址,默认为podIP;也可以在httpHeaders中使用host来定义
httpHeaders:定义的请求报文首部
path:请求的HTTP资源路径,即URL path
scheme:建立连接使用的协议,仅可为HTTP或HTTPS,默认为HTTP
示列:通过lifecycle中的postStart hook创建一个用于httpGet测试的页面文件healthz
~]# cat liveness-http.yaml
apiVersion: v1
kind: Pod
metadata:
labels:
test: liveness
name: lifecycle-http
spec:
containers:
- name: lifecycle-http-demo
image: nginx:1.12-alpine
ports:
- name: http
containersPort: 80
lifecycle:
postStart:
exec:
command: ["/bin/sh","-c","echo 'Healthy' > /usr/share/nginx/html/healthz"]
livenessProbe:
httpGet:
path: /healthz
port: http
scheme: HTTP
上面httpGet测试,请求资源路径为"/healthz",地址默认为Pod IP。
~]# kubectl apply -f liveness-http.yaml
~]# kubectl describe pods liveness-http
接下来手动删除常见的测试路径的文件
~]# kubectl exec liveness-http rm /usr/share/nginx/html/healthz
~]# kubectl describe pods liveness-http #再次查看,探测失败后,容器被杀掉重建
这种检测方式仅对分层架构中的当前一层有效,例如,它能检测应用程序工作正常与否的状态,但重启操作却无法解决其后端服务(如数据库或缓存服务)导致的故障。此时,容器可能会被一次次的重启,直到后端服务恢复正常为止。其他两种方式也存在类似的问题。
(3) 设置TCP探针
基于TCP的存活性探测用于向容器的特定端口发起TCP请求并尝试建立连接进行结果判定,连接建立成功即为通过检测。它比基于HTTP的探测要更高效更节省资源,但精确度略低,毕竟建立连接成功未必意味着页面资源可用。spec.containers.livenessProbe.Socket字段用于定义此类检测,主要包含以下两个可用的属性:
host:请求连接的目标IP地址,默认为podIP
port:请求连接的目标端口,必选字段。
示列:
~]# cat liveness-tcp.yaml
apiVersion: v1
kind: Pod
metadata:
labels:
test: liveness
name: lifecycle-tcp
spec:
containers:
- name: lifecycle-tcp-demo
image: nginx:1.12-alpine
ports:
- name: http
containersPort: 80
livenessProbe:
tcpSocket:
port: http
(4) 存活性探测行为属性
使用kubectl describe命令可查看配置了存活性探测的pod对象的相关信息,相关内容类似如下:
Liveness: exec [test -e /tmp/healthy] delay=0s timeout=1s period=10s #success=1 #failure=3
它给出了探测方式及额外的配置属性delay、timeout、period、success和failure及其各自的相关属性值。用户没有明确定义这些属性字段时,它们会使用各自的默认值。这些属性信息可通过spec.containers.livenessProbe的如下属性字段来给出:
initialDelaySeconds:存活性探测延迟时长,即容器启动多久之后再开始第一次探测操作,显示为delay属性;默认为0s,即容器启动后立刻便开始进行探测。
timeoutSeconds:存活性探测的超时时长,显示为timeout属性,默认为1s,最小值也是1s。
periodSeconds:存活性探测的频度,显示为period属性,默认为10s,最小值为1s;过高的频率会对pod对象带来较大的额外开销,而过低的频率又会使得对错误的反应不及时。
successThreshold:处于失败状态时,探测操作至少连续多少次的成功才被认为是通过检测,显示为success属性,默认值为1,最小值也为1。
failureThreshold:处于成功状态时,探测操作至少连续多少次的失败才被视为检测不通过,显示为failure属性,默认值为3,最小值为1。
2.pod就绪性检测
pod对象启动后,容器应用通常需要一段时间才能完成其初始化过程,避免pod对象启动后立即让其处理客户端请求,而等待容器初始化工作执行完成并转为就绪状态,尤其是存在其他提供相同服务的pod对象的场景更是如此。
与存活性探测机制相同,就绪性探测也支持Exec、HTTP GET和TCPSocket三种探测方式,且各自的定义机制也都相同。但与存活性探测触发的操作不同的是,探测失败时,就绪性探测不会杀死或重启容器以保证其健康性,而是通知其尚未就绪,并触发依赖于其就绪状态的操作(例如,从service对象中移除此pod对象)以确保不会有客户端请求接入此pod对象。未定义就绪性探测的pod对象在pod进入running状态后将立即就绪,在容器需要时间进行初始化的场景中,在应用真正就绪之前必然无法正常相应客户端请求,因此,生成时间中,必须为关键性pod资源中的容器定义就绪性探测机制。
将容器定义中的livenessProbe字段替换为readinessProbe即可定义就绪性探测的配置。
3.资源需求及资源限制
在k8s上,可由容器或pod请求或消费的计算资源时指cpu和内存,这也是目前仅有的受支持的两种类型。相比较来说,cpu属于可压缩资源,即资源额度可按需收缩,而内存则是不可压缩型资源,对其执行收缩操作可能会导致某种程度的问题。
目前来说,资源隔离尚且属于容器级别,cpu和内存资源的配置需要在pod中的容器上运行,每种资源均可由request属性定义其请求的确保可用值,即容器运行可能用不到这些额度的资源,但用到的时候必须要确保有如此多的资源可用,而limits属性则用于吸纳子资源可用的最大值,即硬限制。通常把资源配置称作pod资源的请求和限制,只不过它是pod内所有容器上某种类型资源的请求和限制的总和。
在k8s系统上,1个单位的cpu相当于虚拟机上的1颗cpu(vcpu)或物理机上的一个超线程或逻辑cpu,它支持分数计量方式,一个核心相当于1000个微核心,因此500m相当于是0.5个核心,内存的计量方式与日常使用方式相同,默认单位是字节,也可以使用E、P、T、G、M和K作为单位后缀。
(1) 资源需求
自主式pod要求为stress容器确保128M的内存及五分之一个cpu核心资源可用,它运行stress-ng镜像启动一个进程进行内存性能压力测试,满载测试时它也会尽可能多地占用cpu资源,另外再启动一个专用的cpu压力测试进程。stress-ng是一个多功能系统压力测试工具,master/worker模型,master为主进程,负责生成和控制子进程,worker是负责执行各类特定测试的子进程。
示例:
~]# cat pod-resources-test.yaml
apiVersion: v1
kind: Pod
metadata:
name: stress-pod
spec:
containers:
- name: stress
image: ikubernetes/stress-ng
command: ["/usr/bin/stress-ng", "-m 1", "-c 1", "-metrics-brief"]
resources:
requests:
memory: "128Mi"
cpu: "200m"
~] kubectl create -f pod-resources-test.yaml
集群中的每个节点都拥有定量的cpu和内存资源,调度pod时,仅那些被请求资源的余量可容纳当前调度的pod的请求量的节点才可作为目标节点。也就是说,k8s的调度器会根据容器的requests属性中定义的资源需求量来判定仅哪些节点可接受运行相关的pod资源,而对于一个节点的资源来说,每运行一个pod对象,其requestes中定义的请求量都要被预留,直到被所有pod对象瓜分完毕为止。
对于pod对象内的cpu资源,可以被压缩,内存为非压缩资源,所以pod在内存资源紧张时会因OOM被杀死
(2) 资源限制
容器的资源需求仅能达到为其保证可用的最少资源量的目的,它并不会限制容器的可用资源上限,因此对因应用程序自身存在bug等多种原因而导致的系统资源被长期占用的情况则无计可施,这就需要通过limits属性定义资源的最大可用量。
资源分配时,可压缩型资源cpu的控制阈可自由调节,容器进程无法获得超出其cpu配额的可用时间。不过,如果进程申请分配超出其limits属性定义的硬限制的内存资源时,它将被OOM killer杀死。不过,随后可能会被其控制进程所重启。例如,容器进程的pod对象会被杀死并重启(重启策略为always或onfailure时),或者是容器进程的子进程被其父进程所重启。
示例:
~]# cat memleak-pod.yaml
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"
~] kubectl apply -f pod-resources-test.yaml
与requests不同的是,limits并不会影响pod的调度结果,也就是说,一个节点上的说有pod对象的limits数量之和可以大于节点所拥有的资源量,即支持资源的过载使用。不过,这么一来一旦资源耗尽,尤其是内存资源耗尽,则必然会有容器因OOMKilled而终止。另外,k8s仅会确保pod能够获得他们请求的cpu时间额度,他们能否获得额外的cpu时间,则取决于其他正在运行的作业对cpu资源的占用情况。例如,对于总数为1000m的cpu来说,容器a请求使用200m,容器b请求使用500m,在不超出它们各自的最大限额的前提下,余下的300m在双方都需要时会以2:5的方式进行配置。
(3) 容器的可见资源
于容器中运行top等命令观察资源可用量信息时,即便定义了requests和limits属性,虽然其可用资源受限于此两个属性的定义,但容器中可见的资源量依然是节点级别的可用总量。
(4) pod的服务质量类别
k8s允许节点资源对limits的过载使用,这意味着节点无法同时满足其上的所有pod对象以资源满载的方式运行。于是,在内存资源紧缺时,应该以何种次序先后终止哪些pod对象?k8s无法自行对此做出决策,它需要借助于pod对象的优先级完成判定。根据pod对象的requests和limits属性,k8s将pod对象归类到BestEffort、Burstable和Guaranteed三个服务质量类别下,具体如下:
Guaranteed:每个容器都为cpu资源设置了具有相同值的requests和limits属性,以及每个容器都为内存资源设置了具有相同值的requests和limits属性的pod资源会自动归属于此类别,这类pod资源具有最高优先级.
Burstable:至少有一个容器设置了cpu或内存资源的requests属性,但不满足Guaranteed类别要求的pod资源将自动归属此类别,它们具有中等优先级。
BestEffort:未为任何一个容器设置requests和limits属性的pod资源将自动归属于此类别,它们的优先级为最低级别。
内存资源紧缺时,BestEfford类别的容器将首当其冲地终止,因为系统不为其提供任何级别的资源保证,但换来的好处是,它们能够在可用时做到尽可能多地占用资源。若已然不存在BestEfford类别的容器,则接下来是有着中等优先级的Burstable类别的pod被终止。Guaranteed类别的容器拥有最高优先级,它们不会被杀死,除非其内存资源需求超限,或者OOM时没有其他更低优先级的pod资源存在。
每个运行状态的容器都有其OOM得分,得分越高越会被优先杀死。OOM得分主要根据两个维度进行计算:由QoS类别继承而来的默认分值和容器的可用内存资源比例。
同等类别的pod资源的默认分值相同。同等级别优先级的pod资源在OOM时,与自身requests属性相比,其内存占用比例最大的pod对象将被首先杀死。需要特别说明的是,OOM是内存耗尽时的处理机制,它们与可压缩型资源cpu无关,因此cpu资源的需求无法得到保证时,pod仅仅是暂时获取不到相应的资源而已。