Kubernetes의 resource는 압축이 가능한 CPU resource와 압축이 불가능한 memory, storage(nodefs, imgefs)로 나누어진다. nodefs는 kubelet이 volume, daemon log등을 위해 사용하는 공간이고, imagefs는 container image 저장이나 container의 writable layer를 위한 공간이다. 압축이 가능한 자원의 경우, 자원이 부족한 상황이거나 contention이 발생한 상황이어도 성능이 떨어질 뿐 process가 죽진 않는다. 하지만 압축이 불가능한 자원의 경우, 자원이 부족하면 해당 node에 존재하는 process가 정상 동작하지 않을 수 있다. 그러한 상황은 pod들이 request보다 resource를 더 쓸 때와 host의 process가 resource를 많이 사용할 때 발생할 수 있다.
Kubernetes는 운영하는 node의 resource 부족 현상을 해결하고자 Eviction Threshold를 제공한다. 각 resource에 대해 사용자가 지정한 threshold를 넘게될 경우, kubelet이 해당 resource를 확보하는 작업을 진행한다.
eviction threshold는 [eviction-signal][operator][quantity] 형식으로 표현된다. eviction-signal은 memory.available, (nodefs|imagefs).(available|inodesFree)가 있고, operator로는 비교문, quantity는 일정 크기 또는 %로 표현된다. 예를 들면 memory.available<10% 이다. 또한 eviction threshold는 soft와 hard로 나누어진다. soft eviction threshold는 pod을 eviction할 때 graceful period를 기다리지만 hard eviction은 그렇지 않다. hard eviction threshold는 default가 존재하며 그 값은 아래와 같다.
memory.available<100Mi
nodefs.available<10%
nodefs.inodesFree<5%
imagefs.available<15%
node에서 resource 사용률이 설정된 eviction threshold에 도달할 경우 kubelet은 해당 signal이 사라질 때까지 process를 reclaim하여 resource를 확보한다. kubelet은 먼저 node level의 resource를 reclaim하고, 그 다음 end user의 pod을 eviction한다. imagefs가 설정되어 있지 않은 cluster의 경우 nodefs의 eviction threshold에 도달할 때 nodefs와 imagefs에 대한 reclaim을 같이 진행한다. node level의 resource를 reclaim하는 과정은 아래와 같다.
부족한 자원 | kubelet action |
nodesfs | dead pod들과 그 pod에 속한 container들을 삭제함 |
imagefs | 사용되지 않는 image들을 삭제함 |
end-user의 pod을 eviction하는 과정은 QoS와 priority에 기반하여 memory를 확보하는 것도 포함되며 아래와 같다.
부족한 자원 | kubelet action |
memory |
1) request를 초과하여 resource를 사용하는 BestEffort 또는 Burstable pod을 우선적으로 eviction함 2) usage가 request보다 아래인 Guaranteed pod과 Burstable pod은 가장 마지막에 eviction함 3) 만약 모두 같은 case라면, 낮은 priority를 가진 pod부터 eviction함 |
nodesfs | local volume과 log 크기가 큰 pod 순서로 삭제함 |
imagefs | writable layer 사용 크기가 큰 pod 순서로 삭제함 |
eviction이 발생하여도 아주 적은 양의 resource만 reclaim되서 짧은 시간안에 eviction threshold에 다시 도달하는 현상이 반복될 수 있다. 특히 disk의 경우 eviction하는데 시간이 오래걸리기 때문에 그러한 현상이 더 잘 발생할 수 있다. 이를 해결하고자 최소 reclaim크기를 정하는 minimum-reclaim 설정을 제공한다. 아래는 예시이다.
--eviction-hard=memory.available<500Mi,nodefs.available<1Gi,imagefs.available<100Gi
--eviction-minimum-reclaim="memory.available=0Mi,nodefs.available=500Mi,imagefs.available=2Gi"`
위와 같은 설정일 때 eviction이 발생하면 memory는 500mi, nodefs는 1.5gi, imagefs는 102gi까지 확보한다. minimum-reclaim의 경우 모든 자원에 대해 기본적으로 0의 값을 가진다.
kubelet은 node의 resource 상황을 표현하기 위해, 하나 이상의 eviction signal을 node에 매핑하는 Node Condition을 제공한다. node가 memory.available threshold에 도달하면 node condition은 MemoryPressure로, (nodefs|imagefs).(available|inodesFree) threshold에 도달하면 DiskPressure가 된다. node condition은 node-status-update-frequency에 명시된 값의 주기에 따라 update되며 default는 10초이다. MemoryPressure일 땐 해당 node에 BestEffort pod이 스케줄링되지 않고, DiskPressure일 땐 모든 pod이 스케줄링되는 것을 막는다. 만약 resource가 soft eviction threshold를 기준으로 grace period를 넘지 않는 선에서 위 아래로 변동하면 node condition 또한 자주 변동될 수 있다. 이는 scheduling decision 성능에 영향을 미칠 수 있어, eviction-pressure-transition-period를 통해 pressure condition 변경 유예 시간을 설정할 수 있다.
kubelet이 memory를 reclaim하기 전에 OOM(out of memory) event가 발생하다면, OOM killer가 가장 높은 oom_score를 가진 pod을 죽일 수 있다. oom_score는 process가 node에서 memory를 사용하는 비율과 해당 process에 설정된 oom_score_adj 값을 더해서 계산된다. request에 비해 가장 많은 양의 메모리를 소비하고 QOS가 가장 낮은 container가 가장 먼저 죽도록 설정된다. kubelet은 pod의 QOS에 따라 각 container에 oom_score_adj값을 아래와 같이 설정한다.
Quality of Service | oom_score_adj |
Guaranteed | -998 |
BestEffort | 1000 |
Burstable | min(max(2, 1000 - (1000 * memoryRequestBytes) / machineMemoryCapacityBytes), 999) |
OOM event 발생으로 인해 process가 죽는 것을 방지하기 위해 eviction threshold를 이용할 수 있다. memory에 대한 eviction threshold값을 설정함으로 memory가 부족하기 전에 미리 pod을 죽임으로써 해당 노드의 memory를 확보할 수 있다. 그 외에도 kubernetes는 system daemon을 위한 memory나 cpu를 미리 할당할 수 있도록 하는 Node Allocatable 기능을 제공한다. 아래의 그림은 kubernetes cluster에서 한 node에서 resource를 차지하는 그룹들이다.
kube-reserved는 kubelet, container runtime, node problem detector 와 같은 kubernetes system daemon을 위한 공간이다. kube system daemon들에 대한 충분한 모니터링을 통해 usage heuristic에 기반한 사용을 권장하고 있다.
system-reserved는 sshd udev와 같은 OS system daemon을 위한 공간이다. 이 공간이 잡혀있는 경우, resource가 부족하여 CPU starved나 OOM killer 작동이나 fork를 할 수 없는 상황이 발생할 수 있으므로 process가 oom_killed되어도 복구할 수 있는 능력을 보장할 수 있거나 node의 exhausitive를 정확히 측정하였을 때 사용하는 것을 권장한다.
eviction-threshold는 위에서 설명했듯이, uncompressible한 resource의 부족으로 인한 node의 불안정성을 피하기 위해 제공되는 기능이다. 이 영역은 pod들이 사용할 수 없다.
그 외에 reserved-cpus라는 것은 위의 두 daemon들을 위한 cpu 공간이다. 1.17버전부터 추가되었으며, Telco/NFV와 같이 interrupt/timers가 성능에 영향을 미치는 case를 위해 설계되었다.
아래는 운영하는 클러스터의 예시이다.
요구조건
1) Node memory capacity: 10Gi
2) system daemon을 위해 10% memory 예약
3) OOM 발생 빈도를 줄이기 위해 pod에게 95% memory utilization 제공
결과
1) --eviction-hard=memory.available<500Mi
2) --system-reserved=memory=1.5Gi
system-reserved의 경우 eviction threshold의 크기까지 포함해야 해서 1Gi + 10Gi x 5% 인 1.5Gi로 설정되어야 한다.
마지막으로 pod priority와 eviction-threshold 이전에 도입되어 상당수 deprecation된 garbage-collection 부분은 다음 포스트에서 작성하도록 하겠다.
Reference
1) https://kubernetes.io/docs/tasks/administer-cluster/out-of-resource/
2) https://kubernetes.io/docs/tasks/administer-cluster/reserve-compute-resources/#