构建api gateway之 基于etcd实现动态配置同步

配置中心

在之前 tcp的yaml配置 介绍了如何监听yaml文件变化然后更新配置。

当然假如我们有很多实例,那么yaml改动将是非常痛苦的事情,那么如何做到配置文件统一管理,实时更新呢?

我们可以引入配置中心,从而达到这样的效果。

业界已经有非常多配置中心了,这里为了简化内容,将选用etcd作为配置中心来介绍实现。

etcd

etcd 是一个分布式键值对存储系统。

设计用于可靠存储不频繁更新的数据,并提供可靠的观察查询。

etcd 暴露键值对的先前版本来支持不昂贵的快速和观察历史事件(“time travel queries”)。

对于这些使用场景,持久化,多版本,并发控制的数据模型是非常适合的。

ectd 使用多版本持久化键值存储来存储数据。

当键值对的值被新的数据替代时,持久化键值存储保存先前版本的键值对。

键值存储事实上是不可变的;它的操作不会就地更新结构,替代的是总是生成一个新的更新后的结构。

在修改之后,key的所有先前版本还是可以访问和观察的。为了防止随着时间的过去为了维护老版本导致数据存储无限增长,存储应该压缩来脱离被替代的数据的最旧的版本。

所以其非常适合作为配置中心,每一个配置变动都是有序的。

使用 etcd

大家测试可以使用docker 实验

docker run -p 2479:2479 -p 2480:2480 --mount type=bind,source=$(shell pwd)/tmp/etcd-data.tmp,destination=/etcd-data --name etcd  gcr.io/etcd-development/etcd:v3.5.0  /usr/local/bin/etcd  --name s1  --data-dir /etcd-data  --listen-client-urls http://0.0.0.0:2479  --advertise-client-urls http://0.0.0.0:2479  --listen-peer-urls http://0.0.0.0:2480  --initial-advertise-peer-urls http://0.0.0.0:2480  --initial-cluster s1=http://0.0.0.0:2480  --initial-cluster-token tkn  --initial-cluster-state new  --log-level info  --logger zap  --log-outputs stderr

cli

可以使用 cli 工具: https://github.com/etcd-io/etcd/tree/main/etcdctl

./etcdctl put foo bar --lease=1234abcd # OK ./etcdctl get foo # foo # bar ./etcdctl put foo --ignore-value # to detache lease # OK

ui

或者使用ui工具: https://github.com/evildecay/etcdkeeper

构建api gateway之 基于etcd实现动态配置同步

实践

以下内容更新到 openresty-dev-1.rockspec

-- 依赖包 dependencies = {     "lua-resty-etcd >= 1.9.0", }

然后执行

luarocks install openresty-dev-1.rockspec --tree=deps --only-deps --local

代码内容:

worker_processes  1;        #nginx worker 数量 error_log logs/error.log;   #指定错误日志文件路径 events {     worker_connections 1024; }  http {     log_format main '$remote_addr [$time_local] $status $request_time $upstream_status $upstream_addr $upstream_response_time';     access_log logs/access.log main buffer=16384 flush=3;            #access_log 文件配置      lua_package_path  "${prefix}deps/share/lua/5.1/?.lua;${prefix}deps/share/lua/5.1/?/init.lua;${prefix}?.lua;${prefix}?/init.lua;;./?.lua;/usr/local/openresty/luajit/share/luajit-2.1.0-beta3/?.lua;/usr/local/share/lua/5.1/?.lua;/usr/local/share/lua/5.1/?/init.lua;/usr/local/openresty/luajit/share/lua/5.1/?.lua;/usr/local/openresty/luajit/share/lua/5.1/?/init.lua;";     lua_package_cpath "${prefix}deps/lib64/lua/5.1/?.so;${prefix}deps/lib/lua/5.1/?.so;;./?.so;/usr/local/lib/lua/5.1/?.so;/usr/local/openresty/luajit/lib/lua/5.1/?.so;/usr/local/lib/lua/5.1/loadall.so;";     # 开启 lua code 缓存     lua_code_cache on;      upstream nature_upstream {         server 127.0.0.1:6699; #upstream 配置为 hello world 服务          # 一样的balancer         balancer_by_lua_block {             local balancer = require "ngx.balancer"             local upstream = ngx.ctx.api_ctx.upstream             local ok, err = balancer.set_current_peer(upstream.host, upstream.port)             if not ok then                 ngx.log(ngx.ERR, "failed to set the current peer: ", err)                 return ngx.exit(ngx.ERROR)             end         }     }      # 换为init_worker 是因为 init 不允许请求etcd     init_worker_by_lua_block {          node = nil         -- 匹配路由, 为了演示,这里简化为单个节点,并且路由处理也去掉了         router_match = function()             return node         end          -- 从etcd 加载配置,同理为了演示简单,这里只做单个节点         local etcdlib = require("resty.etcd").new({             protocol = "v3",             api_prefix = "/v3",             http_host = 'http://127.0.0.1:2479',             key_prefix = '/test/'         })          -- 这里为了简单,展示轮询方式, watch 的方式可以参考 https://github.com/fs7744/nature/blob/main/nature/config/etcd.lua         ngx.timer.every(1, function()             local res, err = etcdlib:get('node')             local json = require('cjson.safe')                          if res ~= nil and res.body ~= nil and res.body.kvs ~= nil and res.body.kvs[1] ~= nil then                 node = res.body.kvs[1].value                 ngx.log(ngx.ERR, json.encode(node))             end         end)     }      server { 		#监听端口,若你的8699端口已经被占用,则需要修改         listen 8699 reuseport;          location / {              # 在access阶段匹配路由             access_by_lua_block {                 local upstream = router_match()                 if upstream then                     ngx.ctx.api_ctx = { upstream = upstream }                 else                     ngx.exit(404)                 end             }              proxy_http_version                  1.1;             proxy_pass http://nature_upstream; #转发到 upstream         }     }       #为了大家方便理解和测试,我们引入一个hello world 服务     server { 		#监听端口,若你的6699端口已经被占用,则需要修改         listen 6699;         location / {             default_type text/html;              content_by_lua_block {                 ngx.say("HelloWorld")             }         }     } }

启动服务并测试

$ openresty -p ~/openresty-test -c openresty.conf #启动 $ curl --request GET 'http://127.0.0.1:8699/aa/d'  #第一次测试 <html> <head><title>404 Not Found</title></head> <body> <center><h1>404 Not Found</h1></center> </body> </html> $ ./etcdctl put /test/node {"host":"127.0.0.1","port":6699}  # 写入测试节点数据 $ curl --request GET 'http://127.0.0.1:8699/aa/d'  #第二次测试 HelloWorld

可以看到获取到了etcd的配置变化

目录

发表评论

相关文章