Flood of TIME_WAIT Connections from a Go HTTP Client: Unread Response Bodies and the Liveness Probe
Posted onEdited onViews: Word count in article: 1.9kReading time ≈2 mins.
A Go HTTP client produced masses of TIME_WAIT and short-lived connections. Tracing it from request code through response body handling and connection reuse, then Transport tuning.
Notes from tuning TIME_WAIT connections in a Go HTTP Client.
Business flow
Analysis
Container monitoring showed a large number of short-lived connections between the service and the load balancer in front of the event bus. Time to go back to the code.
// The http Client and Transport guarantee that Body is always // non-nil, even on responses without a body or responses with // a zero-length body. It is the caller's responsibility to // close Body. The default HTTP client's Transport may not // reuse HTTP/1.x "keep-alive" TCP connections if the Body is // not read to completion and closed.
10.200.76.64 is the IP of the host machine the pod runs on
So there were short-lived connections between the pod and its host. What were those connections doing? Time for a packet capture:
Look closely at frame No. 502:
1 2 3 4 5 6 7 8 9 10 11 12 13
Frame 502: 177 bytes on wire (1416 bits), 177 bytes captured (1416 bits) Ethernet II, Src: ee:ee:ee:ee:ee:ee (ee:ee:ee:ee:ee:ee), Dst: b6:cd:6a:f8:69:5e (b6:cd:6a:f8:69:5e) Internet Protocol Version 4, Src: 10.200.76.64, Dst: 172.16.3.247 Transmission Control Protocol, Src Port: 29978, Dst Port: 8080, Seq: 1, Ack: 1, Len: 111 Hypertext Transfer Protocol GET /healthz HTTP/1.1\r\n <-- note this line: this is the service's configured liveness probe endpoint Host: 172.16.3.247:8080\r\n User-Agent: kube-probe/1.21\r\n Accept: */*\r\n Connection: close\r\n <-- note this line \r\n [Response in frame: 506] [Full request URI: http://172.16.3.247:8080/healthz]
The Connection header:
Connection: keep-alive — once a page has loaded, the TCP connection used to transfer HTTP data between client and server stays open. If the client hits the same server again, it reuses the connection that is already established.
Connection: close — once a request completes, the TCP connection between client and server is closed. The next request from the client has to establish a new TCP connection.
The note on the Connection line says it all: a request carrying Connection: close will be a short-lived connection.
Now let’s look at the service’s Deployment configuration:
At this point everything adds up: Kubernetes requests the service’s liveness endpoint every 10 seconds, each request is a short-lived connection, and the default TIME_WAIT duration is 120s.
So the service should hover at around 11–13 TIME_WAIT connections at all times.
With that, every remaining question was explained.
Conclusions
After a Go HTTP client request completes, even if the calling code does not use the response body, you still have to read the body.
As long as a service has a liveness probe configured, there will be short-lived connections; their number depends on the probe interval.