共计 4109 个字符,预计需要花费 11 分钟才能阅读完成。
罗泽轩,API7.ai 技术专家 / 技术工程师,Apache APISIX PMC 成员。
原文链接
Apache APISIX 现有基于 HTTP 的 etcd 操作的局限性
etcd 在 2.x 版本的时候,对外裸露的是 HTTP 1(以下简称 HTTP)的接口。etcd 降级到 3.x 版本后,其对外 API 的协定从一般的 HTTP 切换到了 gRPC。为了兼顾那些不能应用 gRPC 的非凡群体,etcd 通过 gRPC-gateway 的形式代理 HTTP 申请,以 gRPC 模式去拜访新的 gRPC API。
APISIX 开始用 etcd 的时候,用的是 etcd v2 的 API。从 2020 年的 APISIX 2.0 版本起,咱们把要求的 etcd 版本升级到 3.x。etcd 对 HTTP 的兼容帮了咱们很大的忙,这样就不必花很大心理去从新实现操作 etcd 的形式了,只需调整下新的一组 API 的调用形式和响应解决。然而在实际过程中,咱们也发现了跟 etcd 的 HTTP API 相干的一些问题。事实上,领有 gRPC-gateway 并不意味着可能完满反对 HTTP 拜访,这里还是有些轻微的差异。
我把过来几年来在 etcd 上遇到的相干问题列了一下:
- 在某些状况下,etcd 默认没有启用 gRPC-gateway。因为维护者的忽略,在某些平台上,etcd 在配置时没有默认启用 gRPC-gateway,所以咱们不得不在文档中增加一个正文,以查看 etcd 以后是否启用了 gRPC-gateway。具体可参考 https://github.com/apache/api…。
- gRPC 默认将响应限度在 4MB 以内。etcd 在其提供的 sdk 中解除了这一限度,但遗记了在 gRPC-gateway 中解除。因而,官网的 etcdctl(应用了 sdk)工作失常,但 APISIX 却无奈失常拜访。具体可参考 https://github.com/etcd-io/et…。
- 同样的问题 —— 这次是产生在同一连贯的最大申请数上。Go 的 HTTP2 实现有一个 MaxConcurrentStreams 的配置,管制单个客户端能同时发送的申请数,默认是 250。失常状况下,哪个客户端会同时发送超过 250 个申请呢?所以 etcd 始终沿用这一配置。然而 gRPC-gateway 这个代理所有 HTTP 申请到本机的 gRPC 接口的“客户端”,却有可能超出这一限度。具体可参考 https://github.com/etcd-io/et…。
- etcd 在开启 mTLS 后,会用同一个证书来作为 gRPC-gateway 的服务端证书,兼 gRPC-gateway 拜访 gRPC 接口时的客户端证书。如果该证书上启用了 server auth 的拓展,然而没有启用 client auth 的拓展,那么会导致证书测验出错。间接用 etcdctl 拜访不会有这个问题,因为该证书在这种状况下不会作为客户端证书应用。具体可参考 https://github.com/etcd-io/et…。
- etcd 在开启 mTLS 后,容许针对证书外面的用户信息配置安全策略。如上所述,gRPC-gateway 在拜访 gRPC 接口时用的是固定的一个客户端证书,而非一开始拜访 HTTP 接口所用的证书信息。这个性能天然就没方法正确工作了。具体可参考 https://github.com/apache/api…。
造成上述问题的起因能够演绎为两点:
- gRPC-gateway(或者还有其余将 HTTP 转换为 gRPC 的尝试)并不是万能的。
- etcd 的开发者没有对 HTTP 到 gRPC 的门路给予足够的器重。毕竟他们最大的用户,Kubernetes,并不会应用它。
要想从根源上解决这一问题,咱们须要间接通过 gRPC 来操作 etcd,这样就不必走为了兼容而保留的 HTTP 门路。
克服各种挑战,迁徙到 gRPC
跳过 lua-protobuf 的 bug
在迁徙过程中,咱们遇到的第一个问题,是一个料想不到的第三方库的 bug。像绝大多数 OpenResty 利用一样,咱们采纳 lua-protobuf 来实现 protobuf 的 decode/encode。在引入 etcd 的 proto 文件之后,咱们发现 Lua 代码中会呈现偶发的奔溃,报错说遇到了 “table overflow”。因为这个解体不能稳固复现,我的第一反馈是寻找最小可复现的例子。然而乏味的是,如果独自应用 etcd 的 proto 文件,那么无论如何都无奈复现该问题。这个解体仿佛只能呈现在 APISIX 运行的时候,真是神奇。通过一些调试,我把问题锁定到了 lua-protobuf 在解析 proto 文件的 oneof 字段时的逻辑。lua-protobuf 在解析时会尝试预调配 table 大小,而调配的大小是依照某个值计算过去的。有肯定几率,这个值会是个正数,而后 LuaJIT 在调配时把这个数转成很大的负数,后果导致了 “table overflow” 谬误。我向作者报告了该问题,并在咱们外部保护了一个带 workaround 的 fork。lua-protobuf 作者反馈很迅速,次日就提供了一个修复,并在几天后公布了新的版本。原来 lua-protobuf 在清理不再应用的 proto 文件时,已经漏了缩小某些字段,导致在后续解决 oneof 时会失去一个不合理的正数。之所以是偶现的问题,之所以无奈在独自应用 etcd 的 proto 文件时复现,都是因为少了“清理”这一要害的前置操作。
向 HTTP 的行为对齐
世间鲜有货色是从头开始的,往往不得不从以后继承一些累赘。在迁徙过程中,我发现一个问题,就是现有的 API 返回的并不是一个简略的申请执行后果,而是个蕴含 status 和 body 的 HTTP response,而后由调用方去解决这个 HTTP response。这么一来,gRPC 的响应后果就须要套上一个 HTTP response 的壳。不然调用方就须要改变很多处中央来适配新的响应格局。尤其思考到旧的基于 HTTP 的 etcd 操作也须要同时反对,尽管兼容 HTTP response 的套壳行为并非我所欲,然而还是得向事实抬头斗争。除了要加壳之外,咱们还须要对 gRPC 响应做一些裁剪。比方在没有对应数据时,HTTP 不会返回任何数据,而 gRPC 会返回一个空 table。裁剪后果嘛,天然都是向 HTTP 的行为靠齐。
从短连贯到长连贯
在基于 HTTP 的 etcd 操作中,APISIX 都是采纳短连贯,所以不须要思考诸如连贯治理的问题。反正用的时候发动新连贯,用完 close 即可。但 gRPC 就不能这么操作了。迁徙到 gRPC 的一大目标就是为了达到多路复用,如果每次操作都创立新的 gRPC 连贯,那么就无从实现这一目标。这里须要感激 gRPC 性能的内核 gRPC-go 自带了连贯治理的能力,面对连贯中断能够主动重连。这样判断是否须要复用连贯,在 APISIX 这一层就只需思考业务需要。APISIX 的 etcd 操作能够分为两类,一类是去对 etcd 的数据做增删改查;另一类是从管制面同步配置。尽管实践上这两种 etcd 操作能够共享同一个 gRPC 连贯,然而出于权责划分的思考,咱们决定把它们分成两个连贯。对于增删改查的连贯,因为 APISIX 启动时和启动后的连贯须要离开看待,所以在获取新的连贯时加了个判断,如果以后连贯是启动时创立且目前须要的是启动后的连贯,那么会敞开以后连贯并创立新的连贯。对于配置同步,我开发了新的同步形式,让每一种资源都采纳以后连贯上面的一个 stream 来 watch etcd。
迁徙到 gRPC 后,咱们失去了什么样的益处
迁徙到 gRPC 后,一个不言而喻的益处是操作 etcd 所需的连贯大大减少。原来通过 HTTP 操作 etcd 时,APISIX 都是采纳短连贯。而且在同步配置时,每种资源都会有一个独自的连贯。在切换到 gRPC 之后,咱们能够用上 gRPC 的多路复用性能,每个资源都只应用单个 stream,而不是残缺的连贯。这样一来,连接数就不再随着资源数的减少而减少。思考到 APISIX 的后续开发将会引入更多的资源类型,比方以后最新的 3.1 版就新增了 secrets,采纳 gRPC 带来的连接数的缩小将会越来越显著。
应用 gRPC 同步时,每个过程只有一条用于配置同步的连贯(如果开启了 stream 子系统,那么是两条)。在下图中咱们能看到两个过程共有四个连贯,其中两个是配置同步,还有一个连贯由 Admin API 应用,剩下一个连贯是特权 agent 上报 server info。
作为比照,下图是放弃其余配置不变,应用原有的配置同步形式所需的连接数。具体有多少我就不数了。另外这些连贯还是短连贯。
这两个配置的区别,仅在于是否启用了 gRPC 来做 etcd 操作:
etcd:
use_grpc: true
host:
- "http://127.0.0.1:2379"
prefix: "/apisix"
...
除了连接数的缩小外,应用 gRPC 来间接拜访 etcd 而非绕道 gRPC-gateway,可能解决文中一开始提到的对于 mTLS 鉴权等一系列受限于架构的问题。此外,咱们也置信应用 gRPC 后遇到更少的问题,因为 Kubernetes 就是通过 gRPC 来操作 etcd 的,如果有问题,在 Kubernetes 社区应该会更早地发现。
当然,作为一个新生事物,APISIX 通过 gRPC 操作 etcd 时未免会有一些新的问题。毕竟咱们之所以发现了许多 HTTP 门路下的问题,是因为咱们对 HTTP 用得比拟多。所以目前咱们还是默认采纳原有的基于 HTTP 的形式来操作 etcd。不过用户能够自行在 config.yaml
中配置 etcd 下的 use_grpc
为 true,尝试一下新的形式是否更优。咱们也会听取各路反馈意见,持续欠缺基于 gRPC 的 etcd 操作,并继续积攒更多的教训。在后续的某个时候,当咱们判断新形式足够成熟时,就会把它确定为默认的路径。
对于 API7.ai 与 APISIX
API7.ai 是一家提供 API 解决和剖析的开源根底软件公司,于 2019 年开源了新一代云原生 API 网关 — APISIX 并捐献给 Apache 软件基金会。尔后,API7.ai 始终踊跃投入反对 Apache APISIX 的开发、保护和社区经营。与千万贡献者、使用者、支持者一起做出世界级的开源我的项目,是 API7.ai 致力的指标。