原文: https://i4t.com/4424.html
优雅停止(Graceful shutdown)这个说法来自于操作系统,比如我们windows关机系统首先会退出软件然后一步步到达关机,而相对的就是硬终止(Hard shutdown),简单的理解就是直接拔电源
到了微服务中,网关会把流量分配给每个Pod节点上,比如我们上线更新Pod的时候
Pod Hook是由kubelet发起的,当容器中的进程启动前或者容器中的进程终止之前运行,这是包含在容器的生命周期之中。我们可以同时为Pod中的所有容器都配置hook
在k8s中,理想的状态是pod优雅释放,并产生新的Pod。但是并不是每一个Pod都会这么顺利
对于以上问题,k8s的Pod终止流程中还有一个"最多可以容忍的时间",即grace period (在pod的 .spec.terminationGracePeriodSeconds
字段定义),这个值默认是30秒,当我们执行 kubectl delete
的时候也可以通过 --grace-period
参数显示指定一个优雅退出时间来覆盖Pod中的配置,如果我们配置的grace period超过时间之后,k8s就只能选择强制kill Pod
如果PostStart或者PreStop钩子失败,它会杀死容器。所以我们应该让钩子函数尽可能的轻量。当然有些情况下,长时间运行命令是合理的,比如在停止容器之前预先保留状态。
这里稍微简单说一下Pod终止的过程
官方文档介绍
在Pod Hook钩子函数中有Exec和HTTP两种方式
首先我们先进行演示PostStart的两种方式
第一种Exec
我们echo一段话追加到 /tmp/message,在Pod启动前进行操作
cat >>exec_test.yaml<<EOF apiVersion: v1 kind: Pod metadata: name: abcdocker labels: name: abcdocker spec: containers: - name: abcdocker image: nginx ports: - containerPort: 80 lifecycle: postStart: exec: command: - bash - -c - 'echo "https://i4t.com" > /tmp/message' EOF
使用 kubectl apply -f exec_test.yaml
进行创建
可以通过下面查看结果,pod的目录已经有我们在yaml文件写的测试文件
[root@abcdocker yaml]# kubectl get pod NAME READY STATUS RESTARTS AGE abcdocker 1/1 Running 0 37s [root@abcdocker yaml]# kubectl exec -it -n default abcdocker /bin/bash root@abcdocker:/# cat /tmp/message https://i4t.com root@abcdocker:/# root@abcdocker:/# exit
创建容器后,Kubernetes立即发送postStart事件。但是,不能保证在调用Container的入口点之前先调用postStart处理程序。postStart处理程序相对于Container的代码异步运行,但是Kubernetes对容器的管理会阻塞,直到postStart处理程序完成。在postStart处理程序完成之前,容器的状态不会设置为RUNNING。
第二种HTTP方式
使用HttpGet配置Host、Path、Port
apiVersion: v1 kind: Pod metadata: name: abcdocker labels: name: abcdocker spec: containers: - name: abcdocker image: nginx ports: - containerPort: 80 lifecycle: postStart: httpGet: host: i4t.com path: index.html port: 80
这里就不进行演示了,因为日志会看不到这个请求
起因:
在生产环境中使用spring框架,由于服务更新过程中,服务容器被直接充值,部分请求仍被分发到终止的容器(没有配置钩子,熟悉默认环境),导致服务出现500错误,这部分错误请求数据占用比较少,因为Pod滚动更新都是一对一。因为部分用户会产生服务器错误的情况,考虑使用优雅的终止方式,将错误请求降到最低,直至滚动更新不影响用户
Eureka是一个基于REST的服务,作为Spring Cloud服务注册中心,用于定位服务来进行中间层服务器的负载均衡和故障转移。各服务启动时,会向Eureka Server注册自己的信息(IP、端口、服务信息等),Eureka Server会存储这些信息,微服务启动后,会周期性(默认30秒)的向Eureka Server发送心跳以续约自己的租期,并且可以从eureka中获取其他微服务的地址信息,执行相关逻辑
由于Eureka默认的心跳检测为30秒,当K8S下线Pod时Eureka会有30秒的异常问题,所以我们需要在Pod 停止前发送一条请求,通知Eureka进行下线操作,这样进行优雅的停止对用户的影响做到最小
具体yaml如下
apiVersion: v1 kind: Pod metadata: name: abcdocker labels: name: abcdocker spec: containers: - name: abcdocker image: nginx ports: - containerPort: 80 lifecycle: preStop: exec: command: - bash - -c - 'curl -X POST --data DOWN http://127.0.0.1:8080/service-registry/instance-status -H "Content-Type: application/vnd.spring-boot.actuator.v2+json;charset=UTF-8";sleep 30' ####### 参数解释 127.0.0.1:8080 #代表eureka地址 service-registry #代表注册中心 DOWN #执行down请求 sleep #等待30秒
当我们删除Pod的时候就会执行上面的命令操作,并且等待30秒
[root@yzsjhl82-135 yaml]# kubectl get pod NAME READY STATUS RESTARTS AGE abcdocker 1/1 Running 0 2m16s [root@yzsjhl82-135 yaml]# kubectl delete pod abcdocker pod "abcdocker" deleted #此刻Pod不会马上删除,而是执行Exec中的命令,并等待30秒
配置中添加了一个sleep时间,主要是作为服务停止的缓冲时间
总结:Hook调用的日志没有暴露给Pod的Event,所以只能到通过 describe
命令来获取,如果是正常的操作是不会有event,如果有错误可以看到FailedPostStartHook和FailedPreStopHook这种event。并且如果Hook调用出现错误,则Pod状态不会是Running