pod的生命周期
我们知道Pod是Kubernetes集群中的最小单元,而 Pod 是由容器组成的,所以在讨论 Pod 的生命周期的时候我们可以先来讨论下容器的生命周期。
实际上 Kubernetes 为我们的容器提供了生命周期钩子,就是我们说的Pod Hook,Pod Hook 是由 kubelet 发起的,当容器中的进程启动前或者容器中的进程终止之前运行,这是包含在容器的生命周期之中。我们可以同时为 Pod 中的所有容器都配置 hook。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CuyxTkSJ-1628755387532)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210226175041783.png)]
Kubernetes 为我们提供了两种钩子函数:
PostStart:这个钩子在容器创建后立即执行。但是,并不能保证钩子将在容器ENTRY POINT之前运行,因为没有参数传递给处理程序。主要用于资源部署、环境准备等。不过需要注意的是如果钩子花费太长时间以至于不能运行或者挂起, 容器将不能达到running状态。(PostStart 可以在容器启动之后就执行。但需要注意的是,此 hook 和容器里的 ENTRYPOINT 命令的执行顺序是不确定的。)
PreStop:这个钩子在容器终止之前立即被调用。它是阻塞的,意味着它是同步的, 所以它必须在删除容器的调用发出之前完成。主要用于优雅关闭应用程序、通知其他系统等。如果钩子在执行期间挂起, Pod阶段将停留在running状态并且永不会达到failed状态。(PreStop 则在容器被终止之前被执行,是一种阻塞式的方式。执行完成后,Kubelet 才真正开始销毁容器。)
如果PostStart或者PreStop钩子失败, 它会杀死容器。所以我们应该让钩子函数尽可能的轻量。当然有些情况下,长时间运行命令是合理的, 比如在停止容器之前预先保存状态。
另外我们有两种方式来实现上面的钩子函数:
- Exec - 用于执行一段特定的命令,不过要注意的是该命令消耗的资源会被计入容器。
- HTTP - 对容器上的特定的端点执行HTTP请求。
pod的启动终止的过程
pod的终止过程
1.用户向apiServer发送删除pod对象的命令
2.apiServcer中的pod对象信息会随着时间的推移而更新,在宽限期内(默认30s), pod被视为dead
3.将pod标记为terminating状态
4.kubelet在监控到pod对象转为terminating状态的同时启动pod关闭过程
5.端点控制器监控到pod对象的关闭行为时将其从所有匹配到此端点的service资源的端点列表中移除
6.如果当前pod对象定义了preStop钩子处理器,则在其标记为terminating后即会以同步的方式启动执行
7.pod对象中的容器进程收到停止信号
8.宽限期结束后,若pod中还存在仍在运行的进程,那么pod对象会收到立即终止的信号
9.kubelet请求apiServer将此pod资源的宽限期设置为0从而完成删除操作,此时pod对于用户已不可见
pod的创建过程
1.用户通过kubectl或其他api客户端提交需要创建的pod信息给apiServer
2.apiServer开始生成pod对象的信息,并将信息存入etcd,然后返回确认信息至客户端
3.apiServer开始反映etcd中的pod对象的变化,其它组件使用watch机制来跟踪检查 apiServer上的变动
4.scheduler发现有新的pod对象要创建,开始为Pod分配主机并将结果信息更新至apiServer
5.node节点上的kubelet发现有pod调度过来,尝试调用docker启动容器,并将结果回送至
apiServer6. apiServer将接收到的pod状态信息存入etcd中
Pod生命周期内的5种状态
在整个生命周期中,Pod会出现5种状态(相位),分别如下:
挂起(Pending) : apiserver已经创建了pod资源对象,但它尚未被调度完成或者仍处于下载镜像的过程中
运行中(Running) : pod已经被调度至某节点,并且所有容器都已经被kubelet创建完成
成功(Succeeded) : pod中的所有容器都已经成功终止并且不会被重启
失败(Failed):所有容器都已经终止,但至少有一个容器终止失败,即容器返回了非0值的退出状态。
未知(Unknown) : apiserver无法正常获取到pod对象的状态信息,通常由网络通信失败所导致
示例1环境准备使用postStart
以下示例中,定义了一个Nginx Pod,其中设置了PostStart钩子函数,即在容器创建成功后,写入一句话到/usr/share/message文件中。
[root@k8s-master ~]# vim hook.yaml
apiVersion: v1
kind: Pod
metadata:
name: hook-demo1
spec:
containers:
- name: hook-demo1
image: nginx
lifecycle: #生命周期,使用函数调用必须使用
postStart: #使用 postStart函数
exec: #执行
command: ["/bin/sh", "-c", "echo Hello from the postStart handler > /usr/share/message"]
#使用postStart函数在容器就绪前往/usr/share/message里写入Hello from the postStart handler的一句话,
#简单理解就是容器启动后先执行写入文件,在运行nginx进程
进行创建并查看
[root@k8s-master ~]# kubectl create -f hook.yaml
pod/hook-demo1 created
[root@k8s-master ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
hook-demo1 1/1 Running 0 8s
[root@k8s-master ~]# kubectl exec hook-demo1 -it -- cat /usr/share/message
Hello from the postStart handler
#写入的文件存在说明postStart函数生效成功
示例2 优雅删除资源对象
当用户请求删除含有 pod 的资源对象时,K8S 为了让应用程序优雅关闭(即让应用程序完成正在处理的请求后,再关闭软件),K8S提供两种信息通知:
默认:K8S 通知 node 执行docker stop命令,docker 会先向容器中PID为1的进程发送系统信号SIGTERM,然后等待容器中的应用程序终止执行,如果等待时间达到设定的超时时间,或者默认超时时间(30s),会继续发送SIGKILL的系统信号强行 kill 掉进程。
使用 pod 生命周期(利用PreStop回调函数),它执行在发送终止信号之前。
默认所有的优雅退出时间都在30秒内。kubectl delete 命令支持 --grace-period=选项,这个选项允许用户用他们自己指定的值覆盖默认值。值’0’代表 强制删除 pod. 在 kubectl 1.5 及以上的版本里,执行强制删除时必须同时指定 --force --grace-period=0。
强制删除一个 pod 是从集群状态还有 etcd 里立刻删除这个 pod。 当 Pod 被强制删除时, api 服务器不会等待来自 Pod 所在节点上的 kubelet 的确认信息:pod 已经被终止。在 API 里 pod 会被立刻删除,在节点上, pods 被设置成立刻终止后,在强行杀掉前还会有一个很小的宽限期。
同 readinessProbe一样,hook 也有类似的 Handler:
Exec 用来执行 Shell 命令;
HTTPGet 可以执行 HTTP 请求。
示例:让nginx优雅退出
[root@k8s-master ~]# mkdir -p /data/nginx/html #创建数据挂载目录
[root@k8s-master ~]# vim hook1.yaml #编辑yaml文件
apiVersion: v1
kind: Pod
metadata:
name: lifecycle-demo
namespace: default
spec:
containers:
- name: lifecycle-demo-container
image: nginx
ports:
- containerPort: 80
volumeMounts:
- name: message
mountPath: /usr/share/nginx/html
lifecycle:
postStart: #容器启动前向nginx主页文件中添加postStart 字段
exec:
command: ["/bin/sh", "-c", "echo Hello from the postStart handler > /usr/share/nginx/html/index.html"]
preStop: #容器结束前向nginx主页文件中添加preStop字段
exec:
command: ["/bin/sh","-c","echo Hello from the preStop handler > /usr/share/nginx/html/index.html"]
volumes: #将nginx主页文件位置挂载到宿主机/data/nginx/html以便后期查看
- name: message
hostPath:
path: /data/nginx/html
创建及验证postStart函数
[root@k8s-master ~]# kubectl create -f hook1.yaml #生成pod
[root@k8s-master html]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
hook-demo1 1/1 Running 0 51m 10.244.1.2 k8s-node01 <none> <none>
lifecycle-demo 1/1 Running 0 9s 10.244.2.3 k8s-node02 <none> <none>
#查看ip地址及运行在那个node上,(此处是运行在node02上),那数据卷共享也会在node02上,查询preStop函数是否生效时要前往对象节点
#通过命令查看postStart函数是否生效
[root@k8s-master html]# curl 10.244.2.3
#此时主页文件已经被postStart函数中的命令进行更改,说明postStart函数生效成功
Hello from the postStart handler
验证preStop函数
如果将容器销毁 ,PreStop这个钩子在容器终止之前立即被调用,可以看到结果如下
[root@k8s-master html]# kubectl delete -f /root/hook1.yaml
#删除pod验证preStop函数
pod "lifecycle-demo" deleted
#前往node02验证
[root@k8s-node02 ~]# cat /data/nginx/html/index.html
#此时preStop函数的操作已经更改了nginx的主页,说明已经preStop函数生效成功
Hello from the preStop handler
当我们是使用nginx开启会话 保持后,想要结束掉进程时,发现还有用户在访问页面信息,如果强制终止会让用户体验很差,从而影响业务,我们可以借助**preStop
**以优雅的方式停掉 Nginx 服务,从而避免强制停止容器,造成正在处理的请求无法响应。
apiVersion: v1
kind: Pod
metadata:
name: hook-demo2
spec:
containers:
- name: hook-demo2
image: nginx
lifecycle:
preStop: #容器结束前,处理完所有的连接后进行退出
exec:
command: ["/usr/sbin/nginx","-s","quit"]
总结
创建容器后,Kubernetes立即发送postStart事件。但是,不能保证在调用Container的入口点之前先调用postStart处理程序。postStart处理程序相对于Container的代码异步运行,但是Kubernetes对容器的管理会阻塞,直到postStart处理程序完成。在postStart处理程序完成之前,容器的状态不会设置为RUNNING。
Kubernetes会在容器终止之前立即发送preStop事件。除非Pod的宽限期到期,否则Kubernetes对Container的管理将一直保持到preStop处理程序完成为止。
注意: Kubernetes仅在Pod终止时才发送preStop事件。这意味着在Pod完成时不会调用preStop挂钩。