差之毫厘:etcd 3 完美支持 HTTP 访问?

做者罗哲轩,Core developer of Apache APISIXgit

etcd 升级到 3.x 版本后,其对外 API 的协议从普通的 HTTP1 切换到了 gRPC。为了兼顾那些不能使用 gRPC 的特殊群体,etcd 经过 gRPC-gateway 的方式代理 HTTP1 请求,以 gRPC 形式去访问新的 gRPC API。(因为 HTTP1 念起来太过拗口,如下将之简化成 HTTP,正好和 gRPC 可以对应。请不要纠结 gRPC 也是 HTTP 请求的这种问题。)github

Apache APISIX 开始用 etcd 的时候,用的是 etcd v2 的 API。从 Apache APISIX 2.0 版本起,咱们把依赖的 etcd 版本升级到 3.x。因为 Lua 生态圈里面没有 gRPC 库,因此 etcd 对 HTTP 的兼容帮了咱们很大的忙,这样就不用花很大心思去补这个短板了。markdown

从去年 10 月发布 Apache APISIX 2.0 版本以来,如今已通过去了 8 个月。在实践过程当中,咱们也发现了 etcd 的 HTTP API 的一些跟 gRPC API 交互的问题。事实上,拥有 gRPC-gateway 并不意味着可以完美支持 HTTP 访问,这里仍是有些细微的差异。架构

打破 gRPC 的默认限制

就在几天前,etcd 发布了 v3.5.0 版本。这个版本的发布,了却困扰咱们很长时间的一个问题。oop

跟 HTTP 不一样的是,gRPC 默认限制了一次请求能够读取的数据大小。这个限制叫作 “MaxCallRecvMsgSize”,默认是 4MiB。当 Apache APISIX 全量同步 etcd 数据时,假如配置够多,就会触发这一上限,报错 “grpc: received message larger than max”。spa

神奇的是,若是你用 etcdctl 去访问,这时候却不会有任何问题。这是由于这个限制是能够在跟 gRPC server 创建链接时动态设置的,etcdctl 给这个限制设置了一个很大的整数,至关于去掉了这一限制。代理

因为很多用户碰到过一样的问题,咱们曾经讨论过对策。日志

一个想法是用增量同步模拟全量同步,这么作有两个弊端:code

  1. 实现起来复杂,要改很多代码
  2. 会延长同步所需的时间

另外一个想法是修改 etcd。既然可以在 etcdctl 里面去除限制,为何不对 gRPC-gateway 一视同仁呢?一样的改动能够做用在 gRPC-gateway 上。orm

咱们采用了第二种方案,给 etcd 提了个 PR:github.com/etcd-io/etc…

最新发布的 v3.5.0 版本就包含了咱们贡献的这个改动。若是你遇到 “grpc: received message larger than max”,不妨试一下这个版本。这一改动也被 etcd 开发者 backport 到 3.4 分支上了。3.4 分支的下一个发布,也会带上这个改动。

这件事也说明 gRPC-gateway 并不是百试百灵。即便用了它,也不能保证 HTTP 访问可以跟 gRPC 访问有同样的体验。

对服务端证书的有趣用法

Apache APISIX 增长了对 etcd mTLS 的支持后,有用户反馈一直无法完成校验,而用 etcdctl 访问则是成功的。在跟用户交流后,我决定拿他的证书来复现下。 在复现过程当中,我注意到 etcd 日志里面有这样的报错:

2021-06-09 11:10:13.022735 I | embed: rejected connection from "127.0.0.1:50898" (error "tls: failed to verify client's certificate: x509: certificate specifies an incompatible key usage", ServerName "")WARNING: 2021/06/09 11:10:13 grpc: addrConn.createTransport failed to connect to {127.0.0.1:12379 0 }. Err :connection error: desc = "transport: authentication handshake failed: remote error: tls: bad certificate". Reconnecting...
复制代码

“bad certificate” 错误信息,初看像是由于咱们发给 etcd 的客户端证书不对。但仔细瞧瞧,会发现这个报错是在 gRPC server 里面报的。 gRPC-gateway 在 etcd 里面起到一个代理的做用,把外面的 HTTP 请求变成 gRPC server 能处理的 gRPC 请求。 大致架构以下:

etcdctl ----> gRPC serverApache APISIX ---> gRPC-gateway ---> gRPC server
复制代码

为何 etcdctl 直连 gRPC server 能通,而中间加一层 gRPC-gateway 就不行? 原来当 etcd 启用了客户端证书校验以后,用 gRPC-gateway 链接 gRPC server 就须要提供一个客户端证书。猜猜这个证书从哪来? etcd 把配置的服务端证书直接做为这里的客户端证书用了。 一个证书既在服务端上提供验证,又在客户端上代表身份,看上去也没什么问题。除非…… 除非证书上启用了 server auth 的拓展,可是没有启用 client auth。 对有问题的证书执行

openssl x509 -text -noout -in /tmp/bad.crt
复制代码

会看到这样的输出:

X509v3 extensions:  X509v3 Key Usage: critical    Digital Signature, Key Encipherment  X509v3 Extended Key Usage:    TLS Web Server Authentication
复制代码

注意这里的 “TLS Web Server Authentication”,若是咱们把它改为 “TLS Web Server Authentication, TLS Web Client Authentication”,抑或不加这个拓展,就没有问题了。 etcd 上也有关于这个问题的 issue:github.com/etcd-io/etc…

总结

虽然咱们在上文列出了几点小问题,可是瑕不掩瑜,etcd 对 HTTP 访问的支持仍是一个很是有用的特性。 感谢 Apache APISIX 的用户们,正是由于咱们有着广阔的用户群,才能发现 etcd 的这些细节上的问题。咱们做为 etcd 的一大用户,在以后的日子里也将一如既往地跟 etcd 的开发者多多交流。

相关文章
相关标签/搜索