APISIX: etcd操作をHTTPからgRPCに移行

Zexuan Luo

Zexuan Luo

February 10, 2023

Products

Apache APISIXのHTTPベースのetcd操作の制限

etcdがバージョン2.xのとき、公開されていたAPIインターフェースはHTTP 1(以下、HTTPと呼びます)でした。etcdがバージョン3.xにアップグレードされた後、プロトコルはHTTPからgRPCに切り替わりました。gRPCをサポートしていないユーザーのために、etcdはgRPC-Gatewayを提供し、HTTPリクエストをgRPCとしてプロキシして新しいgRPC APIにアクセスできるようにしています。

APISIXがetcdを使い始めたとき、etcd v2 APIを使用していました。APISIX 2.0(2020年)では、etcdの要件をバージョン2.xから3.xに更新しました。etcdのHTTP互換性により、バージョンアップの手間が省けました。呼び出し方法とレスポンス処理のコードを修正するだけで済みました。しかし、長年にわたって、etcdのHTTP APIに関連するいくつかの問題も発見しました。まだいくつかの微妙な違いがあります。gRPC-gatewayがあるからといって、HTTPアクセスを完全にサポートできるわけではないことに気づきました。

以下は、過去数年間にetcdで遭遇した関連する問題のリストです:

  1. gRPC-gatewayがデフォルトで無効になっている。メンテナの不注意により、一部のプロジェクトではetcdのデフォルト設定でgRPC-gatewayが有効になっていませんでした。そのため、ドキュメントに現在のetcdがgRPC-gatewayを有効にしているかどうかを確認する手順を追加する必要がありました。詳細はhttps://github.com/apache/apisix/pull/2940を参照してください。
  2. デフォルトでは、gRPCはレスポンスを4MBに制限しています。etcdは提供するSDKではこの制限を解除していますが、gRPC-gatewayでは解除していません。公式のetcdctl(提供するSDKに基づいて構築されている)は問題なく動作しますが、APISIXは動作しません。詳細はhttps://github.com/etcd-io/etcd/issues/12576を参照してください。
  3. 同じ問題 - 今回は同じ接続に対する最大リクエスト数に関してです。GoのHTTP2実装には、単一のクライアントが同時に送信できるリクエスト数を制御するMaxConcurrentStreams設定があり、デフォルトで250に設定されています。通常、どのクライアントが同時に250以上のリクエストを送信するでしょうか?そのため、etcdは常にこの設定を使用してきました。しかし、すべてのHTTPリクエストをローカルのgRPCインターフェースにプロキシする「クライアント」であるgRPC-gatewayは、この制限を超える可能性があります。詳細はhttps://github.com/etcd-io/etcd/issues/14185を参照してください。
  4. etcdがmTLSを有効にした後、etcdはサーバー証明書とクライアント証明書として同じ証明書を使用します:gRPC-gatewayのサーバー証明書と、gRPC-gatewayがgRPCインターフェースにアクセスするときのクライアント証明書です。証明書にサーバー認証拡張が有効になっているが、クライアント認証拡張が有効になっていない場合、証明書検証でエラーが発生します。再度、etcdctlで直接アクセスすると問題なく動作します(この場合、証明書はクライアント証明書として使用されないため)が、APISIXは動作しません。詳細はhttps://github.com/etcd-io/etcd/issues/9785を参照してください。
  5. mTLSを有効にした後、etcdは証明書のユーザー情報のセキュリティポリシー設定を許可します。前述のように、gRPC-gatewayはgRPCインターフェースにアクセスするときに固定のクライアント証明書を使用するため、最初にHTTPインターフェースにアクセスするために使用された証明書情報は使用されません。したがって、クライアント証明書が固定されて変更されないため、この機能は自然に動作しません。詳細はhttps://github.com/apache/apisix/issues/5608を参照してください。

これらの問題を2点にまとめることができます:

  1. gRPC-gateway(およびおそらくHTTPをgRPCに変換する他の試み)は、すべての問題を解決する銀の弾丸ではありません。
  2. etcdの開発者は、HTTPからgRPCへの方法に十分な重点を置いていません。そして、彼らの最大のユーザーであるKubernetesはこの機能を使用していません。

この問題を解決するためには、gRPC-Gatewayの互換性のために残されたHTTPパスを経由せずに、直接gRPCを通じてetcdを使用する必要があります。

gRPCへの移行の課題を克服する

lua-protobufのバグ

移行プロセス中の最初の問題は、サードパーティライブラリでの予期せぬバグでした。ほとんどのOpenRestyアプリケーションと同様に、私たちはlua-protobufを使用してprotobufをデコード/エンコードしています。

etcdのprotoファイルを統合した後、Luaコードで時折クラッシュが発生し、「テーブルオーバーフロー」というエラーが報告されることがわかりました。このクラッシュは再現性が低いため、最初に最小限の再現例を探すことにしました。興味深いことに、etcdのprotoファイルだけを使用すると、この問題をまったく再現できません。このクラッシュはAPISIXが実行されているときにのみ発生するようです。

いくつかのデバッグの後、protoファイルのoneofフィールドを解析する際にlua-protobufに問題があることがわかりました。lua-protobufは解析時にテーブルサイズを事前に割り当てようとし、割り当てられるサイズは特定の値に基づいて計算されます。この値が負の数になる可能性があり、その場合LuaJITはこの数値を大きな正の数に変換し、「テーブルオーバーフロー」エラーが発生します。この問題を作者に報告し、内部で修正を加えたフォークを維持しました。

lua-protobufの作者は非常に迅速に対応し、翌日に修正を提供し、数日後に新しいバージョンをリリースしました。lua-protobufが使用されなくなったprotoファイルをクリーンアップする際に、いくつかのフィールドのクリーンアップを忘れていたことが判明し、その結果oneofが処理される際に不合理な負の数が生成されていました。この問題は時々発生し、etcdのprotoファイルだけを使用すると再現できない理由は、これらのフィールドをクリーンアップする手順が欠けていたためです。

HTTPの動作に合わせる

移行プロセス中に、既存のAPIが実行結果を正確に返さず、レスポンスステータスとボディを含むHTTPレスポンスを返していることがわかりました。そして、呼び出し側はHTTPレスポンスを自分で処理する必要があります。

レスポンスがgRPCの場合、処理ロジックに合わせるためにHTTPレスポンスのシェルでラップする必要があります。そうでない場合、呼び出し側は新しい(gRPC)レスポンス形式に適応するために複数の場所でコードを修正する必要があります。特に、古いHTTPベースのetcd操作も同時にサポートする必要があることを考えると、これは重要です。

HTTPレスポンスとの互換性のために追加のレイヤーを追加することは望ましくありませんが、これに対処する必要があります。これに加えて、gRPCレスポンスに対してもいくつかの処理を行う必要があります。例えば、対応するデータがない場合、HTTPはデータを返しませんが、gRPCは空のテーブルを返します。これもHTTPの動作に合わせるために調整する必要があります。

短い接続から長い接続へ

HTTPベースのetcd操作では、APISIXは短い接続を使用しているため、接続管理を考慮する必要はありませんでした。必要なときに新しい接続を開始し、終了したら閉じるだけで済みました。

しかし、gRPCではこれができません。gRPCに移行する主な目的の1つは、多重化を実現することです。各操作に対して新しいgRPC接続を作成すると、多重化を実現できません。ここでgRPC-goに感謝します。gRPC-goには接続管理機能が組み込まれており、接続が中断されると自動的に再接続します。そのため、gRPC-goを使用して接続を再利用できます。APISIXレベルではビジネス要件のみを考慮する必要があります。

APISIXのetcd操作は、etcdデータに対するCRUD(追加、削除、変更、クエリ)操作と、コントロールプレーンから設定を同期する操作の2つに分類できます。理論的には、これらの2つのetcd操作は同じgRPC接続を共有できますが、責任の分離のために2つの接続に分割することにしました。CRUD操作の接続については、APISIXは起動時と起動後に別々に扱う必要があるため、新しい接続を取得するときにif文を追加しました。不一致がある場合(つまり、現在の接続が起動時に作成されたが、起動後に接続が必要な場合)、現在の接続を閉じて新しい接続を作成します。設定の同期については、新しい同期方法を開発し、各リソースが既存の接続下でストリームを使用してetcdを監視するようにしました。

gRPCへの移行の利点

gRPCに移行した後の明らかな利点の1つは、etcdを操作するために必要な接続数が大幅に減少したことです。HTTPを通じてetcdを操作する場合、APISIXは短い接続しか使用できませんでした。そして、設定を同期するとき、各リソースは別々の接続を持ちます。

gRPCに切り替えた後、gRPCの多重化機能を使用できるようになり、各リソースは完全な接続ではなく単一のストリームを使用します。これにより、リソース数に応じて接続数が増加しなくなります。APISIXの今後の開発では、より多くのリソースタイプが導入されることを考えると、例えば最新バージョン3.1ではsecretsが追加されましたが、gRPCを使用することで接続数の減少がさらに顕著になります。

gRPCを使用して同期する場合、各プロセスには設定同期のために1つ(ストリームサブシステムが有効になっている場合は2つ)の接続しかありません。以下の図では、2つのプロセスに4つの接続があり、そのうち2つは設定同期用、Admin APIは1つの接続を使用し、残りの接続は特権エージェントがサーバー情報を報告するために使用されています。

gRPCははるかに少ない接続を使用します

比較のために、以下の図は他のパラメータを変更せずに元の設定同期方法を使用する場合に必要な22の接続を示しています。さらに、これらの接続は短い接続です。

接続が多すぎます

これらの2つの設定の唯一の違いは、etcd操作にgRPCが有効になっているかどうかです:

  etcd:
    use_grpc: true
    host:
      - "http://127.0.0.1:2379"
    prefix: "/apisix"
    ...

接続数を減らすことに加えて、gRPC-gatewayではなく直接gRPCを使用してetcdにアクセスすることで、記事の冒頭で述べたmTLS認証などのアーキテクチャ的に制限された一連の問題を解決できます。また、gRPCを使用した後は問題が少なくなります。なぜなら、KubernetesはgRPCを使用してetcdを操作しているため、問題があればKubernetesコミュニティによって発見されるからです。

もちろん、gRPC方法はまだ比較的新しいため、APISIXがgRPCを通じてetcdを操作する際に新しい問題が発生する可能性があります。現在、デフォルトでは依然として元のHTTPベースの方法を使用してetcdを操作しています。ユーザーはconfig.yamlのetcdの下でuse_grpcをtrueに設定するオプションがあります。gRPC方法がより良いかどうかを試すことができます。また、さまざまなソースからのフィードバックを収集して、gRPCベースのetcd操作を改善していきます。gRPCアプローチが十分に成熟していると判断したら、デフォルトのアプローチにします。

APISIXを最大限に活用するには、API7が必要です

あなたはApache APISIXのパフォーマンスを愛していますが、その管理のオーバーヘッドは望んでいません。設定、メンテナンス、更新を心配することなく、コアビジネスに集中できます。

私たちのチームは、Apache APISIXの作成者と貢献者、OpenRestyとNGINXのコアメンテナー、Kubernetesのメンバー、クラウドインフラの業界エキスパートで構成されています。あなたは舞台裏で最高の人材を得ることができます。

自信を持って開発を加速したいですか?APISIXサポートを最大限に活用するには、API7が必要です。あなたのニーズに基づいて、APISIXとAPI管理ソリューションの深いサポートを提供します!

今すぐお問い合わせください:https://api7.ai/contact

Tags: