HTTP 모드에서 HAProxy를 사용하여 응답 스트리밍 호출 중에 gRPC 오류를 수신할 수 없습니다.

HTTP 모드에서 HAProxy를 사용하여 응답 스트리밍 호출 중에 gRPC 오류를 수신할 수 없습니다.

저는 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: 세션이 DATA 단계에 있었습니다.

추가로:

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 StreamEnd Headers플래그가 설정된 응답 헤더를 보냅니다. 그런 다음 코드로 스트림을 닫습니다 NO_ERROR. 에 제공된 답변에 따르면https://stackoverflow.com/questions/55511528/should-grpc-server-side-half-closing-implicitly-terminate-the-client/55522312이 단계에서는 모든 것이 괜찮습니다. 저도 간략하게 리뷰해봤는데요RFC 7540HTTP/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으로 변경된 것을 볼 수 있습니다 CANCEL. 실제로 클라이언트는 예상되는 모든 데이터를 수신하지만 그 후에는 gRPC 오류 RST_STREAM(CANCEL)에 매핑되는 예상치 못한 데이터를 수신합니다.CANCELLEDgRPC 문서.

추가 조사 과정에서 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를 포함한 네트워크 환경 도 포함되어 있습니다 .

관련 정보