Pod对象的生命周期

Pod对象自其创建开始一直到终止退出的时间范围称为其生命周期。在这段时间中,Pod会处于多种不同的状态,并执行一些操作,其中创建主容器(main container)为必须的操作,其他可选的操作还包括运行初始化容器(init container)、容器启动后钩子(post start hook)、容器的存活性探测(liveness probe)、就绪性探测(readness probe)、以及容器终止前钩子(pre stop hook)等,这些操作是否执行取决于你是如何定义Pod的

Pod的相位

无论是由用户手动创建,还是通过deployment等控制器创建,Pod对象总是应该处于其生命周期进程中以下几个相位(phase)之一

  • Pending: API Server创建了Pod资源对象并已存入etcd中,但它尚未被调度完成,或者处于从仓库下载镜像的过程中
  • Running:Pod已经被调度至某节点,并且Pod内的所有容器都已经被kubelet创建完成
  • Succeeded:Pod中的所有容器都已经成功终止并且不会被重启
  • failed: 所有容器都已经终止,但至少有一个容器终止失败,即容器反悔了非0值的退出状态或已经被系统终止
  • Unknown: API Server无法正常获取到Pod对象的状态信息,通常是由于其无法与所在工作节点的kubelet通信所导致

Pod的创建过程

Pod是K8S的基础单元,理解它的创建过程对于了解系统运行大有帮助

  • 用户通过kubectl或其他的API客户端提交pod Spec给API Server
  • API Server尝试着将Pod对象的相信信息写入ETCD中,待写入操作执行完成,API Server会返回确认信息给客户端
  • API Server开始反映ETCD中的状态变化
  • 所有的K8S组件均使用“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
  • API Server将Pod状态信息存入etcd系统中
  • 在etcd确认写入操作成功完成后,API Server将确认信息发送至相关的kubelet

Pod生命周期中的重要行为

除了创建应用容器(主容器及其辅助容器)之外,用户还可以为Pod对象定义其生命周期中的多种行为,如初始化容器,存活性探测及就绪性探测等

初始化容器

初始化容器(init container)即应用程序的主容器启动之前要运行的容器,常用于为主容器执行一些预置操作,它们具有两种典型特征

  • 初始化容器必须运行完成直至结束,若某初始化容器运行失败,那么K8S需要重启它直到成功为止,注意:如果Pod的spec.restartPolicy字段值为Never,那么运行失败的初始化容器不会被重启
  • 每个初始化容器都必须按定义的顺序串行运行
    有不少场景都需要在应用容器启动之前进行部分初始化操作,例如,等待其他关联组件服务可用、基于环境变量或配置模板为应用程序生成配置文件、从配置中心获取配置等。初始化容器的典型应用需求具体包含如下几种
  • 用于运行特定工具程序,出于安全等方面的原因,这些程序不适于包含在主容器镜像中
  • 提供主容器镜像中不具备的工具程序或自定义代码
  • 为容器镜像的构建和部署人员提供了分离、独立工作的途径,使得他们不必协同起来制作单个镜像文件
  • 初始化容器和主容器处于不同的文件系统视图中,因此可以分别安全的使用敏感数据,例如Secrets资源
  • 初始化容器要先于应用容器串行启动并运行完成,因此可用于延后延后应用容器的启动,直到其依赖的条件得到满足
    Pod资源的spec.initContainers字段以列表的形式定义可用的初始容器,其嵌套可用字段类似spec.containers。示例
  1. [root@k8s-master01 nginx]# cat initcontainer.yaml
  2. apiVersion: v1
  3. kind: Pod
  4. metadata:
  5. annotations:
  6. created-by: cluster admin
  7. name: test-nodeselector1
  8. labels:
  9. env: pre
  10. spec:
  11. containers:
  12. - name: busybox
  13. image: registry.cn-hangzhou.aliyuncs.com/jiangyida/busybox:0.1
  14. ports:
  15. - name: http
  16. containerPort: 80
  17. protocol: TCP
  18. nodeSelector:
  19. disktype: ssd
  20. initContainers:
  21. - name: init
  22. image: registry.cn-hangzhou.aliyuncs.com/jiangyida/busybox:0.2
  23. command: ["/bin/sh","-c","sleep 30"]
  24. [root@k8s-master01 nginx]#
  25. 定义一个初始化容器

启动,并观察

  1. [root@k8s-master01 nginx]# kubectl apply -f initcontainer.yaml
  2. pod/test-nodeselector1 created
  3. [root@k8s-master01 nginx]#
  4. 再开一个窗口进行观测`kubectl get pods -w`
  5. [root@k8s-master01 ~]# kubectl get pods -w
  6. NAME READY STATUS RESTARTS AGE
  7. client 0/1 Completed 0 6d1h
  8. my-nginx-854bbd7557-xnbn9 1/1 Running 0 5d5h
  9. test 1/1 Running 0 2d
  10. test-nodeselector 1/1 Running 0 123m
  11. test-nodeselector1 0/1 Pending 0 0s
  12. test-nodeselector1 0/1 Pending 0 0s
  13. test-nodeselector1 0/1 Init:0/1 0 0s
  14. test-nodeselector1 0/1 Init:0/1 0 2s
  15. test-nodeselector1 0/1 PodInitializing 0 32s
  16. test-nodeselector1 1/1 Running 0 33s
  17. 可用看到,只有等初始化容器里面定义的command命令执行完成Init之后,应用容器才会启动

生命周期钩子函数

生命周期钩子函数(lifecycle hook)是编程语言(如Angular)中常用的生命周期管理的组件,它实现了程序运行周期中的关键时刻的可见性,并赋予用户为此采取某种行动的能力。类似的,容器生命周期钩子使它能够感知其自身生命周期管理中的事件,并在相应的时刻到来时运行由用户指定的处理程序代码。K8S为容器提供了两种生命周期钩子

  • postStart: 用于容器创建完成之后立即运行的钩子处理器(handler),不过K8S无法确保它一定会于容器中的ENTRYPOINT之前运行
  • preStop:于容器终止之前立即运行的钩子处理器,它以同步的方式调用,因此在其完成之前会阻塞删除容器的操作
    钩子处理器的实现有”Exec”和”HTTP”两种,前一种在钩子事件触发时直接在当前容器中运行由用户定义的命令,后一种则是在当前容器中向某URL发起HTTP请求
    postStart和preStop处理器定义在容器的spec.containers.lifecycle嵌套字段中,其使用方法如下面的资源清单所示
  1. [root@k8s-master01 nginx]# cat initcontainer.yaml
  2. apiVersion: v1
  3. kind: Pod
  4. metadata:
  5. annotations:
  6. created-by: cluster admin
  7. name: test-nodeselector1
  8. labels:
  9. env: pre
  10. spec:
  11. containers:
  12. - name: busybox
  13. image: registry.cn-hangzhou.aliyuncs.com/jiangyida/busybox:0.1
  14. ports:
  15. - name: http
  16. containerPort: 80
  17. protocol: TCP
  18. lifecycle:
  19. postStart:
  20. exec:
  21. command: ["/bin/sh","-c","echo 'lifecycle hooks handler' > /data/html/test.html"]
  22. nodeSelector:
  23. disktype: ssd
  24. containers字段里面嵌套一个lifecycle字段,然后定义启动前的钩子处理器,使用exec执行指令,往/data/html/test.html里面输入一段"lifecycle hooks handler"
  25. [root@k8s-master01 nginx]#

示例 使用声明式配置管理的方式创建pod,这里把之前先创建的删掉

  1. [root@k8s-master01 nginx]# kubectl delete -f initcontainer.yaml
  2. pod "test-nodeselector1" deleted
  3. [root@k8s-master01 nginx]#
  4. 创建Pod
  5. [root@k8s-master01 nginx]# kubectl apply -f initcontainer.yaml
  6. pod/test-nodeselector1 created
  7. [root@k8s-master01 nginx]#
  8. 验证,我们直接访问这个Pod,因为这个busybox镜像是我自己做的,里面有一个http服务,直接指定了网站根路径/data/html/,所以我们可以直接使用curl命令访问 `curl http://PodIP/test.html`
  9. [root@k8s-master01 nginx]# kubectl get pods -l "env=pre" -o wide
  10. NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
  11. test-nodeselector1 1/1 Running 0 3m23s 10.244.50.8 192.168.1.17 <none> <none>
  12. [root@k8s-master01 nginx]#
  13. 通过如下命令可以看到我们的test.html文件生成了
  14. [root@k8s-node01 ~]# curl 10.244.50.8/test.html
  15. lifecycle hooks handler
  16. [root@k8s-node01 ~]#
  17. 如果你使用的镜像没有用到web服务,那么我们可以直接进入到Pod里面进行查看文件是否生成,使用exec执行一个/bin/sh的程序进入到Pod里面
  18. [root@k8s-master01 nginx]# kubectl get pods -l "env=pre" -o wide
  19. NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
  20. test-nodeselector1 1/1 Running 0 3m23s 10.244.50.8 192.168.1.17 <none> <none>
  21. [root@k8s-master01 nginx]# kubectl exec -it test-nodeselector1 -- /bin/sh
  22. / # ls /data/html/
  23. index.html test.html
  24. / # exit
  25. [root@k8s-master01 nginx]#
  26. 可以看到文件也是生成了的

容器探测

容器探测(container probe)是Pod对象生命周期中的一项重要的日常任务,它是kubelet对容器周期性执行的健康状态诊断,诊断操作由容器的处理器(handler)进行定义。K8S支持三种处理器用于Pod探测

  • ExecAction: 在容器中执行一个命令,并根据其返回的状态码进行诊断的操作称为Exec探测,状态码为0表示成功,否则就为不健康状态,通常也被称为Exec探针
  • TCPSocketAction: 通过与容器的某个TCP端口尝试连接进行诊断,端口能够成功打开就为正常,否则就为不健康状态
  • HTTPGetAction: 通过向容器IP地址的某指定端口的指定path发起HTTP GET请求进行诊断,响应码为2xx或3xx时即为成功,否则为失败
    任何一种探测方式都可能存在三种结果:
  • “success(成功)”
  • “Failure(失败)”
  • “Unknown(未知)”
  • 只有第一种结果表示成功检测
    kubectl可在活动容器上执行两种类型的探测:
  • 存活性探测:用于判定容器是否处于”运行(Running)”状态,一旦此类检测未通过,kubelet将杀死容器并根据其”重启策略(restartPolicy)”决定是否将其重启,未定义存活性探测的容器默认状态为”Success”
  • 就绪性探测: 用于判断容器是否准备就绪并可对外提供服务,未通过检测的容器意味着其尚未准备救赎,断点控制器(如Service对象)会将其IP从所有匹配到此Pod对象的Service对象的端点列表中移除,检测通过之后,会再次将其IP添加至端点列表中,在应用当中,我们应该定义此项,因为正常来说,容器就绪了不代表容器里面的应用就绪了,这个时候如果这个Pod被Service或Ingress包含进去了,那么假如里面的应用还没有启动成功,外部请求流量调度上来,肯定是无法提供服务的。而定义了就绪性探测,只有等应用就绪了才会被添加至端点列表中。定义了此项的话,如果应用没有就绪,那么我们使用kubectl get pods可以看到创建的容器不会是Running状态
    关于如何定义存活性探测和就绪性探测单独的文章地址
    http://blog.hydraslayer.cn/html/9d37bea4.html

容器的重启策略

容器程序发生崩溃或容器申请超出限制的资源等原因都有可能导致Pod对象的终止,此时是否应该重建该Pod对象则取决于重启策略(restartPolicy)属性的定义

  • Alwas: 但凡Pod对象终止就会将其重启,此为默认值
  • OnFailure: 仅在Pod对象出现错误时才会将其重启
  • Never: 从不重启
    需要注意的是,restartPolicy适用于Pod对象中的所有容器,而且它仅用于控制在同一节点上重新启动Pod对象的相关容器。 首次需要重启的容器,将在其需要时立即进行重启,随后再次需要重启的容器将由kubelet延迟一段时间后进行,且反复的重启操作的延迟市场以次为10秒、20面、40秒、80秒、160秒和300秒,300秒是最大延迟时长。事实上,一旦绑定到一个节点,Pod对象将永远不会被重新绑定到另外一个节点,它要么被重启,要么终止,直到节点发生故障或被删除

Pod的终止过程

Pod对象代表了在K8S集群节点上运行的进程,它可能曾用于处理生成数据或向用户提供服务等,于是,当Pod本身不再具备存在价值时,如何将其优雅的终止就显的尤为的重要了,而用户也需要能够在正常提交删除操作后可以获知其何时开始终止并最终完成。操作中,当用户提交删除请求之后,系统就会进行强制删除操作的宽限期倒计时,并将TERM信息发送给Pod对象的每个容器中的主进程。宽限期倒计时结束后,这些进程将收到强制终止的KILL信号,Pod对象随即也将由API Server删除。如果在等待进程终止的过程中,kebelet或容器管理器发生了重启,那么终止操作会重新获得一个满额的删除宽限期并重新执行删除操作,一个Pod删除的过程可以分为如下步骤

  • 用户发送删除Pod对象的命令
  • API服务器中的Pod对象会随着时间的推移而更新,在宽限期内(默认为30秒)Pod将被视为“dead”
  • 将Pod标记为“Terminating”状态
  • (与第三步同时运行)kubelet在监控到Pod对象转为“Terminating”状态的同时启动Pod关闭过程
  • (与第三步同时运行)端点控制器监控到Pod对象的关闭行为时将其从所有匹配到此端点的Service资源的端点列表中移除
  • 如果Pod对象定义了preStop钩子处理器,则在其标记为“Terminating”后即会以同步的方式启动执行,如若宽限期结束后,preStop依旧没有执行结束,则第二步会被重新执行并额外获取一个时长为2秒的小宽限期
  • Pod对象中的容器进程收到TERM信号
  • 宽限期结束后,若存在任何一个依旧在运行的进程,那么Pod对象即会收到SIGKILL信号
  • kubelet请求API Server将此Pod资源的宽限期设置为0从而完成删除操作,它变得对用户不再可见
    默认情况下,所有删除操作的宽限期都是30秒,不过,kubectl delete命令可以使用--grace-period=<seconds>选项自定义其时长,若使用0则表示直接抢注删除指定的资源,不过,此时需要同时为命令使用--force选项