如何开发一个自定义协议

MetaProtocol Proxy 提供了一个良好的协议扩展机制,使得我们可以基于 MetaProtocol Proxy 快速实现一个自定义协议的七层代理。

由于 MetaProtocol Proxy 已经实现了一个七层协议代理所需的大部分功能,包括七层负载均衡、熔断、RDS 动态路由、本地限流、全局限流、Header 修改、请求 Metrics 收集等,更多丰富的功能还在持续开发中。因此基于 MetaProtocol 进行开发极大简化了实现一个七层网络代理的工作,我们只需要实现编解码的少量代码,即可得到一个自定义协议的七层代理。一般来说,实现一个自定义协议只需要数百行代码。

在基于 MetaProtocol 实现数据面代理后,无需任何控制面编码,我们就可以通过 Aeraki 在 Isito 服务网格中对使用自定义协议的服务进行管理,为服务提供流量拆分、灰度发布、流量镜像、监控图表等服务治理能力。这是因为 Aeraki 可以识别基于 MetaProtocol 的任何七层协议,并在控制面提供用户友好的流量规则。Aeraki 会将这些流量规则翻译为 MetaProtocol 的配置后下发到数据面。

除了快速开发,节省工作量之外,采用 MetaProtocol 为服务网格开发自定义协议的另一个好处是该方案对 Istio,Envoy,以及 MetaProtocol Proxy 自身等上游开源项目是完全无侵入的,可以跟随上游项目进行快速迭代,充分利用上游项目新版本提供的新增能力,无需维护自有的 fork 分支。

实现编解码接口

Aeraki 提供了一个应用协议扩展的示例 awesomerpc。示例中包含了实现自定义协议的程序框架,可以该示例为基础进行修改,编写你自己的私有协议。

  1. git clone https://github.com/aeraki-mesh/meta-protocol-awesomerpc.git my-protocol-proxy

我们主要需要关注的是 src/application_protocols/awesomerpc/ 目录下的 awesomerpc_codec.hawesomerpc_codec.cc 。这两个文件定义了应用协议的 codec 的接口和实现代码。

awesomerpc_codec.h 的定义如下,可以看到应用协议的 codec 继承于 MetaProtocolProxy::Codec。实现应用协议只需要实现 decodeencodeonError 三个方法即可。

  1. /**
  2. * Codec for Awesomerpc protocol.
  3. */
  4. class AwesomerpcCodec : public MetaProtocolProxy::Codec,
  5. public Logger::Loggable<Logger::Id::misc> {
  6. public:
  7. AwesomerpcCodec() {};
  8. ~AwesomerpcCodec() override = default;
  9. //协议解码,需要解析 buffer 并填充 Metadata, Metadata 将被用于 MetaProtocol Proxy 的 filter,例如限流,路由的匹配条件
  10. MetaProtocolProxy::DecodeStatus decode(Buffer::Instance& buffer,
  11. MetaProtocolProxy::Metadata& metadata) override;
  12. //协议编码,可以根据 Mutation 对请求或者响应数据包进行修改,例如增加、删除或者修改 header,修改后需要回写到 buffer 中
  13. void encode(const MetaProtocolProxy::Metadata& metadata,
  14. const MetaProtocolProxy::Mutation& mutation, Buffer::Instance& buffer) override;
  15. //错误编码,用于框架向客户端返回错误信息,例如未找到路由或者连接创建失败等,编码的数据需要写入到 buffer 中
  16. void onError(const MetaProtocolProxy::Metadata& metadata, const MetaProtocolProxy::Error& error,
  17. Buffer::Instance& buffer) override;
  18. ...

在编写 codec 时也可以参考 dubbothrift 的 codec 实现。

配置 WORKSPACE

在根目录的 WORKSPACE 文件中配置 metaProtocol, envoy 和 Istio-Proxy 的依赖。

需要在 WORKSPACE 中配置 metaProtocol 的 git commit,envoy 和 Istio-Proxy 的依赖参考 metaProtocol 该 commit 中的 WORKSPACE 中的配置。版本依赖关系参见 Aeraki 和 MetaProtocol 以及 Istio 的版本兼容性

  1. # 从 meta_protocol_proxy 的代码中获取 envoy 的依赖版本信息
  2. ENVOY_SHA = "98c1c9e9a40804b93b074badad1cdf284b47d58b"
  3. ENVOY_SHA256 = "4365a4c09b9a8b3c4ae34d75991fcd046f3e19d53d95dfd5c89209c30be94fe6"
  4. ......
  5. # 从 meta_protocol_proxy 的代码中获取 istio_proxy 的依赖版本信息
  6. http_archive(
  7. name = "io_istio_proxy",
  8. strip_prefix = "proxy-1.10.0",
  9. sha256 = "19d13bc4859dc8422b91fc28b0d8d12a34848393583eedfb08af971c658e7ffb",
  10. url = "https://github.com/istio/proxy/archive/refs/tags/1.10.0.tar.gz",
  11. )
  12. ......
  13. # 设置 metaProtocol 的 git commit
  14. git_repository(
  15. name = "meta_protocol_proxy",
  16. remote = "https://github.com/aeraki-mesh/meta-protocol-proxy.git",
  17. commit = "5ae1d11",
  18. )

编译

建议使用 Ubuntu 18.04 作为编译环境。

安装编译所需软件:

  1. sudo wget -O /usr/local/bin/bazel https://github.com/bazelbuild/bazelisk/releases/latest/download/bazelisk-linux-$([ $(uname -m) = "aarch64" ] && echo "arm64" || echo "amd64")
  2. sudo chmod +x /usr/local/bin/bazel
  3. sudo apt-get install \
  4. autoconf \
  5. automake \
  6. cmake \
  7. curl \
  8. libtool \
  9. make \
  10. ninja-build \
  11. patch \
  12. python3-pip \
  13. unzip \
  14. virtualenv
  15. sudo add-apt-repository ppa:ubuntu-toolchain-r/test
  16. sudo apt update
  17. sudo apt-get install llvm-10 lldb-10 llvm-10-dev libllvm10 llvm-10-runtime clang-10 clang++-10 lld-10 gcc-10 g++-10

构建二进制:

  1. ./build.sh

定义一个 ApplicationProtocol

要在 Istio 中识别自定义协议,需要创建一个 Aeraki 的 ApplicationProtocol CRD 资源。

  1. apiVersion: metaprotocol.aeraki.io/v1alpha1
  2. kind: ApplicationProtocol
  3. metadata:
  4. name: my-protocol
  5. namespace: istio-system
  6. spec:
  7. protocol: my-protocol
  8. codec: aeraki.meta_protocol.codec.my_protocol

协议选择

Aeraki 通过服务的前缀来识别基于 MetaProtocol 的应用协议,服务的端口名必须遵从该命名规则: tcp-metaprotocol-{application protocol}-xxx。 服务定义可以采用 K8s service 或者 Istio ServiceEntry。

下面的 ServiceEntry 定义了一个 dubbo 服务:

  1. apiVersion: networking.istio.io/v1alpha3
  2. kind: ServiceEntry
  3. metadata:
  4. name: dubbo-demoservice
  5. namespace: meta-dubbo
  6. spec:
  7. hosts:
  8. - org.apache.dubbo.samples.basic.api.demoservice
  9. ports:
  10. - number: 20880
  11. name: tcp-metaprotocol-dubbo
  12. protocol: TCP
  13. workloadSelector:
  14. labels:
  15. app: dubbo-sample-provider
  16. resolution: STATIC

下面的 Service 定义了一个 Thrift 服务:

  1. apiVersion: v1
  2. kind: Service
  3. metadata:
  4. name: thrift-sample-server
  5. spec:
  6. selector:
  7. app: thrift-sample-server
  8. ports:
  9. - name: tcp-metaprotocol-thrift-hello-server
  10. protocol: TCP
  11. port: 9090
  12. targetPort: 9090

备注:端口定义的前缀需要包含 “tcp”,这是因为 Istio 会将该服务作为 tcp 服务进行处理。 而 Aeraki 则会识别后面的应用协议并进行相应的七层处理。

最后修改 May 23, 2022: 增加 v1.1 文档目录 (431bf1a)