etcd 网关是一个简单的 TCP 代理,可将网络数据转发到 etcd 集群。网关是无状态且透明的,它既不会检查客户端请求,也不会干扰集群响应,支持多个 etcd 服务器实例,并采用简单的循环策略。
etcd 网关将请求路由到可用端点,并向客户端隐藏故障,使得客户端感知不到服务端的故障。
1.1 适用场景我们使用客户端连接到 etcd 服务器时,每个访问 etcd 的应用程序必须知道所要访问的 etcd 集群实例的地址,即用来提供客户端服务的地址:ETCD_LISTEN_CLIENT_URLS。
如果同一服务器上的多个应用程序访问相同的 etcd 集群,每个应用程序仍需要知道 etcd 集群的广播的客户端端点地址。如果将 etcd 集群重新配置,拥有不同的端点,那么每个应用程序还需要更新其端点列表。在大规模集群环境下,重新配置的操作既造成了重复又容易出错。
以上问题,都可以通过 etcd 网关来解决:
- 使用 etcd 网关作为稳定的本地端点,对于客户端应用程序来说,不会感知到集群实例的变化。
- 典型的 etcd 网关配置是使每台运行网关的计算机在本地地址上侦听,并且每个 etcd 应用程序都连接对应的本地网关,发生 etcd 集群实例的变更时,只需要网关更新其端点,而不需要更新每个客户端应用程序的代码实现。
- 性能提升
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 调用协议同样的功能。



