Docker - 部署 Consul KV 统一管理所有项目的配置
Docker - 部署 Consul KV 统一管理所有项目的配置
一. 前提
有n个.net8项目,框架使用 consul + kong
在多个项目中 都有共同的配置,如数据库配置、外部服务地址配置、鉴权配置等。。
之前是在.env.development/.env.production 中配置的。可是需要在每个项目中都配置一遍。
Docker Secrets 部署
参考:https://www.cnblogs.com/1285026182YUAN/p/19192573
二. 使用 consul kv 统一管理。
创建 docker-compose
其中:CONSUL_HTTP_ADDR 是 consul 的地址,可直接配置,也可以写在文件中注册到 consul kv 中。
config-loader:
image: hashicorp/consul:1.21.1
container_name: configloader
environment:
- CONSUL_HTTP_ADDR=http://10.10.0.96:8500
command: >
sh -c "
echo '等待 Consul 启动...' &&
until consul info > /dev/null 2>&1; do
echo '等待 Consul...'
sleep 5
done &&
echo '开始加载配置...' &&
consul kv put config_consul/ClusterAddresses '[\"http://10.10.0.96:8500\"]' &&
consul kv put config_consul/ServiceName api-assist &&
consul kv put config_consul/ServiceScheme https &&
consul kv put config_consul/ServiceAddress '10.10.0.96' &&
consul kv put config_consul/ServicePort 7096 &&
consul kv put config_consul/Tags '[\"dotnet8\", \"consul\"]' &&
consul kv put config_consul/HealthCheck '/health' &&
consul kv put config_consul/HealthCheckInterval 10 &&
consul kv put config_consul/HealthCheckTimeout 5 &&
consul kv put config_consul/DeregisterCriticalServiceAfter 3 &&
consul kv put config_auth/Authority "http://10.10.0.96:18000/api-back" &&
consul kv put config_auth/isHttps false &&
consul kv put config/ConnectionStrings/RailCDE 'Server=10.5.84.40;port=9000;Database=RailCDE;Uid=root;Pwd={password};SslMode=None;' &&
consul kv put config/ConnectionStrings/RailAssist 'Server=10.5.84.40;port=9000;Database=RailAssist;Uid=root;Pwd={password};SslMode=None;' &&
consul kv put config_db @/configs/database.json &&
echo '配置加载完成!'
"
volumes:
- D:\DockerConfig\configs:/configs
depends_on:
- consul
创建文件:D:\DockerConfig\configs\database.json
{
"RailCDE":{
"Server":"10.5.84.40",
"port": 9000,
"Database": "RailCDE",
"Uid": "crdcuser",
"SslMode": "None"
},
"RailAssist":{
"Server":"10.5.84.40",
"port": 9000,
"Database": "RailCDE",
"Uid": "crdcuser",
"SslMode": "None"
}
}
运行 后
docker-compose up -d config-loader

打开 consul ui 查看 :http://localhost:8500/

注入成功。
三. 在 .net8 中使用
在 consul kv 中都是以字符串存储的。所以尽量以字符串处理
同时覆盖本地的appsetting.json文件,以及其对应的 环境变量配置文件,如 appsettings.Development.json、appsettings.Production.json 等。
1. 更新 docker-compose 如下
config-loader:
image: hashicorp/consul:1.21.1
container_name: configloader
environment:
- CONSUL_HTTP_ADDR=http://10.10.0.96:8500
command: >
sh -c "
echo '等待 Consul 启动...' &&
until consul info > /dev/null 2>&1; do
echo '等待 Consul...'
sleep 5
done &&
echo '开始加载配置...' &&
consul kv put config/Consul_common/ClusterAddresses http://10.10.0.96:8500 &&
consul kv put config/Consul_common/ServiceScheme https &&
consul kv put config/Consul_common/Tags dotnet8,consul &&
consul kv put config/Consul_common/HealthCheck '/health' &&
consul kv put config/Consul_common/HealthCheckInterval 10 &&
consul kv put config/Consul_common/HealthCheckTimeout 5 &&
consul kv put config/Consul_common/DeregisterCriticalServiceAfter 3 &&
consul kv put config/Consul_assist/ServiceName api-assist &&
consul kv put config/Consul_assist/ServiceAddress '10.10.0.96' &&
consul kv put config/Consul_assist/ServicePort 7096 &&
echo '配置加载完成!'
"
volumes:
- D:\DockerConfig\configs:/configs
depends_on:
- consul
2. .net8 中的服务注入
安装 nuget 包:Consul:1.7.14.9
注入:Program.cs
#region == consul kv 加载 ==
var consulKVPath = configuration.GetSection("ConsulKVPath");
if (consulKVPath.Exists())
{
builder.Configuration.AddUnifiedConfiguration(
consulAddress: consulKVPath.Value,
appName: builder.Environment.ApplicationName
);
}
#endregion
3. 创建文件 :UnifiedConfigurationBuilder.cs
using Consul;
using Microsoft.Extensions.Configuration;
using Rail.Medium.Static;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
namespace Rail.Medium.Middleware
{
public static class UnifiedConfigurationBuilder
{
public static IConfigurationBuilder AddUnifiedConfiguration(
this IConfigurationBuilder builder,
string consulAddress,
string appName)
{
// 1. 加载 Docker Secrets
builder.AddDockerSecrets();
// 2. 构建临时 IConfiguration
var tempConfig = builder.Build();
// 3. 加载 Consul 配置,并传入临时 IConfiguration
builder.Add(new ConsulConfigurationSource(consulAddress, appName, tempConfig));
// 4. 加载环境变量
builder.AddEnvironmentVariables();
return builder;
}
private static IConfigurationBuilder AddDockerSecrets(this IConfigurationBuilder builder)
{
//var secretsPath = @"D:\DockerConfig\configs";
var secretsPath = "/run/secrets";
if (Directory.Exists(secretsPath))
{
foreach (var file in Directory.GetFiles(secretsPath))
{
var key = Path.GetFileNameWithoutExtension(file);
var value_en = File.ReadAllText(file).Trim();
var value = AesEncryption.AesEcbDecrypt(value_en, Const.DBAESKEY);
builder.AddInMemoryCollection(new[]
{
new KeyValuePair<string, string>($"Secrets:{key}", value)
});
}
}
return builder;
}
}
public class ConsulConfigurationSource : IConfigurationSource
{
private readonly string _consulAddress;
private readonly string _appName;
private readonly IConfiguration _configuration; // 新增
public ConsulConfigurationSource(string consulAddress, string appName, IConfiguration configuration)
{
_consulAddress = consulAddress;
_appName = appName;
_configuration = configuration;
}
public IConfigurationProvider Build(IConfigurationBuilder builder)
{
return new ConsulConfigurationProvider(_consulAddress, _appName, _configuration, "config");
}
}
public class ConsulConfigurationProvider : ConfigurationProvider
{
private readonly string _consulAddress;
private readonly string _appName;
private readonly string _keyPrefix;
private readonly IConfiguration _configuration; // 新增
public ConsulConfigurationProvider(string consulAddress, string appName, IConfiguration configuration, string keyPrefix = "")
{
_consulAddress = consulAddress;
_appName = appName;
_keyPrefix = keyPrefix;
_configuration = configuration;
}
public override void Load()
{
LoadConfigs().GetAwaiter().GetResult();
}
private async Task LoadConfigs()
{
try
{
using var client = new ConsulClient(config =>
{
config.Address = new Uri(_consulAddress);
});
await LoadAllConfigKeys(client);
Console.WriteLine($"配置加载完成,共 {Data.Count} 个配置项");
}
catch (Exception ex)
{
Console.WriteLine($"加载配置失败: {ex.Message}");
}
}
private async Task LoadAllConfigKeys(ConsulClient client)
{
// 获取根目录下的所有键(根据你的前缀过滤)
var queryResult = await client.KV.List(_keyPrefix);
if (queryResult?.Response != null)
{
var newData = new Dictionary<string, string?>();
foreach (var kvPair in queryResult.Response)
{
if (kvPair.Value != null)
{
var key = RemovePrefix(kvPair.Key);
var value = Encoding.UTF8.GetString(kvPair.Value);
value = GetConnectionString(key, value, _configuration);
newData[key] = value;
}
}
Data = newData;
}
}
private string GetConnectionString(string key, string value, IConfiguration configuration)
{
// 判断是否为数据库连接,如果是,拼接密码
if (key.StartsWith("ConnectionStrings", StringComparison.OrdinalIgnoreCase))
{
// 从已经加载的 Secrets 配置中获取密码
var dbPwd = configuration["Secrets:DbPwdMysql"]; // 或根据 key 动态决定
if (!string.IsNullOrEmpty(dbPwd))
{
value = value.Replace("{password}", dbPwd); // 假设连接字符串里有 {password} 占位符
}
}
return value;
}
private string RemovePrefix(string key)
{
key = key.StartsWith(_keyPrefix) ? key.Substring(_keyPrefix.Length + 1) : key;
key = key.Replace("/", ":");
return key;
}
#region 手动重新加载接口
public async Task ReloadAsync()
{
await LoadConfigs();
OnReload(); // 通知配置已更新
Console.WriteLine("配置手动重新加载完成");
}
public Dictionary<string, string> GetCurrentConfigs()
{
return new Dictionary<string, string>(Data);
}
#endregion
}
}
4. 使用
builder.Configuration 的使用方式不变
只是注意 Consul KV 是以字符串注入的,所以配置尽量都统一成 string
例:
var consulKVPath = configuration.GetSection("ConsulKVPath");
var consulConfig = configuration.GetSection(consul_common_name).Get<ConsulCommonConfig>() ?? new ConsulCommonConfig();
services.Configure<ConsulCommonConfig>(configuration.GetSection(consul_common_name));
end.

浙公网安备 33010602011771号