记一次k8s健康检查导致的问题

问题背景

手机收到一条告警短信,线上环境接口出现异常了!!!告警内容是某一个对外服务API状态码异常,状态码为500。好家伙第一反应去PaaS平台(KuberSphere)查看,发现该服务的一个pod正在重启,并且重启完后又继续重启。就在这时又收到一条告警恢复短信(大概在告警短信一分钟后),状态码为200了。。。这篇文章来盘一下这次问题。

k8s健康检查

k8s探针

k8s探针 是由 kubelet 对容器执行的定期诊断。要执行诊断,kubelet 调用由容器实现的 Handler。有三种类型的处理程序:

  • ExecAction:在容器内执行指定命令。如果命令退出时返回码为 0 则认为诊断成功。
  • CPSocketAction:对指定端口上的容器的 IP 地址进行 TCP 检查。如果端口打开,则诊断被认为是成功的。
  • HTTPGetAction:对指定的端口和路径上的容器的 IP 地址执行 HTTP Get 请求。如果响应的状态码大于等于200 且小于 400,则诊断被认为是成功的。

k8s健康检查 探针

  • livenessProbe(存活探针):用于判断容器是否存活(Running状态),如果livenessProbe探针探测到容器不健康,则 kubelet 将 “杀掉” 该容器,并根据容器的重启策略做相应的处理。如果一个容器不包含livenessProbe探针,那么 kubelet 认为该容器的 livenessProbe探针 返回的值永远是Success。
  • readinessProbe(就绪探针):用于判断容器服务是否可用(Ready状态),达到Ready状态的Pod才可以接收请求。对于被Service管理的Pod、Service与PodEndpoint的关联关系也将基于Pod是否Ready进行设置。如果在运行过程中Ready 状态变为False,则系统自动将其从Service的后端Endpoint列表中隔离出去,后续再把恢复到Ready状态的Pod加回后端Endpoint列表。
  • startupProbe(启动探针):如果配置了startupProbe,就会先禁止其他的探测,直到它成功为止,成功后将不再进行探测。比较适用于容器启动时间长的场景。需要 kubernetes 版本 v1.18 或以上。

问题定位

我们配置的健康检查如下:

livenessProbe:
  httpGet:
    path: /actuator/health
    port: 8080
    scheme: HTTP
  initialDelaySeconds: 90
  timeoutSeconds: 3
  periodSeconds: 10
  successThreshold: 1
  failureThreshold: 3
readinessProbe:
  httpGet:
    path: /actuator/health
    port: 8080
    scheme: HTTP
  initialDelaySeconds: 90
  timeoutSeconds: 3
  periodSeconds: 10
  successThreshold: 1
  failureThreshold: 3
复制代码

这里边有些配置含义如下:

配置 含义
httpGet.path get请求路径
httpGet.port get请求端口
httpGet.scheme get请求协议
initialDelaySeconds 初始延迟(秒),在检查其运行状况之前,容器启动后需要等待多长时间。
timeoutSeconds 超时时间(秒),等待探针完成多长时间。如果超过时间,则认为探测失败。默认为1秒。最小值为1。
periodSeconds 执行探测频率(秒),执行探测的频率(以秒为单位)。默认为10秒。最小值为1。
successThreshold 健康阈值,探测失败后,连续最小成功探测为成功。默认值为1。最小值为1。存活探针和启动探针内必须为1。
failureThreshold 不健康阈值,探针进入失败状态时需要连续探测失败的最小次数。

公司提供的配置文档中不包含启动探针的配置,猜测是部署的k8s的版本还不支持启动探针。

问题背景中我们抽出来几个 关键点

  1. 一个pod正在重启。
  2. pod重启完成后又继续重启。
  3. 告警短信大概一分钟后告警恢复。

到这里可以联想到,存活探针发送get请求获取到的响应的状态码不在 200 和 400之间或者直接超时,所以容器重启直接影响服务,告警通知;但是配置的初始延迟为90秒太短导致一直重启;就在这时就绪探针判断 Ready 状态变为False,则系统自动将其从 Service 的后端 Endpoint 列表中隔离出去,故障 pod 排除掉之后,告警恢复。

是什么原因导致正在运行的容器,PaaS平台是有事件日志的,当时忘记截图了(盘的时候查不到了)... 记得当时有http超时事件,也有状态码为503的事件。超时可能是网络波动或者大概率是初始延迟设置过短。那么这个503状态码到底是为什么呢?

Actuator

Actuator是Springboot的一个模块,模块提供了Spring Boot的所有生产就绪功能。

Endpoints

Actuator 端点允许您监视应用程序并与之交互。 Spring Boot 包括许多内置端点,并允许您添加自己的端点。 例如,提供基本的应用程序运行状况信息的 health 端点。

Actuator的health端点

我们配置健康检查用的接口就是Actuator提供的 health 端点接口。像我们引入DB依赖,Nacos依赖啥的,这些依赖实现了Actuator的health策略接口HealthIndicator,请求health端点的时候就会调用策略实现类检查健康状况。

health端点的返回会返回一个status,可以通过配置management.endpoint.health.show-details=always设置返回详细信息。

下边是一个详细信息的返回值。

{
    "components":{
        "db":{
            "components":{
                "dataSource":{
                    "details":{
                        "database":"MySQL",
                        "result":1,
                        "validationQuery":"/* ping */ SELECT 1"
                    },
                    "status":{
                        "code":"UP",
                        "description":""
                    }
                },
                "dataSource2":{
                    "details":{
                        "database":"MySQL",
                        "result":1,
                        "validationQuery":"/* ping */ SELECT 1"
                    },
                    "status":{
                        "code":"UP",
                        "description":""
                    }
                }
            },
            "status":{
                "code":"UP",
                "description":""
            }
        },
        "discoveryComposite":{
            "components":{
                "discoveryClient":{
                    "details":{
                        "services":[
                            "***",
                            "***",
                            "***-gateway"
                        ]
                    },
                    "status":{
                        "code":"UP",
                        "description":""
                    }
                }
            },
            "status":{
                "code":"UP",
                "description":""
            }
        },
        "diskSpace":{
            "details":{
                "total":"528309530624",
                "free":"463192977408",
                "threshold":10485760
            },
            "status":{
                "code":"UP",
                "description":""
            }
        },
        "mail":{
            "details":{
                "location":"10.************:25"
            },
            "status":{
                "code":"UP",
                "description":""
            }
        },
        "ping":{
            "details":{

            },
            "status":{
                "code":"UP",
                "description":""
            }
        },
        "refreshScope":{
            "details":{

            },
            "status":{
                "code":"UP",
                "description":""
            }
        }
    },
    "groups":[

    ],
    "status":{
        "code":"UP",
        "description":""
    }
}
复制代码

返回的Status的code编码有四种。

/**
 * 指示组件或子系统处于未知状态。
 */
public static final Status UNKNOWN = new Status("UNKNOWN");

/**
 * 指示组件或子系统按预期运行。
 */
public static final Status UP = new Status("UP");

/**
 * 指示组件或子系统发生了意外故障。
 */
public static final Status DOWN = new Status("DOWN");

/**
 * 指示组件或子系统已从服务中取出,不应再使用。
 */
public static final Status OUT_OF_SERVICE = new Status("OUT_OF_SERVICE");
复制代码

翻看源码看到了这四个编码与http状态码的关系,即 DOWN 和 OUT_OF_SERVICE 会返回http状态码 503,其他返回200状态码。

health端点如果异常,即可以通过详细信息定位到异常的组件!

猜你喜欢

转载自juejin.im/post/7215253602204418104