kubernetes / kubernetes/kubectl
kubectl uses far more memory than expected
- Dominant language
- Go
- Stars
- 3.3k
- Forks
- 1k
- PR merge metrics
- No merged PRs in 30d
Description
We use kubectl in some jobs to check on the cluster. However, we have found that it can be rather costly in memory use in either small or large clusters. Even more surprising, getting the results as YAML costs more than twice the memory as getting the results as JSON (even though the YAML output is usually half the size of the JSON output)
**What happened**:
First, the amount of memory used has gone up over the versions of kubernetes - significant increase in 1.18 vs 1.15 (the prior time I looked).
However, there is obviously something wrong with kubectl since the amount of use is far beyond reasonable.
Using the /usr/bin/time command to measure process resource consumption when doing these commands to a cluster we have and using wc to show the number of characters created in the output we see that the number of characters of yaml output is 479,416,955 and the amount of memory used by kubectl is 4,143,732K (4 gigabytes!) or around 9 times the size of the output!
What is worse is when doing the same with yaml we get noticeably smaller 233,672,375 characters of output (which is due to yaml being far more compact representation) and yet we consume 14,683,072K (yes, 14 gigabytes!) which is almost 70 times as much memory as the output and over 3 times as much memory as producing the same data in JSON!
```
$ /usr/bin/time -v kubectl get pods --all-namespaces --output json | wc --chars
Command being timed: "kubectl get pods --all-namespaces --output json"
User time (seconds): 74.95
System time (seconds): 1.75
Percent of CPU this job got: 98%
Elapsed (wall clock) time (h:mm:ss or m:ss): 1:17.59
Average shared text size (kbytes): 0
Average unshared data size (kbytes): 0
Average stack size (kbytes): 0
Average total size (kbytes): 0
Maximum resident set size (kbytes): 4143732
Average resident set size (kbytes): 0
Major (requiring I/O) page faults: 169
Minor (reclaiming a frame) page faults: 42114
Voluntary context switches: 48882
Involuntary context switches: 2159
Swaps: 0
File system inputs: 38976
File system outputs: 96
Socket messages sent: 0
Socket messages received: 0
Signals delivered: 0
Page size (bytes): 4096
Exit status: 0
479416955
```
vs
```
$ /usr/bin/time -v kubectl get pods --all-namespaces --output yaml | wc --chars
Command being timed: "kubectl get pods --all-namespaces --output yaml"
User time (seconds): 166.93
System time (seconds): 16.21
Percent of CPU this job got: 121%
Elapsed (wall clock) time (h:mm:ss or m:ss): 2:30.86
Average shared text size (kbytes): 0
Average unshared data size (kbytes): 0
Average stack size (kbytes): 0
Average total size (kbytes): 0
Maximum resident set size (kbytes): 14683072
Average resident set size (kbytes): 0
Major (requiring I/O) page faults: 61
Minor (reclaiming a frame) page faults: 1607011
Voluntary context switches: 44447
Involuntary context switches: 6393
Swaps: 0
File system inputs: 4680
File system outputs: 96
Socket messages sent: 0
Socket messages received: 0
Signals delivered: 0
Page size (bytes): 4096
Exit status: 0
233672375
```
**What you expected to happen**:
I would expect that this output would be produced at the most at around 1x of data size since serialization of the fields has a lot of repeated elements (field names) that should not be repeated in memory.
Even worse was the drastic jump in memory consumption based on output format! Going to 14 *Gigabytes* of peak RAM use for yaml output!
For contrast, see these two calls (same thing) to a much, much smaller cluster:
JSON output size: 3,385,403 while RAM used is 69,528K
YAML output size: 1,608,293 while RAM used is 182,472K
The YAML output is nearly 3 times the memory used by the kubectl process (and again 1/2 the output size)
```
$ /usr/bin/time -v kubectl get pods --all-namespaces --output json | wc --chars
Command being timed: "kubectl get pods --all-namespaces --output json"
User time (seconds): 0.69
System time (seconds): 0.02
Percent of CPU this job got: 108%
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:00.67
Average shared text size (kbytes): 0
Average unshared data size (kbytes): 0
Average stack size (kbytes): 0
Average total size (kbytes): 0
Maximum resident set size (kbytes): 69528
Average resident set size (kbytes): 0
Major (requiring I/O) page faults: 7
Minor (reclaiming a frame) page faults: 1530
Voluntary context switches: 1115
Involuntary context switches: 81
Swaps: 0
File system inputs: 1064
File system outputs: 0
Socket messages sent: 0
Socket messages received: 0
Signals delivered: 0
Page size (bytes): 4096
Exit status: 0
3385403
```
vs
```
$ /usr/bin/time -v kubectl get pods --all-namespaces --output yaml | wc --chars
Command being timed: "kubectl get pods --all-namespaces --output yaml"
User time (seconds): 1.36
System time (seconds): 0.13
Percent of CPU this job got: 129%
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:01.15
Average shared text size (kbytes): 0
Average unshared data size (kbytes): 0
Average stack size (kbytes): 0
Average total size (kbytes): 0
Maximum resident set size (kbytes): 182472
Average resident set size (kbytes): 0
Major (requiring I/O) page faults: 7
Minor (reclaiming a frame) page faults: 2616
Voluntary context switches: 1058
Involuntary context switches: 72
Swaps: 0
File system inputs: 1224
File system outputs: 0
Socket messages sent: 0
Socket messages received: 0
Signals delivered: 0
Page size (bytes): 4096
Exit status: 0
1608293
```
**How to reproduce it (as minimally and precisely as possible)**:
See above - the cluster above is not tiny - this effect is much more impactful on larger clusters but even on small clusters the memory consumption is far larger than the size of the data.
**Anything else we need to know?**:
We have small periodic jobs that use the kubectl API to do some work. We like the abstraction this gives us from the underlying APIs for these higher level jobs. They have also worked across Kubernetes versions, other than the resource requirements.
We had already switched to json output and processing due to kubectl's large memory footprint for yaml but have again hit issues with the large memory footprint (over 4GB just for kubectl process).
**Environment**:
- Kubernetes 1.18.6 and kubectl 1.18.6
- Client Version: version.Info{Major:"1", Minor:"18", GitVersion:"v1.18.6", GitCommit:"dff82dc0de47299ab66c83c626e08b245ab19037", GitTreeState:"clean", BuildDate:"2020-07-15T16:58:53Z", GoVersion:"go1.13.9", Compiler:"gc", Platform:"linux/amd64"}
- Server Version: version.Info{Major:"1", Minor:"18", GitVersion:"v1.18.6", GitCommit:"dff82dc0de47299ab66c83c626e08b245ab19037", GitTreeState:"clean", BuildDate:"2020-07-15T19:08:46Z", GoVersion:"go1.13.6", Compiler:"gc", Platform:"linux/amd64"}
- Running in Azure
- OS Ubuntu 18.04.5 LTS (workstation and all cluster VMs)
Contributor guide
Research direction
Start by reproducing the reported kubectl get pods --all-namespaces commands with JSON and YAML output, measuring peak memory with /usr/bin/time and output size with wc. The issue names no source files or tests, so trace the kubectl output path first. Done means the excessive memory difference is identified and reduced without changing the requested output.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go, kubernetes
- Domain
- cli, performance
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100