Consul はサービスガバナンスとサービス発見を実現するツールです。複数のサービスが存在する場合、サービスのアドレスを一元的に管理し、アプリケーションがまとまった場所からサービスにアクセスできるようにします。
ASP.NET Core と Ocelot 組み合わせでは、以下の負荷分散方法が可能です。
LeastConnection:現在アクティブな接続数が最も少ないサーバーにリクエストを送信 RoundRobin:サーバーを順番に選択 NoLoadBalancer:最初のサーバーのみを使用
Consul クライアントの基本的な設定方法を示します。
Consul クライアントの設定例 使用する名前空間:Consul クライアントの設定を管理する名前空間
using Consul;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace ServiceDiscovery.Configuration
{
public static class ServiceDiscoverySetup
{
public static async Task<IApplicationBuilder> ConfigureConsulAsync(this IApplicationBuilder app, IConfiguration config)
{
var consulConfig = new ConsulClientConfiguration();
config.GetSection("ServiceDiscovery").Bind(consulConfig);
return await RegisterServiceAsync(app, consulConfig);
}
private static async Task<IApplicationBuilder> RegisterServiceAsync(IApplicationBuilder app, ConsulClientConfiguration config)
{
using var client = new ConsulClient(cfg =>
{
cfg.Address = new Uri(config.ConsulServer.Address);
cfg.Datacenter = config.ConsulServer.Datacenter;
});
await client.Agent.ServiceRegister(new AgentServiceRegistration
{
ID = Guid.NewGuid().ToString(),
Name = config.ServiceRegistration.Name,
Address = config.ServiceRegistration.Address,
Port = config.ServiceRegistration.Port,
Tags = new[] { config.ServiceRegistration.Tag },
Check = new AgentServiceCheck
{
DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(config.ServiceRegistration.DeregisterAfter),
Interval = TimeSpan.FromSeconds(config.ServiceRegistration.CheckInterval),
HTTP = config.ServiceRegistration.HealthCheckUrl,
Timeout = TimeSpan.FromSeconds(config.ServiceRegistration.Timeout)
}
});
return app;
}
}
public class ConsulClientConfiguration
{
public ConsulServer ConsulServer { get; set; }
public ServiceRegistration ServiceRegistration { get; set; }
}
public class ConsulServer
{
public string Address { get; set; }
public string Datacenter { get; set; }
}
public class ServiceRegistration
{
public string Name { get; set; }
public string Address { get; set; }
public int Port { get; set; }
public string Tag { get; set; }
public string HealthCheckUrl { get; set; }
public int CheckInterval { get; set; }
public int Timeout { get; set; }
public int DeregisterAfter { get; set; }
}
サービスの登録設定例を示します。
{
"ServiceDiscovery": {
"ConsulServer": {
"Address": "http://localhost:8500",
"Datacenter": "dc1"
},
"ServiceRegistration": {
"Name": "MainSvc",
"Address": "localhost",
"Port": 5000,
"Tag": "primary",
"HealthCheckUrl": "http://localhost:5000/health",
"CheckInterval": 10,
"Timeout": 5,
"DeregisterAfter": 20
}
}
}
Consul を使用したサービス発見のための Ocelot 設定例を示します。
{
"Routes": [
{
"UpstreamPathTemplate": "/api/auth/{*everything}",
"UpstreamHttpMethod": [ "GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS" ],
"DownstreamPathTemplate": "/api/auth/{*everything}",
"UseServiceDiscovery": true,
"ServiceName": "AuthService",
"LoadBalancerOptions": {
"Type": "RoundRobin"
}
}
],
"GlobalConfiguration": {
"ServiceDiscoveryProvider": {
"Scheme": "http",
"Host": "localhost",
"Port": 8500,
"Type": "Consul"
}
}
}
サービス発見を有効にするときは、以下の点に注意します。
サービスの名前(ServiceName)を正しく指定 Consul サーバーのホスト名とポートを正しく設定 負荷分散の種類(RoundRobin または LeastConnection)を選択
ASP.NET Core アプリケーションへの適用例を示します。
using ServiceDiscovery.Configuration;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.Filters;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
options.OperationFilter<AddResponseHeadersFilter>();
options.OperationFilter<AppendAuthorizeToSummaryOperationFilter>();
options.OperationFilter<SecurityRequirementsOperationFilter>();
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = "JWT 認証用トークンを入力",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey,
BearerFormat = "JWT",
Scheme = "Bearer"
});
options.AddSecurityRequirement(new OpenApiSecurityRequirement {
{
new OpenApiSecurityScheme{
Reference = new OpenApiReference{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
}, new string[] { }
}
});
string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "JwtMainSvc.xml");
options.IncludeXmlComments(path, true);
});
builder.Services.Configure<ConsulClientConfiguration>(builder.Configuration.GetSection("ServiceDiscovery"));
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
var audienceConfig = builder.Configuration.GetSection("Audience");
var symmetricKey = audienceConfig["Secret"];
var keyBytes = Encoding.ASCII.GetBytes(symmetricKey);
var signingKey = new SymmetricSecurityKey(keyBytes);
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = signingKey,
ValidateIssuer = true,
ValidIssuer = audienceConfig["Issuer"],
ValidateAudience = true,
ValidAudience = audienceConfig["Audience"],
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero,
RequireExpirationTime = true,
};
});
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/v1/swagger.json", "v1");
options.RoutePrefix = "";
});
}
app.UseCustomConsulAsync(app.Configuration).Wait();
app.Map("/health", config =>
{
config.Run(async context =>
{
context.Response.StatusCode = 200;
await Task.CompletedTask;
});
});
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();