AWS ALB 上的 HTTP2 PING 帧(gRPC keepalive ping)

HTTP2 PING frames over AWS ALB (gRPC keepalive ping)

我正在使用 AWS 应用程序负载均衡器 (ALB) 公开 ASP.NET 核心 gRPC 服务。这些服务在 Fargate 容器中 运行,并暴露不安全的 HTTP 端口。 ALB 终止外部 TLS 连接,并根据路由将未加密的流量转发到目标组。 gRPC 应用程序有多个客户端流式处理端点,客户端可以暂停流式处理几分钟。我知道有 HTTP2 PING 帧,可以在这种情况下使用,以在一段时间内保持没有数据传输的连接。

gRPC 服务器配置为每 20 秒发送一次 HTTP2 ping 以保持连接有效。我测试了这种方法并且它有效,ping 帧从服务器发出并被客户端确认。 但是当涉及到 ALB 时,这种方法就失败了。在传输暂停期间,我没有看到来自负载平衡器后面的服务器的任何包(我使用 Wireshark)。然后在 1 分钟超时后,ALB 重置连接。

我也尝试使用客户端发送的 HTTP2 ping。但是连接也会在 1 分钟后重置,我没有证据表明这些 ping 包是否真的到达了 ALB 后面的服务器。 我假设 AWS ALB 不允许此类数据包通过它,但我没有找到任何证明它的文档。

ALB 转发基于 HTTP 协议语义的请求,而不是原始 HTTP/2 帧。因此,诸如 ping 帧之类的东西将仅适用于其中一个跃点。

如果你想要一个端到端的 ping,你可以定义一个 gRPC API 来执行 ping。对于服务器到客户端,您需要使用服务器端流 APIs。但实际上让客户端启动 ping 可能更可取,以减少服务器必须执行的工作人员。

AWS 支持团队回复了我的请求,简短的回答是 ALB 不支持 HTTP2 ping 帧。他们建议增加负载均衡器的空闲超时值,但这种解决方案在某些情况下可能不适用。

正如 已经提到的,可能的解决方法是定义一个 gRPC API 来执行 ping。

我们使用 Golang 模拟 gRPC 客户端,HTTP2 ping 帧配置如下

    conn, err := grpc.Dial(ServerAddress,
        grpc.WithTransportCredentials(cred),
        grpc.WithBlock(),
        grpc.WithKeepaliveParams(keepalive.ClientParameters{
            Time:                time.Second * 11,
            Timeout:             time.Second * 3,
            PermitWithoutStream: true,
        }),
    )
    if err != nil {
        log.Fatalf("net.Connect err: %v", err)
    }
    defer conn.Close()

    grpcClient := protocol.NewChatClient(conn)
    ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*3000)
    defer cancel()
    stream, err := grpcClient.Stream(ctx)
    if err != nil {
        log.Fatalf("get Bidirectional stream err: %v", err)
    }

    for i := 0; i < 2; i++ {
        // send stream message
        stream.Send(&protocol.StreamRequest{
            // ...
        })

        if err != nil {
            log.Fatalf("stream request err: %v", err)
        }

        res, err := stream.Recv()
        if err != nil {
            log.Fatalf("Conversations get stream err: %v", err)
        }

        time.Sleep(time.Duration(63) * time.Second)
    }

    select {}

和 运行 具有 GODEBUG=http2debug=2 的模拟客户端。

3秒后,客户端发送RST_STREAM到ALB

http2: Framer xxx: wrote RST_STREAM stream=1 len=4 ErrCode=CANCEL

之后,我们注意到模拟客户端每 11 秒发送一次 ping 帧。

http2: Framer xxx: wrote PING len=8 ping="\x00\x00\x00\x00\x00\x00\x00\x00"
http2: Framer xxx: read PING flags=ACK len=8 ping="\x00\x00\x00\x00\x00\x00\x00\x00"

doc

gRPC sends http2 pings on the transport to detect if the connection is down

IMO,ALB 可以正确处理 PING 帧,并保持 gRPC 连接处于活动状态,除了流被客户端关闭。此外,ALB 的空闲超时可以关闭空闲流,PING 帧可以保持 gRPC 连接。


为了重用gRPC连接,也许我们可以这样尝试,分别用不同的流发送每个请求。

    grpcClient := protocol.NewChatClient(conn)
    ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*3000)
    defer cancel()

    for i := 0; i < 2; i++ {
        stream, err := grpcClient.Stream(ctx)
        if err != nil {
            log.Fatalf("get BidirectionalHello stream err: %v", err)
        }

        // send stream message
        stream.Send(...)
     }

ALB 控制器版本 aws-alb-ingress-controller:v2.1.3