1. etcd 网关
etcd
网关是一个简单的 TCP
代理,可将网络数据转发到 etcd
集群。网关是无状态且透明的,它既不会检查客户端请求,也不会干扰集群响应,支持多个 etcd
服务器实例,并采用简单的循环策略。
etcd
网关将请求路由到可用端点,并向客户端隐藏故障,使得客户端感知不到服务端的故障。
1.1 适用场景
我们使用客户端连接到 etcd
服务器时,每个访问 etcd
的应用程序必须知道所要访问的 etcd
集群实例的地址,即用来提供客户端服务的地址:ETCD_LISTEN_CLIENT_URLS
。
如果同一服务器上的多个应用程序访问相同的 etcd
集群,每个应用程序仍需要知道 etcd
集群的广播的客户端端点地址。如果将 etcd
集群重新配置,拥有不同的端点,那么每个应用程序还需要更新其端点列表。在大规模集群环境下,重新配置的操作既造成了重复又容易出错。
以上问题,都可以通过 etcd
网关来解决:
- 使用
etcd
网关作为稳定的本地端点,对于客户端应用程序来说,不会感知到集群实例的变化。 - 典型的
etcd
网关配置是使每台运行网关的计算机在本地地址上侦听,并且每个etcd
应用程序都连接对应的本地网关,发生etcd
集群实例的变更时,只需要网关更新其端点,而不需要更新每个客户端应用程序的代码实现。
1.2 不适用场景
- 性能提升
etcd
网关不是为提高 etcd
集群性能设计的。它不提供缓存、watch
流合并或批量处理等功能。
- 在集群上运行管理系统
类似 Kubernetes
的高级集群管理系统本身支持服务发现。应用程序可以使用系统默认的 DNS
名称或虚拟 IP
地址访问 etcd
集群。例如,负责为 Service
提供 Cluster
内部的服务发现和负载均衡的 Kube-proxy
其实等效于 etcd
网关的职能。
总而言之,为了自动传播集群端点更改,etcd
网关在每台机器上都运行,为多个应用提供访问相同的 etcd
集群服务。
2. gRPC 网关
gRPC-Gateway
为非 gRPC
的客户端提供 HTTP
接口。
etcd v3
使用 gRPC
作为消息传输协议。
etcd
项目中包括了基于 gRPC
的 Go client
和命令行工具 etcdctl
,客户端通过 gRPC
框架与 etcd
集群通讯。对于不支持 gRPC
的客户端语言,etcd
提供 JSON
的 gRPC-Gateway
,通过 gRPC-Gateway
提供 RESTful
代理,转换 HTTP/JSON
请求为 gRPC
的 Protocol Buffer
格式的消息。
这里你需要注意的是,在 HTTP
请求体中的 JSON
对象,其包含的 key
和 value
字段都被定义成了 byte
数组,因此必须在 JSON
对象中,使用 base64
编码对内容进行处理。
2.1 etcd 版本与 gRPC-Gateway 接口对应的关系
gRPC-Gateway
提供的接口路径自 etcd v3.3
已经变更:
-
etcd v3.2
及之前的版本只能使用[CLIENT-URL]/v3alpha/*
接口; -
etcd v3.3
使用CLIENT-URL/v3alpha/*
; -
etcd v3.4
使用CLIENT-URL/v3beta/
,且废弃了[CLIENT-URL]/v3alpha/
; -
etcd v3.5
只使用CLIENT-URL/v3beta/
;
通过上面的接口与 etcd
版本的对应关系,你可以看到,即使是 v3
版本下的 API
,gRPC-Gateway
提供的接口路径在内部细分的版本下也有不同,所以需要注意当前正在使用的 etcd
版本。
2.2 键值对读写操作
如果没有使用 base64
对键值对进行编码,那么会报以下错误:
wohu@ubuntu:~$ curl -L http://localhost:2379/v3/kv/put -X POST -d '{"key": "/demo", "value": "AAA"}'
{"error":"illegal base64 data at input byte 4","message":"illegal base64 data at input byte 4","code":3}
对其进行 base64
编码
demo
字符串的base64
编码结果为ZGVtbw==
AAA
字符串的base64
编码结果为QUFB
wohu@ubuntu:~$ curl -L http://localhost:2379/v3/kv/put -X POST -d '{"key": "ZGVtbw==", "value": "QUFB"}'
{"header":{"cluster_id":"14841639068965178418","member_id":"10276657743932975437","revision":"26","raft_term":"3"}}
接着,我们通过 /v3/kv/range
接口,来读取刚刚写入的键值对:
wohu@ubuntu:~$ curl -L http://localhost:2379/v3/kv/range -X POST -d '{"key": "ZGVtbw=="}'
{"header":{"cluster_id":"14841639068965178418","member_id":"10276657743932975437","revision":"26","raft_term":"3"},"kvs":[{"key":"ZGVtbw==","create_revision":"26","mod_revision":"26","version":"1","value":"QUFB"}],"count":"1"}
当我们想要获取前缀为指定值的键值对时,可以使用如下请求:
wohu@ubuntu:~$ curl -L http://localhost:2379/v3/kv/range -X POST -d '{"key": "ZGVtbw==", "range_end": "Zm9w"}'
{"header":{"cluster_id":"14841639068965178418","member_id":"10276657743932975437","revision":"26","raft_term":"3"},"kvs":[{"key":"ZGVtbw==","create_revision":"26","mod_revision":"26","version":"1","value":"QUFB"}],"count":"1"}
因为只有一个值,所以返回正确。
2.3 watch 键值
etcd
中提供了 /v3/watch
接口来监测 keys
,我们来 watch
刚刚写入的 ZGVtbw==
,请求如下所示:
curl -N http://localhost:2379/v3/watch -X POST -d '{"create_request": {"key":"ZGVtbw=="}}'
然后另开一个窗口,执行以下命令
etcdctl put demo 123
查看 watch
结果输出:
wohu@ubuntu:~$ curl -N http://localhost:2379/v3/watch -X POST -d '{"create_request": {"key":"ZGVtbw=="} }'
{"result":{"header":{"cluster_id":"14841639068965178418","member_id":"10276657743932975437","revision":"26","raft_term":"3"},"created":true}}
{"result":{"header":{"cluster_id":"14841639068965178418","member_id":"10276657743932975437","revision":"27","raft_term":"3"},"events":[{"kv":{"key":"ZGVtbw==","create_revision":"26","mod_revision":"27","version":"2","value":"MTIz"}}]}}
当写入键值后,触发了监测事件的发生,控制台输出了时间的细节。HTTP
请求客户端与 etcd
服务端建立长连接,当监听的键值对发生变更时,便会将事件通知给客户端。
2.4 HTTP 请求的安全认证
HTTP
的方式访问 etcd
服务端,需要考虑安全的问题,gRPC-Gateway
中提供的 API
接口支持开启安全认证。通过 /v3/auth
接口设置认证,需要实现以下 4 个步骤:
- 创建用户
wohu@ubuntu:~$ curl -L http://localhost:2379/v3/auth/user/add -X POST -d '{"name": "root", "password": "123456"}'
{"header":{"cluster_id":"14841639068965178418","member_id":"10276657743932975437","revision":"27","raft_term":"3"}}
- 创建角色
wohu@ubuntu:~$ curl -L http://localhost:2379/v3/auth/role/add -X POST -d '{"name": "root"}'
{"header":{"cluster_id":"14841639068965178418","member_id":"10276657743932975437","revision":"27","raft_term":"3"}}
- 用户授予角色
wohu@ubuntu:~$ curl -L http://localhost:2379/v3/auth/user/grant -X POST -d '{"user": "root", "role": "root"}'
{"header":{"cluster_id":"14841639068965178418","member_id":"10276657743932975437","revision":"27","raft_term":"3"}}
- 开启认证权限
wohu@ubuntu:~$ curl -L http://localhost:2379/v3/auth/enable -X POST -d '{}'
{"header":{"cluster_id":"14841639068965178418","member_id":"10276657743932975437","revision":"27","raft_term":"3"}}
如上的请求中,我们首先创建了 root
用户和角色,将 root
角色赋予到 root
用户,这样就可以开启用户的权限。接下来就是进行身份验证,并进行 HTTP
访问。流程如下图所示:
使用 /v3/auth/authenticate
API 接口对 etcd
进行身份验证以获取身份验证令牌:
wohu@ubuntu:~$ curl -L http://localhost:2379/v3/auth/authenticate -X POST -d '{"name": "root", "password": "123456"}'
{"header":{"cluster_id":"14841639068965178418","member_id":"10276657743932975437","revision":"27","raft_term":"3"},"token":"BGQDrdEVPaGQBCGh.51"}
请求获取到 token
的值为 BGQDrdEVPaGQBCGh.51
。接下来,设置请求的头部 Authorization
为刚刚获取到的身份验证令牌,以使用身份验证凭据设置 key
值:
curl -L http://localhost:2379/v3/kv/put -H 'Authorization:BGQDrdEVPaGQBCGh.51' -X POST -d '{"key": "ZGVtbw==", "value": "QUFB"}'
如果 token
不合法会报错误:
{"error":"etcdserver: invalid auth token","message":"etcdserver: invalid auth token","code":16}
etcd gRPC-Gateway
中提供的 API 接口还有诸如 /v3/auth/role/delete
、/v3/auth/role/get
等其他接口,请参考 官网
3. 总结
-
etcd
网关通常用于etcd
集群的门户,是一个简单的TCP
代理,将客户端请求转发到etcd
集群,对外屏蔽了etcd
集群内部的实际情况,在集群出现故障或者异常时,可以通过etcd
网关进行切换; -
gRPC-Gateway
则是对于etcd
的gRPC
通信协议的补充,有些语言的客户端不支持gRPC
通信协议,此时就可以使用gRPC-Gateway
对外提供的HTTP API
接口。通过HTTP
请求,实现与gRPC
调用协议同样的功能。