运维开发.Kubernetes探针与应用

运维系列
Kubernetes探针与应用

- 文章信息 - Author: 李俊才 (jcLee95)
Visit me at CSDN: https://jclee95.blog.csdn.net
My WebSitehttp://thispage.tech/
Email: 291148484@163.com.
Shenzhen China
Address of this article:https://blog.csdn.net/qq_28550263/article/details/139361931
HuaWei:https://bbs.huaweicloud.com/blogs/428407

【介绍】:探针让Kubernetes具备了更好的自我修复和自我调节能力,是提高应用整体可靠性不可或缺的利器。本文将详细介绍Kubernetes中探针的原理和用法。

在这里插入图片描述


1. 概述

Kubernetes中,探针(Probe)是一种监控和诊断容器健康状态的机制。它通过定期执行某种操作来判断容器是否正常运行,从而实现自动化的问题检测和恢复。

探针的引入大大提高了Kubernetes集群管理的智能化水平。通过配置合适的探测方式和参数,我们可以让Kubernetes自动处理一些常见的问题,比如:

  • 如果一个容器死锁、崩溃或者没有响应,探针可以感知到问题并自动重启容器,保证服务的持续可用;

  • 对于启动时间较长的应用,探针可以避免在应用完全启动之前将流量导入,防止将请求发送到尚未准备好的容器;

  • 探针可以判断应用是否已经准备好对外提供服务,帮助实现滚动更新的平滑切换。

Kubernetes中主要提供了三种类型的探针:

  1. 存活探针(LivenessProbe):用于判断容器是否存活,如果探测失败,Kubernetes会自动重启容器
  2. 就绪探针(ReadinessProbe):用于判断容器是否已经准备好接收请求,如果探测失败,Kubernetes会将容器从Service的后端Endpoint列表中移除,暂停向其发送请求
  3. 启动探针(StartupProbe):用于判断容器内的应用是否已经启动完成。启动探针通常用于启动时间较长的应用,可以避免存活探针过早地认为应用无响应

这三种探针可以单独使用,也可以结合使用,以实现对容器状态的全方位监控。我们将在后面的章节中详细介绍每一种探针的原理和使用方法。

2. 探针类型

2.1 存活探针(LivenessProbe)

LivenessProbe用于判断容器是否存活,是Kubernetes中最常用的一种探针。它的基本原理是定期执行一个探测操作,如果探测成功,则认为容器是健康存活的;如果探测失败,则认为容器已经死亡,Kubernetes会根据重启策略自动重启容器。

存活探针的主要作用是及时发现容器的异常状态,防止应用程序进入不可用的状态。例如,如果一个Java应用因为内存泄漏导致无法响应请求,存活探针可以探测到这种情况,然后重启容器,恢复服务。

存活探针支持三种探测方式:

  1. Exec探针:在容器内执行一个命令,如果命令的退出状态码为0,则认为探测成功
  2. TCP探针:尝试与容器的指定端口建立TCP连接,如果连接成功,则认为探测成功
  3. HTTP探针:向容器的指定端口和路径发送一个HTTP GET请求,如果响应的状态码在200到400之间,则认为探测成功

当存活探针连续多次探测失败时,Kubernetes就会认为容器已经死亡,并根据Pod的重启策略进行处理:

  • Always(默认):容器会被立即重启,并且将来会一直重启;

  • OnFailure:容器会被立即重启,但是在5分钟内最多重启10次;

  • Never:容器不会被自动重启,Pod将一直处于Failed状态;

需要注意的是,存活探针只能发现容器是否存活,但不能判断容器是否已经准备好处理请求。如果一个应用需要较长的启动时间,存活探针可能会过早地认为应用已经死亡并触发重启,导致应用反复重启却无法正常提供服务。这种情况下,我们可以使用启动探针(StartupProbe)来延迟存活探针的检查,直到应用完全启动。

此外,如果容器的主进程(PID 1)是一个脚本或者其他非常驻进程,存活探针可能会无法正确判断容器的状态。因为当主进程退出时,容器就会被认为已经死亡,即使其他进程还在运行。对于这种情况,我们可以考虑使用进程管理工具如supervisord来管理容器内的多个进程。

存活探针是实现自愈性(self-healing)应用的关键手段之一。通过恰当地配置探测方式和参数,我们可以让Kubernetes自动处理许多常见的故障,大大减轻了运维的负担。但是探针的配置也需要根据实际情况进行调整,不能完全依赖默认值,否则可能会适得其反。

2.2 就绪探针(ReadinessProbe)

ReadinessProbe用于判断容器是否已经准备好接受请求,是实现平滑滚动更新的关键。与LivenessProbe不同,ReadinessProbe并不会触发容器的重启,而是控制容器是否可以被访问。

当一个Pod中的所有容器都通过了就绪探针的检查,这个Pod就被认为是可用的,Kubernetes会将其加入到对应ServiceEndpoint列表中,开始向其发送请求。反之,如果有任何一个容器没有通过就绪探针的检查,这个Pod就会被从ServiceEndpoint列表中移除,暂停接收请求。

就绪探针的主要作用是防止将请求发送到还没有准备好的容器,特别是在滚动更新的场景下。例如,当我们使用Deployment更新一个应用的版本时,新版本的Pod在启动初期可能还无法立即处理请求,如果直接将流量切换到新Pod,可能会导致请求失败。这时,就绪探针可以检查新Pod是否已经完全启动并准备好接收请求,只有通过检查的Pod才会被加入到ServiceEndpoint中,从而实现平滑的流量切换。

就绪探针支持与存活探针相同的三种探测方式:ExecTCPHTTP GET。但是,就绪探针的判断标准通常与存活探针不同。存活探针关注的是容器的运行状态,而就绪探针关注的是应用的业务可用性。例如,一个Web服务器的存活探针可能只检查服务进程是否存在,但是就绪探针可能要检查服务是否已经加载完配置、连接上数据库、可以正常响应请求等。

下面我们通过对比,进一步理解就绪探针与存活探针的区别:

LivenessProbeReadinessProbe
作用判断容器是否存活判断容器是否就绪
探测失败后果重启容器将容器从ServiceEndpoint中移除
典型用途发现容器运行时故障,自动恢复可用性控制哪些Pod可以被访问,实现平滑上线
探测标准容器是否在运行应用是否可以正常处理请求

虽然存活探针和就绪探针的作用不同,但它们可以结合使用,以实现更全面的健康检查。例如,在滚动更新时,我们可以先用就绪探针控制流量切换,等新版本的Pod完全就绪后,再用存活探针监控其运行状态,确保整个更新过程的平稳进行。

此外,并非所有的容器都需要配置就绪探针。对于一些无状态的服务,或者启动时间非常短的应用,存活探针可能就已经足够。而对于有状态服务、启动时间较长的应用,以及需要平滑上线的场景,就绪探针就显得非常必要了。

就绪探针为Kubernetes提供了一种更细粒度的流量控制机制,是实现滚动更新、蓝绿部署等高级发布策略的基础。合理利用就绪探针,可以大大提高应用上线的平稳性和整个系统的稳定性。

2.3 启动探针(StartupProbe)

StartupProbeKubernetes 1.16版本引入的一种新的探针类型,它的主要作用是判断容器内的应用是否已经启动完成。与存活探针和就绪探针不同,启动探针专门用于处理启动时间较长的应用场景。

在某些情况下,应用程序可能需要较长的时间来初始化和启动,例如加载大量的配置文件、预热缓存、建立数据库连接等。如果我们直接使用存活探针来检查这类应用,就可能会遇到一个问题:

存活探针可能会在应用完全启动之前多次检查失败,从而触发容器的重启。这样不仅会影响应用的可用性,还可能加重系统的负载。

启动探针就是为了解决这个问题而设计的。它的基本原理是:

在容器启动之后的一段时间内,只执行启动探针,而不执行存活探针和就绪探针。只有当启动探针检查成功后,才会开始执行存活探针和就绪探针。

这样,我们就可以给应用一个宽限期,让它有足够的时间完成启动,而不会被过早地判定为失败。

启动探针支持与存活探针和就绪探针相同的三种探测方式:ExecTCPHTTP GET。但是,启动探针有一些特殊的配置参数:

  • failureThreshold:启动探针的失败阈值,默认为3。即启动探针最多可以连续失败3次,超过这个次数,容器就会被重启。

  • periodSeconds:启动探针的执行周期,默认为10秒。即每隔10秒执行一次启动探针。

  • timeoutSeconds:启动探针的超时时间,默认为1秒。即启动探针的执行时间不能超过1秒,否则会被视为失败。

下面我们通过一个例子来说明启动探针与其他两种探针的关系:

假设我们有一个应用需要2分钟的时间来完成启动。如果我们只配置了存活探针,并将其初始延迟(initialDelaySeconds)设置为30秒,那么在应用启动的前2分钟内,存活探针会连续多次检查失败,导致容器被反复重启。

现在,我们可以添加一个启动探针,并将其失败阈值设置为12。这意味着启动探针最多可以连续失败12次,即允许应用最多花费2分钟(12 *10秒)来完成启动。在这2分钟内,只会执行启动探针,而不会执行存活探针。只有当启动探针检查成功后,才会开始执行存活探针,此时应用已经完全启动,可以正常处理请求了。

可见,启动探针提供了一种延迟存活探针的机制,让应用有充足的时间来完成启动,避免了过早的重启。同时,启动探针本身也会检查应用的启动状态,如果应用确实无法在指定时间内启动,启动探针最终还是会触发容器的重启,以恢复应用的可用性。

启动探针是对存活探针的一种补充,适用于启动时间较长、启动过程复杂的应用场景。它与就绪探针的关系相对独立,主要影响的是存活探针的行为。在实际使用时,我们可以根据应用的特点,灵活地组合使用这三种探针,以实现更精细的健康检查和状态管理。

3. 探测方式

3.1 ExecAction

ExecActionKubernetes探针的一种探测方式,它通过在容器内执行一个命令来判断容器的健康状态。ExecAction的原理是:

  1. Kubelet根据探针的配置,定期在容器内执行一个命令。这个命令通常是一个用于健康检查的脚本或者工具,例如针对Web服务器的curl命令,或者针对数据库的连接测试命令等;
  2. 命令在容器内运行,并返回一个退出状态码。如果退出状态码为0,则表示命令执行成功,Kubelet认为容器是健康的;如果退出状态码非0,则表示命令执行失败,Kubelet认为容器是不健康的;
  3. Kubelet根据探测结果决定后续的操作:
    • 对于LivenessProbe,如果探测失败,Kubelet会根据重启策略重启容器;
    • 对于ReadinessProbe,如果探测失败,Kubelet会将容器从Service的Endpoint列表中移除;
    • 对于StartupProbe,如果探测失败,Kubelet会根据失败阈值决定是否重启容器。

可以看到,ExecAction通过在容器内运行命令,将容器的健康状态转化为命令的退出状态码,从而实现了对容器的健康检查。这种方式具有以下优点:

  • 灵活性高:可以根据应用的特点,自定义健康检查的逻辑和标准。例如,可以检查进程是否存在、端口是否可用、日志是否包含错误关键字等;

  • 适用性广:可以适用于各种类型的应用,不限于Web服务。只要能找到一种方式将应用的健康状态转化为命令的退出码,就可以使用ExecAction进行检查;

  • 与应用解耦:ExecAction只需要关注命令的退出码,而不需要了解应用的内部实现细节。这样可以将健康检查的逻辑与应用代码分离,提高可维护性。

当然,ExecAction也有一些局限性:

  • 性能开销:每次探测都需要在容器内启动一个新的进程来执行命令,这会消耗一定的CPU和内存资源。如果探测频率较高,可能会对应用性能产生影响;

  • 安全风险:由于需要在容器内执行命令,因此必须谨慎地控制命令的权限和范围。如果使用了不安全的命令或者参数,可能会被攻击者利用,从而威胁到整个系统的安全;

  • 故障诊断:当ExecAction探测失败时,我们只能知道命令的退出码,但无法知道具体的失败原因。这会给问题诊断和修复带来一定的难度。

3.2 TCPSocketAction

TCPSocketActionKubernetes探针的一种探测方式,它通过尝试与容器的指定端口建立TCP连接来判断容器是否健康。如果能够成功建立连接,就认为容器是正常的;如果无法建立连接,就认为容器是异常的。

TCPSocketAction的工作原理是:

  1. Kubelet根据探针的配置,周期性地尝试与容器的指定端口建立TCP连接;
  2. 如果连接成功建立,Kubelet就认为本次探测成功,并记录探测结果;
  3. 如果连接建立失败(比如端口未监听、拒绝连接、超时等),Kubelet就认为本次探测失败,并记录探测结果;
  4. Kubelet会根据连续成功或失败的次数,来决定将容器判定为正常还是异常。

相比于ExecActionTCPSocketAction的优点是:

  • 更轻量级:不需要在容器内执行命令,对容器的影响更小;

  • 更通用:不依赖于容器内的任何程序,适用于所有基于TCP的应用;

  • 更安全:不需要在容器内执行任意命令,降低了安全风险。

但是,TCPSocketAction也有一些局限性:

  • 只能检测端口的可达性,无法判断应用的实际状态;

  • 对于HTTP等基于TCP的应用层协议,无法检测具体的HTTP状态码、响应内容等;

  • 如果容器内的进程绑定了本地回环地址(127.0.0.1),则TCPSocketAction可能会误判;

TCPSocketAction仍然是一种非常实用的探测方式。特别是对于一些无状态的服务(如RedisMemcached等),我们通常只需要确保服务端口可达,而不需要关心更多的细节,这时使用TCPSocketAction就非常方便。

在实际使用中,我们需要根据应用的特点,合理地配置TCPSocketAction的参数,主要包括:

参数名称描述默认值
port要探测的容器端口号。HTTPGetAction会向该端口发送HTTP GET请求。-
host要连接的主机名或IP地址。默认为PodIP,也可以指定为一个域名。PodIP
scheme连接使用的协议,必须是HTTPHTTPSHTTP
path访问的HTTP服务器的路径。/
httpHeaders请求中自定义的HTTP头。这可以用于传递一些特殊的参数,如身份验证信息等。-
initialDelaySeconds容器启动后到开始执行探测的延迟时间,单位为秒。这个参数可以用于给应用程序一个初始化的时间,避免过早地开始探测。0
periodSeconds执行探测的频率,单位为秒。10
timeoutSeconds探测的超时时间,单位为秒。如果在指定时间内没有收到响应,则认为探测失败。1
successThreshold探测失败后认为成功的最小连续成功次数。1
failureThreshold探测成功后认为失败的最小连续失败次数。如果连续失败达到指定次数,就认为容器已经不健康,需要重启或移除。3

通过调整这些参数,我们可以控制探测的时机、频率和灵敏度,以适应不同应用的需求。同时,我们还要注意设置合理的初始延迟和超时时间,以避免因为应用启动慢或者网络延迟而导致探测失败。

3.3 HTTPGetAction

HTTPGetActionKubernetes探针的一种探测方式,它通过向容器发送HTTP GET请求来判断容器的健康状态。与ExecActionTCPSocketAction不同,HTTPGetAction在应用层面检查容器的状态,因此可以执行更加精细和定制化的健康检查逻辑。

HTTPGetAction的工作原理是:

  1. Kubelet根据探针的配置,定期向容器的指定端口发送一个HTTP GET请求。请求的路径、端口、HTTP头等参数都可以在探针中自定义;
  2. 容器收到请求后,由应用程序根据自身的业务逻辑进行处理,并返回一个HTTP响应;
  3. Kubelet接收到HTTP响应后,根据响应的状态码来判断探测是否成功。如果状态码在200到400之间(不包括400),则认为探测成功,否则认为探测失败;
  4. 如果探测成功,Kubelet会认为容器是健康的,继续执行下一次探测。如果探测失败,Kubelet会根据探针的类型(存活、就绪、启动)和重试次数等参数,决定是否重启容器或者将容器从Service的后端中移除。

可以看到,HTTPGetAction将健康检查的主动权交给了应用程序自身。这意味着我们可以在应用程序中实现任意的健康检查逻辑,例如:

  • 检查应用程序的关键组件(如数据库连接、缓存服务等)是否正常工作

  • 检查应用程序的内部状态和性能指标是否正常

  • 根据业务规则动态调整应用程序的健康状态

  • 提供一个专门用于健康检查的API接口,与应用程序的业务接口分离

ExecActionTCPSocketAction相比,HTTPGetAction的优点在于:

  1. 更加灵活和可定制。我们可以在应用程序中自由地实现健康检查逻辑,而不仅仅局限于进程状态和端口连通性;
  2. 与应用程序的业务逻辑更加贴近。通过在应用层面进行健康检查,我们可以更全面地评估应用程序的真实状态,而不是仅仅依赖底层的运行状态;
  3. 可以与应用程序的其他管理功能相结合,例如监控、告警、自愈等,构建一个完整的应用程序管理体系。

当然,HTTPGetAction也有一些需要注意的地方:

  1. 应用程序需要提供一个专门用于健康检查的API接口,这会增加开发和维护的工作量;
  2. 健康检查接口的实现需要谨慎,不能影响应用程序的正常服务,也不能占用过多的系统资源;
  3. 健康检查接口本身的可用性和正确性也需要保证。如果接口本身出现问题,会影响到整个健康检查机制的可靠性。

4. 探针通用参数

Kubernetes中,不同类型的探针都支持一些通用的配置参数,通过这些参数我们可以精细地控制探针的行为。下面我们重点介绍几个关键的参数:

  • initialDelaySeconds: 容器启动后到开始执行探测的延迟时间,单位为秒。默认为0,即容器启动后立即开始探测。但在实际场景中,许多应用在刚启动时还无法正常提供服务,如果立即开始探测可能会产生大量的失败记录,甚至导致容器被误杀。通过设置一个合理的initialDelaySeconds,我们可以给应用一个启动的缓冲期,避免过早探测引起的问题。

  • periodSeconds: 执行探测的频率,单位为秒。默认为10秒,即每10秒执行一次探测。选择探测频率需要权衡及时发现故障和减少探测开销两个因素:

    • 频率太低可能会延迟故障的发现,影响应用的可用性;

    • 频率太高又会增加探测对系统资源的消耗,尤其是使用ExecHTTP探测方式时。

通常建议根据应用的重要程度和状态变化频率来设置,核心应用可以设置得更频繁一些。

  • timeoutSeconds: 探测的超时时间,单位为秒。默认为1秒,即在1秒内如果没有得到探测结果,就认为本次探测超时失败。超时时间的设置需要考虑到容器内进程的响应时间、网络延迟等因素。

    • 如果超时时间太短,正常的探测可能会因为偶尔的延迟而失败;

    • 如果超时时间太长,故障的发现时间就会被延后。

通常建议根据应用的实际响应时间来设置,留出一定的冗余度。

  • successThreshold: 探测失败后认为成功的最小连续成功次数。默认为1,即探测一次成功就认为容器恢复正常。

    在实际使用中,有时为了防止探测结果的抖动,我们会通过successThreshold要求连续多次探测成功才认为恢复正常。例如设置为3,就是要求连续3次探测都成功,才最终判定为成功。

    这个值不宜设置得过大,否则会延长故障状态到恢复状态的判定时间。

  • failureThreshold: 探测成功后认为失败的最小连续失败次数。默认为3,即连续3次探测失败才最终判定为失败。

    failureThreshold的作用是防止偶发的探测失败导致容器被频繁重启。

    只有连续多次失败,才认为容器的确出现了故障,需要重启或移除流量。

    successThreshold类似,这个值也不宜设置得过大,以免延长故障的发现时间。

5. 探针的应用

5.1 在Deployment中使用存活探针和就绪探针

首先,我们创建一个名为liveness-readiness-probe.yaml的文件,内容如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: my-app
        image: my-app:v1
        ports:
        - containerPort: 8080
        livenessProbe:
          httpGet:
            path: /healthz
            port: 8080
          initialDelaySeconds: 30 # 容器启动后30秒开始执行存活探针
          periodSeconds: 10       # 每10秒执行一次存活探针
          timeoutSeconds: 5       # 每次探针超时时间为5秒
          failureThreshold: 3     # 连续3次探测失败则重启容器
        readinessProbe:
          httpGet:
            path: /ready
            port: 8080
          initialDelaySeconds: 10 # 容器启动后10秒开始执行就绪探针
          periodSeconds: 5        # 每5秒执行一次就绪探针
          timeoutSeconds: 2       # 每次探针超时时间为2秒
          successThreshold: 3     # 连续3次探测成功则将Pod标记为就绪

在这个例子中,我们定义了一个Deployment,它管理着3个副本的my-app应用。在template部分,我们定义了容器的规格,包括存活探针(livenessProbe)和就绪探针(readinessProbe)。

对于存活探针,我们使用了httpGet方式,它会向容器的8080端口发送一个GET请求,请求路径为/healthz。我们设置initialDelaySeconds30秒,给应用一个启动的缓冲期。之后每隔10秒(periodSeconds)执行一次探测,如果连续3次(failureThreshold)探测失败,每次探测超过5秒(timeoutSeconds)未响应,就会重启容器。

对于就绪探针,我们也使用了httpGet方式,但是请求路径为/ready。就绪探针的initialDelaySeconds设置为10秒,比存活探针更短,因为通常应用可以更快地准备好接受请求。之后每隔5秒执行一次探测,如果连续3次(successThreshold)探测成功,每次探测超过2秒未响应,就会将Pod标记为就绪,加入到Service的后端。

现在,我们应用这个Deployment

kubectl apply -f liveness-readiness-probe.yaml

然后我们可以观察Pod的状态变化:

kubectl get pods -w

输出结果类似如下:

NAME                      READY   STATUS    RESTARTS   AGE
my-app-6c5f79d9f8-7bglx   0/1     Running   0          5s
my-app-6c5f79d9f8-7bglx   0/1     Running   1          36s
my-app-6c5f79d9f8-7bglx   1/1     Running   1          46s
my-app-6c5f79d9f8-kdg9p   0/1     Running   0          5s
my-app-6c5f79d9f8-kdg9p   0/1     Running   1          36s
my-app-6c5f79d9f8-kd

我们可以看到,Pod刚创建时,READY列为0/1,表示还没有通过就绪探针。30秒后,存活探针开始执行,如果/healthz接口没有正确响应,Pod会被重启,RESTARTS列会增加。当/ready接口正确响应后,Pod最终变为就绪状态,READY列变为1/1

如果我们删除存活探针和就绪探针的定义,再次应用这个Deployment,会发现Pod的行为有所不同:

  • 没有了存活探针,即使应用程序无响应,Pod也不会被自动重启,可能会长时间处于不可用状态;

  • 没有了就绪探针Pod会立即被标记为就绪,加入到Service的后端,但此时应用程序可能还没准备好处理请求,导致请求失败。

通过这个例子,我们可以直观地感受到存活探针和就绪探针在保障应用可用性方面的重要作用。合理地使用探针,配置适当的参数,可以让Kubernetes更智能地管理应用程序,提高整个系统的稳定性和恢复能力。

当然,探针的定义还需要根据实际的应用特点来调整。我们需要提供相应的/healthz/ready接口,合理设置初始延迟和超时时间等参数。

5.2 为有启动时间的应用配置启动探针

在实际生产环境中,我们经常会遇到一些启动时间较长的应用,比如需要加载大量配置、初始化缓存、建立外部连接等。这些应用在刚启动时,虽然进程已经运行,但是还无法正常提供服务。如果我们直接使用存活探针来检测这类应用,就很可能会出现存活探针在应用完全启动之前,连续多次探测失败,导致容器被反复重启,影响服务的可用性。

这时,启动探针就可以帮助我们解决这个问题。我们可以在应用的容器规范中同时定义启动探针和存活探针:

  • 启动探针负责检查应用是否已经启动完成,如果检查失败,会继续等待下一次检查,直到达到最大失败次数(failureThreshold)才会重启容器。

  • 存活探针负责检查应用是否健康运行,如果检查失败,会立即重启容器。

关键的是,在启动探针检查成功之前,存活探针是不会执行的。这就给了应用一个"启动保护期",避免了存活探针过早介入而引发的问题。

下面是一个具体的例子,假设我们有一个Java应用需要60秒的时间来完成初始化,我们可以为它配置如下的启动探针和存活探针:

apiVersion: v1
kind: Pod
metadata:
  name: my-app
spec:
  containers:
  - name: my-app
    image: my-app:v1.0
    startupProbe:
      httpGet:
        path: /healthz
        port: 8080
      failureThreshold: 6  # 最多允许失败6次,即60秒的启动时间
      periodSeconds: 10    # 每10秒执行一次
    livenessProbe:
      httpGet:
        path: /healthz
        port: 8080
      initialDelaySeconds: 60  # 启动60秒后开始执行存活探针
      periodSeconds: 5         # 每5秒执行一次

在这个例子中:

  • startupProbe使用HTTP GET请求访问容器的/healthz接口,如果返回2xx3xx的状态码则认为成功。startupProbe每10秒执行一次(periodSeconds: 10),最多允许失败6次(failureThreshold: 6),即容器有60秒的时间来完成启动。

  • livenessProbe同样使用HTTP GET请求访问/healthz接口,不同的是它要求容器启动60秒后才开始第一次检查(initialDelaySeconds: 60),然后每5秒执行一次(periodSeconds: 5)。

这样,在容器启动的前60秒内,只有startupProbe在执行,而livenessProbe不会干扰应用的启动。一旦startupProbe检查通过,说明应用已经完全启动,这时livenessProbe才开始工作,监控应用的运行健康状态。

如果应用在启动过程中出现了问题,无法在60秒内完成启动,那么startupProbe会在重试6次后重启容器,而不是让应用一直处于中间状态。

可见,启动探针提供了一种"延时"机制,推迟了存活探针的介入时间,避免了存活探针因为应用启动慢而过早判定失败的问题。同时,启动探针本身也会检查应用的启动状态,确保应用可以在有限的时间内完成启动,如果启动失败,则会通过重启容器来恢复。

5.3 使用HTTP探针实现业务层面的健康检查

在前面的章节中,我们介绍了存活探针和就绪探针主要关注容器的运行状态,比如进程是否存在、端口是否可达等。但是有时候,我们还需要在更高的层次上对应用的健康状态进行检查,比如:

  • 应用的关键依赖(如数据库、缓存等)是否正常可用;

  • 应用的关键流程(如登录、下单等)是否能够正常执行;

  • 应用的性能指标(如响应时间、错误率等)是否在正常范围内。

这就需要我们在应用程序内部实现一个专门用于健康检查的接口,通过这个接口暴露应用的内部状态,然后在Kubernetes中用HTTP探针来访问该接口,以判断应用的实际健康情况。

假设我们有一个Django应用,它有两个关键依赖:一个MySQL数据库和一个Redis缓存。

我们希望在健康检查中判断这两个依赖是否可用,同时还要检查Django应用自身是否能够正常处理请求。

首先,我们在Django应用中实现一个/health接口:

urlpatterns = [
    ...
    path('health/', views.health),
    ...
]
from django.http import HttpResponse
from django.db import connections
from django.db.utils import OperationalError
from redis import Redis, ConnectionError

def health(request):
    try:
        # 检查MySQL连接
        cursor = connections['default'].cursor()
        cursor.execute("SELECT 1")
        cursor.fetchone()

        # 检查Redis连接
        redis = Redis(host='redis', port=6379)
        redis.ping()

        # 检查Django应用
        return HttpResponse("OK", status=200)

    except OperationalError:
        return HttpResponse("MySQL is not available", status=500)

    except ConnectionError:
        return HttpResponse("Redis is not available", status=500)

    except Exception as e:
        return HttpResponse(str(e), status=500)

通过这个/health接口,我们可以检查Django应用的关键依赖,如数据库连接、缓存连接等是否正常。这比仅仅检查进程是否存在、端口是否可达更进一步,能够反映应用的真实健康状态。

这个/health接口中,我们分别检查了MySQL连接、Redis连接和Django应用本身的可用性:

  • 对于MySQL,我们通过Djangoconnections对象获取到默认的数据库连接,然后执行一个简单的SELECT 1查询。如果查询成功,说明MySQL连接是正常的;

  • 对于Redis,我们直接创建了一个Redis客户端,然后调用ping()方法。如果连接正常,该方法会返回True,否则会抛出ConnectionError异常;

  • 对于Django应用,我们直接返回了一个200状态码的HttpResponse,表示应用是健康的。如果在处理请求的过程中发生了任何异常,Django会自动返回500错误;

  • 如果所有检查都通过,/health接口会返回字符串’**OK’**和状态码200,表示应用是健康的;

  • 如果MySQL连接失败,就会返回**‘MySQL is not available’**和状态码500;

  • 如果Redis连接失败,就会返回**‘Redis is not available’**和状态码500;

  • 如果发生了其他异常,就会返回错误信息和状态码500

有了这个/health接口,我们就可以在Kubernetes中使用HTTP探针来访问它,以实现业务层面的健康检查。下面是一个Deployment的示例配置:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: my-app
        image: my-app:v1
        ports:
        - containerPort: 8000
        livenessProbe:
          httpGet:
            path: /health
            port: 8000
          initialDelaySeconds: 30  # 容器启动30秒后开始存活性探测
          periodSeconds: 10        # 每10秒执行一次存活性探测
        readinessProbe:
          httpGet:
            path: /health
            port: 8000
          initialDelaySeconds: 10  # 容器启动10秒后开始就绪性探测
          periodSeconds: 5         # 每5秒执行一次就绪性探测

在这个Deployment中,我们为容器定义了存活探针(livenessProbe)和就绪探针(readinessProbe),它们都使用HTTP GET方法访问容器的/health接口,以判断应用的健康状态。

  • 对于存活探针,我们设置了initialDelaySeconds: 30,即容器启动30秒后才开始探测,以留出时间让应用完成初始化。之后每隔10秒(periodSeconds: 10)执行一次探测。

  • 对于就绪探针,我们设置了initialDelaySeconds: 10,即容器启动10秒后就开始探测,以尽快将健康的Pod加入到Service中。之后每隔5秒(periodSeconds: 5)执行一次探测。

通过这样的配置,Kubernetes就可以根据/health接口返回的状态来判断应用的真实健康状况:

  • 如果/health返回200,说明MySQL、Redis、Django应用都是正常的,Kubernetes会认为容器是健康的,不会重启容器,并且会将其加入到Service的后端。
  • 如果/health返回500,说明某个依赖出现了问题,Kubernetes会认为容器是不健康的,会根据存活探针的重启策略重启容器,并根据就绪探针的结果将其从Service的后端移除,避免将流量导入异常的Pod。

这样,我们就通过一个简单的/health接口和HTTP探针,实现了对应用内部状态的全面检查。这种方式相比于仅检查进程和端口,能够更加准确地反映应用的实际健康状况,帮助我们及时发现和解决问题。

当然,/health接口的实现需要根据应用的实际架构和依赖来设计,上面的例子只是一个简单的演示。在实际项目中,我们可能需要检查更多的依赖和服务,判断更复杂的业务逻辑,甚至根据不同的严重程度返回不同的状态码。但是无论如何,遵循"在应用层面暴露健康状态,在Kubernetes层面通过HTTP探针来检查"的基本思路,我们就可以灵活地实现自己的健康检查策略,让Kubernetes更好地管理我们的应用。

除了在Deployment中单独使用,HTTP探针还可以与其他Kubernetes特性结合,发挥更大的作用。比如在滚动更新时,我们可以通过就绪探针来控制新旧版本的切换时机,确保应用在整个更新过程中保持可用。这部分内容我们将在后面的"5.4 利用探针实现零停机滚动更新"一节中详细介绍。

5.4 利用探针实现零停机滚动更新

Kubernetes中,我们通常使用Deployment来管理应用的多个副本,并通过更新Deployment的方式来实现应用的滚动更新。滚动更新的基本过程是:

  1. 创建一个新版本的Pod;
  2. 等待新Pod就绪;
  3. 将流量切换到新Pod;
  4. 删除旧版本的Pod。

在这个过程中,如果新Pod在就绪之前就开始接收流量,就可能会导致请求失败,影响服务的可用性。这时,就绪探针就可以发挥作用了。

我们可以在Deployment中为新版本的Pod模板定义一个就绪探针,例如:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: my-app
        image: my-app:v2
        readinessProbe:
          httpGet:
            path: /healthz
            port: 8080
          initialDelaySeconds: 10  # 容器启动10秒后开始就绪探测
          periodSeconds: 5         # 每5秒执行一次就绪探测
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1  # 滚动更新时最多有1个Pod不可用
      maxSurge: 1        # 滚动更新时最多可以超出期望副本数1个Pod

在这个例子中,我们为新版本的Pod模板定义了一个基于HTTP GET的就绪探针:

  • 探针会向Pod的8080端口发送一个GET请求,请求路径为/healthz

  • 探针会在容器启动10秒后开始执行(initialDelaySeconds),然后每5秒执行一次(periodSeconds);

  • 如果探针请求的HTTP状态码在200到400之间,则认为Pod已经就绪,可以接收流量;否则,认为Pod尚未就绪,不会将其加入到Service的Endpoint中;

我们还通过.spec.strategy字段设置了滚动更新的策略:

  • maxUnavailable: 1表示在滚动更新过程中,最多可以有1个Pod处于不可用状态;

  • maxSurge: 1表示在滚动更新过程中,最多可以超出期望副本数1个Pod

这样可以更精细地控制滚动更新的过程,避免一次性创建或删除过多的Pod,从而影响服务的可用性。

现在,当我们更新这个Deployment时(比如修改Pod模板中的镜像版本),Kubernetes就会执行滚动更新:

  1. Kubernetes会先创建一个新版本的Pod
  2. Pod启动后,就绪探针会开始探测其是否已经就绪;
  3. 只有当新Pod通过就绪探针检查后,Kubernetes才会将其加入到ServiceEndpoint中,开始向其发送流量;
  4. 与此同时,Kubernetes会逐渐减少旧版本Pod的数量,直到所有旧Pod都被替换为止。

在整个滚动更新过程中,就绪探针起到了这些作用:

  • 它确保只有就绪的新Pod才会被加入到Service中,接收流量。这避免了将请求发送到尚未准备好的Pod,保证了服务的连续可用;

  • 它与Kubernetes的滚动更新策略配合,实现了平滑的版本切换。在任何时刻,总有一定数量的Pod(新版本+旧版本)可以处理请求,从而实现了零停机;

假设我们有一个简单的Web应用,提供一个/接口返回一个问候语,一个/healthz接口用于健康检查:

const express = require('express');
const app = express();

// 应用版本
const version = process.env.APP_VERSION || 'v1';

// 问候接口
app.get('/', (req, res) => {
  res.send(`Hello, world! This is ${version}.`);
});

// 健康检查接口
app.get('/healthz', (req, res) => {
  // 检查应用的关键依赖,比如数据库连接
  const dbConnected = checkDatabaseConnection();
  
  if (dbConnected) {
    res.sendStatus(200);
  } else {
    res.sendStatus(500);
  }
});

// 启动服务器
const port = process.env.PORT || 8080;
app.listen(port, () => {
  console.log(`Server is running at http://localhost:${port}`);
});

// 检查数据库连接的函数(这里只是一个示例,实际情况根据应用的架构而定)
function checkDatabaseConnection() {
  // ...
  return true;  // 假设数据库连接正常
}

我们将这个应用打包成两个版本的Docker镜像:myapp:v1myapp:v2,它们的唯一区别是环境变量APP_VERSION的值不同。

然后,我们创建一个Deployment来运行这个应用,并设置就绪探针:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: myapp
        image: myapp:v1
        readinessProbe:
          httpGet:
            path: /healthz
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
  name: myapp
spec:
  selector:
    app: myapp
  ports:
  - port: 80
    targetPort: 8080
  type: LoadBalancer  # 使用LoadBalancer类型,便于从集群外部访问

我们同时创建了一个Service,将流量导向带有app: myapp标签的Pod。当我们应用这些资源时,Kubernetes会创建3个myapp:v1Pod

一旦这些Pod通过就绪探针检查,它们就会被添加到myapp服务的Endpoint中,开始接收流量。

现在,我们可以通过服务的外部IP来访问应用:

kubectl get svc myapp
NAME    TYPE           CLUSTER-IP     EXTERNAL-IP      PORT(S)        AGE
myapp   LoadBalancer   10.98.219.86   123.45.67.890   80:31000/TCP   5m
curl http://123.45.67.890
Hello, world! This is v1.

接下来,我们将DeploymentYAML文件中的镜像更新为myapp:v2,然后用kubectl apply来应用这个更改,模拟一次应用升级:

kubectl apply -f myapp-deployment.yaml
deployment.apps/myapp configured

这时,Kubernetes会开始执行滚动更新:

  1. 创建一个新的myapp:v2Pod
  2. 等待新Pod通过就绪探针检查;
  3. 将新Pod添加到服务的Endpoint中,开始向其发送流量;
  4. 逐渐减少myapp:v1Pod数量,直到全部替换为myapp:v2

在这个过程中,我们可以不断地访问服务,观察其返回结果的变化:

while true; do curl http://123.45.67.890; sleep 1; done
Hello, world! This is v1.
Hello, world! This is v1.
Hello, world! This is v2.
Hello, world! This is v1.
Hello, world! This is v2.
Hello, world! This is v2.
...

可以看到,在滚动更新的过程中,服务会逐渐将流量从v1切换到v2,但在任何时刻,服务都是可用的,请求不会失败。这就实现了零停机的滚动更新。

如果我们在更新过程中描述Deployment的状态,就可以清楚地看到整个过程:

kubectl describe deploy myapp
...
Conditions:
  Type           Status  Reason
  ----           ------  ------
  Available      True    MinimumReplicasAvailable
  Progressing    True    ReplicaSetUpdated
OldReplicaSets:  myapp-5f88c99fb8 (1/1 replicas created)
NewReplicaSet:   myapp-658d4f5d67 (2/2 replicas created)
...

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/666213.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C/S模型测试

1 1.1代码示例 #include<stdio.h> #include<stdio.h>#include <sys/types.h> /* See NOTES */ #include <sys/socket.h>#include <netinet/in.h> #include <netinet/ip.h> /* superset of previous */ #include <arpa/inet.…

SpringMVC:创建一个简单的SpringMVC框架

目录 一、框架介绍 两个重要的xml文件 SpringMVC执行流程 二、Vscode搭建SpringMVC框架 1、maven创建webapp原型项目 2、pom.xml下添加springmvc的相关依赖 3、在web.xml配置 4、springmvc.xml的配置 5、编写Controller控制器类 6、 编写JSP界面 7、项目结构图 一…

12.RedHat认证-Linux文件系统(下)

12.RedHat认证-Linux文件系统(下) swap虚拟内存 我加一个硬盘做实验sdc # 创建交换分区&#xff08;不用做成逻辑卷也能灵活分区&#xff09; [rootcentos8 ~]# fdisk /dev/sdc -l Disk /dev/sdc&#xff1a;10 GiB&#xff0c;10737418240 字节&#xff0c;20971520 个扇区 …

【监控】prometheus自定义指标 exporter

一、【写在前面】 prometheus自定义指标本质是用代码自己写一个网络访问的采集器&#xff0c;你可以在官网看到&#xff0c;Client libraries | Prometheus官方支持的语言有GO JAVA PYTHON RUBY RUST, 第三方的库就支持的更多了&#xff0c;有BASH C CPP LUA C# JS PHP R PER…

ADuM1201可使用π121U31间接替换π122U31直接替换

ADuM1201可使用π121U31间接替换π122U31直接替换 一般低速隔离通信150Kbps电路可使用π121U31&#xff0c;价格优势较大。速度快的有其它型号可达10M,200M,600M。 本文主要介绍ADUM1201,替换芯片π121U31简单资料请访问下行链接 只要0.74元的双通道数字隔离器&#xff0c;1T1…

智和信通助力中国移动湖南某市分公司县级政府外网运维项目

中国移动湖南某市分公司承建市下属某县政务外网网络建设项目&#xff0c;且在网络建设完工后&#xff0c;承担起运维职责&#xff0c;随着工作的推进市移动公司发现仅靠人力难以高效开展运维工作。 设备类型&#xff1a;OLT、ONU等通信设备 设备品牌&#xff1a;华为、中兴等…

记一次安卓.apk加固,加固后安装失败,重新签名也安装失败问题

1、AndroidStudio打包生成.apk文件 2、使用360加固apk&#xff08;或其他平台&#xff09; 注意&#xff1a;加固后的apk必须进行重新签名才能安装&#xff0c;否则安装失败。apk签名可以使用jarsigner 和 apksigner&#xff0c;jarsigner 只能进行v1签名&#xff1b;apksigner…

【PostgreSQL17新特性之-冗余IS [NOT] NULL限定符的处理优化】

在执行一个带有IS NOT NULL或者NOT NULL的SQL的时候&#xff0c;通常会对表的每一行&#xff0c;都会进行检查以确保列为空/不为空&#xff0c;这是符合常理的。 但是如果本身这个列上有非空&#xff08;NOT NULL&#xff09;约束&#xff0c;再次检查就会浪费资源。甚至有时候…

Git使用规范及命令

文章目录 一、Git工作流二、分支管理三、Git命令操作规范1. 切到develop分支&#xff0c;更新develop最新代码2. 新建feature分支&#xff0c;开发新功能3. 完成feature分支&#xff0c;合并到develop分支4. 当某个版本所有的 feature 分支均合并到 develop 分支&#xff0c;就…

CSS--学习

CSS 1简介 1.1定义 层叠样式表 (Cascading Style Sheets&#xff0c;缩写为 CSS&#xff09;&#xff0c;是一种 样式表 语言&#xff0c;用来描述 HTML 文档的呈现&#xff08;美化内容&#xff09;。 1.2 特性 继承性 子级默认继承父级的文字控制属性。层叠性 相同的属性…

不借助三方平台自主搭建量化回测系统 ——以海龟交易策略为例

数量技术宅团队在CSDN学院推出了量化投资系列课程 欢迎有兴趣系统学习量化投资的同学&#xff0c;点击下方链接报名&#xff1a; 量化投资速成营&#xff08;入门课程&#xff09; Python股票量化投资 Python期货量化投资 Python数字货币量化投资 C语言CTP期货交易系统开…

Vue 2.0使用Vue-count-to给数字添加增长动画

在开发后台管理系统时&#xff0c;时常会遇到数据汇总&#xff0c;为了页面展示更生动&#xff0c;用户体验更好&#xff0c;通常会对汇总的数字加一个逐步递增动画。 实现这个效果一般是用的 Vue-count-to这个插件&#xff0c;这是一款简单好用的一个数字滚动插件&#xff0c;…

前端传String字符串 后端使用enun枚举类出现错误

情况 前端 String 后端 enum 前端 后端 报错 2024-05-31T21:47:40.61808:00 WARN 21360 --- [nio-8080-exec-6] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.method.annotation.MethodArgumentTypeMismatchException: Failed to con…

OSPF状态机+SPF算法

OSPF状态机 1.点到点网络类型 down-->init-->(前提为可以建立邻接)exstart——>exchange-->若查看邻接的DBD 目录后发现不用进行LSA 直接进入ful。若查看后需要进行查询、应答先进入loading&#xff0c;在查询应答完后再进入 fuIl: 2.MA网络类型 down --&g…

Linux下配置Pytorch

1.Anaconda 1.1虚拟环境创建 2.Nvidia驱动 3.CUDA驱动安装 4.Pytorch安装 具体的步骤如上&#xff1a;可参考另一位博主的博客非常详细&#xff1a; Linux服务器配置PythonPyTorchCUDA深度学习环境_linux cuda环境配置-CSDN博客https://blog.csdn.net/NSJim/article/detai…

民国漫画杂志《时代漫画》第35期.PDF

时代漫画35.PDF: https://url03.ctfile.com/f/1779803-1248636125-ee3a2b?p9586 (访问密码: 9586) 《时代漫画》的杂志在1934年诞生了&#xff0c;截止1937年6月战争来临被迫停刊共发行了39期。 ps: 资源来源网络!

微信小程序-页面导航-导航传参

1.声明式导航传参 navigator组件的url属性用来指定将要跳转到的页面的路径&#xff0c;同时&#xff0c;路径的后面还可以携带参数&#xff1a; &#xff08;1&#xff09;参数与路径之间使用 ? 分割 &#xff08;2&#xff09;参数键与参数值用 相连 &#xff08;3&…

LeetCode503:下一个更大元素Ⅱ

题目描述 给定一个循环数组 nums &#xff08; nums[nums.length - 1] 的下一个元素是 nums[0] &#xff09;&#xff0c;返回 nums 中每个元素的 下一个更大元素 。 数字 x 的 下一个更大的元素 是按数组遍历顺序&#xff0c;这个数字之后的第一个比它更大的数&#xff0c;这…

CSwin-PNet 新的医学图像分割网络

很长时间没有看到一些比较传统的医学图像分割网络了&#xff0c;2022年&#xff0c;来自哈尔滨工业大学的研究团队在Expert Systems With Applications. 期刊上发表了题为《CSwin-PNet: A CNN-Swin Transformer combined pyramid network for breast lesion segmentation in ul…

Web前端三大主流框:React、Vue 和 Angular

在当今快速发展的 Web 开发领域&#xff0c;选择合适的前端框架对于项目的成功至关重要。React、Vue 和 Angular 作为三大主流前端框架&#xff0c;凭借其强大的功能和灵活的特性&#xff0c;赢得了众多开发者的青睐。本文将对这三大框架进行解析&#xff0c;帮助开发者了解它们…