본문 바로가기

kubernetes

emptyDir 을 sizeLimit 초과사용시 kubelet 코드상 어디에서 축출시킬까?

01. 맥락

스터디 도중 팀원이 다음 의문을 제기했다.

"emptyDir 사용량이 일정 이상이면, 파드를 종료하는데,"
"kubelet 코드상에서 어떻게 구현하였는지 궁금하다."

그래서 삽질을 시작했다. (전혀 실용적이지 않은 이유다.)

 

 

 

02. 목표

우리의 목표는 간단하다.
emptyDir 을 오버해서 사용할 때, 코드상 어떤 부분에서 이 "오버하였음" 을 체크하는 것이고,
이를 어느 라인에서 죽이는 것일지 찾아보는 것.

 

 

 

03. 실마리

우선, 다음 YAML 을 이용해 파드를 배포하였다.

apiVersion: v1
kind: Pod
metadata:
  name: empty-test1
spec:
  nodeName: worker2
  containers:
    - name: test2
      image: bash
      command: ['sh', '-c', 'echo "The app is running!" && tail -f /dev/null']
      volumeMounts:
        - name: empty
          mountPath: /test
  volumes:
    - name: empty
      emptyDir: 
        sizeLimit: 1Mi

 

해당 파드에 exec 로 접속하여 다음 명령어로 /test 디렉토리 내부에 더미 파일을 생성했다.

dd if=/dev/zero of=/test/test.file bs=1K count=10000

다음과 같이 에러코드 137을 뿜으며 파드가 종료된다.

이 때 kubelet은 어떤 로그를 찍는지 확인해보았다.

Jan 16 13:58:48 worker2 kubelet[4102]: I0116 13:58:48.860149    4102 reconciler_common.go:251] "operationExecutor.VerifyControllerAttachedVolume started for volume \"kube-api-access-9kp6p\" (UniqueName: \"kubernetes.io/projected/5cf142d1-7a2d-4698-a7e0-f6ebfc0d4b7d-kube-api-access-9kp6p\") pod \"empty-test1\" (UID: \"5cf142d1-7a2d-4698-a7e0-f6ebfc0d4b7d\") " pod="default/empty-test1"
Jan 16 14:01:14 worker2 kubelet[4102]: I0116 14:01:14.596240    4102 pod_startup_latency_tracker.go:104] "Observed pod startup duration" pod="default/empty-test1" podStartSLOduration=145.120444584 podStartE2EDuration="2m26.596226637s" podCreationTimestamp="2025-01-16 13:58:48 +0000 UTC" firstStartedPulling="2025-01-16 13:58:49.238120102 +0000 UTC m=+32381.255600528" lastFinishedPulling="2025-01-16 13:58:50.713902177 +0000 UTC m=+32382.731382581" observedRunningTime="2025-01-16 13:58:50.961541756 +0000 UTC m=+32382.979022164" watchObservedRunningTime="2025-01-16 14:01:14.596226637 +0000 UTC m=+32526.613707043"
Jan 16 14:01:14 worker2 kubelet[4102]: I0116 14:01:14.657488    4102 eviction_manager.go:627] "Eviction manager: pod is evicted successfully" pod="default/empty-test1"
Jan 16 14:01:14 worker2 kubelet[4102]: I0116 14:01:14.657509    4102 eviction_manager.go:208] "Eviction manager: pods evicted, waiting for pod to be cleaned up" pods=["default/empty-test1"]
Jan 16 14:01:14 worker2 kubelet[4102]: I0116 14:01:14.756795    4102 reconciler_common.go:162] "operationExecutor.UnmountVolume started for volume \"kube-api-access-9kp6p\" (UniqueName: \"kubernetes.io/projected/5cf142d1-7a2d-4698-a7e0-f6ebfc0d4b7d-kube-api-access-9kp6p\") pod \"5cf142d1-7a2d-4698-a7e0-f6ebfc0d4b7d\" (UID: \"5cf142d1-7a2d-4698-a7e0-f6ebfc0d4b7d\") "
Jan 16 14:01:14 worker2 kubelet[4102]: I0116 14:01:14.757409    4102 reconciler_common.go:162] "operationExecutor.UnmountVolume started for volume \"empty\" (UniqueName: \"kubernetes.io/empty-dir/5cf142d1-7a2d-4698-a7e0-f6ebfc0d4b7d-empty\") pod \"5cf142d1-7a2d-4698-a7e0-f6ebfc0d4b7d\" (UID: \"5cf142d1-7a2d-4698-a7e0-f6ebfc0d4b7d\") "
Jan 16 14:01:14 worker2 kubelet[4102]: I0116 14:01:14.760034    4102 operation_generator.go:780] UnmountVolume.TearDown succeeded for volume "kubernetes.io/projected/5cf142d1-7a2d-4698-a7e0-f6ebfc0d4b7d-kube-api-access-9kp6p" (OuterVolumeSpecName: "kube-api-access-9kp6p") pod "5cf142d1-7a2d-4698-a7e0-f6ebfc0d4b7d" (UID: "5cf142d1-7a2d-4698-a7e0-f6ebfc0d4b7d"). InnerVolumeSpecName "kube-api-access-9kp6p". PluginName "kubernetes.io/projected", VolumeGIDValue ""

"eviction_manager.go:627" -> 여기를 보면 되겠군..!

 

https://github.com/kubernetes/kubernetes/blob/50fc400f178d2078d0ca46aee955ee26375fc437/pkg/kubelet/eviction/eviction_manager.go#L627

 

kubernetes/pkg/kubelet/eviction/eviction_manager.go at 50fc400f178d2078d0ca46aee955ee26375fc437 · kubernetes/kubernetes

Production-Grade Container Scheduling and Management - kubernetes/kubernetes

github.com

 

 

04. 소스코드 딥다이브

해당 라인 찾아보니 다음 함수가 기다리더라..

"evictPod()"

 

해당 함수 4군데에서 호출중이었는데, 우리의 관심사에 맞는 코드는 다음부분인것 같았음.

 

다음 부분이 아무래도 emptyDir 의 크기 (used) 와 sizeLimit(추정) (*size) 를 비교하여 축출을 실행할지 결정하는 파트인듯

 

if used != nil && size != nil && size.Sign() == 1 && used.Cmp(*size) > 0 {

 

emptyDirLimitEviction() 이친구를 호출한 부모 함수를 추적하다 보니...

synchronize 라는 함수가 monitoringInterval 주기로 무한히 호출되고 있었음...

 

 

 

 

05. 결론

kubelet 은 monitoringInterval 주기로 synchronize 함수를 호출하는데,

해당 함수 내에서 emptyDir 사용량과 sizeLimit 을 비교하여 파드를 축출할지 결정한다.

 

+++

추가로 저 스토리지 사용량을 어디로부터 받는지도 추적하려고 했으나... 

statsFunc(pod) 함수의 결과로 리턴된 podStats 를 받아서 보고는 있는데

statsFunc(pod) 이녀석을 정의한 녀석이 뭔지 타고 타고 올라가다보니

뭔가 초기의 목표를 벗어나는것 아닌가 하여 그만두었다.