《Kubernetes进阶实战》之管理Pod资源对象
Pod
1. pod创建过程
- 用户通过kubectl或其他API客户端提交给Pod Spec给API Server。
- APIserver尝试着将Pod对象相关信息存入etcd中,待写入操作执行完成,API Server即会返回确认信息至客户端。
- API Server开始反应etcd的状态变化。
- 所有的Kubernetes组件均使用“watch”机制来跟踪检查API Server上的相关的变动
- kube-scheduler(调度器) 通过其“watcher”觉察到API Server创建了新的Pod对象但尚未绑定至任何工作节点。
- kube-scheduler为Pod对象挑选一个工作节点并将结果信息更新至API Server。
- 调度结果信息由API Server更新至etcd存储系统,而且API Server也开始反映此Pod对象的调度结果。
- Pod被调度到的目标工作节点上的kubelet尝试在当前节点上调用Docker启动容器,并将容器的结果状态回送至API Server。
- APIServer将Pod状态信息存人etcd系统中。
- 在etcd确认写人操作成功完成后,API Server将确认信息发送至相关的kubelet,事件将通过它被接受。
2. Pod生命周期的重要行为
2.1. 初始化容器
- 初始化容器(Init container)即应用程序的主容器启动之前要运行的容器,常用于为主容器执行一些预置操作,它们具有两种典型特征。
- 初始化容器必须运行完成直至结束,若某初始化容器运行失败,那么Kubernetes需要重启它直到成功完成。
- 每个初始化容器都必须按定义的顺序串行运行。
注意: 如果Pod的 spec.restartPolicy字段值为“Nerver”,那么运行失败的初始化容器不会被重启。
有不少场景都需要在应用容器启动之前进行部分初始化操作,例如,等待其他关联组件服务可用、基于环境变量或配置模板为应用程序生成配置文件、从配置中心获取配置等。初始化容器的典型应用需求具体包含如下几种。
- 用于运行特定的工具程序,出于安全等方面的原因,这些程序不适于包含在主容器镜像中。
- 提供主容器镜像中不具备的工具程序或自定义代码。
- 为容器镜像的构建和部署人员提供了分离、独立工作的途径,使得他们不必协同起来制作单个镜像文件。
- 初始化容器和主容器处于不同的文件系统视图中,因此可以分别安全地使用敏感数据, 例如Secrets资源。
- 初始化容器要先于应用容器串行启动并运行完成,因此可用于延后应用容器的启动直至其依赖的条件得到满足。
Pod资源的“spec.init Containers”字段以列表的形式定义可用的初始容器,其嵌套可用字段类似于“spec.containers”。自行创建并观察初始化容器的相关状态:下面的资源清单仅是一个初始化容器的使用示例:
1 api Version:v 1 2 kind:Pod 3 metadata: 4 name:myapp-pod 5 labels: 6 app:myapp 7 spec: 8 containers: 9 - name:myapp-container 10 image:ikubernetes/myapp:v1 11 initContainers: 12 - name:init-something 13 image:busybox 14 command:['sh','-c1,'sleep 10']
2.2. 生命周期钩子函数
生命周期钩子函数(lifecycle hook)是编程语言(如Angular) 中常用的生命周期管理的组件,它实现了程序运行周期中的关键时刻的可见性,并赋予用户为此采取某种行动的能力。类似地,容器生命周期钩子使它能够感知其自身生命周期管理中的事件,并在相应的时刻到来时运行由用户指定的处理程序代码。Kubernetes为容器提供了两种生命周期钩子。
- postStart:于容器创建完成之后立即运行的钩子处理器(handler),不过Kubernetes无法确保它一定会于容器中的ENTRYPOINT之前运行。
- preStop:于容器终止操作之前立即运行的钩子处理器,它以同步的方式调用,因此在其完成之前会阻塞删除容器的操作的调用。
钩子处理器的实现方式有“Exec”和“HTTP”两种, 前一种在钩子事件触发时直接在当前容器中运行由用户定义的命令,后一种则是在当前容器中向某URL发起HTTP请求。
post Start和preStop处理器定义在容器的spec.lifecycle嵌套字段中,其使用方法如下面的资源清单所示,读者可自行创建相关的Pod资源对象,并验证其执行结果:
1 api Version:v 1 2 kind:Pod 3 metadata: 4 name:1lfecycle-demo 5 spec: 6 containers: 7 - name:lifecycle-demo-container 8 image:ikubernetes/myapp:v1 9 1ifecycle: 10 postStart: 11 exec: 12 command:[”/bin/sh”,”-c”,"echo 'lifecycle hooks handler' > /usr/share/nginx/html/test.html"]
2.3. 容器探测
容器探测(container probe)是Pod对象生命周期中的一项重要的日常任务,它是kubelet对容器周期性执行的健康状态诊断,诊断操作由容器的处理器(handler) 进行定义。Kubernetes支持三种处理器用于Pod探测。
- ExecAction:在容器中执行一个命令,并根据其返回的状态码进行诊断的操作称为Exec探测, 状态码为0表示成功,否则即为不健康状态。
- TCPSocketAction:通过与容器的某TCP端口尝试建立连接进行诊断,端口能够成功打开即为正常,否则为不健康状态。
- HTTPGetAction:通过向容器IP地址的某指定端口的指定path发起HTTP GET请求进行诊断,响应码为2xx或3xx时即为成功,否则为失败。
任何一种探测方式都可能存在三种结果:“Success”(成功) 、“Failure”(失败) 或“Unknown”(未知) ,只有第一种结果表示成功通过检测。
- kubelet可在活动容器上执行两种类型的检测:存活性检测(livenessProbe)和就绪性检测(readinessProbe)。存活性检测:用于判定容器是否处于“运行”(Running)状态;一旦此类检测未通过,kubelet将杀死容器并根据其restartPolicy决定是否将其重启;未定义存活性检测的容器的默认状态为“ Success"。
- 就绪性检测:用于判断容器是否准备就绪并可对外提供服务;未通过检测的容器意味着其尚未准备就绪,端点控制器(如Service对象)会将其IP从所有匹配到此Pod对象的Service对象的端点列表中移除;检测通过之后,会再次将其IP添加至端点列表中。
2.4. 容器重启策略
容器程序发生崩溃或容器申请超出限制的资源等原因都可能会导致Pod对象的终止,此时是否应该重建该Pod对象则取决于其重启策略(restartPolicy)属性的定义。
1) Always:但凡Pod对象终止就将其重启, 此为默认设定。
2) OnFailure:仅在Pod对象出现错误时方才将其重启。
3) Never:从不重启。
需要注意的是,restartPolicy适用于Pod对象中的所有容器,而且它仅用于控制在同一节点上重新启动Pod对象的相关容器。首次需要重启的容器,将在其需要时立即进行重启,随后再次需要重启的操作将由kubelet延迟一段时间后进行,且反复的重启操作的延迟时长依次为10秒、20秒、40秒、80秒、160秒和300秒,300秒是最大延迟时长。事实上,一旦绑定到一个节点,Pod对象将永远不会被重新绑定到另一个节点,它要么被重启,要么终止,直到节点发生故障或被删除。
2.5. Pod的终止过程
1)用户发送删除Pod对象的命令
2)API服务器中的Pod对象会随着时间的推移而更新,在宽限期内(默认为30秒),Pod被视为“dead”。
3)将Pod标记为“ Terminating”状态。
4)(与第3步同时运行) kubelet在监控到Pod对象转为“ Terminating”状态的同时启动Pod关闭过程。
5)(与第3步同时运行)端点控制器监控到Pod对象的关闭行为时将其从所有匹配到此端点的Service资源的端点列表中移除。
6) 如果当前Pod对象定义了pre Stop钩子处理器, 则在其标记为“terminating”后即会以同步的方式启动执行;如若宽限期结束后,preStop仍未执行结束, 则第2步会被重新执行 并额外获取一个时长为2秒的小宽限期。
7) Pod对象中的容器进程收到TERM信号。
8) 宽限期结束后,若存在任何一个仍在运行的进程,那么Pod对象即会收到SIGKILL信号。
9) Kubelet请求API Server将此Pod资源的宽限期设置为0从而完成删除操作,它变得对用户不再可见。
默认情况下,所有删除操作的宽限期都是30秒,不过,kubectl delete命令可以使用”--grace-period=<seconds>”选项自定义其时长,若使用0值则表示直接强制删除指定的资源,不过,此时需要同时为命令使用“--force”选项。
3. Pod存活性探针
Pod spec为容器列表中的相应容器定义其专用的探针(存活性探测机制)即可启用存活性探测。目前,Kubernetes的容器支持存活性探测的方法包含以下三种:ExecAction、TCPSocketAction和HTTPGetAction。
3.1. 设置exec探针
exec类型的探针通过在目标容器中执行由用户自定义的命令来判定容器的健康状态,若命令状态返回值为0则表示“成功”通过检测,其值均为“失败”状态,“spec.containers.livenessProbe.exec”字段用于定义此类检测,它只有一个可用属性“command”,用于指定要执行的命令。下面是定义在资源清单文件liveness-exec.yaml中的示例:
1 api Version:vl 2 kind:Pod 3 metadata: 4 labels: 5 teat:livene3s-exec 6 name:liveness-exec 7 spec: 8 containers: 9 - name:liveness-exec-domo 10 image:busybox 11 args:[”/bin/sh”,"-c",touch /tmp/healthy:sleep60:rm-rf/tmp/healthy;sleep 600”] 12 1ivenessProbe: 13 exec: 14 command:["test","-e","/tmp/healthy"]
上面的资源清单中定义了一个Pod对象,基于busybox镜像启动一个运行“touch /tmp/healthy;sleep 60;rm-rf/tmp/healthy;sleep 600”命令的容器,此命令在容器启动时创建/tmp/healthy文件,并于60秒之后将其删除。存活性探针运行“test-e /tmp/healthy”命令检查/tmp/healthy文件的存在性,若文件存在则返回状态码0,表示成功通过测试。
在60秒之内使用“kubectl describe pods/liveness-exec”查看其详细信息,其存活性探测不会出现错误。而超过60秒之后, 再次运行“kubectl describe pods/liveness-exec”查看其详细信息可以发现,存活性探测出现了故障,并且隔更长一段时间之后再查看甚至还可以看到容器重启的相关信息:
另外, 输出信息的“Containers”一段中还清晰地显示了容器健康状态检测及状态变化的相关信息:容器当前处于“Running”状态, 但前一次是为“Terminated”, 原因是退出码
为137的错误信息,它表示进程是被外部信号所终止的。137事实上是由两部分数字之和生成的:128+signum, 其中signum是导致进程终止的信号的数字标识, 9表示SIGKILL, 这意味着进程是被强行终止的:
需要特别说明的是,exec指定的命令运行于容器中,会消耗容器的可用资源配额,另外,考虑到探测操作的效率本身等因素,探测操作的命令应该尽可能简单和轻量。
3.2. 设置HTTP探针
基于HTTP的探测(HTTPGetAction)向目标容器发起一个HTTP请求,根据其响应码进行结果判定,响应码形如2xx或3xx时表示检测通过。”spec.containers.livenessProbe.httpGet”字段用于定义此类检测,它的可用配置字段包括如下几个。
- host<string>:请求的主机地址,默认为Pod IP; 也可以在http Headers中使用“Host:”来定义。
- port<string>:请求的端口,必选字段。
- httpHeaders<[]Object>:自定义的请求报文首部。
- path<string>:请求的HTTP资源路径,即URL path。
- scheme:建立连接使用的协议,仅可为HTTP或HTTPS,默认为HTTP。
下面是一个定义在资源清单文件liveness-http.yaml中的示例, 它通过lifecycle中的postStart hook创建了一个专用于http Get测试的页面文件healthz:
1 apiVersion:v1 2 kind:Pod 3 metadata i 4 labels: 5 test:live ne8s 6 name:liveness-http 7 spec: 8 containers: 9 - name:liveness-http-demo 10 image:nginx:1.12-alpine 11 ports: 12 - name:http 13 containerPort:80 14 1ifecycle: 15 postStart: 16 exec: 17 command:["/bin/sh","-c","echo Healthy > /usr/share/nginx/html/healthz"] 18 livenessProbe: 19 httpGet: 20 path:/healthz 21 port:http 22 scheme:HTTP
上面清单文件中定义的http Get测试中, 请求的资源路径为“/healthz”,地址默认为Pod IP,端口使用了容器中定义的端口名称HTTP,这也是明确为容器指明要暴露的端口的用途之一。
接下来借助于“kubectl exec”命令删除经由postStart hook创建的测试页面healthz:
1 kubectl exec liveness-http rm /use/share/nginx/html/healthz
而后再次使用“kubectl describe pods liveness-http”査看其详细的状态信息, 事件输出中的信息可以表明探测测试失败,容器被杀掉后进行了重新创建:
一般来说,HTTP类型的探测操作应该针对专用的URL路径进行,例如,前面示例中特别为其准备的“/healthz”。另外,此URL路径对应的Web资源应该以轻量化的方式在内部对应用程序的各关键组件进行全面检测以确保它们可正常向客户端提供完整的服务。
需要注意的是,这种检测方式仅对分层架构中的当前一层有效,例如,它能检测应用程序工作正常与否的状态,但重启操作却无法解决其后端服务(如数据库或缓存服务)导致的故障。此时,容器可能会被一次次的重启,直到后端服务恢复正常为止。其他两种检测方式也存在类似的问题。
3.3. 设置TCP探针
基于TCP的存活性探测(TCPSocketAction) 用于向容器的特定端口发起TCP请求并尝试建立连接进行结果判定,连接建立成功即为通过检测。相比较来说,它比基于HTTP的探测要更高效、更节约资源,但精准度略低,毕竟连接建立成功未必意味着页面资源可用。“spec.containers.liveness Probe.tcp Socket”字段用于定义此类检测, 它主要包含以下两个可
用的属性。
1) host<string>:请求连接的目标IP地址, 默认为Pod IP。
2) port<string>:请求连接的目标端口, 必选字段。
下面是一个定义在资源清单文件liveness-tcp.yaml中的示例, 它向Pod IP的80/tcp端口发起连接请求,并根据连接建立的状态判定测试结果:
1 apiVersion:v1 2 kind:Pod 3 metadata: 4 labels: 5 test:liveness 6 name:liveness-tcp 7 spec: 8 containers: 9 - name:liveness-tcp-demo 10 image:nginx:1.12-alpine 11 ports: 12 - name:http 13 containerPort:80 14 livenessProbe: 15 tcpSocket: 16 port:http
自行测试。
3.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.liveness Probe”的如下属性字段来给出。
- initial Delay Seconds<integer>:存活性探测延迟时长, 即容器启动多久之后再开始第一次探测操作, 显示为delay属性; 默认为0秒, 即容器启动后立刻便开始进行探测。
- timeout Seconds<integer>:存活性探测的超时时长, 显示为timeout属性, 默认为1s,最小值也为1s。
- period Seconds<integer>:存活性探测的频度, 显示为period属性, 默认为10s, 最小值为1s; 过高的频率会对Pod对象带来较大的额外开销, 而过低的频率又会使得对
错误的反应不及时。 - success Threshold<integer>:处于失败状态时,探测操作至少连续多少次的成功才被认为是通过检测,显示为#success属性,默认值为1,最小值也为1。
- failure Threshold:处于成功状态时,探测操作至少连续多少次的失败才被视为是检测不通过,显示为#failure属性,默认值为3,最小值为1。
例如,这里可将4.6.1节中清单文件中定义的探测示例重新定义为如下所示的内容:
1 spec: 2 containers: 3 ... ... 4 livenessProbe: 5 exec: 6 command:['test”,"-e", "/tmp/healthy"] 7 initialDelaySeconds 5s 8 timeoutSeconds 2s 9 periodSeconds 5s
根据修改的清单再次创建Pod对象并进行效果测试, 可以从输出的详细信息中看出Liveness已经更新到自定义的属性, 其内容如下所示。具体过程这里不再给出, 请感兴趣的
读者自行测试。
Liveness:exec[test -e /tmp/healthy] delay=5s timeout=2s period=5s #success=1#failure=3
4. Pod就绪性探测
与存活性探测机制相同,就绪性探测也支持Exec、HTTP GET和TCP Socket三种探测方式,且各自的定义机制也都相同。但与存活性探测触发的操作不同的是,探测失败时,就绪性探测不会杀死或重启容器以保证其健康性,而是通知其尚未就绪,并触发依赖于其就绪状态的操作(例如,从Service对象中移除此Pod对象) 以确保不会有客户端请求接入此Pod对象。不过 即便是在运行过程中,Pod就绪性探测依然有其价值所在,例如Pod A依赖到的Pod B因网络故障等原因而不可用时,Pod A上的服务应该转为未就绪状态,以免无法向客户端提供完整的响应。
1 apiVersion:v1 2 kind:Pod 3 metadata: 4 labels: 5 test:readiness-exec 6 name:readiness-exec 7 spec: 8 containers: 9 - name:readiness-demo 10 image:busybox 11 args:[”/bin/sh”,"-c",”while true;do rm-f /tmp/ready;sleep 30;touch /tmp/ready;sleep 300;done”] 12 readinessProbe: 13 exec: 14 command:[”test", "-e”, ”/tmp/ready”] 15 initialDelaySeconds:5 16 periodSeconds:5