LabHub
배우기 러닝패스 코스

내가 부순다 — 가설을 먼저 쓰는 카오스 실험실 · CPU 는 느리게 만들고 메모리는 죽인다 · 이론

CPU 는 느리게 만들고 메모리는 죽인다

LabHub 에서 이어서 보기

한 줄 요약

CPU 한계와 메모리 한계는 이름만 닮았지 전혀 다르게 문다. 하나는 느리게 만들고, 하나는 죽인다.

왜 이게 필요했나

limits 를 적는 일은 대개 습관이다. 앞사람이 쓴 값을 복사하거나, 심사에서
"한계를 지정하세요" 라는 말을 듣고 적당한 숫자를 넣는다. 그러고 나면 두 종류의
사건이 몇 달 뒤에 찾아온다. 하나는 "아무것도 안 바꿨는데 갑자기 느려졌다"
이고, 다른 하나는 "재시작이 계속 돌고 로그에는 아무 단서가 없다" 이다.
두 사건은 원인이 다른데, 둘 다 limits 한 줄에서 나왔다.

어떻게 동작하나

[Pod 와 컨테이너의 자원 관리 문서](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/)는
두 한계의 집행 방식이 서로 다르다고 명확히 구분한다.

cpu 한계는 스로틀링으로 집행된다. 커널이 정해진 몫만큼만 CPU 를 쓰게 하고,
그 몫을 다 쓰면 다음 주기가 올 때까지 그 컨테이너를 세워 둔다. 커널이 강제하는
단단한 한계라 컨테이너는 한계보다 더 쓸 수 없다. 대신 죽지는 않는다.
문서가 "런타임은 CPU 를 많이 썼다고 Pod 나 컨테이너를 종료하지 않는다" 고
따로 적어 둘 만큼 자주 오해받는 부분이다. 증상은 오직 느려지는 것뿐이고,
프로세스도 프로브도 다 살아 있으니 로그에는 아무것도 남지 않는다.

memory 한계는 OOM kill 로 집행된다. 컨테이너가 한계를 넘으면 커널이
종료할 수 있다. 다만 문서는 이것이 반응형이라고 말한다 — 커널이 메모리
압박을 감지했을 때 일어나므로, 한계를 넘긴 컨테이너가 즉시 죽지 않을 수도
있다. 죽을 때는 종료 코드 137 과 OOMKilled 이유가 남는다.

             집행 방식        증상                  남는 흔적  cpu        스로틀링        지연만 늘어난다        없음(로그가 조용하다)  memory     OOM kill        재시작·CrashLoop       exit 137 · OOMKilled

여기에 덜 알려진 함정이 하나 더 있다. **메모리를 매체로 쓰는 emptyDir 볼륨은
컨테이너의 메모리 사용량으로 계산된다.** 같은 문서가 "kubelet 은 tmpfs
emptyDir 볼륨을 로컬 임시 저장소가 아니라 컨테이너 메모리 사용으로 추적한다"
고 적고 있다. 임시 파일을 빠르게 쓰려고 메모리 볼륨을 붙여 두면, 디스크에
파일을 쓴다고 생각한 코드가 메모리 한계를 밀어 올려 컨테이너를 죽인다.
그리고 증상은 "파일을 쓰다가 죽었다" 가 아니라 그냥 OOMKilled 다.

requestslimits 의 역할도 나눠서 기억해야 한다. requests 는 스케줄러가
어디에 놓을지 정할 때 쓰는 값이고, limits 는 kubelet 과 커널이 **얼마나
쓰게 둘지** 정할 때 쓰는 값이다. 그래서 requests 만 적으면 노드가 한가할 때
그보다 많이 써도 되고, limits 만 적으면 쿠버네티스가 같은 값을 requests 로
복사해 넣는다.

한 가지 더. 한계를 낮출 때는 요청량과의 관계를 함께 봐야 한다. 한계는
요청량보다 작을 수 없어서, 메모리 한계만 40Mi 로 내리려 해도 요청량이 64Mi 로
적혀 있으면 API 서버가 그 변경을 아예 거절한다. 실험을 준비하다 보면 "고장이
날 줄 알았는데 명령이 거절당하는" 경우를 자주 만나는데, 그때는 대개 이런
정합성 규칙에 걸린 것이다. 이 실습의 앱이 요청량을 넉넉히 낮게 잡아 둔 것도
실험 중에 이 벽에 부딪히지 않게 하기 위해서다.

현장에서 만나는 모습

가장 흔한 사고는 "안전하게" cpu 한계를 100m 로 낮춰 둔 배치다. 평소에는
아무 일도 없다가 트래픽이 조금 오르는 순간 응답 시간이 몇 배로 뛴다. 성공률은
그대로라 알림이 울리지 않고, CPU 사용률 그래프는 한계에 딱 붙어 평평해서
오히려 "여유 있어 보인다". 이런 사건은 스로틀링을 직접 재 보기 전까지
원인을 못 찾는다.

반대 방향의 사고도 있다. 메모리 한계를 넉넉히 주면 OOM 은 사라지지만, 요청량을
같이 올려 두지 않으면 노드가 빠듯해졌을 때 그 Pod 가 먼저 쫓겨난다. 문서는
요청량을 넘겨 쓰는 컨테이너가 있는 Pod 는 노드에 메모리가 모자랄 때 축출될
가능성이 크다고 적는다. 즉 **한계는 개별 컨테이너의 상한을 정하고, 요청량은
자원이 모자랄 때의 순서를 정한다.** 실험으로 확인할 때도 이 둘을 따로 건드려야
무엇이 원인이었는지 말할 수 있다.

그래서 자원 관련 사건을 다룰 때는 증상으로 먼저 갈래를 나눈다. **로그가 조용한
지연이면 cpu 쪽을, 재시작과 종료 코드 137** 이면 memory 쪽을 먼저 본다.
이 갈래만 제대로 나눠도 조사 시간이 크게 줄어든다. 반대로 "느려졌으니 메모리를
늘려 보자" 같은 대응은 아무것도 바꾸지 못한 채 비용만 올린다. 한계를 건드리는
변경은 롤아웃을 일으켜 Pod 를 새로 만들기 때문에, 바꾼 직후에는 잠깐 좋아 보이는
착시까지 생긴다. 그 착시에 속지 않으려면 **바꾸기 전과 후를 같은 방식으로 잰
숫자**가 있어야 한다.

다음 퀴즈에서 확인할 것

cpu 한계와 memory 한계의 집행 방식 차이, OOM 집행이 반응형이라는 뜻,
메모리 매체 emptyDir 이 무엇으로 계산되는지 확인한다.