本文主要是详细介绍K8S中的健康检查的2类方式, 即: 存活(liveness)探针和就绪(readiness)探针, 前者关乎pod是否要重启, 后者关乎service 端点列表是否要拿掉该pod. 介绍完之后并附上最佳实践案例, 涵盖: web server, tomcat等中间件, redis等缓存服务器, mysql等开源数据库, spring微服务...
同时从K8S的健康检查展开, 延伸到传统运维场景下的健康检查, 其实这2类探针也存在, 但我们用的够好够细了么?
存活(Liveness) 和 就绪(Readiness) 探针(Probe)是 Kubernetes的功能, 使团队能够使其容器化的应用程序更可靠、更健壮。但是,如果使用不当,可能不但不会得到期望中的好处, 还会使基于微服务的应用程序不稳定.
每个探针的用途都非常简单. 概述如下:
结合探针的作用, 可以在pod内执行以下几种测试:
200-399
之间的响应则认为 成功.0
则成功.因此,要使用存活(Liveness)探针,我们必须从三个可用的选项中确定一个合适的测试,如果测试失败,则需要执行pod重启.
首先, 务必保证有存活(liveness)探针的pod就是需要重启的pod. 就是说: 这个探针就是用来探测这个pod的存活状态的, 而不是探测其他pod, 甚至多个pod, 或者其他事务的状态的.
其次, 存活(liveness)探针也不能太过简单, 否则的话可能永远不会给出容器健康状况不佳的有意义的指示.
最后, 存活(liveness)探针并不是管理和监控基于微服务的复杂应用程序的唯一工具, 但是它是最基本的保障. 如果有更复杂的需求, 可以通过日志监控和metrics监控来实现, 比如: EFK 和 Prometheus.
还是以之前的旅行预订网站 web 应用程序来作为示例. 这个web应用有多个微服务, 包括:
应用程序服务响应 URL endpoint(如 /flights、/trains和 /hotels)上的请求。这些 URL 中的每一个都会导致一个事务,该事务需要与查找座位或房间可用性的另一个容器化应用程序进行交互。他们还可以执行诸如获取用户配置文件和查找其经常旅行点等任务。简而言之,这不是一个微不足道的事务,它涉及多个下游接口。如果我们使用上述 URL endpoints之一作为存活(liveness)探针的一部分,则结果可能是在一个下游服务发生故障或响应缓慢后重新启动这个容器。其实这个微服务可能运行的好好的,但我们错误的配置导致它重新启动。
或者,我们寻找不同的endpoint 来指示 pod 运行状况。添加新 URL(如 /health,用于验证该微服务是否正在运行和服务请求),只有在微服务无法响应简单请求时才会重新启动 pod。
🔖 书签:
/health
或 /status
等)但是, 事情没有这么简单. 在找到了合适的endpoint后, 还必须确定存活(liveness)探针测试的合适参数, 以确保它在正确的环境中运行. 配置URL 响应测试所需的参数涉及以下内容:
initialDelaySeconds
参数必须设置为应开始运行状况检查探针的适当值。由于 /health
探针与其他资源消耗较多的 URL 在同一应用程序服务器平台上运行,初始延迟必须足够长,以确保运行状况检查 URL 处于活动状态。将此值设置得过高将留下一段时间,在此期间容器应用程序处于活动状态,并且探针未处于活动状态。
应谨慎对待periodSeconds
参数,因为这个配置的是 Kubernetes 平台探测pod以查看其是否成功运行的频率。设置过于激进会导致不必要的工作负载,而探针之间的间隙过大会导致探针操作之间的长时间延迟。
下面的示例通过时间线演示了执行的探针。第一个探针成功,但第二个、第三个和第四个探针失败。假设failureThreshold
的默认设置为 3 ,则pod将在第四个探针失败后重新启动.
🔖 书签: 配置如下:
假设一个pod启动失败,由于存活(liveness)探针的作用,在pod重新启动之前所能经过的最短时间是
time = initialDelaySeconds + (failureThreshold – 1) * periodSeconds + timeoutSeconds
在正常稳定状态操作下,假设pod在一段时间内运行成功,则initialDelaySeconds
参数将变得无关紧要。在这种情况下,故障和pod重新启动之间的最短时间间隔是:
time = (failureThreshold – 1) * periodSeconds + timeoutSeconds
如下图所示,故障点可以发生在探测之前,也可以发生在探测成功之后。如果周期时间很长,对pod的干扰很小,那么pod重新启动之前的时间可能会导致在重新启动之前添加几乎一个额外的periodSeconds
时间间隔。
必须谨慎使用failureThreshold
参数。如果参数设置得过高,则存在在pod发生故障且未重新启动时浪费时间的危险。如果此参数设置得太低,则如果pod承受较大的负载,则存在过早重新启动pod的危险。如果出现这种情况并重新启动pod,则系统会丢失部分服务于客户请求的工作负荷(比如本来4个pod, 重启了1个, 就只有3个在服务了),并将更多的工作负载放在剩余的 Pod 上,这将使其整体性能进一步下降。
上面所述的关于存活探针的所有内容都同样适用于就绪探针。明显的区别是探针执行操作时的最终结果,在就绪探针的情况下,操作是从可用服务端点列表中删除 pod。在正常情况下,端点会报告支持它的所有 Pod,如下所示:
oc get ep/node-app-slave -o json
{
"apiVersion": "v1",
"kind": "Endpoints",
...
"subsets": [
{
"addresses": [
{
"ip": "10.128.2.147",
运行就绪探针失败后, 地址行更改为:
oc get ep/node-app-slave -o json
{
"apiVersion": "v1",
"kind": "Endpoints",
...
"subsets": [
{
"notReadyAddresses": [
{
"ip": "10.128.2.147",
存活探针和就绪探针之间的一个明显区别是,在就绪探针采取行动后,pod仍在运行。这意味着successThreshold
参数可以发挥更大的作用。即使将pod从端点列表中取下,就绪探针将继续探测pod。如果pod以某种方式设法自我纠正(可能是由于它暂时承受着严重的工作负载,并且无法对探针做出响应),则pod可能会开始成功响应探针。成功阈值的默认值仅为 1,因此只需一个成功的探测响应,就取消隔离期并将 pod 重新添加到服务端点列表中。因此,就绪探针在给pod一些喘息空间以从不堪重负中恢复过来方面表现特别好。
再次,必须提出这样的问题——"考虑到应用程序的总体架构和预期的工作负载(应用程序必须在此工作负载下运行),当pod不堪重负时,我们希望采取什么操作?" 类似地,不必要的pod重启会给系统内的其他pod带来额外的工作负载,临时将一个pod“停用”可能只是表明应用程序的功能或整个体系结构需要重新考虑其在负载下的行为。
对于就绪探针,failureThreshold
参数定义探针在从端点列表中删除pod之前必须失败的次数。考虑就绪探针的failureThreshold
为 5 和默认successThreshold
为 1 的情况。在下图中,pod连续三次未能响应探测,随后出现一次成功响应(探针 5)。此成功响应在故障时重置计数器,然后探针10 从端点表中移除pod之前,又发生了五个故障探测(探针 6 到 10)。
对于就绪探针,successThreshold
参数与failureThreshold
一起工作,以定义将 pod 重新加到端点列表的情况。显然,successThreshold
参数对存活(liveness)探针没有影响。考虑就绪探针的failureThreshold
为 5 和successThreshold
为 3 的情况。在下图中,pod 在探针 5 处出现第五次响应失败,导致pod从端点列表中移除。请注意,在从探针 1 到探针 5 的时间段内,即使该pod难以成功响应, 它仍保留在端点列表中。在pod 运行状况改善并在探针 7 处成功响应之前,探针 6 上又发生一次探针故障。由于成功阈值设置为 3,因此在将pod加回到端点列表之前,在探针 8 和 9 处需要另外两次成功的探测响应。
一个pod可以恢复健康但仍不能处理请求的最小时间是:
time = (successThreshold – 1) * periodSeconds
前面说了那么多废话, 现在上一点干货.
livenessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 30
timeoutSeconds: 3
readinessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 3
timeoutSeconds: 3
NGINX的存活(liveness)探针配置和就绪(readiness)探针配置类似, 只有初始延迟时间不一样, 都是检测 8080端口的/
页面状态.
针对tomcat标准容器, 就只配置了就绪探针: 就是查看tomcat的Catalina type Server stateName
的状态是否为STARTED
readinessProbe:
exec:
command:
- /bin/bash
- '-c'
- >-
curl --noproxy '*' -s -u
${JWS_ADMIN_USERNAME}:${JWS_ADMIN_PASSWORD}
'http://localhost:8080/manager/jmxproxy/?get=Catalina%3Atype%3DServer&att=stateName'
|grep -iq 'stateName *= *STARTED'
对于实现了actuator
的微服务, 2类探针可以这么配置:
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
timeoutSeconds: 3
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 180
timeoutSeconds: 3
readinessProbe:
exec:
command:
- /bin/sh
- '-i'
- '-c'
- >-
test "$(redis-cli -h 127.0.0.1 -a $REDIS_PASSWORD
ping)" == "PONG"
initialDelaySeconds: 5
timeoutSeconds: 1
livenessProbe:
initialDelaySeconds: 30
tcpSocket:
port: 6379
timeoutSeconds: 1
readinessProbe:
exec:
command:
- /bin/sh
- '-i'
- '-c'
- >-
MYSQL_PWD="$MYSQL_PASSWORD" mysql -h 127.0.0.1 -u
$MYSQL_USER -D $MYSQL_DATABASE -e 'SELECT 1'
initialDelaySeconds: 5
timeoutSeconds: 1
name: mariadb
livenessProbe:
initialDelaySeconds: 30
tcpSocket:
port: 3306
timeoutSeconds: 1
mysql的存活(liveness)探针配置如下: 检测3306 端口是否正常, 不正常就重启pod.
mysql的就绪(readiness)探针配置如下: 检测是否能执行最简单的sql SELECT 1
, 不能执行的话就提出服务端点列表(类似于踢出F5的pool)
mysql
命令登录并执行SELECT 1
SQL对于一些更为复杂的健康检查需求, 我们可以通过编写自定义检查脚本来实现.
这里在容器里封装了2个专门用于检测状态的脚本:
livenessProbe:
exec:
command:
- /bin/bash
- '-c'
- /opt/eap/bin/livenessProbe.sh
initialDelaySeconds: 60
readinessProbe:
exec:
command:
- /bin/bash
- '-c'
- /opt/eap/bin/readinessProbe.sh
比如, 对于MQ类的容器, 自定义脚本可以包含以下判断内容:
存活(liveness)和就绪(readiness)探针在可靠、高效地提供应用程序服务方面可以发挥作用。通过准确考虑用于探测的内容以及我们想要对故障和恢复采取什么操作,可以很好地利用探针来帮助管理微服务应用程序的继续交付。
从K8S的健康检查展开, 我们延伸到传统运维场景下的健康检查, 其实这2类探针也存在, 但是我们可以用的更细化, 更加自动化.
拿典型的一种架构来举例: F5 + 应用服务器 + Oracle 数据库
F5就相当于K8S中的Service, F5的健康检查就类似于: 就绪(readiness)探针. F5作为商业产品, 健康检查的功能更加丰富. 我们的常用有2种:
/ok.html
, 只要访问正常就认为正常.当然, 回顾以上K8S健康检查的内容, 举一反三, 其实F5的健康检查配置可以更加细化, 针对不同的应用类型或系统来定制. 在这一层面, 我们可以做的更多的点是: 细化
针对应用服务器, 我们确实通过类似httpGet
的方式来访问特定的应用页面, 以此来判断应用是否正常. 判断的结果也往往是相当准确的, 但是相比K8S, 我们发现传统应用异常, 并没有进行后续的自动化处理, 如自动重启, 而是仍然采用人工分析处理的方式. 那么我们应用服务器方面, 可以从K8S健康检查学到的点是: 自动化重启 应用服务器节点以缩小 MTTR.
以上.