Go Etcd 开发简易的配置中心

Golang 1013 字

在这篇文章中,我们将介绍利用 Confid 配合 Etcd 来完成建议配置中心。

随着服务越来越多每个服务都是一个独立的项目,在项目中可能会涉及到数据库和一些配置,如果这些配置一旦发生改变了那我们就可能到服务器上进行修改,然后重启服务,如果服务只有一两个,那么没有必要使用配置中心。

如果服务有几十个,甚至上百个,我们就需要有一个配置中心来帮助我们管理配置文件,配置中心产生的原因,其实就是随着服务节点的增多而出现的,为了增加我们的可维护性专门有一个配置中心,统一配置。

能够完成这项工作的软件有很多,Apollo 等等,可视化肯定要比自己手工写配置中心要好一些,如果想要使用轻量级的,完全可以利用 etcd 来完成。

confd 从 etcd 中拉取数据

confd 是一款高可用统一配置管理工具,源代码是用 Go 编写的,其地址如下:

https://github.com/kelseyhightower/confd

它可以监听 etcd 中的内容,然后根据模板来生成配置文件,在这里我们使用 docker 来构建 confd,我们现将源代码下载到本地。

https://codeload.github.com/kelseyhightower/confd/tar.gz/refs/tags/v0.16.0

,下载完成后解压到工程目录下。

FROM golang:1.12-alpine as confd
ADD ./confd-0.16.0/ /go/src/github.com/kelseyhightower/confd
RUN export CGO_ENABLED=0 && \
    sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories && \
    apk update && \
    apk add --no-cache bzip2 make && \
    cd /go/src/github.com/kelseyhightower/confd && \
    go install github.com/kelseyhightower/confd
ENTRYPOINT ["/go/bin/confd"]

构建镜像:

docker build -t confd:my .

我们在 etcd 里面插入两个值

$ docker exec -it etcd-etcd-1 etcdctl put /config_center/mysql/user root
$ docker exec -it etcd-etcd-1 etcdctl put /config_center/mysql/pass 123123

接下来我们利用 confd 生成配置文件,首先在本地创建一个文件夹叫做 confdfiles ,里面分别创建两个文件夹 conf.d和 templates

$ mkdir -p confdfiles/{conf.d,templates,dest}

其中 conf.d 存放配置文件,templates 方模板文件。

先编写 confd 的配置文件

[template]
src = "config.conf.tmpl"
dest = "/etc/confd/dest/config.conf"
keys = [
    "/config_center/mysql/user",
    "/config_center/mysql/root"
]
  • src 代表模板地址
  • dest 代表获取配置后生成到哪里
  • keys 代表要从 etcd 读取的 key

编写模板文件,conf.d 会根据模板文件生成最终的配置文件。

[config]
database_user = {{getv "/config_center/mysql/user"}}
database_pass = {{getv "/config_center/mysql/pass"}}

其中的 getv 代表着从 key 中取值。

注意:在编写配置文件的时候一定要注意,不能写错!

我们来试一下启动容器获取配置

$ docker run -it --rm --name confd \
 -v $PWD/confdfiles:/etc/confd confd:my \
 -onetime -backend etcdv3 -node http://192.168.124.4:2379
[config]
database_user = root
database_pass = 123123

http 服务读取配置文件

我们先编写一个简单的 httpserver,其主要功能是展示配置文件中的db_user和db_pass。

package main

import (
    "flag"
    "gopkg.in/ini.v1"
    "log"
    "net/http"
    "strconv"
)

func main() {
    port := flag.Int("p", 0, "服务端口")
    flag.Parse()
    if *port == 0 {
        log.Fatalln("请指定端口")
    }

    cfg, err := ini.Load("my.ini")
    if err != nil {
        log.Fatalln(err)
    }
    http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
        dbUser := cfg.Section("db").Key("db_user").Value()
        dbPass := cfg.Section("db").Key("db_pass").Value()
        writer.Write([]byte("<h1>" + dbUser + "</h1>"))
        writer.Write([]byte("<h1>" + dbPass + "</h1>"))
    })
    http.ListenAndServe(":"+strconv.Itoa(*port), nil)

}

我们修改之前的confd 的配置文件

[template]
src = "config.ini.tmpl"
dest = "/etc/confd/dest/config.ini"
keys = [
    "/config_center/mysql/user",
    "/config_center/mysql/pass"
]

这里需要注意,我们变更里配置,对应的文件名也需要进行改变 config.ini.tmpl

[db]
db_user = {{getv "/config_center/mysql/user"}}
db_pass = {{getv "/config_center/mysql/pass"}}

启动应用

$ go run main.go -p 2000 -c ./confdfiles/dest/config.ini

我们可以看到在浏览器中已经展示出了当前的值!

当配置发生变化后的简单重载策略,监听

在上面我们使用 -interval 来设置定期扫描,etcd 是支持 watch 的,我们只需要使用过 -watch 参数即可开启监听功能。

 $ docker run -it -d --rm --name confd \
 -v $PWD/confdfiles:/etc/confd confd:my \
 -watch -backend etcdv3 -node http://192.168.124.4:2379

在 confd 中提供了一个功能,当配置发生变化后可以出发 hook——reload_cmd 配置。

[template]
src = "config.ini.tmpl"
dest = "/etc/confd/dest/config.ini"
keys = [
    "/config_center/mysql/user",
    "/config_center/mysql/pass"
]
reload_cmd = "curl http://192.168.124.4:20001/reload"

我在应用中开放一个接口用来更新配置。

http.HandleFunc("/reload", func(writer http.ResponseWriter, request *http.Request) {
        newCfg, err := ini.Load(*config)
        if err != nil {
            log.Fatalln(err)
        }
        cfg = newCfg
    })

这个接口用来重新加载配置文件,并且将新的配置复制给老的配置。这种方式很简单,但是如果服务比较多那就会比较复杂了。

当我们修改配置文件后,容器会异常退出,这是因为在当前容器中并没有 curl 命令,所以我们需要修改 Dockerfile 并重新 build 镜像。

FROM golang:1.12-alpine as confd
ADD ./confd-0.16.0/ /go/src/github.com/kelseyhightower/confd
RUN export CGO_ENABLED=0 && \
    sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories && \
    apk update && \
    apk add --no-cache bzip2 make curl && \
    cd /go/src/github.com/kelseyhightower/confd && \
    go install github.com/kelseyhightower/confd
ENTRYPOINT ["/go/bin/confd"]
docker build confd:v2 -t .
 $ docker run -it -d --rm --name confd \
 -v $PWD/confdfiles:/etc/confd confd:v2 \
 -watch -backend etcdv3 -node http://192.168.124.4:2379

当配置被更新后就会重新加载配置了。

线上千万别用这种方式!!!!!

监控文件变化

上面我们用 confd 监听变化出发重新加载配置文件,在这里我们让服务自己监控文件,如果文件发生了变化,进行重启服务。

最简单的方案是使用 md5,如果上一次的文件 md5和现在的 MD5不一致则说明文件被更改了,就出发更新操作.

func getFileMd5(path string) (string, error) {
    file, err := os.Open(path)
    if err != nil {
        return "", err
    }
    hash := md5.New()
    if _, err = io.Copy(hash, file); err != nil {
        return "", err
    }
    hashInBytes := hash.Sum(nil)[:16]
    return hex.EncodeToString(hashInBytes), nil
}

这个函数就是去获取 md5值的。

go func() {
        fileMd5, err := getFileMd5(*config)
        if err != nil {
            log.Println(err)
            return
        }
        for {
            newMd5, err := getFileMd5(*config)
            if err != nil {
                log.Println(err)
                break
            }
            if strings.Compare(newMd5, fileMd5) != 0 {
                fileMd5 = newMd5
                fmt.Println("文件变更了!")

                # 重启服务....
            }
            time.Sleep(time.Second * 2)
        }
    }()

我们开启一个协程去不断的监听配置的变化,如果发生变化了,就开启平滑重启,逻辑很麻烦,我们利用第三方的库来实现。

有兴趣的可以去看一下这个库:github.com/jpillora/overseer

maksim
Maksim(一笑,吡罗),PHPer,Goper
OωO
开启隐私评论,您的评论仅作者和评论双方可见