개요
k8s에서 resource(CPU, Memory, HugePages, Ephemeral storage)를 제어하는 방법에 대하여 알아보겠습니다.
그중 특히 CPU에 대한 것을 집중 적으로 알아보겠습니다.
k8s에서의 resource 분류
Compressible resources
cpu
Incompressible resources
Memory, HugePages, Ephemeral storage
Compressible vs Incompressible resources
두 분류의 구분 기준?
"resource 사용을 제한했을 때 pcoress를 실행 할 수 있는가"로 구분합니다.
CPU의 경우 resource 사용을 제한했을 때 pcoress를 실행 할 수 있습니다.
Memory, HugePages, Ephemeral storage는 resource 사용을 제한 했을 때 실행이 불가능합니다.
이러한 이유로 CPU는 Compresiible(압축가능)한 자원이고 Memory, HugePages, Ephemeral storage는 압축 불가능한 resource입니다.
예를 들어 CPU 사용률이 100%인 경우, CPU를 필요로 하는 Process는 CPU 시간을 받을 때까지 기다려야 합니다.
Memory가 부족한 상황에서 새로운 Process에게 메모리를 할당하려면, 이미 Memory를 사용 중인 Process를 종료해야 하거나 해당 Process가 종료됩니다.
지금부터 k8s에서 사용되는 CPU resource관리에 대하여 좀 더 집중적으로 알아보겠습니다.
참고로 k8s에서는 CFS를 사용하여 CPU관리를 합니다. 그래서 CFS에 대하여 집중적으로 알아본 후 k8s에서 어떻게 사용되는지 알아보겠습니다.
CPU resouce 관리
CPU자원 할당
CPU의 경우 CFS 를 사용하여 CPU 사용량을 할당합니다.
그래서 Linux에서 CPU 사용량을 관리하는 방법인 CFS부터 알아보겠습니다.
CFS란?
CPU를 공평하게 할당하기 위해서 사용하는 CFS(Completely Fair Scheduler)라는 scheduller입니다.
CFS에서 CPU를 할당하는 방법
CFS는 "cgroup의 cpu.shares"라는 파일을 사용하여 cpu를 공평하게 할당을 합니다.
"cgroup의 cpu.shares"를 간략히 설명하면 Linux Kernel의 CPU schedulling의 기능으로, 각 Process 또는 Process Group이 사용할 수 있는 CPU 자원의 할당량을 상대적으로 설정할 수 있게 합니다.
예를 들어 cpu.shares의 기본 값은 1024입니다. 그런데 2048 값으로 설정하게 되면 상대적으로 1024 값을 가진 cgroup에 비해서 2배로 cpu 사용량을 더 할 당 받습니다.
이러한 원리를 사용하여 CFS는 상대적인 가중치 값을 사용하여 CPU 사용량을 process 또는 process group에 할당합니다.
시스템의 cpu.shares 파일의 위치는 아래와 같습니다.
위치 : /sys/fs/cgroup/cpu, cpuacct/cpu.shares
값 : 1024
CFS는 동작 방식
CFS는 내부적으로 Red-Black Tree를 사용하는데 Tree 특성상 스스로 balance를 조정합니다. 즉, 이 Tree의 모든 경로는 다른 경로보다 두 배 이상 길어지지 않는다 입니다. 그리고 가장 작은 값이 항상 Tree의 맨 왼쪽 leaf node에 온다는 것입니다.
그래서 CPU를 상대적으로 많이 사용하는 process(즉, cpu.shares가 높은 process)를 우선순위를 조정하는 값인 vruntime 낮게 설정하여하여 맨 왼쪽 leaf에 오게 하면 CFS는 트리의 맨 왼쪽 leaf에 있는 node에 cpu를 할당하기 때문에 cpu.shares가 높은 process 가 상대적으로 CPU를 더 많이 할당받을 수 있습니다.
CPU resource 사용 제한 방법
CFS Bandwidth Control를 사용하여 CPU 사용량을 제한합니다.
CFS Bandwidth Control를 사용한 cpu 제한 원리
CFS Bandwidth Control는 group의 cpu.cfs_quota_us (할당량)와 cpu.cfs_period_us (기간) 응 사용하여 container의 cpu사용량을 제한합니다.
원리는 process의 cpu사용을 cpu.cfs_period_us 동안 cpu.cfs_quota_us 만큼만 사용할 수 있게 하는 방식입니다.
process는 이 지정된 기간 동안 할당된 CPU 다 사용하면 다음 기간까지 CPU사용이 제한(throttled)이 됩니다.
cgroup에 적용된 period와 quota 값이 얼마이고 얼마만큼 throttled 되었는지는 /sys/fs/cgroup/cpu,cpuacct/ 에서 다음 3개의 파일로 확인 가능 합니다.
cpu 제한 관련 : cpu.cfs_period_us와 cpu.cfs_quota_us
* 단위설명
us : 마이크로 초 (μs로 표기)는 백만 분의 1초
ms : 밀리 초 (ms 또는 msec으로 표기)는 천 분의 1초
100,000us == 100 ms == 0.1s
단위 계산 사이트
파일 | 제어 항목 | 단위 | 기본 값 | 설명 |
cpu.cfs_period_us | period | us | 100,000 | cpu 할당 기간 200,000ns : 0.2초 동안 cpu를 할당 1,000,000ns : 1초 동안 cpu를 할당 ( 최대 값 ) |
cpu.cfs_quota_us | quota | us | -1 | cfs_period_us 에서의 CPU 실행 시간 제한을 의미 -1 : 제한 없음 |
cpu 제한 관련 통계 관련 : cpu.stat
파일 | 항목 | 단위 | 설명 |
cpu.stat |
nr_periods | 횟수 |
cpu.cfs_period_us에서 지정한 기간 간격으로 실행된 횟수입니다.
즉 CFS가 얼마나 많이 CPU를 할당 했는지 알 수 있습니다. |
nr_throttled | 횟수 | cgroup 내 작업이 쓰로틀링된 횟수를 나타냅니다 즉, 할당된 할당량에 명시된 대로 사용 가능한 모든 시간을 소진하여 실행할 수 없는 경우 = 시간이 부족해서 실행 처리를 다 못한 횟수 |
|
throttled_time | ns | cgroup내에서 개별 thread가 throttled 된 총 시간 |
예제
single cgroup 에서의 CFS Bandwidth Control 동작
1 CPU + "실행시간(runtime)이 200ms(0.2초)인 process"
cpu.cfs_period_us : 100,000us (=100 ms) (=0.1초)
cpu.cfs_quota_us : - 1 (제한 없음)
기본 CPU 할당 주기가 0.1초상황에서 process에 2번의 cpu할당이 발생하고, 오직 process가 1개 일경우 아래처럼 동작 할 것입니다.
1 CPU + "실행시간(runtime)이 200ms인 process" + quota
cpu.cfs_period_us : 100,000us (100 ms == 0.1초)
cpu.cfs_quota_us : 40,000us ( 40 ms == 0.04초)
결국, 200ms process는 quota 기능 때문에 기존 200ms가 지나면 끝날수 있는 게 440ms까지 가야 끝납니다.
multi cgroup CFS Bandwidth Control 동작
multi cgroup 에서는 cpu 자원을 cgroup들이 공유해야 합니다.
이때 공유 비율을 정하는 것이 cpu.shares (기본 값 1024)입니다.
예를 들어 2개의 cgroup이 아래와 같이 존재할 때
그룹 | 값 |
cgroup1 | 512 |
cgroup2 | 512 |
이 경우 cgroup1과 cgroup2는 동일한 cpu 시간을 할당받습니다.
그러나 아래와 같다면
그룹 | 값 |
cgroup1 | 1024 |
cgroup2 | 512 |
이 경우 cgroup1이 cgroup2보다 2배의 cpu 할당 시간을 받습니다.
k8s과 Resource 제어와 CFS관계
K8s의 Resource 제어
최소 필요한 resource 설정 (requests)
Pod 실행을 위해 필요한 최소 resource를 설정합니다.
최대 resource 사용량 제한 (limits)
Pod 사용할 수 있는 최대의 resource 사용량 설정 합니다.
설정 방식
Pod에 resources 항목을 통하여 resource 용량을 제어합니다.
설정 값 설명
resources 항목 | 상세 설명 |
requests | Pod 실행을 위해 필요한 최소 resource를 설정 합니다. node에 request 만큼의 여유 resource가 있어야 scheduling을 하겠다는 의미입니다. |
limits | Pod 사용할 수 있는 최대의 resource 사용량 설정 합니다. pod의 resource 사용량을 limit까지만 허가 하겠다. |
resource 단위
기본 단위
상세 단위
1000m (milicores) = 1 core = 1 vCPU = 1 AWS vCPU = 1 GCP Core.
500m (milicores) = 0.5 core = 0.5 vCPU = 0.5 AWS vCPU = 0.5 GCP Core.
100m (milicores) = 0.1 core = 0.1 vCPU = 0.1 AWS vCPU = 0.1 GCP Core.
k8s과 Resource 제어와 CFS관계
k8s에서는 CFS의 아래 2개의 파일을 사용하여 CPU 사용량을 관리합니다.
cgroup 파일 | 단위 | k8s에서의 용도 |
cpu.shares | 없음 |
아래의 cpu 필드 값에 1024를 곱한 값이 cpu.shares의 값으로 사용됩니다.
1 == 1000m (milicores)과 같습니다. resources: requests:
cpu: "1"
|
cpu.cfs_quota_us |
us
1,000,000 분의 1초. |
아래의 cpu 필드 값에 100를 곱한 값이 cpu.cfs_quota_us의 값으로 사용됩니다.
1 == 1000m (milicores) 과 같습니다. resources: limits:
cpu: "1"
|
k8s resource -> cfs로 단위 변환
k8s에서 resource 기본 단위
1000m (milicores) = 1 core = 1 vCPU = 1 AWS vCPU = 1 GCP Core.
500m (milicores) = 0.5 core = 0.5 vCPU = 0.5 AWS vCPU = 0.5 GCP Core.
100m (milicores) = 0.1 core = 0.1 vCPU = 0.1 AWS vCPU = 0.1 GCP Core.
k8s에서 request cpu와 cpu.shares의 관계
cpu.shares = vCPU * 1024
k8s 에서 limit cpu 와 cpu.cfs_quota_us의 관계
cpu.cfs_quota_us = vCPU * 100
검증
1. kubectl describe pod <pod_name>으로 Container ID 찾기
Container ID: containerd://c15a4584d72fb7c67dd1df1378f8e6d8fcaf9fec1e7e52f75f1f8df2ff6e5c8c
2. kubectl get pod -o wide로 배포된 node 찾기
3. /sys/fs/cgroup/cpu,cpuacct/system.slice/containerd.service 에서 container ID를 가진 directory로 들어가기
4. cpu.shares 와 cpu.cfs_quota_us 값이 시나리오 대로 변경되었는지 확인
결론
쉽게 결론을 내리면 cpu 값이 1이면 1core사용하겠다. "500m"이면 0.5 core를 사용하겠다.
Node별 자원 확인
node의 전체 자원 보는 법
root@master:~# kubectl get node -o json | jq '.items[].status.capacity'
{
"cpu": "4",
"ephemeral-storage": "19947120Ki",
"hugepages-2Mi": "0",
"memory": "4017752Ki",
"pods": "110"
}
{
"cpu": "4",
"ephemeral-storage": "19947120Ki",
"hugepages-2Mi": "0",
"memory": "4017752Ki",
"pods": "110"
}
{
"cpu": "4",
"ephemeral-storage": "19947120Ki",
"hugepages-2Mi": "0",
"memory": "4017752Ki",
"pods": "110"
}
node에서 사용가능한 자원 계산 방법.
https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
사용가능한 자원 = node 총 자원 - "kube-reserved" + "system-reserved" + "evictino-threshold"
kube-reserved
kubelet, 컨테이너 런타임, 노드 문제 감지기 등과 같은 kubernetes 시스템 데몬에 대한 리소스 예약을 캡처하기 위한 자원
system-reserved
sshd, udev 등과 같은 OS 시스템 데몬에 대한 리소스 예약을 캡처하기 위한 자원
eviction-threshold
노드가 중요한 리소스를 고갈시키지 않도록 보장하는 데 사용되는 자원
node에 사용 가능한 자원 정보
할당 가능한 각 자원은 Kubernetes 스케줄러가 스케줄링 결정에 사용하는 벡터입니다.
root@master:~# kubectl get node -ojson | jq '.items[].status.allocatable'
{
"cpu": "4",
"ephemeral-storage": "18383265762",
"hugepages-2Mi": "0",
"memory": "3915352Ki",
"pods": "110"
}
{
"cpu": "4",
"ephemeral-storage": "18383265762",
"hugepages-2Mi": "0",
"memory": "3915352Ki",
"pods": "110"
}
{
"cpu": "4",
"ephemeral-storage": "18383265762",
"hugepages-2Mi": "0",
"memory": "3915352Ki",
"pods": "110"
}
Node에 Resource 부족시 해결 방법
QoS class
쿠버네티스에서는 Resource가 부족할 때 QoS Class에 따라 어떤 Pod의 Container를 Kill 할지 정합니다. kill되는 순서는 다음과 같습니다. 참고로 QoS Class는 따로 선택하는 것이 아니라 k8s에서 자동으로 지정합니다.
BestEffort -> Burstable -> Guranteed 순으로 kill 됩니다.
Guranteed는 시스템이 메모리를 필요로 하는 경우에만 kill됩니다. 만일 동일한 QoS의 경우 OutOfMemory Score에 따라 어떤 프로세스를 kill 할지 비교하여 정합니다.
(상세) https://kubernetes.io/docs/tasks/configure-pod-container/quality-service-pod/
- BestEffort
- Pod의 모든 container는 CPU와 Memory에 대한 명확한 요청이나 제한을 가지고 있지 않습니다.
- 이 QoS 클래스는 리소스 보장이 없습니다.
- 이러한 Pod는 시스템 리소스가 부족할 때 가장 먼저 추방됩니다.
- Burstable
- Pod의 하나 이상의 container는 CPU와 Memory에 대해 Request와Limit 을 정의합니다.
Request와 Limit 값들은 반드시 일치할 필요는 없습니다.
그러나 Request <= Limit 관계여야 합니다. - 일정 수준의 리소스 보장을 제공하지만, 리소스 사용이 요청을 초과할 수 있습니다.
- 이 클래스의 Pod는 Guaranteed보다 먼저 추방될 수 있지만, BestEffort보다는 나중에 추방됩니다.
- Pod의 하나 이상의 container는 CPU와 Memory에 대해 Request와Limit 을 정의합니다.
- Guaranteed
- Pod의 모든 container는 명확한 CPU와 Memory에대한 Request와Limit 을 정의합니다.
이 값들은 동일해야 합니다.
- Pod의 모든 container는 명확한 CPU와 Memory에대한 Request와Limit 을 정의합니다.
QoS Class예제
BestEffort
apiVersion: v1
kind: Pod
metadata:
name: qos-demo-3
namespace: qos-example
spec:
containers:
- name: qos-demo-3-ctr
image: nginx
Burstable
apiVersion: v1
kind: Pod
metadata:
name: qos-demo-2
namespace: qos-example
spec:
containers:
- name: qos-demo-2-ctr
image: nginx
resources:
limits:
memory: "200Mi"
requests:
memory: "100Mi"
Guaranteed
apiVersion: v1
kind: Pod
metadata:
name: qos-demo
namespace: qos-example
spec:
containers:
- name: qos-demo-ctr
image: nginx
resources:
limits:
memory: "200Mi"
cpu: "700m"
requests:
memory: "200Mi"
cpu: "700m"
QoS Class확인 방법
kubectl describe pod <pod-name> 실행시 QoS Class 확인
참조
https://stackoverflow.com/questions/53255956/what-is-the-meaning-of-cpu-and-core-in-kubernet
https://dataonair.or.kr/db-tech-reference/d-lounge/technical-data/?mod=document&uid=236896
https://directeam.io/blog/kubernetes-resources-under-the-hood-part-1/
https://blog.outsider.ne.kr/1653
https://devbull.xyz/cgroups-cfs-scheduler-mesos-1
https://devbull.xyz/cgroups-cfs-scheduler-mesos-2/
https://medium.com/indeed-engineering/unthrottled-fixing-cpu-limits-in-the-cloud-a0995ede8e89
정리
K8s에서의 Pod에 대한 Resource 제어는 크게 2가지로 이루어집니다.
1. Request(최소 필요 resource 정의)
2. Limits (최대 사용 resource 정의)
그리고 특히 CPU 제어를 하기 위해서 cgroup의 2개의 파일을 사용합니다.
1. Request는 cpu.shares를 사용하여 CPU할당량을 제어 합니다.
2. Limits는 cpu.cfs_quota_us를 사용하여 CPU의 사용량을 제어합니다.
그리고 이 두 개의 파일은 CFS에서 최종적으로 사용되어 Resource에 대한 제어가 이루어지게 됩니다.
나머지 Memory제어 부분은 정의된 만 큼 단순하게 제어가 이루어집니다.
Next Post
'Cloud > k8s-CKA' 카테고리의 다른 글
[CKA] 11. DaemonSet (0) | 2023.03.04 |
---|---|
[CKA] 10. Namespace resource 제어 방법 (0) | 2023.03.04 |
[CKA] 8. schedule (Node Selector, Node Affinity) (0) | 2023.02.11 |
[CKA] 7. schedule (nodeName, taint, toleration) (0) | 2023.02.02 |
[CKA] 6. Object를 관리하는 방법 3가지 (0) | 2023.01.29 |