深入理解Kubernetes探针和.NET服务健康检查机制

news2024/9/24 11:32:03

前言

随着越来越多的软件采用云原生和微服务架构,我们面临着更多的技术挑战,比如:

  • Kubernetes如何在容器服务异常终止、死锁等情况下,发现并自动重启服务;
  • 当服务依赖的关键服务(例如数据库,Redis)无法正常连接时,如何及时发出告警;
  • 在需要同时部署有依赖关系的服务时,如何确保它们可以按正确的顺序进行初始化;
  • ......

我将介绍如何利用.NET框架的健康检查机制以及Kubernetes的探针来确保微服务集群的稳定性和可靠性。

本文主要分为Kubernetes探针介绍,.NET健康检测机制介绍,最佳实践原则,以及一个简单的Demo。你可以在我的Azure仓库找到完整的项目文件,并在本地部署和测试:

k8s-helm-micro-services - Repos (azure.com)icon-default.png?t=N7T8https://dev.azure.com/1903268310/k8s-helm-tpl/_git/k8s-helm-micro-services

Kubernetes Probe

什么是探针

Kubernetes使用三种容器探针,分别是Liveness、Readiness和Startup,来检查容器的运行状态。

简而言之,探针检测需要在服务中创建健康检查接口,该接口根据语义正确返回服务当前的健康状态。然后,Kubernetes根据部署时的探针配置,自动调用服务的健康检查接口,并根据返回的状态码判断当前服务是否健康。

Kubernetes支持HTTP、TCP和Grpc三种类型的探针接口,本文将重点介绍HTTP类型的探针接口,其中大于等于200并且小于400的状态码表示服务健康,其他状态码表示服务不健康。

Liveness Probe

Liveness Probe用于表示容器服务是否活跃。Kubernetes会在容器生命周期中不断检测Liveness Probe状态,当Liveness Probe返回失败并且次数超过配置阈值时,Kubernetes将认为容器服务已经终止或者无法继续处理请求,进而杀死容器所在的Pod并重新创建。

Readiness Probe

Readiness Probe用于表示服务当前是否可以正常处理请求,例如当服务依赖的某个重要组件状态异常时,我们可以让Readiness接口返回Unhealthy,当Kubernetes发现Readiness探针失败时,它会停止向Pod发送请求,但不会重启Pod,直到服务变为可用为止。

Startup Probe

与前两者不同,启动探针的目的是为了保护启动慢的容器。在很多情况下,我们的服务不得不在启动时完成一些可能费时的操作,在启动探针探测成功之前,Kubernetes不会向Pod发送流量,也不会启动Liveness和Readiness探测。

这样做可以避免故障误判,控制多服务启动顺序,避免过早初始化等。

在Kubernetes为init container提供了更好的支持之后,越来越多的服务开始将Startup检查放在独立的init container来进行,这样的好处是容器职责更加清晰,并且init container作为服务的前置容器,可以使用独立的技术栈,拥有独立的生命周期,且易于批量定制化和替换。

.NET健康检查

接下来,让我们结合.NET项目,在实践中了解Kubernetes探针检测和.NET服务健康检查机制。

服务状态

ASP.NET Core框架将服务定义为三种健康状态:Healthy(状态码:200)、Degraded(状态码:200)、Unhealthy(状态码:503)。

  • Healthy:服务健康,可以正常处理外部请求。
  • Degraded:应用程序处于降级状态,例如依赖的某些非关键服务失败,导致某些功能或性能受影响。Degraded可能是一个暂时的状态,服务可能会在一段时间内恢复到正常状态。
  • Unhealthy:服务处于不健康状态,无法处理外部请求。

IHealthCheck

在.NET项目中,我们需要多种IHealthCheck接口的实现,以检查依赖的服务是否可用。一个广泛应用的健康检查开源库是Github: AspNetCore.Diagnostics.HealthChecks,它提供了许多服务和中间件的健康检查方案。Github: AspNetCore.Diagnostics.HealthChecks

以针对PostgreSQL数据库的检查为例,该检查从服务配置中读取PostgreSQL相关配置,在CheckHealthAsync方法中尝试打开数据库连接并执行一条测试语句。成功执行则认为PostgreSQL服务是健康的;否则,认为数据库服务不健康,并返回相应的健康状态。

public class NpgSqlHealthCheck : IHealthCheck
{
    private readonly NpgSqlHealthCheckOptions _options;

    public NpgSqlHealthCheck(NpgSqlHealthCheckOptions options)
    {
        Debug.Assert(options.ConnectionString is not null || options.DataSource is not null);
        Guard.ThrowIfNull(options.CommandText, true);
        _options = options;
    }

    /// <inheritdoc />
    public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
    {
        try
        {
            await using var connection = _options.DataSource is not null
                ? _options.DataSource.CreateConnection()
                : new NpgsqlConnection(_options.ConnectionString);

            _options.Configure?.Invoke(connection);
            await connection.OpenAsync(cancellationToken).ConfigureAwait(false);

            using var command = connection.CreateCommand();
            command.CommandText = _options.CommandText;
            var result = await command.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false);

            return _options.HealthCheckResultBuilder == null
                ? HealthCheckResult.Healthy()
                : _options.HealthCheckResultBuilder(result);
        }
        catch (Exception ex)
        {
            return new HealthCheckResult(context.Registration.FailureStatus, description: ex.Message, exception: ex);
        }
    }
}

注册健康检测

在.NET项目中,我们可以通过注册实现了IHealthCheck接口的服务来进行健康检测。为了后续创建相应的探针端点,我们可以为每个HealthCheck指定name或者tag。

public const string StartupCheck = "/health/startup";
public const string ReadinessCheck = "/health/ready";
public const string LivenessCheck = "/health/live";

public static IHealthChecksBuilder AddAppsHealthChecks(this IHealthChecksBuilder healthChecksBuilder) =>
    healthChecksBuilder
        .AddCheck<App3StartupCheck>(StartupCheck)
        .AddCheck<App2ReadinessCheck>(ReadinessCheck)
        .AddCheck(LivenessCheck, () => HealthCheckResult.Healthy());

创建探针接口

.NET提供了两种方式来创建健康检测接口:UseHealthChecksMapHealthChecks。Kubernetes将利用这些接口来确定容器当前状态,它们的最大区别在于中间件管道中的顺序不同。

以下是一个示例,我们将创建一个请求路径为"/health/startup"的健康检测端点,该端点将根据HealthCheck的注册名字来筛选Startup Probe所需检查的服务。

        public static IApplicationBuilder UseAppsHealthChecks(this IApplicationBuilder app) =>
            app.AddLivenessProbe().AddReadinessProbe().AddLivenessProbe();

        public static IApplicationBuilder AddStartupProbe(this IApplicationBuilder app) =>
            app.UseHealthChecks(new PathString(StartupCheck), new HealthCheckOptions
            {
                Predicate = check => check.Name == StartupCheck,
            });

实践原则

服务依赖

在微服务架构中,服务之间的依赖关系会非常复杂。因此,在创建探针接口时,我们要充分考虑Kubernetes三种探针的运行机制以及服务之间的依赖关系,避免形成死锁,例如A依赖B可用,B又依赖C可用,而C又依赖A可用等。

探针配置

在Kubernetes中,配置容器的探针检测周期与超时时间时,需要综合考虑服务的负载和问题发现的时效性。通过结合日志和监控,可以调整服务探针机制到最佳状态。

探针实现

通常来说,Startup探针的目的是检测容器工作时机是否合适。因为其成功后通常不再运行,适合用于充分的状态检查。

对于Liveness探针,通常越简单越好。我们可以使用下面的代码添加一个基本的Liveness健康检查,即服务能够接受请求并返回,即认为其存活。

healthChecksBuilder.AddCheck(LivenessCheck, () => HealthCheckResult.Healthy());

Readiness探针的设计需要根据服务的具体情况而定,必须弄清楚在当前微服务架构中,哪些服务对于当前服务是必须的,或者在什么状态下需要将当前服务标记为Unhealthy,即无法正常处理请求。

实践教程

在示例代码k8s-helm-micro-services - Repos (azure.com)中,我创建了三个服务并使用helm template创建了helm chart,其中,app1依赖app2,app2又依赖app3。

探针配置

因为我使用helm template创建deployment等文件,因此我在values.yaml文件中配置了探针相关参数,并在deployment.yaml中引用,以app1为例:

values.yaml

安装微服务

我们先使用minikube和helm命令,部署app2和app3到cluster:

docker build -t app3:v1 .
minikube image load app3:v1
helm upgrade -i app3 .\app3\ -n test

docker build -t app2:v1 .
minikube image load app2:v1
helm upgrade -i app2 .\app2\ -n test

测试Readiness探针

为了方便测试,我在app3中添加了一个REST接口用来修改服务的HealthStatus,并且作为Readiness检查的结果:

        public static HealthStatus HealthStatus;

        [HttpGet("set")]
        public HealthStatus Set(HealthStatus healthStatus)
        {
            HealthStatus = healthStatus;
            return HealthStatus;
        }

    public class ReadinessCheck : IHealthCheck
    {
        public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) 
            => Task.FromResult(new HealthCheckResult(App3Controller.HealthStatus));
    }

由于HealthStatus默认值是Unhealthy,因此在部署之后我们可以看到Pod的状态是:Readiness probe failed: HTTP probe failed with statuscode: 503

此时,如果我们从app2中请求app3的接口,将会得到一个Connection refused错误,这是因为Readiness probe failed时,集群将停止向该Pod发送任何请求。

但此时app3容器是依然存活的,且其中的.NET服务依然在运行,因此我们可以通过kubectl port-forward等手段,访问到它的接口。 我们通过调用set接口,将服务健康状态设置为Healthy。

之后,app3服务探针检查恢复正常,app2可以成功的从app3中查询数据了

测试Startup和Liveness探针

我在app1当中添加了Startup探针,检测的内容是app3是否存活,因此在部署app1到cluster之前,我们先删除app3,以此来测试Startup探针的工作情况。

public class App3StartupCheck : IHealthCheck
    {
        private readonly ILogger<App3StartupCheck> _logger;
        public App3StartupCheck(ILogger<App3StartupCheck> logger)
        {
            _logger = logger;
        }

        public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
        {
            try
            {
                using var client = new HttpClient { BaseAddress = new Uri("http://app3.test") };

                var result = await client.GetStringAsync("health/live");
                return new HealthCheckResult(Enum.Parse<HealthStatus>(result));
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "App3 is not ready");
                return HealthCheckResult.Unhealthy();
            }
        }
    }

可以看到,在部署app1之后,因为app3服务已经被我们删除掉了,所以会出现:Startup probe failed: HTTP probe failed with statuscode: 503Startup探针检查失败导致该Pod一直被k8s重启。

当我们将app3重新部署回来之后,因为app3的Readiness默认健康状态是Unhealthy,所以尽管它处于存活状态,但此时仍无法处理任何请求。app1仍然无法通过接口获取到app3的Liveness状态,所以app1依然处于Startup probe failed。

接下来,我们通过上面提到的port-forward的方式,直接访问app3的容器接口,将HealthStatus的值修改为Healthy:

我们可以看到app3的探针状态已经恢复正常,而app1也会在下一次Pod重建时成功通过Startup探针检查。

 总结

本文以非常简单的示例,为大家介绍了如何将.NET服务的健康检查机制与Kubernetes探针结合使用,希望能够对大家有所启发。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[足式机器人]Part2 Dr. CAN学习笔记- 最优控制Optimal Control Ch07-3 线性二次型调节器(LQR)

本文仅供学习使用 本文参考&#xff1a; B站&#xff1a;DR_CAN Dr. CAN学习笔记 - 最优控制Optimal Control Ch07-3 线性二次型调节器&#xff08;LQR&#xff09; 1. 数学推导2. 案例反洗与代码详解 1. 数学推导 2. 案例反洗与代码详解

RK3568笔记十一:mpp编解码

若该文为原创文章&#xff0c;转载请注明原文出处。 主要是想测试MPP的解码&#xff0c;为后续做测试。 一、环境 1、平台&#xff1a;rk3568 2、开发板:ATK-RK3568正点原子板子 3、环境&#xff1a;buildroot 二、编译 使用的是正点原子提供的虚拟机&#xff0c;搭建好环…

TensorRT部署--Linux(Ubuntu)环境配置

系列文章目录 TensorRT环境配置–Linux(Ubuntu) 文章目录 系列文章目录前言一、环境配置二、CUDA下载安装三、cuDNN下载安装四、TensorRT下载安装五、模型创建总结 前言 TensorRT部署-Windows环境配置: https://blog.csdn.net/m0_70420861/article/details/135658922?csdn_s…

写着玩的程序:pycharm实现无限弹窗程序(非病毒程序,仅整蛊使用)

运行环境 PyCharm 2023.2.1 python3.11 具体内容 源代码 import tkinter as tk from tkinter import messagebox import threadingclass PopupGenerator:def __init__(self):self.root tk.Tk()self.root.geometry("200x120")self.root.title("无限弹窗&qu…

《WebKit 技术内幕》学习之十(2): 插件与JavaScript扩展

2 Chromium PPAPI插件 2.1 原理 插件其实是一种统称&#xff0c;表示一些动态库&#xff0c;这些动态库根据定义的一些标准接口可以跟浏览器进行交互&#xff0c;至于这个标准接口是什么都可以&#xff0c;重要的是大家都遵循它们&#xff0c;NPAPI接口标准只是其中的一种&a…

C# CefSharp 输入内容,点击按钮,并且滑动。

前言 帮别人敲了个Demo,抱试一试心态&#xff0c;居然成功了&#xff0c;可以用。给小伙伴们看看效果。 遇到问题 1&#xff0c;input输入value失败&#xff0c;里面要套了个事件&#xff0c;再变换输入value。后来用浏览器开发工具&#xff0c;研究js代码&#xff0c;太难了&a…

IMX6ULL|GPIO子系统

一.GPIO子系统 GPIO是General Purpose I/O的缩写&#xff0c;即通用输入输出端口&#xff0c;简单来说就是MCU/CPU可控制的引脚&#xff0c;这些引脚通常有多种功能&#xff0c;最基本的是高低电平输入检测和输出&#xff0c;部分引脚还会与主控器的片上外设绑定&#xff0c;如…

Spring Boot3整合knife4j(swagger3)

目录 1.前置条件 2.导依赖 3.配置 1.前置条件 已经初始化好一个spring boot项目且版本为3X&#xff0c;项目可正常启动。 作者版本为3.2.2最新版 2.导依赖 knife4j官网&#xff1a; Knife4j 集Swagger2及OpenAPI3为一体的增强解决方案. | Knife4j (xiaominfo.com)http…

Unity - 简单音频

“Test_04” AudioTest public class AudioTest : MonoBehaviour {// 声明音频// AudioClippublic AudioClip music;public AudioClip se;// 声明播放器组件private AudioSource player;void Start(){// 获取播放器组件player GetComponent<AudioSource>();// 赋值…

Django ORM 中高级单表查询 API(2)

Django ORM 中的单表查询 API&#xff08;1&#xff09;https://blog.csdn.net/Python_1981/article/details/135653173 在上一篇博文中&#xff0c;我们探讨了 Django ORM 中单表查询 API 的基础知识&#xff0c;重点是 all()、filter()、get()、first() 和 last()。在…

记一次 stackoverflowerror 线上排查过程

一.线上 stackOverFlowError xxx日,突然收到线上日志关键字频繁告警 classCastException.从字面上的报警来看,仅仅是类型转换异常,查看细则发现其实是 stackOverFlowError.很多同学面试的时候总会被问到有没有遇到过线上stackOverFlowError?有么有遇到栈溢出?具体栈溢出怎么来…

Javat集合之Lis---(ArrayList和LinkedList)

文章目录 一、 List概述1.1概念1.2list体系结构图1.3 通用方法测试代码 二、List的特点三、遍历方式foreachfor循环迭代器 四、ArrayListArrayList概述概念数据结构 ArrayList的特点 ArrayList去重字符串去重对象去重 五、LinkedListLinkedList概述概念数据结构LinkedList的特点…

FTP网络文件共享服务

ftp的存储类型 1.直连式&#xff1a;距离最近&#xff0c;存储设备爱只连接到服务器上&#xff0c;速度最快&#xff0c;因为不经过网络 2.存储区域网络&#xff08;SAN&#xff09;&#xff1a;适用于大型应用或数据库系统&#xff0c;可以使用空间&#xff0c;也可以管理。…

RK3399平台开发系列讲解(网络篇)什么是Linux路由

🚀返回专栏总目录 文章目录 一、什么是路由二、路由配置命令沉淀、分享、成长,让自己和他人都能有所收获!😄 一、什么是路由 一张路由表中会有多条路由规则。每一条规则至少包含这三项信息。 目的网络:这个包想去哪儿?出口设备:将包从哪个口扔出去?下一跳网关:下一个…

基于springboot+vue的甘肃非物质文化网站(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 研究背景…

VisualSVN Server下载安装和使用方法、服务器搭建、使用TortoiseSvn将项目上传到云端服务器、各种错误解决方法

VisualSVN Server下载安装和使用方法、服务器搭建、使用TortoiseSvn将项目上传到云端服务器、各种错误解决方法 0.写在前面00.电脑配置01.思路 1.VisualSVN Server下载安装01.下载02.安装03.电脑命名不能有中文04.制作VisualSVN Server快捷方式05.License limits exceeded, Som…

使用WAF防御网络上的隐蔽威胁之目录穿越

目录穿越&#xff08;Directory Traversal&#xff09;是一种网络安全攻击手段&#xff0c;也被称为路径穿越。 这种攻击允许攻击者访问存储在Web服务器文件系统上的文件和目录&#xff0c;这些文件和目录原本不应该对用户可见或可访问。 通过利用安全漏洞&#xff0c;攻击者…

yolov5 opencv dnn部署自己的模型

yolov5 opencv dnn部署自己的模型 github开源代码地址使用github源码结合自己导出的onnx模型推理自己的视频推理条件c部署c 推理结果 github开源代码地址 yolov5官网还提供的dnn、tensorrt推理链接本人使用的opencv c github代码,代码作者非本人&#xff0c;也是上面作者推荐的…

Axure RP 9 动态面板

目录 轮播图绘制 多种方式登录 前言: 轮播图绘制、多种方式登录界面绘制 轮播图绘制 首先绘制一个动态面板 在概要区域选中动态面板进入State1面板中插入图片绘制 双击图片绘制插入本地图片&#xff0c;右键State1重复状态并更改图片 点击交互面板新建交互将需要添加…

嵌入式软件工程师面试题——2025校招社招通用(计算机网络篇)(三十二)

说明&#xff1a; 面试群&#xff0c;群号&#xff1a; 228447240面试题来源于网络书籍&#xff0c;公司题目以及博主原创或修改&#xff08;题目大部分来源于各种公司&#xff09;&#xff1b;文中很多题目&#xff0c;或许大家直接编译器写完&#xff0c;1分钟就出结果了。但…