第 9 章 微服务系统的配置

微服务系统中的配置需要关注更多其他方面的因素,包括:

  • 配置值的安全读写
  • 值变更的审计能力
  • 配置信息源本身的韧性和可靠性
  • 少量的环境变量难以承载大型、复杂的配置信息
  • 应用要决定是否支持配置值的在线更新和实时变更,还要决定如何实现
  • 对功能开关和层级化设置的支持
  • 对敏感信息以及加密密钥本身进行存储和读取支持

本章首先讨论在应用中使用环境变量的机制,并演示 Docker 的支持情况

接着探索一个来自 Netflix OSS 技术栈的配置服务器产品

最后将运用 etcd,它是一个常用于配置管理的开源分布式键值数据库

在 Docker 中使用环境变量

为配置提供默认值时,还应该考虑哪些设置在应用启动期间需要通过环境变量进行覆盖

为配置设置值时,可使用键值对显示指定,如下所示:

$ sudo docker run -e SOME_VAR='foo' \ -e PASSWORD='foo' \
-e USER='bar' \
-e DB_NAME='mydb' \
-p 3000:3000 \
--name container_name microservices-aspnetcore/image:tag

或者,如果不希望在命令行中显示传入值,也可以把来自启动环境的环境变量转发到容器内部,只要不传入包含值的等式即可,例如:

$ docker run -e PORT -e CLIENTSCRET -e CLIENTKEY [...]

这一命令将把命令行所在终端中的 PORT、CLIENTSECRET 和 CLIENTKEY 环境变量的值传入 Docker 容器中,在这个过程中它们的值不会在命令行文本中公开,以防范潜在的安全漏洞和敏感信息泄露

如果需要向容器传入大量的环境变量,可以向 docker 命令指定一个包含键值对列表的文件:

$ docker run --env-file ./myenv.file [...]

使用 Spring Cloud 配置服务器

围绕服务的配置管理的最大难题之一,并非如何将值注入到环境变量,而在于这些值本身的日常维护

当配置的原始源处的值发生变更时,我们如何得到通知

更进一步,当值发生变更时,我们如何回溯并查看之前的值

你可能发现,这似乎可用使用类似于 Git 仓库的方法来管理配置值

Spring Cloud 配置服务器(SCCS)的开发人员也持相同看法

要在 .NET Core 应用中添加 SCCS 客户端的支持,只需要在项目中添加对 Steeltoe.Extensions.Configuration.ConfigServer NuGet 包的引用

接着,我们需要配置应用,让它从正确的位置获取设置信息

我们需要定义一个 Spring 应用名称,并在 appsettings.json 文件中添加配置服务器的 URL

{
"spring": {
"application": {
"name": "foo"
},
"cloud": {
"config": {
"uri": "http://localhost:8888"
}
}
},
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
}
}

配置完成后,Startup 构造方法仍然与其他应用几乎一致

public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false)
.AddEnvironmentVariables()
.AddConfigServer(env); Configuration = builder.Build();
}

要添加对配置服务器的支持,接下来需要修改 ConfigureServices 方法

首先调用 AddConfigServer 向依赖注入子系统加入配置客户端

接着指定泛型参数并调用 Configure 方法

这一操作能把从配置服务器获取的配置信息包装为一个 IOptionsSnapshot 对象,然后可由控制器和其他代码使用

public void ConfigureServices(IServiceCollection services)
{
services.AddConfigServer(Configuration);
services.AddMvc(); services.Configure<ConfigServerData>(Configuration);
}

此处,用于表示从配置服务器获取的数据的数据模型,是基于 Spring Cloud 服务器示例仓库中的示例配置进行建模的

public class ConfigServerData
{
public string Bar { get; set; }
public string Foo { get; set; }
public Info Info { get; set; }
} public class Info
{
public string Description { get; set; }
public string Url { get; set; }
}

然后,在需要时,就可注入这个类的实例,以及配置服务器的客户端参数

public class MyController : MyController
{
private IOptionsSnapshot<ConfigServerData> MyConfiguration { get; set; }
private ConfigServerClientSettingsOptions ConfigServerClientSettingsOptions { get; set; }
public MyController(IOptionsSnapShot<ConfigServerData> opts, IOptions<ConfigServerClientSettingsOptions> clientOpts)
{
...
} ...
}

上述配备完成后,如果配置服务器已处于运行状态,构造器中的 opts 变量将包含应用所有的相关配置

启动配置服务器最简单的方法就是直接通过 Docker 镜像运行以下代码

$ docker run -p 8888:8888 \
-e SPRING_CLOUD_CONFIG_SERVER_GET_URI=http://github.com/spring-cloud-samples/ \config-repohyness/spring-cloud-config-server

如果服务器运行正确,应该能通过以下命令获取配置信息

curl http://localhost:8888/foo/development

在本地用 Docker 镜像启动配置服务器后,使用上面展示的 C# 代码,就能体验将外部配置数据提供给 .NET Core 微服务的过程

使用 etcd 配置微服务

Spring Cloud 配置服务器的替代品不计其数,etcd 是其中很流行的一个

上一章简单提到,etcd 是一个轻量级的分布式键值数据库

它就是为你存储分布式系统所需要的最关键信息的位置

etcd 是一个集群产品,其节点之间的通信是基于 Raft 共识算法实现的

etcd 的一个最常见运用场景就是存储和检索配置信息以及功能标志

在本章的例子里,我访问 compose.io 并注册了一个免费试用的托管 etcd

创建 etcd 配置提供程序

GitHub链接:https://github.com/microservices-aspnetcore/etcd-client

创建配置源

using System;
using Microsoft.Extensions.Configuration; namespace ConfigClient
{
public class EtcdConfigurationSource : IConfigurationSource
{
public EtcdConnectionOptions Options { get; set; } public EtcdConfigurationSource(EtcdConnectionOptions options)
{
this.Options = options;
} public IConfigurationProvider Build(IConfigurationBuilder builder)
{
return new EtcdConfigurationProvider(this);
}
}
}

创建配置构建器

using System;
using System.Collections.Generic;
using EtcdNet;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Primitives; namespace ConfigClient
{
public class EtcdConfigurationProvider : ConfigurationProvider
{
private EtcdConfigurationSource source; public EtcdConfigurationProvider(EtcdConfigurationSource source)
{
this.source = source;
} public override void Load()
{
EtcdClientOpitions options = new EtcdClientOpitions()
{
Urls = source.Options.Urls,
Username = source.Options.Username,
Password = source.Options.Password,
UseProxy = false,
IgnoreCertificateError = true
};
EtcdClient etcdClient = new EtcdClient(options);
try
{
EtcdResponse resp = etcdClient.GetNodeAsync(source.Options.RootKey,
recursive: true, sorted: true).Result;
if (resp.Node.Nodes != null)
{
foreach (var node in resp.Node.Nodes)
{
// child node
Data[node.Key] = node.Value;
}
}
}
catch (EtcdCommonException.KeyNotFound)
{
// key does not
Console.WriteLine("key not found exception");
}
}
}
}

借助如下扩展方法

using Microsoft.Extensions.Configuration;

namespace ConfigClient
{
public static class EtcdStaticExtensions
{
public static IConfigurationBuilder AddEtcdConfiguration(this IConfigurationBuilder builder,
EtcdConnectionOptions connectionOptions)
{
return builder.Add(new EtcdConfigurationSource(connectionOptions));
}
} public class EtcdConnectionOptions
{
public string[] Urls { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string RootKey { get; set; }
}
}

便能在 Startup 类中把 etcd 添加为配置源

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; namespace ConfigClient
{
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEtcdConfiguration(new EtcdConnectionOptions
{
Urls = new string[] {
"https://portal1934-21.euphoric-etcd-31.capital-one-3.composedb.com:17174",
"https://portal2016-22.euphoric-etcd-31.capital-one-3.composedb.com:17174"
},
Username = "root",
Password = "changeme",
RootKey = "/myapp"
})
.AddEnvironmentVariables();
Configuration = builder.Build();
} public static IConfigurationRoot Configuration { get; set; } // This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
} // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole();
loggerFactory.AddDebug(); app.UseMvc();
}
}
}

使用来自 etcd 的配置值

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using EtcdNet;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Configuration; namespace ConfigClient.Controllers
{
[Route("api/[controller]")]
public class ValuesController : Controller
{
private ILogger logger; public ValuesController(ILogger<ValuesController> logger)
{
this.logger = logger;
} // GET api/values
[HttpGet]
public IEnumerable<string> Get()
{
List<string> values = new List<string>();
values.Add(Startup.Configuration.GetSection("/myapp/hello").Value);
values.Add(Startup.Configuration.GetSection("/myapp/rate").Value); return values;
} // GET api/values/5
[HttpGet("{id}")]
public string Get(int id)
{
return "value";
} // POST api/values
[HttpPost]
public void Post([FromBody]string value)
{
} // PUT api/values/5
[HttpPut("{id}")]
public void Put(int id, [FromBody]string value)
{
} // DELETE api/values/5
[HttpDelete("{id}")]
public void Delete(int id)
{
}
}
}

现在访问 http://localhost:3000/api/values 端点,将返回这些值:

{"world", "12.5"}

这些正是本节前面面向 etcd 服务器添加的值

只使用了少数几行代码,我们便创建了一个由远程配置服务器支持的、稳定而符合标准的 ASP.NET 配置源

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。

欢迎转载、使用、重新发布,但务必保留文章署名 郑子铭 (包含链接: http://www.cnblogs.com/MingsonZheng/ ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。

如有任何疑问,请与我联系 (MingsonZheng@outlook.com) 。

《ASP.NET Core 微服务实战》-- 读书笔记(第9章)的更多相关文章

  1. JavaScript DOM编程艺术读书笔记(三)

    第七章 动态创建标记 在web浏览器中往文档添加标记,先回顾下过去使用的技术: <body> <script type="text/javascript"> ...

  2. JavaScript DOM编程艺术读书笔记(二)

    第五章 最佳实践 平稳退化(graceful degradation):如果正确使用了JavaScript脚本,可以让访问者在他们的浏览器不支持JavaScript的情况下仍能顺利地浏览你网站.虽然某 ...

  3. JavaScript DOM编程艺术读书笔记(一)

    第一章,第二章 DOM:是一套对文档的内容进行抽象和概念化的方法. W3C中的定义:一个与系统平台和编程语言无关的接口,程序和脚本可以通过这个接口动态的访问和修改文档的内容,结构和样式. DHTML( ...

  4. JavaScript DOM编程艺术 - 读书笔记1-3章

    1.JavaScript语法 准备工作 一个普通的文本编辑器,一个Web浏览器. JavaScript代码必须通过Html文档才能执行,第一种方式是将JavaScript代码放到文档<head& ...

  5. JavaScript DOM编程艺术 读书笔记

    2. JavaScript语法 2.1 注释      HTML允许使用"<!--"注释跨越多个行,但JavaScript要求这种注释的每行都必须在开头加上"< ...

  6. JavaScript DOM编程艺术读书笔记(四)

    第十章 实现动画效果 var repeat = "moveElement('"+elementID+"',"+final_x+","+fin ...

  7. JavaScript DOM编程艺术-学习笔记(第二章)

    1.好习惯从末尾加分号:开始 2.js区分大小写 3.程序界万能的命名法则:①不以,数字开头的数字.字母.下划线.美元符号 ②提倡以下划线命名法来命名变量,以驼峰命名法来命名函数.但是到了公司往往会身 ...

  8. 《javascript dom编程艺术》笔记(一)——优雅降级、向后兼容、多个函数绑定onload函数

    刚刚开始自学前端,如果不对请指正:欢迎各位技术大牛指点. 开始学习<javascript dom编程艺术>,整理一下学习到的知识.今天刚刚看到第六章,记下get到的几个知识点. 优雅降级 ...

  9. JavaScript DOM编程艺术学习笔记(一)

    嗯,经过了一周的时间,今天终于将<JavaScript DOM编程艺术(第2版)>这本书看完了,感觉受益匪浅,我和作者及出版社等等都不认识,无意为他们做广告,不过本书确实值得一看,也值得推 ...

  10. JavaScript DOM编程艺术-学习笔记

    发现基础不是很好,补习一下.37买了2本书(dom编程和高级程序设计). 以前读书总是自己勾勾画画,有点没意思.现在写下来,说不定会成为传世经典.哈哈...........随便扯扯淡. 第一天(201 ...

随机推荐

  1. 基于 SpringBoot + magic-api + Vue3 + Element Plus + amis3.0 快速开发管理系统

    Tansci-Boot 基于 SpringBoot2 + magic-api + Vue3 + Element Plus + amis3.0 快速开发管理系统 Tansci-Boot 是一个前后端分离 ...

  2. C#设计模式06——适配器的写法

    什么是适配器模式? 适配器模式是一种结构型设计模式,用于将现有接口转换为符合客户端期望的接口.适配器模式允许不兼容的类可以相互协作. 为什么需要适配器模式? 在实际开发中,经常会遇到需要复用一些已有的 ...

  3. Python Code_04InputFunction

    代码部分 # coding:utf-8 # author : 写bug的盼盼 # development time : 2021/8/28 6:55 present = input('你想要什么?') ...

  4. [转帖]060-轻量级基于curl的seafile上传脚本

    https://anjia0532.github.io/2021/04/07/seafile-client-curl/ 这是坚持技术写作计划(含翻译)的第 60 篇,定个小目标 999,每周最少 2 ...

  5. [转帖]SIMD+SSE+AVX

    http://home.ustc.edu.cn/~shaojiemike/posts/simd/   SIMD SIMD全称Single Instruction Multiple Data,单指令多数 ...

  6. [转帖]Oracle入门精读28-字符集 AL32UTF8与UTF8

    字符(Character) 字符是各种文字和符号的总称,包括各国家文字.标点符号.图形符号.数字等. 字符编码(Character Encoding) 是一套法则,使用该法则能够对自然语言的字符的一个 ...

  7. 据说这道Go面试题90%的人都搞错了!

    [Go面试向]defer与time.sleep初探 大家好,我是阳哥,这是我们Go就业训练营小伙伴 寸铁同学 整理的一道很有意思的面试题. 知其然更要知其所以然,通过断点调试的思路带你搞清楚来龙去脉. ...

  8. js中toString方法的三个作用

    toString方法的三个作用: 1.返回一个[表示对象]的[字符串] 2.检测对象的类型 Object.prototype.toString.call(arr)==="[object Ar ...

  9. 装elemnetUI中用户头像上传

    组件.vue 在使用的时候,入股想出现边框.要自己在添加一个类哈 自己还有在添加一个哈 .avatar-uploader { border:1px solid red; width: 178px; h ...

  10. vue报错:'XX' is defined but never used no-unused-vars

    参考地址:http://www.gold404.cn/info/87 导致这个报错的原因就是eslint校验, 就是你当初在vue创建脚手架的时候选择了eslint校验: 后面你绝对会碰到这样的报错, ...