私は、http モードを使用して HAProxy の背後で提供される gRPC アプリケーションに取り組んでいます。サーバー アプリケーションがすぐに (つまり、応答を送信する前に) 特定のエラーで応答ストリーミング呼び出しを中止した場合、クライアント アプリケーションはCANCELLED
送信されたエラーではなくエラーを受け取ります。エラーの詳細は次のとおりです。エラーコード 8 で RST_STREAM を受信しました私は HAProxy 2.3.2 と grpc 1.34.0 を使用しています。
このようなリクエストごとに、HAProxyログエントリSD--
にはフラグが設定されます。切断時のセッション状態フィールド、例:
<134>Jan 9 18:09:39 1a8328663d74 haproxy[8]: 172.28.0.4:41698 [09/Jan/2021:18:09:39.346] grpc-server-fe grpc-server-fe/grpc-server-be 0/0/0/-1/+0 -1 +115 - - SD-- 1/1/0/0/0 0/0 "POST http://proxy:6000/service.Service/StreamStream HTTP/2.0"
HAProxy ドキュメントでは、これらのフラグは次のように定義されています。
- S: TCP セッションがサーバーによって予期せず中止されたか、サーバーによって明示的に拒否されました。
- D: セッションはデータ フェーズでした。
さらに:
SDデータ転送中にエラーが発生し、サーバーへの接続が切断されました。これは通常、haproxy がサーバーとデータを交換中にサーバーから RST を受信したか、中間機器から ICMP メッセージを受信したことを意味します。これは、サーバーのクラッシュまたは中間機器のネットワークの問題によって発生する可能性があります。
それを知っており、HTTP 接続がデータ ストリーミングを目的として開かれたことを念頭に置いて、回避策として、エラーを発生させる前に空の gRPC メッセージを送信してみました。この解決策は部分的には役立ちました。ほとんどのリクエストでクライアントがエラー コードを受信できたかもしれませんが、それでも時々問題が発生しました。
次のステップとして、Wireshark を使用してネットワーク トラフィックを検査しました。以下は、即時呼び出し中止イベント時に gRPC サーバーによって提供される HTTP 応答のトレースです。
HyperText Transfer Protocol 2
Stream: SETTINGS, Stream ID: 0, Length 0
Length: 0
Type: SETTINGS (4)
Flags: 0x01
0... .... .... .... .... .... .... .... = Reserved: 0x0
.000 0000 0000 0000 0000 0000 0000 0000 = Stream Identifier: 0
Stream: HEADERS, Stream ID: 1, Length 88, 200 OK
Length: 88
Type: HEADERS (1)
Flags: 0x05
.... ...1 = End Stream: True
.... .1.. = End Headers: True
.... 0... = Padded: False
..0. .... = Priority: False
00.0 ..0. = Unused: 0x00
0... .... .... .... .... .... .... .... = Reserved: 0x0
.000 0000 0000 0000 0000 0000 0000 0001 = Stream Identifier: 1
[Pad Length: 0]
Header Block Fragment: 88400c636f6e74656e742d74797065106170706c69636174...
[Header Length: 120]
[Header Count: 4]
Header: :status: 200 OK
Header: content-type: application/grpc
Header: grpc-status: 7
Header: grpc-message: Details sent by the server
Stream: RST_STREAM, Stream ID: 1, Length 4
Length: 4
Type: RST_STREAM (3)
Flags: 0x00
0... .... .... .... .... .... .... .... = Reserved: 0x0
.000 0000 0000 0000 0000 0000 0000 0001 = Stream Identifier: 1
Error: NO_ERROR (0)
そこでサーバーはエラーの詳細とEnd Stream
フラグEnd Headers
をセットしたレスポンスヘッダーを送信します。そしてNO_ERROR
コードでストリームを閉じます。https://stackoverflow.com/questions/55511528/should-grpc-server-side-half-closing-implicitly-terminate-the-client/55522312現時点ではすべて問題ありません。また、RFC7540 の翻訳HTTP/2 プロトコルに関して問題となる点は見つかりませんでした。
引用された gRPC サーバーの HTTP 応答の後に HAProxy 発信 TCP が続きACK
、次に HAProxy がその応答をクライアントに送信します。
HyperText Transfer Protocol 2
Stream: HEADERS, Stream ID: 1, Length 75, 200 OK
Length: 75
Type: HEADERS (1)
Flags: 0x05
.... ...1 = End Stream: True
.... .1.. = End Headers: True
.... 0... = Padded: False
..0. .... = Priority: False
00.0 ..0. = Unused: 0x00
0... .... .... .... .... .... .... .... = Reserved: 0x0
.000 0000 0000 0000 0000 0000 0000 0001 = Stream Identifier: 1
[Pad Length: 0]
Header Block Fragment: 885f106170706c69636174696f6e2f67727063000b677270...
[Header Length: 120]
[Header Count: 4]
Header: :status: 200 OK
Header: content-type: application/grpc
Header: grpc-status: 7
Header: grpc-message: Details sent by the server
Stream: RST_STREAM, Stream ID: 1, Length 4
Length: 4
Type: RST_STREAM (3)
Flags: 0x00
0000 0000 = Unused: 0x00
0... .... .... .... .... .... .... .... = Reserved: 0x0
.000 0000 0000 0000 0000 0000 0000 0001 = Stream Identifier: 1
Error: CANCEL (8)
フラグとHEADERS
フレームのすべてのコンテンツが配置されているため、エラーの詳細がクライアントに渡されますが、 のコードRST_STREAM
が に変更されています。実際には、クライアントは予想されるすべてのデータを受信しますが、その後、で指定されているように、gRPC エラーにマップされているCANCEL
予期しないものを受け取ります。RST_STREAM(CANCEL)
CANCELLED
gRPC ドキュメント。
さらに調査を進める中で、HAProxyのソースコードを参照しました。コードはh2_do_shutr
の方程式mux_h2.c
(カスタム HAProxy ビルドの実験により、実際にこの場所であることが証明されました)。関連するコード ブランチには次のコメントがあります。
最終応答がすでに提供されているため、このストリームは不要です。これは、アップロードが終了する前にサーバーが応答し、すぐに閉じた場合に発生する可能性があります (リダイレクト、拒否など)
以上が、私がこの問題に関して収集できた詳細です。問題が gRPC コア (HTTP2 ストリーム処理の点で乱雑すぎる) にあるか、HAProxy (RST_STREAM
コードの書き換え時に不注意すぎる) にあるかは完全にはわかりません。最後の質問は、即時呼び出し中止のイベントで正しく動作するように HAProxy と gRPC コア サーバーの構成を調整するにはどうすればよいかということです。問題を再現する最小限の HAProxy 構成は次のとおりです。
global
log stdout local0
listen grpc-server-fe
bind *:6000 proto h2
mode http
log global
option logasap
option httplog
timeout connect 5000ms
timeout client 50000ms
timeout server 50000ms
server grpc-server-be server:6000 proto h2
私は準備しました最小限の例を含むリポジトリシンプルな Python クライアントとサーバーが含まれています。また、docker-compose
構成された HAProxy を含むネットワーク環境も含まれています。