Google Cloud HTTP(S)负载平衡器不会取消与后端的连接

时间:2020-06-03 17:13:08

标签: go kubernetes google-kubernetes-engine http-1.1 google-cloud-load-balancer

我有一个Google Kubernetes Engine集群,它在几个带有NodePort的Pod中,并且全部通过Ingress公开,这创建了一个HTTP负载平衡器(LB)。我正在使用具有LB的Google托管SSL证书的自定义域。

我的后端是使用其"net/http"包以Go语言编写的HTTP服务器。它正在对带有LB的mTLS使用自签名证书(Google的HTTP LB接受任何针对mTLS的证书)。

除一种情况外,一切正常,即客户端与LB创建HTTP 1.1连接,然后取消请求。这会取消客户端与LB之间的连接,但是LB与我的后端保持开放连接,直到服务器超时。

我的用例要求即使几个小时也要打开请求,因此我的服务器具有巨大的超时值。请求中的业务逻辑正确使用了请求的Context,并考虑了客户端是否取消了请求。

如果客户端发出HTTP2请求并取消该请求,则一切正常,即取消了与我后端的整个连接。

这是一个Go处理程序示例,它模拟可取消的长期运行任务:

func handleLongRunningTask(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context()
    t := time.Now()

    select {
    case <-ctx.Done():
        log.Println("request canceled")
    case <-time.After(30 * time.Second):
        log.Println("request finished")
    }
    log.Printf("here after: %v\n", time.Since(t))

    w.WriteHeader(http.StatusOK)
}

对于取消的HTTP 1.1请求,永远不会调用case <-ctx.Done():

为了便于测试,我使用curl和Ctrl+C;可行:

curl -v --http2 'https://example.com/long_running_task'

这不是:

curl -v --http1.1 'https://example.com/long_running_task'

NodePort是HTTPS还是HTTP2都没有关系,LB对于客户端取消的请求具有完全相同的行为。

我尝试使用Go 1.14.4和1.13.12编译服务器,结果是相同的。

这是Kubernetes,Ingress,Google Kubernetes Engine,Google的HTTP负载均衡器,Go的HTTP服务器中的错误吗?还是缺少HTTP 1.1?可能出什么问题了,我该如何解决?

...不可能知道后端的HTTP版本,因此我可以拒绝所有HTTP 1.1请求。无论客户端的HTTP版本如何,LB与后端进行通信时始终使用相同的HTTP版本。

1 个答案:

答案 0 :(得分:1)

从您的描述看来,问题出在GFE和后端之间,因为GFE might hold the connections for reuse

我的理解是,您看到协议版本之间存在这种差异,因为两者都处理连接持久性的方式。

对于HTTP2,连接将打开until one of the parties send a termination signal and the earliest takes preference。但是对于HTTP1.1,it might be prolonged until an explicit connection header is sent,指定终止:

HTTP / 1.1服务器可以假定HTTP / 1.1客户端打算维持持久连接,除非在请求中发送了包含连接令牌“ close”的Connection标头。如果服务器选择在发送响应后立即关闭连接,则应发送包含连接令牌关闭的连接标头。

这也许可以解释为什么HTTP1.1遵循same timeout configuration as the LB而HTTP2没有遵循。

无论何时要终止连接,我都建议尝试主动发送终止标头。 example taken from Github

func (m *MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    log.Printf("HTTP request from %s", r.RemoteAddr)
    // Add this header to force to close the connection after serving the request.
    w.Header().Add("Connection", "close")
    fmt.Fprintf(w, "%s", m.hostname)
}

另外,似乎有一些成功的故事将群集切换为VPC Native,因为它排除了kube-proxy连接管理的问题。

最后,可能是您处于非常特殊的情况,值得单独评估。您可能想尝试使用Issue Tracker向GKE小组发送一些复制步骤。

我希望这会有所帮助。