如何开发一个自定义协议

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。示例中包含了实现自定义协议的程序框架,可以该示例为基础进行修改,编写你自己的私有协议。

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 三个方法即可。

/**
 * Codec for Awesomerpc protocol.
 */
class AwesomerpcCodec : public MetaProtocolProxy::Codec,
                  public Logger::Loggable<Logger::Id::misc> {
public:
  AwesomerpcCodec() {};
  ~AwesomerpcCodec() override = default;

  //协议解码,需要解析 buffer 并填充 Metadata, Metadata 将被用于 MetaProtocol Proxy 的 filter,例如限流,路由的匹配条件
  MetaProtocolProxy::DecodeStatus decode(Buffer::Instance& buffer,
                                         MetaProtocolProxy::Metadata& metadata) override;

  //协议编码,可以根据 Mutation 对请求或者响应数据包进行修改,例如增加、删除或者修改 header,修改后需要回写到 buffer 中
  void encode(const MetaProtocolProxy::Metadata& metadata,
              const MetaProtocolProxy::Mutation& mutation, Buffer::Instance& buffer) override;

  //错误编码,用于框架向客户端返回错误信息,例如未找到路由或者连接创建失败等,编码的数据需要写入到 buffer 中
  void onError(const MetaProtocolProxy::Metadata& metadata, const MetaProtocolProxy::Error& error,
               Buffer::Instance& buffer) override;

...

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

配置 WORKSPACE

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

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


# 从 meta_protocol_proxy 的代码中获取 envoy 的依赖版本信息
ENVOY_SHA = "98c1c9e9a40804b93b074badad1cdf284b47d58b"
ENVOY_SHA256 = "4365a4c09b9a8b3c4ae34d75991fcd046f3e19d53d95dfd5c89209c30be94fe6"
......

# 从 meta_protocol_proxy 的代码中获取 istio_proxy 的依赖版本信息
http_archive(
    name = "io_istio_proxy",
    strip_prefix = "proxy-1.10.0",
    sha256 = "19d13bc4859dc8422b91fc28b0d8d12a34848393583eedfb08af971c658e7ffb",
    url = "https://github.com/istio/proxy/archive/refs/tags/1.10.0.tar.gz",   
)
...... 

# 设置 metaProtocol 的 git commit
git_repository(
  name = "meta_protocol_proxy",
  remote = "https://github.com/aeraki-mesh/meta-protocol-proxy.git",
  commit = "5ae1d11",  
)

编译

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

安装编译所需软件:

sudo wget -O /usr/local/bin/bazel https://github.com/bazelbuild/bazelisk/releases/latest/download/bazelisk-linux-$([ $(uname -m) = "aarch64" ] && echo "arm64" || echo "amd64")
sudo chmod +x /usr/local/bin/bazel

sudo apt-get install \
autoconf \
automake \
cmake \
curl \
libtool \
make \
ninja-build \
patch \
python3-pip \
unzip \
virtualenv

sudo add-apt-repository ppa:ubuntu-toolchain-r/test
sudo apt update
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

构建二进制:

./build.sh

定义一个 ApplicationProtocol

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

apiVersion: metaprotocol.aeraki.io/v1alpha1
kind: ApplicationProtocol
metadata:
  name: my-protocol
  namespace: istio-system
spec:
  protocol: my-protocol
  codec: aeraki.meta_protocol.codec.my_protocol

协议选择

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

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

apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
  name: dubbo-demoservice
  namespace: meta-dubbo
spec:
  hosts:
    - org.apache.dubbo.samples.basic.api.demoservice
  ports:
    - number: 20880
      name: tcp-metaprotocol-dubbo
      protocol: TCP
  workloadSelector:
    labels:
      app: dubbo-sample-provider
  resolution: STATIC

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

apiVersion: v1
kind: Service
metadata:
  name: thrift-sample-server
spec:
  selector:
    app: thrift-sample-server
  ports:
    - name: tcp-metaprotocol-thrift-hello-server
      protocol: TCP
      port: 9090
      targetPort: 9090

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


最后修改 March 22, 2022: circuit breaking (00b6d8a)