zoukankan      html  css  js  c++  java
  • 《ASP.NET Core 微服务实战》-- 读书笔记(第9章)

    第 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) 。

  • 相关阅读:
    一个docker容器中运行多个服务还是弄一堆docker容器运行?
    golang配置 GoGetProxyConfig,goproxy.io的介绍
    Docker下运行Mysql报错 mbind: Operation not permitted
    linux允许root用户远程登录
    docker-compose 安装 mysql并初始化用户与sql文件
    spring报错 xxxxxxxxxxxx has been injected into other beans
    mysql报错[Warning] IP address 'xxxx' could not be resolved: Name or service not known错误解决
    idea 高效找出全部未被使用的代码
    springboot配置Filter的两种方法
    .net core Elasticsearch 查询更新
  • 原文地址:https://www.cnblogs.com/MingsonZheng/p/12289596.html
Copyright © 2011-2022 走看看