|
微处事或者说云原生应用的配置最佳实践是将配置文件和应用代码分创打点——不将配置文件放入代码仓库,也不打包进容器镜像,而是在处事运行时,把配置文件挂载进去或者直接从配置中心加载。Kratos的config组件就是用来辅佐应用从各种配置源加载配置。
设计理念
1.撑持多种配置源
Kratos定义了尺度化的Source和Watcher接口来适配各种配置源。
框架内置了当地文件file和环境变量env的实现。
此外,在contrib/config下面,我们也提供了如下的配置中心的适配供使用:
- apollo
- consul
- etcd
- kubernetes
- nacos
如果上述的配置加载方式无法涵盖您的环境,您也可以通过实现接口来适配您本身的配置加载方式。
2.撑持多种配置格式
配置组件复用了encoding中的反序列化逻辑作为配置解析使用。默认撑持以下格式的解析:
框架将按照配置文件类型匹配对应的Codec,进行配置文件的解析。您也可以通过实现Codec并用encoding.RegisterCodec方式,将它注册进去,来解析其它格式的配置文件。
配置文件类型的提取,按照配置源具体实现分歧而略有区别,内置的file是把文件后缀作为文件类型的,其它配置源插件的具体逻辑请参考对应的文档。
3.热更新
Kratos的config组件撑持配置的热更新,您可以使用配置中心配合config的热更新功能,在处事不从头发布/不竭机/不重启的情况下,在线更新处事的配置,改削处事的一些行为。
4.配置合并
在config组件中,所有的配置源中的配置(文件)将被逐个读出,分袂解析成map,并合并到一个map中去。因此在加载完毕后,不需要再理会配置的文件名,不用文件名来进行查找,而是用内容中的布局来对配置的值进行索引即可。设计和编写配置文件时,请注意各个配置文件中,根层级的key不要反复,否则可能会被覆盖。
举例:
有如下两个配置文件:- # 文件1
- foo:
- baz: ”2”
- biu: ”example”
- hello:
- a: b
- # 文件2
- foo:
- bar: 3
- baz: aaaa
- hey:
- good: bad
- qux: quux
复制代码 .Load后,将被合并为如下的布局:- {
- ”foo”: {
- ”baz”: ”aaaa”,
- ”bar”: 3,
- ”biu”: ”example”
- },
- ”hey”: {
- ”good”: ”bad”,
- ”qux”: ”quux”
- },
- ”hello”: {
- ”a”: ”b”
- }
- }
复制代码 我们可以发现,配置文件的各层级将分袂合并,在key冲突时会发生覆盖,而具体的覆盖挨次,会由配置源实现中的读取挨次决定,因此这里从头提醒一下,各个配置文件中,根层级的key不要反复,也不要依赖这个覆盖的特性,从底子上避免分歧配置文件的内容互相覆盖造成问题。
在使用时,可以用.Value(”foo.bar”)直接获取某个字段的值,也可以用.Scan方式来将整个map读进某个布局体中,具体使用方式请看下文。
使用
1.初始化配置源
使用file,即从当地文件加载: 这里的path就是配置文件的路径,这里也可以填写一个目录名,这样会将整个目录中的所有文件进行解析加载,合并到同一个map中。- import (
- ”github.com/go-kratos/kratos/v2/config”
- ”github.com/go-kratos/kratos/v2/config/file”
- )
- path := ”configs/config.yaml”
- c := config.New(
- config.WithSource(
- file.NewSource(path),
- )
- )
复制代码 如果想用外部的配置中心,可以在contrib/config里面找一个,以consul为例:- import (
- ”github.com/go-kratos/kratos/contrib/config/consul/v2”
- ”github.com/hashicorp/consul/api”
- )
- consulClient, err := api.NewClient(&api.Config{
- Address: ”127.0.0.1:8500”,
- })
- if err != nil {
- panic(err)
- }
- cs, err := consul.New(consulClient, consul.WithPath(”app/cart/configs/”))
- if err != nil {
- panic(err)
- }
- c := config.New(config.WithSource(cs))
复制代码 分歧的配置源插件使用方式略有分歧,您可以参考它们各自的文档或examples。
2.读取配置
首先要定义一个布局体用来解析字段,如果您使用的是kratos-layout创建的项目,可以参考后面讲解kratos-layout的部门,使用proto文件定义配置和生成struct。
我们这里演示的是手工定义布局,您需要在布局体上用json tag来定义您配置文件的字段。- var v struct {
- Service struct {
- Name string `json:”name”`
- Version string `json:”version”`
- } `json:”service”`
- }
复制代码 使用之前创建好的config实例,调用.Scan方式,读取配置文件的内容到布局体中,这种方式适用于完整获取整个配置文件的内容。- // Unmarshal the config to struct
- if err := c.Scan(&v); err != nil {
- panic(err)
- }
- fmt.Printf(”config: %+v”, v)
复制代码 使用config实例的.Value方式,可以单独获取某个字段的内容。- name, err := c.Value(”service.name”).String()
- if err != nil {
- panic(err)
- }
- fmt.Printf(”service: %s”, name)
复制代码 3.监听配置变换
通过.Watch方式,可以监听配置中某个字段的变换,在当地或远端的配置中心有配置文件变换时,执行回调函数进行自定义的措置- if err := c.Watch(”service.name”, func(key string, value config.Value) {
- fmt.Printf(”config changed: %s = %v\n”, key, value)
- // 在这里写回调的逻辑
- }); err != nil {
- log.Error(err)
- }
复制代码 4.读取环境变量
如果有配置需要从环境变量读取,请使用以下方式:
配置环境变量配置源env:- c := config.New(
- config.WithSource(
- // 添加前缀为 KRATOS_ 的环境变量,不需要的话也可以设为空字符串
- env.NewSource(”KRATOS_”),
- // 添加配置文件
- file.NewSource(path),
- ))
-
- // 加载配置源:
- if err := c.Load(); err != nil {
- log.Fatal(err)
- }
- // 获取环境变量 KRATOS_PORT 的值,这里用去掉前缀的名称进行读取
- port, err := c.Value(”PORT”).String()
复制代码 除了上面使用Value方式直接读的方式,也可以在配置文件内容里使用占位符来把环境变量中的值衬着进去:- service:
- name: ”kratos_app”
- http:
- server:
- # 使用 service.name 的值
- name: ”${service.name}”
- # 使用环境变量 PORT 替换,若不存在,使用默认值 8080
- port: ”${PORT:8080}”
- # 使用环境变量 TIMEOUT 替换,无默认值
- timeout: ”$TIMEOUT”
复制代码 5.配置解析Decoder
Decoder用于将配置文件内容用特定的反序列化方式解析出来,默认decoder会按照文件的类型自动识别类型并解析,凡是情况不需要自定义这个,您可以通过后文的实现Codec的方式来注册更多文件类型。
在初始化config时插手WithDecoder参数,可以将Decoder覆盖为自定义的逻辑。如下代码展示了配置自定义Decoder的方式,这里使用了yaml库解析所有配置文件,您可以使用这种方式来使用特定的配置文件解析方式,但更保举使用后文的实现Codec的方式,能同时撑持多种格式的解析。- import ”gopkg.in/yaml.v2”
- c := config.New(
- config.WithSource(
- file.NewSource(flagconf),
- ),
- config.WithDecoder(func(kv *config.KeyValue, v map[string]interface{}) error {
- return yaml.Unmarshal(kv.Value, v)
- }),
- )
复制代码 6.配置措置Resolver
Resolver用于对解析完毕后的map布局进行再次措置,默认resolver会对配置中的占位符进行填充。您可以通过在初始化config时插手WithResolver参数,来覆盖resolver的行为。- c := config.New(
- config.WithSource(
- file.NewSource(flagconf),
- ),
- config.WithResolver(func (input map[string]interface{}) (err error) {
- // 在这里对input进行措置即可
- // 您可能需要定义一个递归的函数,来措置嵌套的map布局
- return
- }),
- )
复制代码 7.撑持其它格式的配置文件
首先实现Codec,这里以yaml为例- import (
- ”github.com/go-kratos/kratos/v2/encoding”
- ”gopkg.in/yaml.v3”
- )
- const Name = ”myyaml”
- func init() {
- encoding.RegisterCodec(codec{})
- }
- // codec is a Codec implementation with yaml.
- type codec struct{}
- func (codec) Marshal(v interface{}) ([]byte, error) {
- return yaml.Marshal(v)
- }
- func (codec) Unmarshal(data []byte, v interface{}) error {
- return yaml.Unmarshal(data, v)
- }
- func (codec) Name() string {
- return Name
- }
复制代码 然后注册该Codec 这里由于我们把注册代码encoding.RegisterCodec(codec{})写在了包的init方式中,所以在包被import的时候,将会运行这个init方式,也就是进行注册。所以您可以在代码入口(比如main.go)对它进行注册- import _ ”path/to/your/codec”
复制代码 随后,config组件就能把上面代码中const Name = ”myyaml”这部门作为格式类型名,调用该Codec解析这个文件。
kratos-layout
理念
1.项目布局
layout中涉及到配置文件有以下部门,简单介绍一下它们的感化
- cmd/server/main.go 这个是处事的入口,我们默认使用了内置的config/file组件从当地文件系统读取配置文件,默认会读取相对路径configs目录,您可以改削这个文件里config.New()参数中使用的配置源,从其它配置源(比如配置中心)进行加载配置。配置在这里将被加载到conf.Bootstrap布局体中,这个布局体的内容可以通过依赖注入,注入到处事内部的其它层,比如server或data,这样各层就能读取到各自需要的配置,完成本身的初始化。
- configs/config.yaml 这是一个示例配置文件,configs目录的内容凡是不参与处事的出产环境运行,您可以用它来进行当地开发时的配置文件的加载,便利应用能当地能跑起来调试,不要将出产环境的配置放在这里。
- internal/conf 在这里放配置文件的布局定义,我们在这里使用.proto文件来进行配置定义,然后通过在根目录执行make config,就可以将对应.pb.go文件生成到不异目录下供使用。在初始状态下,这个conf.proto所定义的布局,就是configs/config.yaml的布局,请保持两者一致。
- make config Makefile中的这个指令,用于生成.proto定义的配置对应的.pb.go文件(就是调了一下protoc),要记得每次改削定义后,必然要执行这个指令来从头生成go文件
2.配置生成命令
我们已经把按照proto生成布局体的指令预置在Makefile里面了,通过在项目根目录下执行make config即可生成。它实际上是调用了protoc东西,扫描internal目录下的proto文件进行生成。
3.使用Protobuf定义配置
正如前文所说,我们可以在代码中直接用struct来定义配置布局进行解析。但您可能会发现,我们的最佳实践项目模板kratos-layout中采用了Protobuf来定义配置文件的布局。通过Protobuf定义,我们可以同时撑持多种格式如json、xml或者yaml等多种配置格式统一解析,这样在读配置时会变得非常便利。
layout中使用了如下的.proto文件定义配置文件的字段:- syntax = ”proto3”;
- package kratos.api;
- option go_package = ”github.com/go-kratos/kratos-layout/internal/conf;conf”;
- import ”google/protobuf/duration.proto”;
- message Bootstrap {
- Server server = 1;
- }
- message Server {
- message HTTP {
- string network = 1;
- string addr = 2;
- google.protobuf.Duration timeout = 3;
- }
- message GRPC {
- string network = 1;
- string addr = 2;
- google.protobuf.Duration timeout = 3;
- }
- HTTP http = 1;
- GRPC grpc = 2;
- }
复制代码 我们可以看出,Protobuf的定义布局清晰,而且可以指定字段的类型,这在后续的配置文件解析中可以起到校验的感化,保证加载配置文件的有效性。
在定义好布局后,我们需要用protoc东西来生成对应的.pb.go代码,也就是相应的Go struct和序列化反序列化代码,供我们使用。
使用
1.定义
改削internal/conf/config.proto文件的内容,在这里使用Protobuf IDL定义你配置文件的布局。您也可以在这个目录下创建新的proto文件来定义额外的配置格式。
2.生成
在项目根目录执行下面的命令即可生成用来解析配置文件的布局体:执行成功后,您应该能看到config.pb.go生成在config.proto文件的旁边,您就可以使用里面的布局体,比如Bootstrap来读取您的配置。
3.使用
读取配置项、监听配置变换和其它高级用法等使用方面的内容,与前文介绍的一致,这里就不再赘述。 |
|