Docker-HealthCheck指令探测ASP.NET Core容器健康状态
<h2 id="2397-1557132350116">写在前面</h2><p> HealthCheck 不仅是对应用程序内运行情况、数据流通情况进行检查, 还包括应用程序对外部服务或依赖资源的健康检查。</p>
<p>健康检查通常是以<strong>暴露应用程序的HTTP端点的形式</strong> 实施,可用于配置健康探测的的场景有 :</p>
<p> ① 容器或负载均衡器 探测应用状态,执行既定策略,例如:容器探测到应用unhealthy可<em>终止后续的滚动部署</em>或者<em>重启容器</em>;负载均衡器探测到实例unhealthy能将请求路由到健康的运行实例。</p>
<p> ② 对应用程序种依赖的第三方服务进行健康探测,比如redis、database、外部服务接口 </p>
<p> ③ 内存、硬盘、网络等物理依赖资源的探测</p>
<p>HealthCheck提供<strong>对外暴露程序运行状态的机制</strong>。</p>
<h2>容器HEALTHCHECK指令</h2>
<p> 一般情况下我们很容易知道容器正在运行, 但容器作为相对独立的应用执行环境,有时候并不知道容器是否以预期的方式正确运作</p>
<p>Dockerfile HEALTHCHECK指令提供了<strong>探测容器以预期工作的轮询机制</strong>,轮询内容可由应用自身决定。</p>
<p><span style="background-color: rgba(255, 255, 255, 1)">通过在容器内运行shell命令来探测容器健康状态,Dockerfile以command的<span style="background-color: rgba(255, 255, 0, 1)"><strong>退出码表示容器健康状态</strong></span>:</span></p>
<p><span style="background-color: rgba(255, 255, 255, 1)"> 0 指示容器健康</span></p>
<p><span style="background-color: rgba(255, 255, 255, 1)"> 1 指示容器不健康</span></p>
<p> 2 指示不使用这个退出码 </p>
<pre>(docker-compose.yml 也有相应的配置节完成HealthCheck)</pre>
<div>
<div class="cnblogs_code">
<pre><span style="color: rgba(51, 153, 102, 1)">// 可定义轮询interval、探测超时timeout、 重试retries参数轮训探测</span></pre>
<pre>HEALTHCHECK CMD command<span style="color: rgba(51, 153, 102, 1)"><br></span></pre>
</div>
<blockquote>
<p>Every Linux or Unix command executed by the shell script or user has an exit status. Exit status is an integer number. 0 exit status means the command was successful without any errors. A non-zero (1-255 values) exit status means command was a failure. 传送门</p>
<p>故为方便对接Docker- HealcthCheck,以上CMD之后我们一般都接 || exit 1</p>
</blockquote>
<p>对于容器内Web应用,自然而然会联想到 使用端点访问的形式去探测容器应用: <strong><span style="color: rgba(0, 0, 0, 1)">应用端点返回成功对应返回0 ;返回失败对应返回1</span></strong></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(51, 153, 102, 1)">// <strong>shell将成功的退出状态(0)映射为真,任何失败退出/非0都映射为假, 这样做可以有条件的执行链接shell命令http://www.dovov.com/0shelltruefalse1.html</strong></span></pre>
<pre>HEALTHCHECK --interval=5m --timeout=3s --retries=<span style="color: rgba(128, 0, 128, 1)">3</span> CMD (<span style="color: rgba(255, 102, 0, 1)">curl -f http://localhost:5000/healthz) || exit 1</span></pre>
</div>
<p>探测命令在stdout或stderr 输出的任何内容 会在 容器Health Status中存储,可通过docker inspect 查看HealthCheck状态节点。</p>
<p>下面我们会将渐进式演示使用Docker平台的HEALTHCHECK指令对接 ASP.NET Core程序的健康检查能力。</p>
<p> <img src="https://img2018.cnblogs.com/blog/587720/201905/587720-20190509155625360-1628917888.png" alt=""></p>
<h3>实现AspNetCore HealthCheck端点</h3>
<p> ASPNET Core在2.2版本内置了健康检查的能力: 终端中间件(满足该路径的url请求,将会被该中间件处理)。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> ConfigureServices(IServiceCollection services)
{
services.AddHealthChecks();
}
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseHealthChecks(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/healthcheck</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
}</span></pre>
</div>
<p> 请求/healthcheck端点, 程序会进行健康检查逻辑并响应输出, 默认的行为:</p>
<p> ①<span style="background-color: rgba(255, 255, 0, 1)"> 对healthy、degraded状态返回200 OK 响应码; 对于unhealthy返回503 Service Unavailable 响应码</span></p>
<p> ② 响应体只会包含简单的HealthStatus枚举字符串</p>
<p> ③ 将每次健康检查的结果写入HealthReport对象。</p>
<p> 作为企业级项目,存在对Web项目物理资源和服务依赖的健康检查需求, 这里我们为避免重复造轮子,引入了开源的力量。</p>
<p> </p>
<h3>开源社区对HealthCheck的支持</h3>
<blockquote>
<p> 开源的企业级AspNetCore.Diagnostics.HealthChecks系列组件,该系列组件支持多种物理资源和服务依赖的健康检查,支持报告推送,支持友好的检查报告UI(支持后台轮训检查)、支持webhook通知。</p>
</blockquote>
<div>
<p>下面的步骤演示了对web程序HTTP请求、Redis、Sqlite等服务进行健康检查的端点配置</p>
<p> ① 引入AspNetCore.HealthChecks.Redis 、 AspNetCore.HealthChecks.Sqlite nuget库</p>
<p> ② Startup.cs配置并启用健康检查</p>
</div>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 以下代码截取自 Startup.ConfigureServices方法,对swagger服务地址、redis、sqlte进行健康检查</span>
services.AddHealthChecks().AddAsyncCheck(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Http</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(0, 0, 255, 1)">async</span> () =><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">using</span> (HttpClient client = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> HttpClient())
{
</span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">var</span> response = <span style="color: rgba(0, 0, 255, 1)">await</span> client.GetAsync(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">http://localhost:5000/swagger</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 0, 1)">response.IsSuccessStatusCode)
{
</span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> Exception(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Url not responding with 200 OK</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
}
}
</span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception)
{
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> Task.FromResult(HealthCheckResult.Unhealthy());
}
}
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> Task.FromResult(HealthCheckResult.Healthy());
})
.AddSqlite(
sqliteConnectionString: Configuration.GetConnectionString(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">sqlite</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">),
healthQuery: </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">select count(*) as count from ProfileUsageCounters;</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
name: </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">sqlite</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
failureStatus: HealthStatus.Degraded,
tags: </span><span style="color: rgba(0, 0, 255, 1)">new</span> <span style="color: rgba(0, 0, 255, 1)">string</span>[] { <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">db</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">sqlite</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">sqlite</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)"> }
)
.AddRedis(Configuration.GetConnectionString(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">redis</span><span style="color: rgba(128, 0, 0, 1)">"</span>), <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">redis</span><span style="color: rgba(128, 0, 0, 1)">"</span>, HealthStatus.Unhealthy, <span style="color: rgba(0, 0, 255, 1)">new</span> <span style="color: rgba(0, 0, 255, 1)">string</span>[] { <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">redis</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">redis</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)"> })
.Services
.AddMvc();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 以下代码截取自Startup.Configure方法: 启用/healthz作为检查端点</span>
app.UseHealthChecks(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/healthz</span><span style="color: rgba(128, 0, 0, 1)">"</span>).UseMvcWithDefaultRoute(); //<span style="color: rgba(51, 153, 102, 1)">这里仍然只会响应 <strong>200/503状态码+简单的HealthStatus枚举值</strong></span></pre>
</div>
<p> 小技巧:你也可以使用UseHealthChecks()扩展方法修改默认的响应输出, 这里我们可引入HealthChecks.UI.Client nuget package输出更加详细的的HealthReport</p>
<div class="cnblogs_code">
<pre>app.UseHealthChecks(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/healthz</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> HealthCheckOptions()
{
Predicate </span>= _ => <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">,
ResponseWriter </span>=<span style="color: rgba(0, 0, 0, 1)"><span style="color: rgba(153, 51, 102, 1)">UIResponseWriter.WriteHealthCheckUIResponse<span style="color: rgba(51, 153, 102, 1)">// 该响应输出是一个json,包含所有检查项的详细检查结果</span></span>
});</span></pre>
</div>
<p>注意,容器HealthCheck指令不关注响应体, 只关注CMD命令的执行结果: 0 表示容器健康, 1 表示容器不健康, 所以不管以何种CMD ,我们都需要将CMD的结果转换为 0,1 </p>
<p><em>ps: docker-compose.yml 文件中可参考如下配置:</em></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)"> healthcheck:
test: <span style="color: rgba(255, 102, 0, 1)">curl </span></span><span style="color: rgba(255, 102, 0, 1)">-u huangjun:Iampossword -f http://</span><span style="color: rgba(0, 128, 0, 1)"><span style="color: rgba(255, 102, 0, 1)">localhost/healthcheck || exit 1</span> # -u 指定了用于基本身份验证的用户名和密码</span>
<span style="color: rgba(0, 0, 0, 1)"> interval: 1m30s
timeout: 10s
retries: </span><span style="color: rgba(128, 0, 128, 1)">3</span></pre>
</div>
<p> </p>
<h3>容器HEALTHCHECK指令输出</h3>
<p> 使用docker ps命令可查看容器的状态, 通过docker inspect 查看容器HealthCheck的输出,容器启动输出:starting,一旦监测到成功的响应状态码,将会转换为healthy 并将会持续轮询检查。</p>
<div class="cnblogs_code">
<pre>//---------截取自 docker inspect 【containerid】 输出--------------------------------</pre>
<p> "State": {<br> "Status": "running",<br> "Running": true,<br> "Paused": false,<br> "Restarting": false,<br> "OOMKilled": false,<br> "Dead": false,<br> "Pid": 2645,<br> "ExitCode": 0,<br> "Error": "",<br> "StartedAt": "2019-09-29T04:04:42.395037744Z",<br> "FinishedAt": "0001-01-01T00:00:00Z",<br> "Health": {<br> "Status": "healthy",<br> "FailingStreak": 0,<br> "Log": [<br> {<br> "Start": "2019-09-29T12:06:12.400153719+08:00",<br> "End": "2019-09-29T12:06:12.478927574+08:00",<br> <span style="color: rgba(255, 0, 0, 1)"><strong>"ExitCode": 0</strong></span>,<br> "Output": "% Total % Received % XferdAverage Speed Time Time TimeCurrent\n DloadUpload Total Spent LeftSpeed\n\r0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0\r100 206 0 206 0 012030 0 --:--:-- --:--:-- --:--:-- 12875\n{\"status\":\"Healthy\",\"totalDuration\":\"00:00:00.0080008\",\"entries\":{\"sqlite\":{\"data\":{},\"duration\":\"00:00:00.0075454\",\"status\":\"Healthy\"},\"redis\":{\"data\":{},\"duration\":\"00:00:00.0003594\",\"status\":\"Healthy\"}}}"<br> },<br> {<br> "Start": "2019-09-29T12:07:42.479160725+08:00",<br> "End": "2019-09-29T12:07:42.538163351+08:00",<br> "ExitCode": 0,<br> "Output": "% Total % Received % XferdAverage Speed Time Time TimeCurrent\n DloadUpload Total Spent LeftSpeed\n\r0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0\r100 206 0 206 0 014312 0 --:--:-- --:--:-- --:--:-- 14714\n{\"status\":\"Healthy\",\"totalDuration\":\"00:00:00.0081428\",\"entries\":{\"sqlite\":{\"data\":{},\"duration\":\"00:00:00.0077286\",\"status\":\"Healthy\"},\"redis\":{\"data\":{},\"duration\":\"00:00:00.0003531\",\"status\":\"Healthy\"}}}"<br> },<br> {<br> "Start": "2019-09-29T12:09:12.53837533+08:00",<br> "End": "2019-09-29T12:09:12.596907251+08:00",<br> "ExitCode": 0,<br> "Output": "% Total % Received % XferdAverage Speed Time Time TimeCurrent\n DloadUpload Total Spent LeftSpeed\n\r0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0\r100 206 0 206 0 014001 0 --:--:-- --:--:-- --:--:-- 14714\n{\"status\":\"Healthy\",\"totalDuration\":\"00:00:00.0085169\",\"entries\":{\"sqlite\":{\"data\":{},\"duration\":\"00:00:00.0080190\",\"status\":\"Healthy\"},\"redis\":{\"data\":{},\"duration\":\"00:00:00.0004430\",\"status\":\"Healthy\"}}}"<br> }<br> ]<br> }<br> },</p>
<p>......</p>
</div>
<p> </p>
<h3>HealthChecks-UI 了解一下</h3>
<p> 抛开Docker的HEALTHCHECK指令、负载均衡器的轮询机制不谈,我们的Web自身也可以进行 轮询健康检查并给出告警。</p>
<p>就我们上面的Web 实例来说,我们只对外提供的是一个 /healthcheck 检查端点,引入HealthChecks.UI.dll 将会在前端生成友好的HealthReport 界面, 该库支持后台轮询检查、支持webhook 通知。</p>
<p>这里就不展开说明,自行前往AspNetCore.Diagnostics.HealthChecks查看相应文档,效果如下:</p>
<div><img src="https://img2018.cnblogs.com/blog/587720/201905/587720-20190509123339925-2033164458.png" alt="">
<p> </p>
<p>至此,本文内容完毕:</p>
<p> - 使用ASP.NETCore 框架实现一个稍复杂的HealthCheck端点<span style="color: rgba(255, 102, 0, 1)"> /healthz</span></p>
<p> - 使用docker的HEALTHCHECK 指令对接Web程序健康检查端点</p>
<p>(完成以上步骤,为实现<strong>容器自愈</strong>打下基础, 请关注后续博文)</p>
<p> </p>
<p>+ linux shell 指令: http://www.dovov.com/0shelltruefalse1.html</p>
<div id="MySignature" style="display: block">
<div style="display: block; border: 2px solid rgba(110, 202, 168, 1); padding: 10px; background: rgba(240, 248, 255, 1)">
<div>
<div>作者:JulianHuang</div>
<div>
<p>码甲拙见,如有问题请下方留言大胆斧正;码字+Visio制图,均为原创,看官请不吝好评+关注,<span style="color: rgba(255, 0, 0, 1)"> ~。。~</span></p>
<p>本文欢迎转载,请转载页面明显位置注明原作者及原文链接<strong>。</strong></p>
</div>
</div>
<div> </div>
</div>
</div>
</div>
</div>
</div>
<div id="MySignature" role="contentinfo">
<HR style="FILTER: alpha(opacity=100,finishopacity=0,style=3)" width="80%" color=#987cb9 SIZE=3>
<div style="text-align:center;">
<p>本文来自博客园,作者:{有态度的马甲},转载请注明原文链接:https://www.cnblogs.com/JulianHuang/p/10837804.html</p>
<strong style="color: red; ">欢迎关注我的原创技术、职场公众号, 加好友谈天说地,一起进化</strong>
<div><imgstyle="width: 250px;height:250px;" src="https://blog-static.cnblogs.com/files/JulianHuang/QR.gif" /> </div>
</div><br><br>
来源:https://www.cnblogs.com/JulianHuang/p/10837804.html
頁:
[1]