k8s、springboot、mysql、flyway、Spring boot 2.0 Actuator健康检查组件等
项目中集成了flyway,当项目第一次启动时,都会初始化flyway文件,再加上各种组件的初始化,导致主服务启动的时间达到7分钟;
另外pod有时候会自动重启,如果在初始化flyway时中断重启,那么下次启动时程序会报flyway的问题,导致服务永远起不来;
查看pod状态,一直显示健康检查失败;
pod重启了3次,显示running状态,但是并没有ready;
程序日志:报flyway初始化异常;
1、服务启动慢,启动过程中突然中断,以为服务器内存不够或者cpu不够
给服务加内存:
resources:
requests:
memory: 6Gi
cpu: 0.5
limits:
memory: 8Gi
cpu: 1
在服务启动时,一直使用命令查看pod占用内存情况:
kubectl top pod
发现启动时,内存就只吃了几百兆,cpu也只用的500m左右;
2、经过步骤一的步骤排查,确定不是服务器环境问题;但是启动过程中老是中断,而且查看pod状态时,显示健康检查拒绝;服务就算不中断,启动起来也需要个六七分钟:
仔细看了看服务yaml的健康检查参数:
# 存活探针
livenessProbe:
httpGet:
path: /iot/sacp/actuator/health # 健康检查接口
port: sacp
scheme: HTTP
timeoutSeconds: 30 # 访问接口的超时时间
initialDelaySeconds: 20 # 初始化延迟时间 即20s开始访问健康检查接口
periodSeconds: 10 # 20s后 每10s访问一次健康检查接口 探测频率
successThreshold: 1 # 探测至少成功1次,就认为pod是健康的
failureThreshold: 20 # 探测连续失败20次,任务此pod是不健康的; 此时kubectl会重启pod
# 就绪探针
readinessProbe:
failureThreshold: 20
initialDelaySeconds: 20
periodSeconds: 10
successThreshold: 1
tcpSocket:
port: sacp
timeoutSeconds: 30
总结起来就是:由于程序启动过慢,健康检查探测了20 + 20*10 = 220s;程序还没有起来;此时kubectl会自动重启容器;如果处于flyway文件初始化阶段,那么中断了初始化,程序就会一直起不来;如果重启之后能够正常起来,则pod状态仍然是健康检查失败,拒绝健康检查;
健康检查一直被拒绝、失败的原因是,因为pod一直起不来,健康检查接口访问不通,所以拒绝、失败;pod中断则是因为健康检查失败次数太多,kubectl依照重启策略进行了中断重启;
使用启动探针startupProbe:k8s在 v1.16 中添加了 startup 探针作为 Alpha 功能,并在 v1.18 中升级为 Beta;
当程序启动时,就开启动启动探针,然后开始探测,只要探测成功一次,就不在探测(说明程序启动成功),此时存活探针和就绪探针才会开始工作;通过将 failureThreshold * periodSeconds
参数设置为足够长的时间来应对糟糕情况下的启动时间;
startupProbe:
httpGet:
path: /iot/sacp/actuator/health
port: sacp
scheme: HTTP
failureThreshold: 37
periodSeconds: 10
大概估一下程序启动的时间六七分钟;因此设置 37 * 10 = 370s; 这意味着当程序启动,有370s的启动时间,启动探针在这个过程中一直探测,只要程序探测成功一次,存活探针才会工作;那么我们的pod就不会再发生中断重启了;
org.springframework.boot
spring-boot-starter-actuator
只要加上了这個 maven dependency,SpringBoot 在运行时就会开放/actuator/health和/actuator/info这两个endpoint,我們就可以通过这两个 endpoint 查看当前 SpringBoot 运行的情況;
# 开放所有端点endpoints(不包含shutdown端点)
management.endpoints.web.exposure.include=*
# 指定开放某些端点
management.endpoints.web.exposure.include=beans,mappings
# exclude用来关闭端点
# exclude通常会跟include一起用,就是先include了全部,然后再exclude /actuator/beans这个endpoint
management.endpoints.web.exposure.exclude=beans
management.endpoints.web.exposure.include=*
# 如果要开放/actuator/shutdown,要在额外加上这一行
management.endpoint.shutdown.enabled=true
使用启动探针保护慢启动容器;有时候,会有一些现有的应用在启动时需要较长的初始化时间,在这种情况下,如果没有配置启动探针,而直接配置存活探针,可能容器在启动过程中,健康检查长时间没有响应,达到存活探针的失败次数,会中断启动而重新启动容器;
在一些情况下,往往只是新的Pod完成自身初始化,系统尚未完成EndPoint、负载均衡器等外部可达的访问信息刷新,老得Pod就立即被删除,最终造成服务短暂的额不可用,这对于生产来说是不可接受的;
readiness 探针可以让 kubelet 知道应用程序何时准备接受新流量,如果应用程序在进程启动后需要一些时间来初始化状态,要配置 readiness 探针让 Kubernetes 在发送新流量之前进行等待;
Liveness 探针liveness 探针用于重新启动不健康的容器。Kubelet 会定期地 ping liveness 探针,以确定健康状况,并在 liveness 检查不通过的情况下杀死 Pod。liveness 检查可以帮助应用程序从死锁中恢复。如果不进行 liveness 检查,Kubernetes 会认为死锁中的 Pod 处于健康状态,因为从 Kubernetes 的角度来看,Pod 的子进程仍在运行,是健康的。通过配置 liveness 探针,kubelet 可以检测到应用程序处于不健康状态,并重新启动 Pod 以恢复可用性;
initialDelaySeconds
:启动 liveness、readiness 探针前要等待的秒数。periodSeconds
:检查探针的频率。timeoutSeconds
:将探针标记为超时(未通过运行状况检查)之前的秒数。successThreshold
:探针需要通过的最小连续成功检查数量。failureThreshold
:将探针标记为失败之前的重试次数。对于 liveness 探针,这将导致 Pod重新启动。对于 readiness 探针,将标记 Pod 为未就绪(unready);