使用confd + etcd实现Nginx动态配置

etcd

etcd是一个高可用的分布式键值(key-value)数据库。内部采用raft协议作为一致性算法,基于Go语言实现。etcd数据库与redis类似,其独特性在于:

  1. 分布式部署,扩展性强,且数据和事务保持一致
  2. 提供watch接口,可监听多个键的变化
  3. 对于单个键而言,每次更新其值都会保留上一个版本,可以对键进行版本回溯
  4. ttl使用租约实现

etcd更强调的是各个节点之间的通信,同步,确保各个节点上数据和事务的一致性,使得服务更稳定,本身单节点的写入能力并不强。
redis更像是内存型缓存,虽然也有cluster做主从同步和读写分离,但节点间的一致性主要强调的是数据,并不在乎事务,因此读写能力很强,qps甚至可以达到10万+

安装

$ mkdir etcd
$ cd etcd
$ curl -L https://github.com/etcd-io/etcd/releases/download/v3.3.13/etcd-v3.3.13-linux-amd64.tar.gz -o 
$ ./etcd-v3.3.13-linux-amd64.tar.gz
$ tar xzvf etcd-v3.3.13-linux-amd64.tar.gz --strip-components=1
$ ./etcd -version
$ ./etcdctl -version

本地多成员集群

针对单机用户,开启多进程,模拟多机器集群(本次模拟开启三个etcd集群)

1. 安装go
2. 安装goreman(进程管理工具)
$ go get github.com/mattn/goreman
3. 查看gopath
$ go env
GOPATH="/home/apple/go"

所以goreman运行路径为home/apple/go/bin/goreman

4. 编写goroman配置文件

goroman配置文件默认名称为Procfile,可以更换,但启动时,需要通过-c指定配置文件
goroman管理进程的配置文件由 进程名:执行命令组成

$ vim Procfile

编辑内容如下

etcd1: ./etcd --name infra1 --listen-client-urls http://127.0.0.1:2379 --advertise-client-urls http://127.0.0.1:2379 --listen-peer-urls http://127.0.0.1:12380 --initial-advertise-peer-urls http://127.0.0.1:12    380 --initial-cluster-token etcd-cluster-1 --initial-cluster 'infra1=http://127.0.0.1:12380,infra2=http://127.0.0.1:22380,infra3=http://127.0.0.1:32380' --initial-cluster-state new --enable-pprof
etcd2: ./etcd --name infra2 --listen-client-urls http://127.0.0.1:22379 --advertise-client-urls http://127.0.0.1:22379 --listen-peer-urls http://127.0.0.1:22380 --initial-advertise-peer-urls http://127.0.0.1:    22380 --initial-cluster-token etcd-cluster-1 --initial-cluster 'infra1=http://127.0.0.1:12380,infra2=http://127.0.0.1:22380,infra3=http://127.0.0.1:32380' --initial-cluster-state new --enable-pprof
etcd3: ./etcd --name infra3 --listen-client-urls http://127.0.0.1:32379 --advertise-client-urls http://127.0.0.1:32379 --listen-peer-urls http://127.0.0.1:32380 --initial-advertise-peer-urls http://127.0.0.1:    32380 --initial-cluster-token etcd-cluster-1 --initial-cluster 'infra1=http://127.0.0.1:12380,infra2=http://127.0.0.1:22380,infra3=http://127.0.0.1:32380' --initial-cluster-state new --enable-pprof
5. 运行
$ /home/apple/go/bin/goreman -f  Procfile start

此时etcd集群被开启

6.查看集群列表

此时代表集群正确安装并启动

$ export ETCDCTL_API=3
$ ./etcdctl member list
8211f1d0f64f3269: name=infra1 peerURLs=http://127.0.0.1:12380 clientURLs=http://127.0.0.1:2379 isLeader=false
91bc3c398fb3c146: name=infra2 peerURLs=http://127.0.0.1:22380 clientURLs=http://127.0.0.1:22379 isLeader=true
fd422379fda50e48: name=infra3 peerURLs=http://127.0.0.1:32380 clientURLs=http://127.0.0.1:32379 isLeader=false

etcd基本使用

API地址https://godoc.org/github.com/coreos/etcd/client
官方包提供了对于etcd所有操作的API

创建用于操作etcd键值的KeysAPI对象kv
var cli client.Client
var kv client.KeysAPI

func handleError(e error, msg string) {
    if e != nil {
        log.Fatal(e)
    }
    if msg != "" {
        log.Println(msg)
    }
}

func init(){
    cfg := client.Config{
        Endpoints: []string{"http://127.0.0.1:2379", "http://127.0.0.1:22379", "http://127.0.0.1:32379"},
    }
    var e error

    cli, e = client.New(cfg)
    handleError(e, "")
    kv = client.NewKeysAPI(cli)
}
设置键
func setVal(kv client.KeysAPI, key string, val string) {
    log.Printf("设置键%s值%s\n", key, val)
    _, e := kv.Set(context.Background(), key, val, nil)
    if e != nil {
        log.Println(e)
    }
}
获取键值
func getVal(kv client.KeysAPI, key string) string {
    // 获取键
    log.Printf("开始获取键%s \n", key)
    resp, e := kv.Get(context.TODO(), key, nil)
    handleError(e, "")
    index, value := resp.Index, resp.Node.Value
    log.Printf("获取当前版本:%d 值:%s", index, resp.Node.Value)
    return string(value)
}
创建文件夹

etcd的键值对存储可以理解为文件存储在目录中,键为目录,值为文件
创建文件夹的目的为:使用etcd提供的watch方法可以监控整个文件夹中键(即文件)的变化

func mkdir(kv client.KeysAPI, dir string) {
    o := client.SetOptions{Dir: true}
    _, e := kv.Set(context.Background(), dir, "", &o)
    handleError(e, "创建完成")
}
调用
func TestSetVal(t *testing.T) {
    setVal(kv, "/nginx/foo", "bar")
}

func TestGetVal(t *testing.T) {
    getVal(kv, "/nginx/foo")
}

func TestMkdir(t *testing.T) {
    mkdir(kv, "/nginx")
}

confd

轻量级的配置管理工具,主要有两个目的

  1. 读取etcd保存的配置信息,同步到本地配置文件中,并保证本地配置文件是最新的
  2. 同步配置文件之后可以指定命令使配置生效

安装

https://github.com/kelseyhightower/confd/blob/master/docs/installation.md

使用

把核心信息,比如upstreamserver_name等存储在etcd中,使用confd来自动生成nginx配置文件,并reload使配置生效

upstream www_test {
    server 196.75.121.112:443;     (动态生成)
}

server {
    listen       443 ssl; (动态生成)
    server_name  www.test.com; (动态生成)
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;; 
    ssl_certificate             /home/build/openresty/nginx/cert/dealssl/www.bestenover.com.crt; (动态生成)

    location / { 
        proxy_pass https://www_test; (动态生成)
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
        proxy_redirect off;
    }   
}

要实现动态配置首先要把核心信息存储到etcd中

func TestNginxMkdir(t *testing.T) {
    mkdir(kv, "/nginx")
    mkdir(kv, "/nginx/https")
    mkdir(kv, "/nginx/http")
    mkdir(kv, "/nginx/ssl")
    mkdir(kv, "/nginx/https/www")
    mkdir(kv, "/nginx/https/www/server")
    mkdir(kv, "/nginx/https/www/upstream")
    mkdir(kv, "/nginx/https/www/server/location")
}

confd注册监控etcdkey/nginx/,只要发生变化就通知confd根据模板生成配置。confd默认的配置路径为/etc/confd/,创建conf.dtemplate两个目录,分别存放配置资源和配置模板。

nginx的配置资源如下所示:test.conf.toml

[template]
src = "test.conf.tmpl"
dest = "/tmp/test.conf"
keys = [
    "/nginx",
]
check_cmd = "echo a"
reload_cmd = "echo b"

nginx的配置模板如下所示:test.conf.tmpl

upstream www_{{getv "/nginx/https/www/server/server_name"}} {
    {{range getvs "/nginx/https/www/upstream/*"}}server {{.}};
    {{end}}
}

server {
    server_name         {{getv "/nginx/https/www/server/server_name"}}:443;
    ssl on
    ssl_certificate     {{getv "/nginx/https/www/server/ssl_certificate"}};
    ssl_certificate_key {{getv "/nginx/https/www/server/ssl_certificate_key"}};
    location / {
        proxy_pass        http://www_{{getv "/nginx/https/www/server/server_name"}};
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
        proxy_redirect    off;
    }
}

开启confd : confd -watch -backend etcd -node http://127.0.0.1:2379
设置内容

func TestNginxSetVal(t *testing.T) {
    setVal(kv, "/nginx/https/www/server/server_name", "test.com")
    setVal(kv, "/nginx/https/www/server/ssl_certificate", "client.crt")
    setVal(kv, "/nginx/https/www/server/ssl_certificate_key", "client.key")
    setVal(kv, "/nginx/https/www/upstream/server1", "192.168.4.2:443")
    setVal(kv, "/nginx/https/www/upstream/server2", "192.168.5.2:443")
}

生成结果

  1 upstream www_test.com {                                                                                                                                                                                         
  2     server 192.168.4.2:443;
  3     server 192.168.5.2:443;
  4 
  5 }
  6 
  7 server {
  8     server_name         test.com:443;
  9     ssl on
 10     ssl_certificate     client.crt;
 11     ssl_certificate_key client.key;
 12     location / {
 13         proxy_pass        http://www_test.com;
 14         proxy_set_header Host $host;
 15         proxy_set_header X-Real-IP $remote_addr;
 16         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 17         proxy_set_header X-Forwarded-Proto https;
 18         proxy_redirect    off;
 19     }
 20 }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容