This investigation happened in November 2020. I never had time to write a blog describing the course of events; now’s a good chance to write it all up.
The Specific Phenomenon
An application in the production environment had slow interface problems!!
From this phenomenon alone, countless causes could be listed. This blog mainly narrates the several investigations and how the cause was finally determined. It may not apply to other clusters — take it as a reference. The investigation was fairly lengthy; too much time has passed and I can’t possibly recall every detail — please forgive me.
Network Topology
When network requests flow into the cluster, for our cluster’s structure:
1 | user request => Nginx => Ingress => uwsgi |
Don’t ask why there’s Nginx as well as Ingress. Historical reasons — some work temporarily needs to be borne by Nginx.
First Localization
When requests slow down, the immediate thought is: did the program slow down? So after discovering the problem, I first added a simple small interface in uwsgi — an interface that processes fast and returns data immediately — then requested it periodically. After running several days, I confirmed this interface’s access speed was also slow, ruling out program problems and preparing to search the chain for the cause.
Second Localization – Simple Full-Chain Data Statistics (TL;DR)
Since our Nginx has 2 layers, each needed separate confirmation to see which layer was slow. The request volume is fairly large; checking every request would be inefficient and might mask the real cause, so this process used statistics — viewing the two Nginx layers’ logs separately. Since logs were already connected to elk, the elk data-filtering script is roughly:
1 | { |
Data Processing Scheme
Based on trace_id, the Nginx logs and Ingress logs can be obtained via elk’s api.
1 | # this data structure records statistics results; |
I made statistics for request_time(nginx), request_time(ingress), and requet_time(nginx) - request_time(ingress) respectively.
The final statistics were roughly:

Result Analysis
We had about 3000 records in total!
Chart 1: Over half the requests fell in the 1
1.1s interval; 1s2s requests were fairly even, then fewer and fewer.
Chart 2: About 1/4 of requests had actually returned within 0.1s, but 1/4 also landed in 1~1.1s; subsequent results resemble chart 1.
Combining charts 1 and 2: some requests’ processing time on the Ingress side was actually fairly short.
Chart 3: Fairly obvious — 2/3 of requests stay consistent in response time; 1/3 have about 1s of delay.
Summary
From the statistics: both Nginx => Ingress and Ingress => upstream had varying degrees of delay. For applications exceeding 1s, about 2/3 of the delay came from Ingress=>upstream and 1/3 from Nginx=>Ingress.
Deeper Investigation - Packet Capture Handling (TL;DR)
Packet capture investigation mainly targeted Ingress=>uwsgi. Since packet delays were only sporadic, all packets had to be captured then filtered…
This is a record with a long request time; this interface itself should return quickly.
1 | { |
Ingress-side packets

uwsgi-side packets

Packet Flow
Recall the TCP three-way handshake:

First from the Ingress side: the connection started at
21.585446; at22.588023a packet retransmission occurred.From the Node side: the node received the syn shortly after the ingress packet was sent and immediately returned the syn — but somehow it only appeared at the ingress 1s later.


One point is rather concerning: even though packet retransmission occurred, there was no packet loss. From the two machines’ packet flow, in this request most of the time was caused by packets arriving late — retransmission was only the surface phenomenon; the real problem was packet delay.
Not Only ack Packets Were Delayed
From random packet captures, not only SYN ACK was retransmitted:

Some FIN ACK were too — packet delay is probabilistic behavior!!!
Summary
Looking at this capture alone might only confirm packet loss occurred. But combined with Ingress and Nginx request logs:
if packet loss happens during the tcp connection phase, then in the Ingress we can view upstream_connect_time
to roughly estimate the timeout situation. This was how I organized the records at the time:
I initially guessed this time was mainly consumed during TCP connection establishment, because connection establishment exists in both Nginx forwards, and our chain uses short connections entirely. Next I planned to add the
$upstream_connect_timevariable to record connection establishment time. http://nginx.org/en/docs/http/ngx_http_upstream_module.html
Follow-up Work
Since we could learn that tcp connection establishment took a long time, we could use it as a measurement metric. I also modified wrk, adding connection time measurement; the concrete PR is here (https://github.com/wg/wrk/pull/447). We can use this metric to gauge backend service conditions.

Seeking Experts — Whether They Met Similar Problems
I did the above work several times back and forth with no clues, so I consulted other K8S experts in the company. One expert provided an idea:
If host machine latency is also high, temporarily rule out the host-to-container path. We previously investigated a latency problem: it was caused by k8s monitoring tools periodically cat-ing cgroup statistics under proc, but due to docker’s frequent destruction/recreation and the kernel cache mechanism, each cat took very long, occupying the kernel and causing network latency. Could you check whether your hosts have a similar situation? It’s not necessarily cgroup — any operation frequently trapping into the kernel could cause high latency
This looks just like the cgroup we investigated: hosts have some periodic tasks; as executions accumulate, they occupy more and more kernel resources, and past a certain level they affect network latency
The experts also provided a kernel checking tool (can track and locate interrupt or soft-interrupt disable times):
https://github.com/bytedance/trace-irqoff
The problematic ingress machines had especially many latency entries — many errors like this, which other machines didn’t have:


Then I traced the kubelet on the machines once; from the flame graph it was confirmed that massive time was spent reading kernel information.

The concrete code is as follows:

Summary
Following the direction the expert gave, the real cause could basically be determined: too many scheduled task executions on the machine, kernel cache kept growing, slowing the kernel. Once it slowed, tcp handshake times lengthened, finally degrading user experience. Having found the problem, the solution was easy to search up: add tasks checking whether the kernel has slowed; if slow, clean it once:
1 | sync && echo 3 > /proc/sys/vm/drop_caches |
Summary
This investigation began with an application-layer problem affecting user experience, then extended to the network layer, going through a long packet-capture process.
I also added my own scripts for metric measurement, then located the concrete application via kernel tools,
and finally located a more precise anomaly position via the flame graph made with the application’s pprof tool.
During this I couldn’t handle the problem alone, so I asked other experts for help; the experts are well-informed and could offer possible conjectures — very helpful indeed.
When you find a machine slow at everything, yet cpu and kernel aren’t the bottleneck, it’s possible the kernel has slowed.
I hope this article helps everyone investigating cluster problems in the future.