최근 노드 memory 사용률이 높아지면서 노드가 NotReady
로 바뀌는 현상이 종종 발견되어, 노드 memory에 대해 자세히 알아보려 합니다.
노드의 Allocatable 용량과 cgroup, OOM killer 등에 대한 내용이 포함되어 있습니다.
노드의 Allocatable 용량 계산 방식(memory에 중점)
kubernetes 노드에 파드를 배치할 때 기본적으로 실행되는 system 데몬들과 kubelet을 위한 자원을 고려합니다.
또한, 노드의 memory가 부족할 때 전체 노드에 영향을 주지 않도록 evictionHard
를 통해 최소로 필요한 가용 memory 용량을 설정합니다.
Allocatable 용량에서 필수 자원을 제외하여, 해당 자원을 제외하고 파드를 노드에 스케쥴링할 수 있도록 합니다.
예약 자원은 kubelet 설정 중 아래 항목들을 통해 설정할 수 있습니다.
- kubeReserved : kubelet, container runtime(containerd) 등의 영역
- systemReserved : OS system daemons 영역(sshd, udev 등)
- evictionHard : 노드의 memory pressure 여부를 판단하는 threshold
노드의 capacity와 kubelet 설정을 기반으로 계산한 Allocatable 용량이 실제 노드의 Allocatable 용량과 일치하는지 확인해보겠습니다.
노드 capacity
capacity:
cpu: "32"
ephemeral-storage: 104836076Ki
hugepages-1Gi: "0"
hugepages-2Mi: "0"
hugepages-32Mi: "0"
hugepages-64Ki: "0"
memory: 260790784Ki
pods: "234"
vpc.amazonaws.com/pod-eni: "54"
노드 kubelet 설정
--max-pods=234
--system-reserved=cpu=100m,ephemeral-storage=1Gi,memory=100Mi
--kube-reserved=cpu=200m,ephemeral-storage=3Gi,memory=100Mi
--eviction-hard=nodefs.inodesFree<10%,memory.available<256Mi,nodefs.available<10%
--eviction-soft=memory.available<500Mi,nodefs.available<15%,nodefs.inodesFree<15%
--eviction-soft-grace-period=nodefs.available=1m30s,nodefs.inodesFree=2m0s,memory.available=1m0s
--eviction-max-pod-grace-period=60
Allocatable 용량 계산
- cpu
= 노드 cpu capacity - kubeReserved cpu - systemReserved cpu
= (32 * 1000) - 100m - 200m
= 31,700m - memory
= 노드 memory capacity - kubeReserved memory - systemReserved memory - evictionHard memory.available
= 260,790,784Ki - (100Mi + 100Mi + 256Mi) * 1024
= 260,323,840Ki
실제 노드의 Allocatable 용량
allocatable:
cpu: 31700m
ephemeral-storage: "92321960186"
hugepages-1Gi: "0"
hugepages-2Mi: "0"
hugepages-32Mi: "0"
hugepages-64Ki: "0"
memory: 260323840Ki
pods: "234"
vpc.amazonaws.com/pod-eni: "54"
계산 결과와 실제 노드의 Allocatable 용량이 일치하는 것을 확인할 수 있습니다.
참고로, EKS를 사용하는 경우에 별도로 kubelet 설정을 변경하지 않았다면, /etc/eks/bootstrap.sh
의 아래 계산 방식을 통해 cpu, mem reserved 용량이 계산됩니다.
# Calculates the amount of memory to reserve for kubeReserved in mebibytes. KubeReserved is a function of pod
# density so we are calculating the amount of memory to reserve for Kubernetes systems daemons by
# considering the maximum number of pods this instance type supports.
# Args:
# $1 the max number of pods per instance type (MAX_PODS) based on values from /etc/eks/eni-max-pods.txt
# Return:
# memory to reserve in Mi for the kubelet
get_memory_mebibytes_to_reserve() {
local max_num_pods=$1
memory_to_reserve=$((11 * $max_num_pods + 255))
echo $memory_to_reserve
}
...
# Calculates the amount of CPU to reserve for kubeReserved in millicores from the total number of vCPUs available on the instance.
# From the total core capacity of this worker node, we calculate the CPU resources to reserve by reserving a percentage
# of the available cores in each range up to the total number of cores available on the instance.
# We are using these CPU ranges from GKE (https://cloud.google.com/kubernetes-engine/docs/concepts/cluster-architecture#node_allocatable):
# 6% of the first core
# 1% of the next core (up to 2 cores)
# 0.5% of the next 2 cores (up to 4 cores)
# 0.25% of any cores above 4 cores
# Return:
# CPU resources to reserve in millicores (m)
get_cpu_millicores_to_reserve() {
local total_cpu_on_instance=$(($(nproc) * 1000))
local cpu_ranges=(0 1000 2000 4000 $total_cpu_on_instance)
local cpu_percentage_reserved_for_ranges=(600 100 50 25)
cpu_to_reserve="0"
for i in "${!cpu_percentage_reserved_for_ranges[@]}"; do
local start_range=${cpu_ranges[$i]}
local end_range=${cpu_ranges[(($i + 1))]}
local percentage_to_reserve_for_range=${cpu_percentage_reserved_for_ranges[$i]}
cpu_to_reserve=$(($cpu_to_reserve + $(get_resource_to_reserve_in_range $total_cpu_on_instance $start_range $end_range $percentage_to_reserve_for_range)))
memory는 EC2의 max_pod 수가 고려되고, cpu는 코어 수에 따라 계산됩니다.
계산 결과는 /etc/kubernetes/kubelet/kubelet-config.json
파일에서 확인할 수 있습니다.
Memory cgroup
이제 노드의 kubeReserved
, systemReserved
영역이 어떻게 보호되는지 cgroup과 함께 확인해보겠습니다.
cgroup의 특징을 간단하게 알아보면 아래와 같습니다.
- cgroup은 계층 구조로 상위 cgroup(parent)이 하위 cgroup을 제어한다.
- cgroup을 통해 프로세스에 대해 cpu, memory 등의 사용량 limit을 설정할 수 있다.
먼저, 노드에서 파드의 cgroup이 어떻게 설정되어 있는지 알아보겠습니다.
cgroup - cpu
$ pwd
/sys/fs/cgroup/cpu
$ ls
cgroup.clone_children cpuacct.usage cpuacct.usage_percpu_user cpu.cfs_quota_us cpu.stat runtime.slice
cgroup.procs cpuacct.usage_all cpuacct.usage_sys cpu.rt_period_us kubepods.slice system.slice
cgroup.sane_behavior cpuacct.usage_percpu cpuacct.usage_user cpu.rt_runtime_us notify_on_release tasks
cpuacct.stat cpuacct.usage_percpu_sys cpu.cfs_period_us cpu.shares release_agent user.slice
$ ls kubepods.slice/
cgroup.clone_children cpuacct.usage_user kubepods-pod0692697e_c83f_47da_8100_f9cc2af3c798.slice
cgroup.procs cpu.cfs_period_us kubepods-pod1ce331e6_f516_4ca9_a60c_6b83ba3c76fe.slice
cpuacct.stat cpu.cfs_quota_us kubepods-pod9b2f5029_1f16_45d4_96ba_351b0664c90c.slice
cpuacct.usage cpu.rt_period_us kubepods-pod9ee49265_1546_4557_bf81_02e44b6e763c.slice
cpuacct.usage_all cpu.rt_runtime_us kubepods-podcddc20b6_a954_4b56_9af7_4207f559813c.slice
cpuacct.usage_percpu cpu.shares notify_on_release
cpuacct.usage_percpu_sys cpu.stat tasks
cpuacct.usage_percpu_user kubepods-besteffort.slice
cpuacct.usage_sys kubepods-burstable.slice
$ tree
.
├── cgroup.clone_children
├── cgroup.procs
├── cgroup.sane_behavior
├── cpuacct.stat
├── cpuacct.usage
├── cpuacct.usage_all
├── cpuacct.usage_percpu
├── cpuacct.usage_percpu_sys
...
├── kubepods.slice
│ ├── cgroup.clone_children
│ ├── cgroup.procs
...
│ ├── cpu.cfs_period_us
│ ├── cpu.cfs_quota_us
│ ├── cpu.rt_period_us
│ ├── cpu.rt_runtime_us
│ ├── cpu.shares
│ ├── cpu.stat
│ ├── kubepods-besteffort.slice
│ │ ├── cgroup.clone_children
│ │ ├── cgroup.procs
│ │ ├── cpuacct.stat
│ │ ├── cpuacct.usage
...
│ │ ├── kubepods-besteffort-poda6cf7f25_76c2_45c5_8a70_7037983c98e3.slice
│ │ │ ├── cgroup.clone_children
│ │ │ ├── cgroup.procs
│ │ │ ├── cpuacct.stat
│ │ │ ├── cpuacct.usage
...
│ │ │ ├── cri-containerd-85c25d8435200b8eea26555b44997906465b38e3d5e1c3abd8c663328b9ee518.scope
│ │ │ │ ├── cgroup.clone_children
│ │ │ │ ├── cgroup.procs
│ │ │ │ ├── cpuacct.stat
│ │ │ │ ├── cpuacct.usage
│ │ │ │ ├── cpuacct.usage_all
│ │ │ │ ├── cpuacct.usage_percpu
│ │ │ │ ├── cpuacct.usage_percpu_sys
...
│ │ │ ├── cri-containerd-cb80932f676a879e2bb295b0ccf438e20f739a1f87012dbb975bc2ddcdc256bb.scope
...
cgroup - memory
$ pwd
/sys/fs/cgroup/memory
$ ls
cgroup.clone_children memory.kmem.limit_in_bytes memory.limit_in_bytes memory.oom_control release_agent
cgroup.event_control memory.kmem.max_usage_in_bytes memory.max_usage_in_bytes memory.pressure_level runtime.slice
cgroup.procs memory.kmem.slabinfo memory.memsw.failcnt memory.soft_limit_in_bytes system.slice
cgroup.sane_behavior memory.kmem.tcp.failcnt memory.memsw.limit_in_bytes memory.stat tasks
kubepods.slice memory.kmem.tcp.limit_in_bytes memory.memsw.max_usage_in_bytes memory.swappiness user.slice
memory.failcnt memory.kmem.tcp.max_usage_in_bytes memory.memsw.usage_in_bytes memory.usage_in_bytes
memory.force_empty memory.kmem.tcp.usage_in_bytes memory.move_charge_at_immigrate memory.use_hierarchy
memory.kmem.failcnt memory.kmem.usage_in_bytes memory.numa_stat notify_on_release
$ ls kubepods.slice/
cgroup.clone_children memory.failcnt memory.kmem.usage_in_bytes memory.pressure_level
cgroup.event_control memory.force_empty memory.limit_in_bytes memory.soft_limit_in_bytes
cgroup.procs memory.kmem.failcnt memory.max_usage_in_bytes memory.stat
kubepods-besteffort.slice memory.kmem.limit_in_bytes memory.memsw.failcnt memory.swappiness
kubepods-burstable.slice memory.kmem.max_usage_in_bytes memory.memsw.limit_in_bytes memory.usage_in_bytes
kubepods-pod0692697e_c83f_47da_8100_f9cc2af3c798.slice memory.kmem.slabinfo memory.memsw.max_usage_in_bytes memory.use_hierarchy
kubepods-pod1ce331e6_f516_4ca9_a60c_6b83ba3c76fe.slice memory.kmem.tcp.failcnt memory.memsw.usage_in_bytes notify_on_release
kubepods-pod9b2f5029_1f16_45d4_96ba_351b0664c90c.slice memory.kmem.tcp.limit_in_bytes memory.move_charge_at_immigrate tasks
kubepods-pod9ee49265_1546_4557_bf81_02e44b6e763c.slice memory.kmem.tcp.max_usage_in_bytes memory.numa_stat
kubepods-podcddc20b6_a954_4b56_9af7_4207f559813c.slice memory.kmem.tcp.usage_in_bytes memory.oom_control
$ tree
.
├── cgroup.clone_children
├── cgroup.event_control
├── cgroup.procs
├── cgroup.sane_behavior
├── kubepods.slice
│ ├── cgroup.clone_children
│ ├── cgroup.event_control
│ ├── cgroup.procs
│ ├── kubepods-besteffort.slice
│ │ ├── cgroup.clone_children
│ │ ├── cgroup.event_control
│ │ ├── cgroup.procs
│ │ ├── kubepods-besteffort-poda6cf7f25_76c2_45c5_8a70_7037983c98e3.slice
│ │ │ ├── cgroup.clone_children
│ │ │ ├── cgroup.event_control
│ │ │ ├── cgroup.procs
│ │ │ ├── cri-containerd-85c25d8435200b8eea26555b44997906465b38e3d5e1c3abd8c663328b9ee518.scope
│ │ │ │ ├── cgroup.clone_children
│ │ │ │ ├── cgroup.event_control
│ │ │ │ ├── cgroup.procs
│ │ │ │ ├── memory.failcnt
│ │ │ │ ├── memory.force_empty
│ │ │ │ ├── memory.kmem.failcnt
│ │ │ │ ├── memory.kmem.limit_in_bytes
│ │ │ │ ├── memory.kmem.max_usage_in_bytes
│ │ │ │ ├── memory.kmem.slabinfo
│ │ │ │ ├── memory.kmem.tcp.failcnt
│ │ │ │ ├── memory.kmem.tcp.limit_in_bytes
│ │ │ │ ├── memory.kmem.tcp.max_usage_in_bytes
│ │ │ │ ├── memory.kmem.tcp.usage_in_bytes
│ │ │ │ ├── memory.kmem.usage_in_bytes
│ │ │ │ ├── memory.limit_in_bytes
...
│ │ │ ├── cri-containerd-cb80932f676a879e2bb295b0ccf438e20f739a1f87012dbb975bc2ddcdc256bb.scope
│ │ │ │ ├── cgroup.clone_children
│ │ │ │ ├── cgroup.event_control
│ │ │ │ ├── cgroup.procs
...
cpu, memory에 대해 /sys/fs/cgroup/[cpu | memory]/kubepods.slice/[파드]/[컨테이너]
와 같은 path로 cgroup이 생성되어 있는 것을 확인할 수 있습니다.
cgroup은 계층적 구조이므로 상위 cgroup(kubepods.slice
)이 파드와 컨테이너의 cgroup을 제어합니다.
이제 kubepods.slice
의 cgroup에 설정된 값을 알아보겠습니다.
참고) EC2 r6g.8xlarge(32vCPU, 256GiB) 기준
- Allocatable 용량
allocatable:
cpu: 31700m
ephemeral-storage: "92321960186"
hugepages-1Gi: "0"
hugepages-2Mi: "0"
hugepages-32Mi: "0"
hugepages-64Ki: "0"
memory: 260323840Ki
pods: "234"
vpc.amazonaws.com/pod-eni: "54"
kubepods.slice cgroup - cpu
$ cat /sys/fs/cgroup/cpu/kubepods.slice/cpu.shares
32460
$ cat /sys/fs/cgroup/cpu/kubepods.slice/cpu.cfs_quota_us
-1
32,460 = 31700/1000*1024 = Allocatable cpu
kubepods.slice cgroup - memory
$ cat /sys/fs/cgroup/memory/kubepods.slice/memory.limit_in_bytes
266840047616
266,840,047,616 ≈ 260323840KiB * 1024 = 248GiB = Allocatable memory
cpu, memory cgroup 설정을 살펴보면, 각각 Allocatable 용량이 반영된 것을 알 수 있습니다.
따라서, cgroup을 통해서 kubeReserved
및 systemReserved
용량을 확보하고, 모든 파드들은 해당 limit값이 적용된다고 볼 수 있습니다.
추가적으로 kubeReserved
와 systemReserved
cgroup 설정에 대해 알아보겠습니다.
참고로, kubeReserved
와 systemReserved
cgroup 이름은 /etc/kubernetes/kubelet/kubelet-config.json
파일에서 확인할 수 있습니다.
"systemReservedCgroup": "/system",
"kubeReservedCgroup": "/runtime"
해당 설정이 없다면, 별도로 cgroup이 생성되지 않는 상황이라고 볼 수 있습니다.
참고로, Amazon Linux2 EKS Optimized AMI를 사용한다면, bootstrap.sh에서 해당 설정을 확인할 수 있습니다. (코드)
##allow --reserved-cpus options via kubelet arg directly. Disable default reserved cgroup option in such cases
if [[ "${USE_RESERVED_CGROUPS}" = true ]]; then
echo "$(jq '.systemReservedCgroup="/system"' "${KUBELET_CONFIG}")" > "${KUBELET_CONFIG}"
echo "$(jq '.kubeReservedCgroup="/runtime"' "${KUBELET_CONFIG}")" > "${KUBELET_CONFIG}"
fi
kubeReserved cgroup
$ cat /sys/fs/cgroup/cpu/runtime.slice/cpu.shares
1024
$ cat /sys/fs/cgroup/cpu/runtime.slice/cpu.cfs_quota_us
-1
$ cat /sys/fs/cgroup/memory/runtime.slice/memory.limit_in_bytes
9223372036854771712
systemReserved cgroup
$ cat /sys/fs/cgroup/cpu/system.slice/cpu.shares
1024
$ cat /sys/fs/cgroup/cpu/system.slice/cpu.cfs_quota_us
-1
$ cat /sys/fs/cgroup/memory/system.slice/memory.limit_in_bytes
9223372036854771712
kubeReserved
와 systemReserved
cgroup 값을 확인했을 때, kubelet에 설정한 kubeReserved
, systemReserved
자원과 연관이 없는 것을 볼 수 있습니다.
현재 사용 중인 환경(EKS, Karpenter, AL2 사용 중)에서는 별도로 해당 값을 cgroup에 반영하지 않는 것으로 추측할 수 있습니다.
Memory가 부족할 때 일어나는 일
지금까지 Kubernetes 노드의 memory에 관련하여 알아보았고, 이 내용을 바탕으로 노드 memory가 부족할 때 어떤 일이 발생하는지 알아보겠습니다.
기본적으로 memory가 부족할 때 아래 3개의 요소들이 동작합니다.
1) kubelet eviction manager
kubelet eviction manager는 kubelet의 구성 요소 중 하나로, evictionHard
및 evictionSoft
에 설정된 threshold를 기준으로 파드를 노드에서 내쫓습니다.
기본적으로 10초 마다 memory 사용량을 체크하며, cAdvisor로 부터 정보를 얻어옵니다.
polling 방식으로 동작하기 때문에 노드의 memory 사용량이 급격하게 올라가는 경우, MemoryPressure
상태를 빠르게 인지하지 못할 수 있습니다.
이 문제를 보완하기 위해 kernelMemcgNotification
옵션을 활용할 수 있습니다. 이 옵션을 사용하면 커널의 notification API를 통해 cgroup memory limit 초과 시 OOM 이벤트를 받아볼 수 있습니다. (아직 사용해보지는 않았습니다.)
2) memory cgroup OOM Killer
cgroup에 설정된 memory limit을 초과하면 해당 프로세스를 종료합니다.
파드에 설정한 memory limit이 cgroup에 limit으로 설정되기 때문에, 파드의 메모리 사용량이 limit을 초과하면 cgroup OOM Killer에 의해 컨테이너가 종료됩니다.
OS 메세지에서 아래와 같은 로그가 있다면, cgroup limit을 초과하여 프로세스(컨테이너)가 종료되었음을 의미합니다.
로그 예시)
[840215.541967] Memory cgroup out of memory: Killed process 670955 (reports-control) total-vm:5849876kB, anon-rss:260164kB, file-rss:58996kB, shmem-rss:0kB, UID:65532 pgtables:924kB oom_score_adj:996
[840286.272803] reports-control invoked oom-killer: gfp_mask=0xcc0(GFP_KERNEL), order=0, oom_score_adj=996
[840286.278802] oom_kill_process+0x24c/0x250
3) Kernel OOM Killer
Kernel OOM Killer는 시스템(OS)의 memory 사용률이 높아져 가용 메모리가 부족해지는 경우, 프로세스를 종료합니다.
가용 메모리가 /proc/sys/vm/min_free_kbytes 에 설정된 값보다 적어지면, Kernel OOM Killer가 동작하고, 이때 oom_score
를 고려하여 종료할 프로세스를 선별합니다. oom_score
는 프로세스의 메모리 사용량과 중요도를 기반으로 계산되며, 점수가 높은 프로세스가 종료 대상이 됩니다. oom_score
계산 시에는 oom_score_adj
값이 영향을 미칩니다.
oom_score
계산에 사용되는 oom_badness()
를 살펴보겠습니다.
/**
* oom_badness - heuristic function to determine which candidate task to kill
* @p: task struct of which task we should calculate
* @totalpages: total present RAM allowed for page allocation
*
* The heuristic for determining which task to kill is made to be as simple and
* predictable as possible. The goal is to return the highest value for the
* task consuming the most memory to avoid subsequent oom failures.
*/
long oom_badness(struct task_struct *p, unsigned long totalpages)
{
long points;
long adj;
if (oom_unkillable_task(p))
return LONG_MIN;
p = find_lock_task_mm(p);
if (!p)
return LONG_MIN;
/*
* Do not even consider tasks which are explicitly marked oom
* unkillable or have been already oom reaped or the are in
* the middle of vfork
*/
adj = (long)p->signal->oom_score_adj;
if (adj == OOM_SCORE_ADJ_MIN ||
test_bit(MMF_OOM_SKIP, &p->mm->flags) ||
in_vfork(p)) {
task_unlock(p);
return LONG_MIN;
}
/*
* The baseline for the badness score is the proportion of RAM that each
* task's rss, pagetable and swap space use.
*/
points = get_mm_rss(p->mm) + get_mm_counter(p->mm, MM_SWAPENTS) +
mm_pgtables_bytes(p->mm) / PAGE_SIZE;
task_unlock(p);
/* Normalize to oom_score_adj units */
adj *= totalpages / 1000;
points += adj;
return points;
}
메모리 사용량(RSS, 페이지 테이블 크기, swap 사용량 등)과 oom_score_adj
가 관여하며, oom_score_adj
가 낮을수록 종료 대상 우선순위가 낮아질 가능성이 있습니다.
oom_score_adj
범위는 -1000 ~ 1000 입니다.
참고로 kubelet에 의해 oom_score_adj
는 파드의 QoS에 따라 다르게 설정됩니다. (참고)
QoS | oom_score_adj |
Guaranteed | -997 |
BestEffort | 1000 |
Burstable | min(max(2, 1000 - (1000 × memoryRequestBytes) / machineMemoryCapacityBytes), 999) |
Kernel OOM Killer에 의해 프로세스(컨테이너)가 종료될 때 아래와 같은 메세지를 확인할 수 있습니다.
로그 예시)
[1301334.004840] Out of memory: Killed process 155761 (mysqld) total-vm:1298812kB, anon-rss:432376kB, file-rss:0kB, shmem-rss:0kB, UID:113 pgtables:1144kB oom_score_adj:0
[1302863.154766] Out of memory: Killed process 156184 (mysqld) total-vm:1300248kB, anon-rss:426200kB, file-rss:0kB, shmem-rss:0kB, UID:113 pgtables:1176kB oom_score_adj:0
[1312609.442638] Out of memory: Killed process 156772 (mysqld) total-vm:1357840kB, anon-rss:469212kB, file-rss:0kB, shmem-rss:0kB, UID:113 pgtables:1268kB oom_score_adj:0
참고) 주요 프로세스의 oom_score_adj
, oom_score 값
kubelet, containerd, sshd의 oom_score_adj
를 확인해보면, 최저값과 유사하게 설정되어 있습니다.
이는 중요 프로세스가 OOM killer에 의해 종료될 확률이 낮다고 볼 수 있습니다.
$ ps -ef | grep kubelet
root 1566 1 2 Jul14 ? 09:31:26 /usr/bin/kubelet --config /etc/kubernetes/kubelet/kubelet-config.json --kubeconfig /var/lib/kubelet/kubeconfig --container-runtime-endpoint unix:///run/containerd/containerd.sock --image-credential-provider-config /etc/eks/image-credential-provider/config.json --image-credential-provider-bin-dir /etc/eks/image-credential-provider --node-ip=10.56.110.44 --pod-infra-container-image=602401143452.dkr.ecr.ap-northeast-2.amazonaws.com/eks/pause:3.5 --v=2 --hostname-override=ip-10-56-110-44.ap-northeast-2.compute.internal --cloud-provider=external --node-labels=k8s.woowa.in/access-type=private,k8s.woowa.in/capacity-type=on-demand,k8s.woowa.in/cluster-name=p02-platform-dev-003,k8s.woowa.in/consolidation-policy=WhenUnderutilized,k8s.woowa.in/instance-category=r,k8s.woowa.in/managed-by=karpenter,k8s.woowa.in/nodegroup=monitoring,karpenter.sh/capacity-type=on-demand,karpenter.sh/nodepool=monitoring-003,woowa.in/role=eks-platform-monitoring,woowa.in/service=eks-platform,woowa.in/zone=dev --register-with-taints=dedicated=monitoring:NoSchedule --max-pods=58 --system-reserved=cpu=100m,ephemeral-storage=1Gi,memory=100Mi --kube-reserved=memory=100Mi,cpu=200m,ephemeral-storage=3Gi --eviction-hard=nodefs.inodesFree<10%,memory.available<256Mi,nodefs.available<10% --eviction-soft=nodefs.inodesFree<15%,memory.available<500Mi,nodefs.available<15% --eviction-soft-grace-period=nodefs.inodesFree=2m0s,memory.available=1m0s,nodefs.available=1m30s --eviction-max-pod-grace-period=60
$ cat /proc/1566/oom_score
2
$ cat /proc/1566/oom_score_adj
-999
$ ps -ef | grep containerd
root 1415 1 0 Jul14 ? 03:18:12 /usr/bin/containerd
$ cat /proc/1415/oom_score
2
$ cat /proc/1415/oom_score_adj
-999
$ ps -ef | grep sshd
root 1036 1 0 Jul14 ? 00:00:00 /usr/sbin/sshd -D
$ cat /proc/1036/oom_score
0
$ cat /proc/1036/oom_score_adj
-1000
참고) OOM Killer 관련 코드
out_of_memory 함수에서 memory cgroup oom 여부에 따라 로그가 달라지는 것을 확인할 수 있습니다.
/**
* out_of_memory - kill the "best" process when we run out of memory
* @oc: pointer to struct oom_control
*
* If we run out of memory, we have the choice between either
* killing a random task (bad), letting the system crash (worse)
* OR try to be smart about which process to kill. Note that we
* don't have to be perfect here, we just have to be good.
*/
bool out_of_memory(struct oom_control *oc)
{
...
if (oc->chosen && oc->chosen != (void *)-1UL)
oom_kill_process(oc, !is_memcg_oom(oc) ? "Out of memory" :
"Memory cgroup out of memory");
return !!oc->chosen;
}
...
static void __oom_kill_process(struct task_struct *victim, const char *message)
{
...
mark_oom_victim(victim);
pr_err("%s: Killed process %d (%s) total-vm:%lukB, anon-rss:%lukB, file-rss:%lukB, shmem-rss:%lukB, UID:%u pgtables:%lukB oom_score_adj:%hd\n",
message, task_pid_nr(victim), victim->comm, K(mm->total_vm),
K(get_mm_counter(mm, MM_ANONPAGES)),
K(get_mm_counter(mm, MM_FILEPAGES)),
K(get_mm_counter(mm, MM_SHMEMPAGES)),
from_kuid(&init_user_ns, task_uid(victim)),
mm_pgtables_bytes(mm) >> 10, victim->signal->oom_score_adj);
task_unlock(victim);
...
}
참고) https://elixir.bootlin.com/linux/v5.10.210/source/mm/oom_kill.c#L1140
파드의 상태와 노드 OS 로그를 바탕으로, 메모리가 부족할 때 누가 파드 또는 컨테이너를 종료했는지 확인할 수 있습니다.
kubelet eviction manager는 파드를 다른 노드로 옮기지만, memory cgroup OOM Killer와 Kernel OOM Killer는 컨테이너를 종료시키기 때문에, 파드의 restartPolicy
에 따라서 파드는 종료되지 않고 컨테이너만 재시작될 수 있습니다.
지금까지 노드의 memory에 관하여 자세히 알아봤습니다. 결론적으로 파드에 memory limit이 없다면, 노드의 Allocatable 자원을 모두 소진할 수 있고, memory 사용률이 높아지며 OOM이 발생하거나 전반적인 노드 성능과 kubelet 동작 등에 영향을 미칠 수 있습니다.
하나의 파드가 동일 노드 전체 파드에 영향을 미치지 않도록 memory limit을 설정하는 것이 필요합니다.
참고 문서
블로그에서 같이 보면 좋은 글
'Kubernetes' 카테고리의 다른 글
EKS Node의 최대 Pod 수 - Security Groups per Pod 사용 시 (0) | 2024.11.18 |
---|---|
여러 노드와 존에 파드를 분산하여 배포하는 방법 (0) | 2024.04.06 |
파드의 컨테이너 pid 및 cgroup에 할당된 cpu, mem 확인 방법(containerd) (1) | 2024.03.12 |
컨테이너 root로 접속하는 방법(containerd) (3) | 2023.11.07 |
EKS Node의 최대 Pod 수 (0) | 2022.08.01 |