原文发表于kubernetes中文社区,为作者原创翻译 ,原文地址

更多kubernetes文章,请多关注kubernetes中文社区

目录

背景:容器被广泛使用

优雅停机( Gracefully Shutdown )

存活状态( Liveness )

就绪状态( Readiness )

1.如何更新状态?

2.如何获取应用状态?

示例:Spring Boot 2.3.0配置Readiness和Liveness

代码示例

测试:Readiness和Liveness


Spring Boot团队最近发布了2.3.0版本,该版本具有许多增强功能,升级功能。(具体可以参考,Spring Boot 2.3.0 发行说明)。

在早些时候,Spring团队就已经宣布2.3版本将专注于Kubernetes。考虑到这一点,本文我们将尝试探索其中一些功能。

背景:容器被广泛使用

众所周知,容器是独立的单元,它是将应用程序及其依赖项打包为一个单元。因此,应用程序可以实现从一个环境快速可靠地迁移运行到另一个环境。*只要不同环境中存在兼容的容器运行时,就可以满足容器的可移植性,。

容器是操作系统虚拟化的一种,它包含运行应用程序所需的一切:可执行二进制文件,类库,配置文件,配置文件和系统库。与服务器虚拟化相比,容器不为每个虚拟环境提供单独的操作系统,容器使用主机上的操作系统。

由于对容器的前所未有的使用,对诸如KubernetesDocker Swarm之类的容器编排和管理工具的需求也在增加。这些工具通过许多功能简化了我们管理容器的工作,这些功能包括:

  • 容器分组 ( Container grouping )
  • 自我修复
  • 自动扩展
  • DNS管理
  • 负载均衡
  • 滚动更新或回滚
  • 资源监控和记录

几乎所有功能都围绕应用程序(容器)提供以下方面的状态:

  • 优雅停机( Gracefully Shutdown )
  • 存活状态( Liveness )
  • 就绪状态( Readiness )

让我们详细研究它们中的每一个,以及如何在启动Spring Boot 2.3.0中实现它们。

优雅停机( Gracefully Shutdown )

通常,优雅停机意味着在关闭应用程序之前,应设置超时期限,以允许仍在进行中的请求操作完成。在此超时期间,将不允许新请求。这将使你在应用请求处理方面保持一致,即没有未处理请求。

Spring Boot 2.3.0.RELEASE引入了Graceful Shutdown的功能。其中所有四个嵌入式Web服务器(Tomcat,Undertow,Netty和Jetty)都为响应式和基于Servlet的Web应用程序提供优雅停机功能。优雅停机是关闭应用程序上下文的一部分,并且在SmartLifecycle bean停止的最早阶段执行。

应用程序在宽限期内停止新请求的恰当方式,取决于所使用的服务器。根据官方文档Tomcat,Jetty和Reactor Netty将在网络层停止接受请求。Undertow将接受请求,但立即会以HTTP 503(服务不可用)来响应。

请注意,Tomcat 9.0.33或更高版本,才具备优雅停机功能。

在Spring Boot 2.3.0中,优雅停机非常容易实现,并且可以通过在应用程序配置文件中设置两个属性来进行管理。

  1. server.shutdown:此属性可以支持的值有
    1. immediate:这是默认值,将导致服务器立即关闭。
    2. graceful:启用优雅停机,并遵守spring.lifecycle.timeout-per-shutdown-phase属性中给出的超时。
  2. spring.lifecycle.timeout-per-shutdown-phase:采用java.time.Duration格式的值。

例如:

Properties 文件

# Enable gracefule shutdown
server.shutdown=graceful
# Allow grace timeout period for 20 seconds
spring.lifecycle.timeout-per-shutdown-phase=20s
# Force enable health probes. Would be enabled on kubernetes platform by default
management.health.probes.enabled=true

现在,当我们配置了优雅停机时,可能会有两种可能性:

  1. 应用中没有正在进行的要求。在这种情况下,应用程序将会直接关闭,而无需等待宽限期结束后才关闭。
  2. 如果应用中有正在处理的请求,则应用程序将等待宽限期结束后才能关闭。如果应用在宽限期之后仍然有待处理的请求,应用程序将抛出异常并继续强制关闭。

 

存活状态( Liveness )

就应用程序而言,存活状态是指应用程序的状态是否正常。如果存活状态不正常,则意味着应用程序本身已损坏,无法恢复。在Kubernetes中,如果存活探针检测失败,则kubelet将杀死Container,并且Container将接受其重新启动策略。如果容器未提供存活探针,则默认状态为“ Success ”。

Spring Boot 2.3.0引入了org.springframework.boot.availability.LivenessState。可用状态为

  1. CORRECT :该应用程序正在运行,并且其内部状态正常。
  2. BROKEN:应用程序正在运行,但内部状态被打破。

就绪状态( Readiness )

就绪状态,指的是应用程序是否已准备好接受并处理客户端请求。出于任何原因,如果应用程序尚未准备好处理服务请求,则应将其声明为繁忙,直到能够正常响应请求为止。如果“Readiness”状态尚未就绪,则不应将流量路由到该实例。

例如,在Kubernetes中,如果就绪探针失败,则 Endpoints 控制器将从与Endpoints中删除Pod的IP地址。设置就绪状态为“Failure”。如果容器未提供就绪探针,则默认状态为“Success”。

Spring Boot在ReadinessState的帮助下引入了就绪状态。可以设置的值有:

  1. ACCEPTING_TRAFFIC:应用程序准备好接收流量。这是默认值。
  2. REFUSING_TRAFFIC:应用程序拒绝接收流量。

现在,当我们了解概念后,脑海中会有两个问题。

1.如何更新状态?

Spring Boot选择了Spring应用程序事件模型来更改可用性状态。Spring Boot还配置了ApplicationAvailabilityBean类型的Bean,它是ApplicationAvailability接口的实现。该bean监听这些事件并保持最新状态。因此,我们使用ApplicationAvailability来获取应用程序的状态

// Available as a component in the application context
ApplicationAvailability availability;
LivenessState livenessState = availability.getLivenessState();
ReadinessState readinessState = availability.getReadinessState();

我们还可以使用AvailabilityChangeEvent来更新状态。

2.如何获取应用状态?

最常见的用例是部署支持探针的Web应用程序。Spring boot Actuator是唯一需要的依赖项。因为Spring Boot Actuator已经公开了应用程序运行状况的端点( endpoint )。

启动Spring Boot 2.3.0 Actuator还将在Health指示器中公开可用性状态。这些指标将全部显示在“/actuator/health*”上。这些健康状况端点也可以作为单独的HTTP端点使用:“ /actuator/health/liveness ”和“ /actuator/health/readiness ”。

在Kubernetes平台上运行时,默认情况下这些指标( indicators)包括在actuator/health的endpoint中,而在另一个平台上则不可用。但是,你始终可以通过将management.health.probes.enabled属性设置为true来覆盖此行为。

Properties 文件

# Force enable health probes. Enabled on Kubernetes platform by default
management.health.probes.enabled=true

请注意,所有与可用性相关的组件都org.springframework.boot.availability软件包的一部分。

示例:Spring Boot 2.3.0配置Readiness和Liveness

在我们编写代码之前,请记住

  • 使用ApplicationAvailability获取应用程序的状态。
  • 发布AvailabilityChangeEvent来更新状态。

出于演示的目的,我将在单个RestController中操作,并使用HTTP GET端点调用它们。在理想情况下,这些将由负责管理应用程序不同组件的bean完成。例如缓存,如果缓存失败,则你的缓存bean可以发布ReadinessState.REFUSING_TRAFFIC以拒绝请求到达此应用程序。

代码示例

下面的代码仅用于演示如何根据需要更改状态。不能在生产中使用。

package org.sk.ms.probes;
​
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.availability.AvailabilityChangeEvent;
import org.springframework.boot.availability.LivenessState;
import org.springframework.boot.availability.ReadinessState;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/** 
 * This is sample code to display how to use probes available in spring boot 2.3.0. Not to be used in production. This must be updated by {@link Component} beans for example caching or connection revalidators
 */
@RestController
public class ExampleController {
    private final Logger logger = org.slf4j.LoggerFactory.getLogger(ExampleController.class);
​
    @Autowired
    private  ApplicationEventPublisher eventPublisher;
​
    public ExampleController(ApplicationEventPublisher eventPublisher) {
        this.eventPublisher = eventPublisher;
    }
​
    @GetMapping("/complete-normally")
    public String completeNormally() throws Exception {
        return "Hello from Controller";
    }
    
    @GetMapping("/i-will-sleep-for-30sec")
    public String destroy() throws Exception {
        logger.info("------------------ Sleeping for 30 sec");
        Thread.sleep(30000);
        return "sleep complete";
    }
​
    @GetMapping("/readiness/accepting")
    public String markReadinesAcceptingTraffic() {
        AvailabilityChangeEvent.publish(eventPublisher, this, ReadinessState.ACCEPTING_TRAFFIC);
        return "Readiness marked as ACCEPTING_TRAFFIC";
    }
​
    @GetMapping("/readiness/refuse")
    public String markReadinesRefusingTraffic() {
        AvailabilityChangeEvent.publish(eventPublisher, this, ReadinessState.REFUSING_TRAFFIC);
        return "Readiness marked as REFUSING_TRAFFIC";
    }
​
    @GetMapping("/liveness/correct")
    public String markLivenessCorrect() {
        AvailabilityChangeEvent.publish(eventPublisher, this, LivenessState.CORRECT);
        return "Liveness marked as CORRECT";
    }
​
    @GetMapping("/liveness/broken")
    public String markLivenessBroken() {
        AvailabilityChangeEvent.publish(eventPublisher, this, LivenessState.BROKEN);
        return "Liveness marked as BROKEN";
    }
}

测试:Readiness和Liveness

1.检查可用端点

检查可用端点的初始值:

 

2.宽限期(没有正在进行中的请求)

我们将宽限期设置为20秒。而且没有正在进行中的请求。观察日志。

 

3.宽限期已过

我们有20秒的宽限期。让我们发出一个需要30秒才能完成的请求,然后尝试停止该应用程序。

在这种情况下,应用程序将因错误而关闭。注意观察日志。

 

4.将存活状态更新为“BROKEN”,然后将其设置为“CORRECT”

默认情况下,存活状态是由Spring应用程序上下文设置的。

存活状态,一旦标记为 correct ,它将反映在运行状况端点中,并且容器管理器将知道此实例。

例如,你的应用程序依赖于缓存,并且无法刷新缓存,应用程序的存活状态就会被标记为 broken ,容器管理器会基于配置采取适当的措施处理。

 

5.将就绪状态设置为REFUSING_TRAFFIC,并在恢复后返回ACCEPTING_TRAFFIC

一旦你的应用程序准备好处理请求,可以将就绪状态设置为REFUSING_TRAFFIC。

例如,在缓存更新时,应该将应用程序标记为REFUSING_TRAFFIC,否则可能会处理过时的数据。在这种情况下,容器管理器应停止向该实例发送流量。

但请记住,恢复后应将状态标记回ACCEPTING_TRAFFIC,以便可以将流量再次路由到该实例。

 

你可以在此GitHub仓库找到以上示例的完整代码。

译文链接: https://dzone.com/articles/configuring-graceful-shutdown-readiness-and-livene

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐