Kubernetes-Pod退出流程
在k8s中我们在部署更新服务的时候,旧的pod会被杀掉,新的pod会生成并接替工作。但是在这个交接工作过程中旧的pod有一个很长的操作,我们想在这个操作成功后再杀掉这个pod。不然的话可能会丢失一定的流量,或者外界无法感知到该pod被杀死
Pod终止过程
终止过程主要分为如下几个步骤:
用户发出删除 pod 命令
通过 kubectl get pod -oyaml
查看
- K8S 会给旧POD发送SIGTERM信号;将 pod 标记为“Terminating”状态;pod 被视为“dead”状态,此时将不会有新的请求到达旧的pod;
- 执行删除后会有一个等待宽限期(terminationGracePeriodSeconds 参数定义,默认情况下30秒)等待这么长的时间
- 第三步同时运行,监控到 pod 对象为“Terminating”状态的同时启动 pod 关闭过程
- 第三步同时进行,endpoints 控制器监控到 pod 对象关闭,将pod与service匹配的 endpoints 列表中删除,也就是它会在endpoint中删除service.IP地址
- 如果 pod 中定义了 preStop 处理程序,则 pod 被标记为“Terminating”状态时以同步的方式启动执行;若宽限期结束后,preStop 仍未执行结束,第二步会重新执行并额外获得一个2秒的小宽限期(最后的宽限期,所以定义prestop 注意时间,和terminationGracePeriodSeconds 参数配合使用),
- Pod 内对象的容器收到 TERM 信号
- 宽限期结束之后,若存在任何一个运行的进程,pod 会收到 SIGKILL 信号
- Kubelet 请求 API Server 将此 Pod 资源宽限期设置为0从而完成删除操作
SpringCloud的架构
有的业务可能把SpringCloud的整个架构都搬到了k8s上面了,包括euraka,这是非常常见的架构模式。
-
加入我们使用了euraka,而且这个euraka也在k8s中;
-
首先我们启动一个新的容器,这个容器在启动过程中会把自己的IP地址+端口号注册到eureka中
-
注册完之后euraka会把这个地址推送到SpringCloud;或者SpringCloud会定期的从euraka上同步其他service的地址。
-
这个新的应用已经起来了,那么我们就需要把那个旧的下线,或者会有一个删除或者下线的操作。
下线操作流程:
-
下线应用请求一下euraka的shutdown接口(这个接口是我们自己去写的)告诉它,我要被删除掉了
-
euraka把应用的IP地址从euraka中删除掉,应用请求了eureka之后,会把pod本身的地址给关闭掉。也就是这个pod的euraka地址在eureka注册表中关闭掉了
-
其它的service并不知道我这个容器已经被关掉了。所以说它是还会去做这个操作。
-
若是你的euraka配置了主动推送,它会把这个新的注册表推送给SpringCloud的其它的应用;
-
若是没有配置主动推送,只能是等待一段时间,注册表的刷新时间,等待SpringCloud再次去euraka中同步新的注册表
-
这个新的注册表是没有已经被删除的Pod应用的
-
在其它的service在同步注册表的时间间隔的这个过程中,我这个Pod是不能被删除掉的。
解决方案:
- 我们需要等待这个springboot去同步其他应用的新的注册表。
- 等待时间一般是90秒或者60秒,根据自己的需求来配置。
[root@k8s-master01 ~]# kubectl explain pod.spec.terminationGracePeriodSeconds
KIND: Pod
VERSION: v1
FIELD: terminationGracePeriodSeconds <integer>
DESCRIPTION:
Optional duration in seconds the pod needs to terminate gracefully. May be
decreased in delete request. Value must be non-negative integer. The value
zero indicates stop immediately via the kill signal (no opportunity to shut
down). If this value is nil, the default grace period will be used instead.
The grace period is the duration in seconds after the processes running in
the pod are sent a termination signal and the time when the processes are
forcibly halted with a kill signal. Set this value longer than the expected
cleanup time for your process. Defaults to 30 seconds.
[root@k8s-master01 ~]# kubectl explain pod.spec.containers.lifecycle.preStop
KIND: Pod
VERSION: v1
RESOURCE: preStop <Object>
DESCRIPTION:
PreStop is called immediately before a container is terminated due to an
API request or management event such as liveness/startup probe failure,
preemption, resource contention, etc. The handler is not called if the
container crashes or exits. The Pod's termination grace period countdown
begins before the PreStop hook is executed. Regardless of the outcome of
the handler, the container will eventually terminate within the Pod's
termination grace period (unless delayed by finalizers). Other management
of the container blocks until the hook completes or until the termination
grace period is reached. More info:
https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks
LifecycleHandler defines a specific action that should be taken in a
lifecycle hook. One and only one of the fields, except TCPSocket must be
specified.
FIELDS:
exec <Object>
Exec specifies the action to take.
httpGet <Object>
HTTPGet specifies the http request to perform.
tcpSocket <Object>
Deprecated. TCPSocket is NOT supported as a LifecycleHandler and kept for
the backward compatibility. There are no validation of this field and
lifecycle hooks will fail in runtime when tcp handler is specified.
测试删除pod,看是否会按照preStop配置的- sleep 60否
[root@k8s-master01 ~]# grep -v '^#' pod.yaml
apiVersion: v1 # 必选 API的版本号
kind: Pod # 必选 类型Pod
metadata: # 必选 元数据
name: nginx # 必选 符合RFC 1035规范的Pod名称
#namespace: default # 可选 Pod所在的命名空间 不指定默认为default 可以使用-n指定namespace
labels: # 可选 标签选择器 一般用于过滤和区分Pod
app: nginx-ready
spec: # 必选 用于定义容器的详细信息
containers: # 必选 容器列表
- name: nginx # 必选 符合RFC 1035规范的容器名称
image: nginx:latest # 必选 容器所用的镜像的地址
imagePullPolicy: Always # 可选 镜像拉取策略 IfNotPresent:如果宿主机有这个镜像,就不用拉取了 Always:总是拉取 Never:不管存在不存在,都不拉取
ports: # 可选 容器需要暴露的端口号列表
- name: http # 端口名称
containerPort: 80 # 端口号
protocol: TCP # 端口协议 默认TCP
lifecycle:
preStop:
exec:
command:
- sh
- -C
- sleep 60
restartPolicy: Always # 可选 默认Always 容器故障或者没有启动成功 自动重启该容器 Onfailure: 容器以不为0的状态码终止 自动重启该容器 Never:无论何种状态 都不会重启
---
apiVersion: v1
kind: Service
metadata:
name: ready-nodeport
labels:
name: ready-nodeport
spec:
type: NodePort
ports:
- port: 88
protocol: TCP
targetPort: 80
nodePort: 30880
selector:
app: nginx-ready
查看terminationGracePeriodSeconds: 配置
terminationGracePeriodSeconds:
添加至pod.yaml修改为90
删除,重新创建pod
[root@k8s-master01 ~]# kubectl delete -f pod.yaml
pod "nginx" deleted
service "ready-nodeport" deleted
[root@k8s-master01 ~]# kubectl apply -f pod.yaml
pod/nginx created
service/ready-nodeport created
[root@k8s-master01 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx 1/1 Running 0 5s
可以看到时间为60多秒,preStop配置的- sleep 60已经生效
总结
preStop: 先去请求eureka接口,把自己的IP地址和端口进行下线,eureka从注册表中删除该应用的IP地址,然后容器进行等待其他应用同步注册表,等待时间可以设置为sleep 90秒(时间尽量大于注册表刷新的时间),单独设置sleep 90是不生效的,不能超过等待宽限期(terminationGracePeriodSeconds 参数定义,默认情况下30秒)超过等待宽限期会以宽限期terminationGracePeriodSeconds配置为准,如果等待terminationGracePeriodSeconds配置的30秒之后检测到还有一个preStop没有执行的话,只会再申请两秒的宽限期,两秒之后会强制删除这个pod,sleep等待时间一定要小于terminationGracePeriodSeconds配置的时间,关闭进程可以在sleep前后都可以,一般在sleep后进行kill
pgrep java