惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
爱范儿
爱范儿
H
Help Net Security
V
Visual Studio Blog
J
Java Code Geeks
Stack Overflow Blog
Stack Overflow Blog
Microsoft Security Blog
Microsoft Security Blog
Apple Machine Learning Research
Apple Machine Learning Research
MyScale Blog
MyScale Blog
The Cloudflare Blog
Martin Fowler
Martin Fowler
D
Docker
腾讯CDC
F
Fortinet All Blogs
雷峰网
雷峰网
GbyAI
GbyAI
G
Google Developers Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Recent Announcements
Recent Announcements
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Blog — PlanetScale
Blog — PlanetScale
Engineering at Meta
Engineering at Meta
博客园 - 聂微东
博客园 - 叶小钗

博客园 - 无心々菜

InfluxDB2 在 docker 环境下部署,以及备份还原 高并发 - 配置2 高并发 - 配置 地图 - QGIS加载geojson,并导入Postgis数据库 VUE2 - Geoserver打印矢量PDF地图 - 在VUE2中的应用 地图 - Geoserver打印矢量PDF地图 Docker - 离线+无root环境安装 docker-rootless 运维 - 哪吒监控 Postgres 数据库在docker环境下分布式部署(arm64框架) Postgres 数据库在docker环境下分布式部署 Https - 配置方案 OpenObserver - 系统接口监控 VUE3 - Docker部署 + traefik反代 VUE2 - Docker部署 + traefik反代 高斯DB 数据库的安装 NETCORE - 使用 EFCORE 调用 高斯DB数据库 JMeter - 压力测试 NETCORE - IdentityServer4 多节点部署 NETCORE - IdentityServer4 相关文档 .NET框架 - NETCORE + API + EF(DBFirst) + PostgresSql Docker 部署 APISIX + etcd Docker部署martin 矢量切片服务器 Postgres 数据库的安装 NETCORE - 调用 RUSTFS Docker - 部署 RustFS 对像存储 Docker - 使用 Docker Secrets 敏感数据传输 Docker - 部署 HashiCorp Vault 开源密钥管理工具 kong 网关下集成 Consul服务注册与发现 Docker 部署 kong 网关 Docker - 部署Consul 新
Docker - 部署 Consul KV 统一管理所有项目的配置
无心々菜 · 2025-11-04 · via 博客园 - 无心々菜

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.