Aeraki 教程系列:如何開發一個自定義協議?

語言: CN / TW / HK

MetaProtocol Proxy 提供了一個良好的協議擴充套件機制,使得我們可以基於 MetaProtocol Proxy 快速實現一個自定義協議的七層代理。

由於 MetaProtocol Proxy 已經實現了一個七層協議代理所需的大部分功能,包括七層負載均衡、RDS 動態路由、本地限流、全侷限流、請求 Metrics 收集等,更多豐富的功能還在持續開發中。因此基於 MetaProtocol 進行開發極大簡化了實現一個七層網路代理的工作,我們只需要實現編解碼的少量程式碼,即可得到一個自定義協議的七層代理。一般來說,實現一個自定義協議只需要數百行程式碼。

在基於 MetaProtocol 實現資料面代理後,無需任何控制面編碼,我們就可以通過 Aeraki 在 Isito 服務網格中對使用自定義協議的服務進行管理,為服務提供流量拆分、灰度釋出、流量映象、監控圖表等服務治理能力。這是因為 Aeraki 可以識別基於 MetaProtocol 的任何七層協議,並在控制面提供使用者友好的流量規則。Aeraki 會將這些流量規則翻譯為 MetaProtocol 的配置後下發到資料面。

除了快速開發,節省工作量之外,採用 MetaProtocol 為服務網格開發自定義協議的另一個好處是該方案對 Istio,Envoy,以及 MetaProtocol Proxy 自身等上游開源專案是完全無侵入的,可以跟隨上游專案進行快速迭代,充分利用上游專案新版本提供的新增能力,無需維護自有的 fork 分支。

實現編解碼介面

Aeraki 提供了一個應用協議擴充套件的示例 awesomerpc 。示例中包含了實現自定義協議的程式框架,可以該示例為基礎進行修改,編寫你自己的私有協議。

git clone http://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 = "http://github.com/istio/proxy/archive/refs/tags/1.10.0.tar.gz",   
)

...... 

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

編譯

建議使用 Ubuntu 18.04 作為編譯環境。

安裝編譯所需軟體:

sudo wget -O /usr/local/bin/bazel http://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

參考文件