Go Etcd 开发简易的配置中心
在这篇文章中,我们将介绍利用 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