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

image

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

image

注入成功。

 

三. 在 .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. 

 

posted @ 2025-11-04 10:34  无心々菜  阅读(17)  评论(0)    收藏  举报