实战中的asp.net core结合Consul集群&Docker实现服务治理

本文涉及的产品
密钥管理服务KMS,1000个密钥,100个凭据,1个月
简介: 文章来源于阿里云 MVP郭联钰。

<h1><span style="color: #3194d0;">0、目录</span></h1>


整体架构目录:ASP.NET Core分布式项目实战-目录


一、前言

在写这篇文章之前,我看了很多关于consul的服务治理,但发现基本上都是直接在powershell或者以命令工具的方式在服务器上面直接输入consul agent .... 来搭建启动consul集群,一旦把命令工具关掉,则consul无法再后台启动,尤其是在linux系统中。

如果在window系统中,采用bat文件到时可以做成开机自启,或者在linux中把命令做成一个service 服务文件来启动就可以实现后台运行。

因此为了社区能更好的发展技术,且能把consul应用在生产环境中,我认真回顾了在公司项目中的应用,特此把我开发及总结的分享给大家,此篇文章采用docker引擎,并且只涉及到consul集群部署以及结合asp.net core微服务注册。

当然如果内容有涉及安全隐患的,大家可以积极留言,共同进步。

 注:以下所涉及到部署过程全部经过楼主我反复的验证,总结得来。大家在应用的过程中如果出现问题,可以留言咨询。

二、部署consul集群

1、介绍一下Consul


consul 关键特性
  服务发现:支持服务发现。你可以通过 DNS 或 HTTP 的方式获取服务信息。
  健康检查:支持健康检查。可以提供与给定服务相关联的任何数量的健康检查(如 web 状态码或 cpu 使用率)。
  K/V 存储:键/值对存储。你可用通过 consul 存储如动态配置之类的相关信息。
  多数据中心:支持多数据中心,开箱即用。
  WEB UI:支持 WEB UI。点点点,你就能够了解你的服务现在的运行情况,一目了然,对开发运维是非常友好的。

说明:
在Consul方案中,每个提供服务的节点上都要部署和运行Consul的Client Agent
,所有运行Consul Agent节点的集合构成Consul Cluster。
Consul Agent有两种运行模式:Server和Client。这里的Server和Client只是Consul集群层面的区分,与搭建在Cluster之上的应用服务无关。以Server模式运行的Consul Agent节点用于维护Consul集群的状态,官方建议每个Consul Cluster至少有3个或以上的运行在Server Mode的Agent,Client节点不限。

Consul支持多数据中心,每个数据中心的Consul Cluster都会在运行于Server模式下的Agent节点中选出一个Leader节点,这个选举过程通过Consul实现的raft协议保证,多个 Server节点上的Consul数据信息是强一致的。处于Client Mode的Consul Agent节点比较简单,无状态,仅仅负责将请求转发给Server Agent节点。


Consul与其他工具的比较



consul的端口解释



好了,该介绍的已经介绍了,接下来就是正式部署吧

2、Docker上运行Consul

第一步:安全配置,适用授权和数据加密传输(生产环境是必须的)

为了consul的安全,需要进行gossip加密以及RPC加密结合TLS。
Gossip加密:主要用于节点间的接收发送集群信息安全。
RPC加密:主要用于Agent之间调用RPC授权的安全性。
此处大家可以参考官网或者一下资料:
https://www.jianshu.com/p/3d074ed76a68
https://www.consul.io/docs/agent/encryption.html

第二步:consul集群部署

准备服务器(可以采用虚拟机部署)
image.png
部署server1
docker run -d --net=host -v $PWD/data:/consul/data -v $PWD/config:/consul/config --restart always --name consulServer1 consul agent -server -bind=192.168.216.160 -node=server1 -bootstrap-expect 3 -data-dir=/consul/data -config-file=/consul/config -client 0.0.0.0 -ui

部署server2
docker run -d --net=host -v $PWD/data:/consul/data -v $PWD/config:/consul/config --restart always --name cs2 consul agent -server -bind=192.168.216.161 -node=server2 -bootstrap-expect 3 -data-dir=/consul/data -config-file=/consul/config -client 0.0.0.0 -ui -retry-join=192.168.216.160

部署server3
docker run -d --net=host -v $PWD/data:/consul/data -v $PWD/config:/consul/config --restart always --name cs3 consul agent -server -bind=192.168.216.162 -node=server3 -bootstrap-expect 3 -data-dir=/consul/data -config-file=/consul/config -client 0.0.0.0 -ui -retry-join=192.168.216.160

部署client-连接server


docker run -d --net=host -v $PWD/data:/consul/data -v $PWD/config:/consul/config --restart always --name cc1 consul agent -bind=192.168.216.163 -node=client1 -data-dir=/consul/data -config-file=/consul/config -client 0.0.0.0 -retry-join=192.168.216.160

注:(针对以上的参数以及配置解释)
1、此处的数据挂载就是把容器内的data以及配置挂载到外部地址,$PWD代表当前目录即你运行docker命令时的目录,当然$PWD可以替换为具体的路径
2、-data-dir=/consul/consul -config-file=/consul/config :这两个地方代表在consul启动后会把数据以及配置文件放到指定的目录下,这个对于在服务器模式下运行的代理尤其重要,因为它们必须能够保持群集状态。
3、总结,以上两点结合就是说明 consul产生的数据然后挂载在外部地址进行持久化存储。
4、server端 的-client 参数可以不用,官方推荐是 服务注册到client端,由client端再把数据统一提交到server端。
参数说明

-client : 0.0.0.0 代表绑定到所有接口的选项,如果没有此选项,则asp.net core无法进行服务注册使用
-bind :该地址用来在集群内部的通讯,集群内的所有节点到地址都必须是可达的

--net=host 使得docker容器越过了net namespace的隔离,免去手动指定端口映射的步骤

-retry-join 允许你在第一次失败后进行尝试,加入一个已经启动的agent的ip地址

-bootstrap-expect 提供的server节点数目

-ui 启动自有主机的界面

-data-dir :提供一个目录用来存放agent的状态,所有的agent允许都需要该目录,该目录必须是稳定的,系统重启后都继续存在

CONSUL_LOCAL_CONFIG={"skip_leave_on_interrupt": true}:在0.7版本之后默认是true,因此不是必须的。


CONSUL_LOCAL_CONFIG={"leave_on_terminate": true}:在0.7版本之后默认是true,因此不是必须的。如果启用,当代理收到TERM信号时,它将向Leave群集的其余部分发送消息并正常离开

打开浏览器器查看 consul :



看所有consul节点情况
docker exec -t cs1 consul members



查看server的状态,以及哪一个节点是leader


docker exec -t cs1 consul operator raft list-peers



注:


如果其中一台或者多台server端挂掉,则Consul集群就会重新选举新的Leader,
但是一旦挂掉阿的节点数量超过一半,则Consul集群就无法工作了。


三、asp.net core微服务进行服务注册




1、新建一个项目asp.net core webapi项目


引入 consul ,nuget包


2、在控制器中新建一个控制器Health,代表健康检查


复制代码

[Produces("application/json")]
[Route(</span><span style="color: #800000;">"</span><span style="color: #800000;">api/Health</span><span style="color: #800000;">"</span><span style="color: #000000;">)]
</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">class</span><span style="color: #000000;"> HealthController : Controller
{
    [HttpGet]
    </span><span style="color: #0000ff;">public</span> IActionResult Get() =&gt; Ok(<span style="color: #800000;">"</span><span style="color: #800000;">ok</span><span style="color: #800000;">"</span><span style="color: #000000;">);
}</span></pre>

复制代码

 


3、然后在appsetting.json中添加配置文件


需要手动配置一下地址和端口号。

复制代码

{
"Logging": {
</span><span style="color: #800000;">"</span><span style="color: #800000;">IncludeScopes</span><span style="color: #800000;">"</span>: <span style="color: #0000ff;">false</span><span style="color: #000000;">,
</span><span style="color: #800000;">"</span><span style="color: #800000;">Debug</span><span style="color: #800000;">"</span><span style="color: #000000;">: {
  </span><span style="color: #800000;">"</span><span style="color: #800000;">LogLevel</span><span style="color: #800000;">"</span><span style="color: #000000;">: {
    </span><span style="color: #800000;">"</span><span style="color: #800000;">Default</span><span style="color: #800000;">"</span>: <span style="color: #800000;">"</span><span style="color: #800000;">Warning</span><span style="color: #800000;">"</span><span style="color: #000000;">
  }
},
</span><span style="color: #800000;">"</span><span style="color: #800000;">Console</span><span style="color: #800000;">"</span><span style="color: #000000;">: {
  </span><span style="color: #800000;">"</span><span style="color: #800000;">LogLevel</span><span style="color: #800000;">"</span><span style="color: #000000;">: {
    </span><span style="color: #800000;">"</span><span style="color: #800000;">Default</span><span style="color: #800000;">"</span>: <span style="color: #800000;">"</span><span style="color: #800000;">Warning</span><span style="color: #800000;">"</span><span style="color: #000000;">
  }
}

},

" ServiceRegister ": { // 服务注册
<span style="color: #800000;">"</span><span style="color: #800000;">IsActive</span><span style="color: #800000;">"</span>: <span style="color: #0000ff;">true</span><span style="color: #000000;">,
</span><span style="color: #800000;">"</span><span style="color: #800000;">ServiceName</span><span style="color: #800000;">"</span>: <span style="color: #800000;">"</span><span style="color: #800000;">testconsul6</span><span style="color: #800000;">"</span><span style="color: #000000;">,
</span><span style="color: #800000;">"</span><span style="color: #800000;">ServiceHost</span><span style="color: #800000;">"</span>: <span style="color: #800000;">"</span><span style="color: #800000;">192.168.216.163</span><span style="color: #800000;">"</span><span style="color: #000000;">,
</span><span style="color: #800000;">"</span><span style="color: #800000;">ServicePort</span><span style="color: #800000;">"</span>: <span style="color: #800080;">5006</span><span style="color: #000000;">,
</span><span style="color: #800000;">"</span><span style="color: #800000;">Register</span><span style="color: #800000;">"</span><span style="color: #000000;">: {
  </span><span style="color: #800000;">"</span><span style="color: #800000;">HttpEndpoint</span><span style="color: #800000;">"</span>: <span style="color: #800000;">"</span><span style="color: #800000;">http://192.168.216.163:8500</span><span style="color: #800000;">"</span><span style="color: #000000;">
}

}
}


复制代码

4、这一步是服务注册的类


ServiceRegisterOptions.cs


复制代码

public class ServiceRegisterOptions
{
    </span><span style="color: #808080;">///</span> <span style="color: #808080;">&lt;summary&gt;</span>
    <span style="color: #808080;">///</span><span style="color: #008000;"> 是否启用
    </span><span style="color: #808080;">///</span> <span style="color: #808080;">&lt;/summary&gt;</span>
    <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">bool</span> IsActive { <span style="color: #0000ff;">get</span>; <span style="color: #0000ff;">set</span><span style="color: #000000;">; }
    </span><span style="color: #808080;">///</span> <span style="color: #808080;">&lt;summary&gt;</span>
    <span style="color: #808080;">///</span><span style="color: #008000;"> 服务名称
    </span><span style="color: #808080;">///</span> <span style="color: #808080;">&lt;/summary&gt;</span>
    <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">string</span> ServiceName { <span style="color: #0000ff;">get</span>; <span style="color: #0000ff;">set</span><span style="color: #000000;">; }
    </span><span style="color: #808080;">///</span> <span style="color: #808080;">&lt;summary&gt;</span>
    <span style="color: #808080;">///</span><span style="color: #008000;"> 服务IP或者域名
    </span><span style="color: #808080;">///</span> <span style="color: #808080;">&lt;/summary&gt;</span>
    <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">string</span> ServiceHost { <span style="color: #0000ff;">get</span>; <span style="color: #0000ff;">set</span><span style="color: #000000;">; }
    </span><span style="color: #808080;">///</span> <span style="color: #808080;">&lt;summary&gt;</span>
    <span style="color: #808080;">///</span><span style="color: #008000;"> 服务端口号
    </span><span style="color: #808080;">///</span> <span style="color: #808080;">&lt;/summary&gt;</span>
    <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">int</span> ServicePort { <span style="color: #0000ff;">get</span>; <span style="color: #0000ff;">set</span><span style="color: #000000;">; }
    </span><span style="color: #808080;">///</span> <span style="color: #808080;">&lt;summary&gt;</span>
    <span style="color: #808080;">///</span><span style="color: #008000;"> consul注册地址
    </span><span style="color: #808080;">///</span> <span style="color: #808080;">&lt;/summary&gt;</span>
    <span style="color: #0000ff;">public</span> RegisterOptions Register { <span style="color: #0000ff;">get</span>; <span style="color: #0000ff;">set</span><span style="color: #000000;">; }
}</span></pre>

复制代码

 RegisterOptions.cs



public class RegisterOptions
{
    </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">string</span> HttpEndpoint { <span style="color: #0000ff;">get</span>; <span style="color: #0000ff;">set</span><span style="color: #000000;">; }
}</span></pre>

 5、在startup.cs 中的服务注册


复制代码

public void ConfigureServices(IServiceCollection services)
    {
        </span><span style="color: #0000ff;">#region</span> 服务注册基础信息配置<span style="color: #000000;">
        services.Configure</span>&lt;ServiceRegisterOptions&gt;(Configuration.GetSection(<span style="color: #800000;">"</span><span style="color: #800000;">ServiceRegister</span><span style="color: #800000;">"</span><span style="color: #000000;">));
        services.AddSingleton</span>&lt;IConsulClient&gt;(p =&gt; <span style="color: #0000ff;">new</span> ConsulClient(cfg =&gt;<span style="color: #000000;">
        {
            </span><span style="color: #0000ff;">var</span> serviceConfiguration = p.GetRequiredService&lt;IOptions&lt;ServiceRegisterOptions&gt;&gt;<span style="color: #000000;">().Value;
            </span><span style="color: #0000ff;">if</span> (!<span style="color: #0000ff;">string</span><span style="color: #000000;">.IsNullOrEmpty(serviceConfiguration.Register.HttpEndpoint))
            {
                cfg.Address </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> Uri(serviceConfiguration.Register.HttpEndpoint);
            }
        }));
        </span><span style="color: #0000ff;">#endregion</span><span style="color: #000000;">

        services.AddMvc();
    }</span></pre>

复制代码

在Configure 方法中配置代码


复制代码

private static void RegisterService(IApplicationBuilder app,
        IOptions</span>&lt;ServiceRegisterOptions&gt;<span style="color: #000000;"> serviceRegisterOptions,
        IConsulClient consul,
        IApplicationLifetime appLife)
    {
        </span><span style="color: #0000ff;">var</span> serviceId = $<span style="color: #800000;">"</span><span style="color: #800000;">{serviceRegisterOptions.Value.ServiceName}_{serviceRegisterOptions.Value.ServiceHost}:{serviceRegisterOptions.Value.ServicePort}</span><span style="color: #800000;">"</span><span style="color: #000000;">;

        </span><span style="color: #0000ff;">var</span> httpCheck = <span style="color: #0000ff;">new</span><span style="color: #000000;"> AgentServiceCheck()
        {
            DeregisterCriticalServiceAfter </span>= TimeSpan.FromSeconds(<span style="color: #800080;">5</span>),<span style="color: #008000;">//</span><span style="color: #008000;">服务启动多久后注册</span>
            Interval = TimeSpan.FromSeconds(<span style="color: #800080;">30</span>),<span style="color: #008000;">//</span><span style="color: #008000;">健康检查时间间隔,或者称为心跳间隔</span>
            HTTP = $<span style="color: #800000;">"</span><span style="color: #800000;">http://{serviceRegisterOptions.Value.ServiceHost}:{serviceRegisterOptions.Value.ServicePort}/api/health</span><span style="color: #800000;">"</span>,<span style="color: #008000;">//</span><span style="color: #008000;">健康检查地址</span>

};

        </span><span style="color: #0000ff;">var</span> registration = <span style="color: #0000ff;">new</span><span style="color: #000000;"> AgentServiceRegistration()
        {
            Checks </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;">[] { httpCheck },
            Address </span>=<span style="color: #000000;"> serviceRegisterOptions.Value.ServiceHost,
            ID </span>=<span style="color: #000000;"> serviceId,
            Name </span>=<span style="color: #000000;"> serviceRegisterOptions.Value.ServiceName,
            Port </span>=<span style="color: #000000;"> serviceRegisterOptions.Value.ServicePort
            </span><span style="color: #008000;">//</span><span style="color: #008000;">Tags = new[] { $"urlprefix-/{serviceRegisterOptions.Value.ServiceName}" }</span><span style="color: #008000;">//</span><span style="color: #008000;">添加 urlprefix-/servicename 格式的 tag 标签,以便 Fabio 识别</span>

};

        </span><span style="color: #008000;">//</span><span style="color: #008000;">.GetAwaiter().GetResult()</span>

consul.Agent.ServiceRegister(registration).GetAwaiter().GetResult();

        appLife.ApplicationStopping.Register(() </span>=&gt;<span style="color: #000000;">
        {
            consul.Agent.ServiceDeregister(serviceId).GetAwaiter().GetResult();</span><span style="color: #008000;">//</span><span style="color: #008000;">服务停止时取消注册</span>

});

    }
    </span><span style="color: #0000ff;">#endregion</span></pre>

复制代码

自此,可以把我们的项目部署到163服务器上面的docker上啦。


我们看一下效果,成功注册到了




参考资料
Conusl TLS配置:https://www.jianshu.com/p/3d074ed76a68
https://www.consul.io/docs/agent/encryption.html

consul 参数配置:

https://blog.csdn.net/zl1zl2zl3/article/details/79622476
consul服务治理:
http://michaco.net/blog/ServiceDiscoveryAndHealthChecksInAspNetCoreWithConsul
https://www.cnblogs.com/myzony/p/9168851.html
https://www.cnblogs.com/edisonchou/p/9124985.html

文章转载自阿里云 MVP郭联钰,查看原文

目录
相关文章
|
1月前
|
存储 开发框架 JSON
ASP.NET Core OData 9 正式发布
【10月更文挑战第8天】Microsoft 在 2024 年 8 月 30 日宣布推出 ASP.NET Core OData 9,此版本与 .NET 8 的 OData 库保持一致,改进了数据编码以符合 OData 规范,并放弃了对旧版 .NET Framework 的支持,仅支持 .NET 8 及更高版本。新版本引入了更快的 JSON 编写器 `System.Text.UTF8JsonWriter`,优化了内存使用和序列化速度。
|
7天前
|
消息中间件 开发框架 .NET
.NET 8 强大功能 IHostedService 与 BackgroundService 实战
【11月更文挑战第7天】本文介绍了 ASP.NET Core 中的 `IHostedService` 和 `BackgroundService` 接口及其用途。`IHostedService` 定义了 `StartAsync` 和 `StopAsync` 方法,用于在应用启动和停止时执行异步操作,适用于资源初始化和清理等任务。`BackgroundService` 是 `IHostedService` 的抽象实现,简化了后台任务的编写,通过 `ExecuteAsync` 方法实现长时间运行的任务逻辑。文章还提供了创建和注册这两个服务的实战步骤,帮助开发者在实际项目中应用这些功能。
|
1月前
|
开发框架 NoSQL MongoDB
C#/.NET/.NET Core开发实战教程集合
C#/.NET/.NET Core开发实战教程集合
|
2月前
|
开发框架 监控 前端开发
在 ASP.NET Core Web API 中使用操作筛选器统一处理通用操作
【9月更文挑战第27天】操作筛选器是ASP.NET Core MVC和Web API中的一种过滤器,可在操作方法执行前后运行代码,适用于日志记录、性能监控和验证等场景。通过实现`IActionFilter`接口的`OnActionExecuting`和`OnActionExecuted`方法,可以统一处理日志、验证及异常。创建并注册自定义筛选器类,能提升代码的可维护性和复用性。
|
2月前
|
开发框架 .NET 中间件
ASP.NET Core Web 开发浅谈
本文介绍ASP.NET Core,一个轻量级、开源的跨平台框架,专为构建高性能Web应用设计。通过简单步骤,你将学会创建首个Web应用。文章还深入探讨了路由配置、依赖注入及安全性配置等常见问题,并提供了实用示例代码以助于理解与避免错误,帮助开发者更好地掌握ASP.NET Core的核心概念。
92 3
|
1月前
|
开发框架 JavaScript 前端开发
一个适用于 ASP.NET Core 的轻量级插件框架
一个适用于 ASP.NET Core 的轻量级插件框架
|
2月前
|
开发框架 NoSQL .NET
利用分布式锁在ASP.NET Core中实现防抖
【9月更文挑战第5天】在 ASP.NET Core 中,可通过分布式锁实现防抖功能,仅处理连续相同请求中的首个请求,其余请求返回 204 No Content,直至锁释放。具体步骤包括:安装分布式锁库如 `StackExchange.Redis`;创建分布式锁服务接口及其实现;构建防抖中间件;并在 `Startup.cs` 中注册相关服务和中间件。这一机制有效避免了短时间内重复操作的问题。
|
2月前
|
SQL 关系型数据库 数据库
七天.NET 8操作SQLite入门到实战详细教程(选型、开发、发布、部署)
七天.NET 8操作SQLite入门到实战详细教程(选型、开发、发布、部署)
|
3月前
|
测试技术 API 开发者
.NET单元测试框架大比拼:MSTest、xUnit与NUnit的实战较量与选择指南
【8月更文挑战第28天】单元测试是软件开发中不可或缺的一环,它能够确保代码的质量和稳定性。在.NET生态系统中,MSTest、xUnit和NUnit是最为流行的单元测试框架。本文将对这三种测试框架进行全面解析,并通过示例代码展示它们的基本用法和特点。
281 8
|
3月前
|
开发框架 缓存 前端开发
实战.NET Framework 迁移到 .NET 5/6
从.NET Framework 迁移到.NET 5/6 是一次重要的技术革新,涵盖开发环境与应用架构的全面升级。本文通过具体案例详细解析迁移流程,包括评估现有应用、利用.NET Portability Analyzer 工具识别可移植代码、创建新项目、逐步迁移代码及处理依赖项更新等关键步骤。特别关注命名空间调整、JSON 序列化工具更换及数据库访问层重构等内容,旨在帮助开发者掌握最佳实践,确保迁移过程平稳高效,同时提升应用性能与可维护性。
121 2