Kubernetes

노드의 memory에 대하여 - cgroup과 OOM killer에 대해 알아보기

백곰곰 2024. 7. 31. 23:16
728x90
반응형

최근 노드 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을 통해서 kubeReservedsystemReserved 용량을 확보하고, 모든 파드들은 해당 limit값이 적용된다고 볼 수 있습니다.

 

추가적으로 kubeReservedsystemReserved cgroup 설정에 대해 알아보겠습니다.

참고로, kubeReservedsystemReserved 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

 

kubeReservedsystemReserved cgroup 값을 확인했을 때, kubelet에 설정한 kubeReserved, systemReserved 자원과 연관이 없는 것을 볼 수 있습니다.

현재 사용 중인 환경(EKS, Karpenter, AL2 사용 중)에서는 별도로 해당 값을 cgroup에 반영하지 않는 것으로 추측할 수 있습니다.
 


 

Memory가 부족할 때 일어나는 일

지금까지 Kubernetes 노드의 memory에 관련하여 알아보았고, 이 내용을 바탕으로 노드 memory가 부족할 때 어떤 일이 발생하는지 알아보겠습니다.
기본적으로 memory가 부족할 때 아래 3개의 요소들이 동작합니다.

1) kubelet eviction manager

kubelet eviction manager는 kubelet의 구성 요소 중 하나로, evictionHardevictionSoft에 설정된 threshold를 기준으로 파드를 노드에서 내쫓습니다.

기본적으로 10초 마다 memory 사용량을 체크하며, cAdvisor로 부터 정보를 얻어옵니다.

polling 방식으로 동작하기 때문에 노드의 memory 사용량이 급격하게 올라가는 경우, MemoryPressure 상태를 빠르게 인지하지 못할 수 있습니다.

이 문제를 보완하기 위해 kernelMemcgNotification 옵션을 활용할 수 있습니다. 이 옵션을 사용하면 커널의 notification API를 통해 cgroup memory limit 초과 시 OOM 이벤트를 받아볼 수 있습니다. (아직 사용해보지는 않았습니다.)

 
참고) eviction_manager.go

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에 따라 다르게 설정됩니다. (참고)

QoSoom_score_adj
Guaranteed-997
BestEffort1000
Burstablemin(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

로그 출처 : https://stackoverflow.com/questions/64602324/i-am-using-t2-micro-ec2-instance-it-goes-out-of-memory-and-kills-the-process-an
 

참고) 주요 프로세스의 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을 설정하는 것이 필요합니다.
 
 
참고 문서

 
블로그에서 같이 보면 좋은 글

728x90