Kubernetes의 Resource Management
Kubernetes에서 resource management하는 것에 대해 정리해보고자 한다. 꽤나 복잡해서 하나의 포스팅으로는 끝나지 않을 것 같아 내용을 나눈다.
이번 포스팅은 pod의 spec 중 container별로 설정할 수 있는 resource requests와 limits에 대한 내용이다.
Resource Requests & Limits
Kubernetes에서 pod의 spec을 정의할 때, container별로 CPU와 Memory에 대해서 requests와 limits를 설정할 수 있다.
1 | apiVersion: v1 |
CPU의 경우 단위는 core이고, 0.1과 100m, 100 millicore는 모두 같은 값을 의미한다.
Memory의 경우 단위는 byte이다.
Requests
Requests는 pod을 scheduling할 때 참고하며, 실제 container process의 사용량이 이것을 넘는 것은 중요하지 않다.
사용자가 생각할 때 ‘이 container는 이 정도의 자원을 필요로 할 것이다’라는 예상치로 정하는 값이다. Soft limit정도로 생각하면 된다.
cgroup에 설정되는 값
CPU의 경우 cpu.shares 값이 설정된다. 만약 container1은 CPU requests로 0.5를, container2는 CPU requests로 1을 지정했다면, A와 B는 1:2의 비율로 스케줄링된다.
Memory는 별도로 설정하는 값이 없다.- 테스트한 conainer yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22apiVersion: v1
kind: Pod
metadata:
name: request-test
spec:
containers:
- name: request-test-ctr-1
image: busybox
stdin: true
tty: true
resources:
requests:
cpu: 0.5
memory: 300Mi
- name: request-test-ctr-2
image: busybox
stdin: true
tty: true
resources:
requests:
cpu: 1
memory: 300Mi - cgroup 확인
pod uuid와 docker container uuid 확인
다른 방법들이 많겠지만 나는 그냥 worker node로 접속해서 docker ps를 쳐서 알아내는 편이다.
Docker container name의 형식은 k8s_{container name}{pod name}{namespace}_{pod uuid}이다.해당 pod의 cgroup 접근
- 참고) kubernetes cgroup layout (v1.17 기준) /sys/fs/cgroup/{resource type}/kubepods/{QoS class}/pod{pod uuid}이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16kubepods
\_ besteffort
\_ pod{uuid}
\_ {container uuid}
\_ {pause container uuid}
\_ ...
\_ burstable
\_ pod{uuid}
\_ {container uuid}
\_ {pause container uuid}
\_ ...
\_ pod{uuid} # guaranteed pods
\_ {container uuid}
\_ {pause container uuid}
\_ ...
\_ ...
QoS class가 BestEffort, Burstable, Guaranteed가 있으며, Guaranteed의 경우 추가 cgroup 없이 바로 pod cgroup이 위치한다.
kubepods의 cpu.shares는 {node의 core 수} * 1024, besteffort의 cpu.shares는 2, burstable의 cpu.shares는 256이다. guaranteed cgroup이 따로 없고 바로 pod cgroup이 있는 건 kubernetes가 (같은 core에 대해 경쟁해야하는 상황인 경우)guaranteed pod에게 최대한 자원을 보장하겠다는 의지가 반영된 것이 아닐까 싶다. kubepod의 cpu.shares 수치로 보아서 해당 node는 온전히 kubernetes만을 위한 것이라고 생각하는 것이 아닐까. 뭐 이건 다 추측이다.
- 참고) kubernetes cgroup layout (v1.17 기준)
container별 cpu.shares 확인
Pod cgroup의 cpu.shares는 1536, container1 cgroup의 것은 512, container2 cgroup의 것은 1024, pause container의 것은 2가 설정되어 있다.
Kubernetes는 container별로 cpu.shares를 {CPU requests} * 1024 값으로 설정하고, pod의 cpu.shares는 이렇게 request가 지정된 container의 cpu.shares의 합으로 설정한다. (1536 = 512 + 1024)만약 requests를 설정하지 않으면 2이며, 이 때는 pod cgroup에 합산하지 않는다.
만약 container1의 cpu request를 0.5, container2는 request를 설정하지 않았으면, pod의 cpu.shares는 512이다.
만약 container 1과 2 둘 다 request를 설정하지 않았으면, pod의 cpu.shares는 2이다.
(이러한 계산은 실험공학으로 알아낸 것이므로, 정확한 알고리즘은 나중에 코드를 확인해봐야할 것 같다.)
- 테스트한 conainer yaml
Limits
Limits는 container의 cgroup을 설정할 때 참고하는 값이므로, 실제 container process의 사용량은 이것을 넘지 못한다. Hard limit정도로 생각하면 된다.
Requests를 설정했다면 limits는 requests보다 크거나 같은 값이어야 한다. (Requests를 더 큰 값으로 설정할 경우 deploy 시 에러가 발생한다.)
Requests를 설정하지 않은 채 limits를 설정한다면, requests는 limits와 동일한 값으로 설정된다.
참고로 requests는 설정했지만 limits를 설정하지 않을 수도 있다. 물론 둘 다 설정하지 않아도 된다. CPU와 Memory 둘 중에 하나만 설정할 수도 있다.
(어떻게 설정하느냐에 따라서 아까 언급했던 QoS class가 나뉘는데, 이 역시 다른 포스팅에서…)
cgroup에 설정되는 값
CPU의 경우 CPU bandwidth quota인 cpu.cfs_quota_us 값이 설정된다.
Memory의 경우 memory 사용률 제한인 memory.limit_in_bytes 값이 설정된다.- 테스트한 conainer yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14apiVersion: v1
kind: Pod
metadata:
name: request-test
spec:
containers:
- name: request-test-ctr-1
image: busybox
stdin: true
tty: true
resources:
limits:
cpu: 0.5
memory: 300Mi - cgroup 확인
container별 cpu.cfs_quota_us 확인
Pod의 quota는 50000, container1의 quota도 50000, pause container의 quota는 설정하지 않는다(-1).
Kubernetes는 cfs_quota_us를 {CPU limit} * {cfs_period_us}로 설정하고, cfs_period_us는 kernel default값인 100000을 그대로 사용한다.참고로 cfs_quota_us는 -1일 때는 bandwidth 제한을 하지 않는다.
만약 CPU limit을 주지 않을 경우 cfs_quota_us는 -1로 설정된다.container별 memory.limit_in_bytes 확인
Pod의 limit은 314572800, container1의 limit도 314572800, pause container의 경우 설정하지 않는다.
Kubernetes는 memory.limit_in_bytes를 Memory limit값을 byte로 변환하여 설정한다.
- 테스트한 conainer yaml
Memory stress test
시나리오
- request & limit 설정하지 않았는데 out-of-memory가 발생하는 경우
- request 값 < 실 사용량
- limit 값 < 실 사용량
테스트 과정
request & limit 설정하지 않았는데 out-of-memory가 발생하는 경우
- test한 yaml
1
2
3
4
5
6
7
8
9
10
11apiVersion: v1
kind: Pod
metadata:
name: memory-demo
spec:
restartPolicy: Never
containers:
- name: memory-demo-ctr
image: polinux/stress
command: ["stress"]
args: ["--vm", "1", "--vm-bytes", "1700M", "--vm-hang", "1"] - 테스트 결과
Pod describe 결과
해당 pod을 describe해보면 State가 Terminated로 바뀌었고, Reason이 Error라고 명시되어있다.
OOM killer 동작 여부 확인
- test한 yaml
request만 설정하는 경우
test한 yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14apiVersion: v1
kind: Pod
metadata:
name: memory-demo
spec:
restartPolicy: Never
containers:
- name: memory-demo-ctr
image: polinux/stress
resources:
requests:
memory: 300Mi
command: ["stress"]
args: ["--vm", "1", "--vm-bytes", "500M", "--vm-hang", "1"]해당 pod을 describe해보면 State는 Running으로, 아무런 일도 일어나지 않았다.
limit 설정하는 경우
test한 yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14apiVersion: v1
kind: Pod
metadata:
name: memory-demo
spec:
restartPolicy: Never
containers:
- name: memory-demo-ctr
image: polinux/stress
resources:
limits:
memory: 300Mi
command: ["stress"]
args: ["--vm", "1", "--vm-bytes", "500M", "--vm-hang", "1"]테스트 결과
해당 pod을 describe해보면 State가 Terminated로 바뀌었고, Reason이 OOMkilled라고 명시되어있다.
OOM killer 역시 동작했다.