OOM 이벤트 문제 해결


이 페이지는 Google Kubernetes Engine(GKE)에서 메모리 부족(OOM) 이벤트를 해결하는 데 도움을 줍니다. OOM 이벤트의 일반적인 원인을 파악하고, 컨테이너 수준과 노드 수준의 발생을 구분하고, 해결책을 적용하는 방법을 알아봅니다.

이 페이지는 앱이 성공적으로 배포되었는지 확인하려는 애플리케이션 개발자와 OOM 이벤트의 근본 원인을 파악하고 플랫폼 구성을 확인하려는 플랫폼 관리자 및 운영자를 대상으로 합니다. Google Cloud 콘텐츠에서 참조하는 일반적인 역할 및 예시 태스크에 대해 자세히 알아보려면 일반 GKE Enterprise 사용자 역할 및 태스크를 참고하세요.

OOM 이벤트의 일반적인 원인

OOM 이벤트는 일반적으로 부하 또는 트래픽이 급증하여 앱 메모리 사용량이 급증하고 컨테이너에 구성된 메모리 한도에 도달하는 동안 발생합니다.

다음과 같은 시나리오에서 OOM 이벤트가 발생할 수 있습니다.

  • 메모리 한도 부족: 포드 매니페스트의 resources.limits.memory 설정이 앱의 일반적인 메모리 요구량 또는 최대 메모리 요구량에 비해 너무 낮습니다.
  • 정의되지 않은 메모리 요청 또는 한도: resources.limits.memoryresources.requests.memory가 모두 정의되지 않은 경우 컨테이너의 메모리 사용량은 제한되지 않습니다.
  • 과도하거나 급격한 부하: 갑작스럽게 극도로 급증하는 부하는 한도가 일반적으로 적절하더라도 메모리를 비롯한 시스템 리소스를 과도하게 사용할 수 있습니다.
  • 메모리 누수: 앱에 메모리를 제대로 해제하지 못하게 하는 코드 결함이 있을 수 있습니다.

OOM 이벤트는 트래픽을 처리할 컨테이너가 줄어들어 나머지 컨테이너의 부하가 증가하므로 연쇄 장애를 시작할 수 있습니다. 그러면 이러한 컨테이너도 종료될 수 있습니다.

Kubernetes에서 OOM 이벤트를 처리하는 방법

Linux OOM Killer는 모든 OOM 이벤트를 처리합니다. OOM Killer는 시스템의 메모리가 매우 부족할 때 활성화되는 커널 프로세스입니다. 프로세스를 전략적으로 종료하여 리소스를 확보하고 전체 시스템 비정상 종료를 방지하는 것이 목적입니다. 커널은 점수 시스템을 사용하여 시스템 안정성을 유지하고 데이터 손실을 최소화하기 위해 중지할 프로세스를 선택합니다.

Kubernetes 환경에서 OOM Killer는 두 가지 서로 다른 범위에서 작동합니다. 하나의 컨테이너에 영향을 미치는 제어 그룹(cgroup)과 전체 노드에 영향을 미치는 시스템입니다.

컨테이너 수준 OOM 종료

컨테이너 수준 OOM 종료는 컨테이너가 사전 정의된 메모리 한도를 초과하려고 할 때 발생합니다. Kubernetes는 각 컨테이너를 하드 메모리 한도가 있는 특정 cgroup에 할당합니다. 컨테이너의 메모리 사용량이 이 한도에 도달하면 커널은 먼저 해당 cgroup 내에서 메모리를 회수하려고 시도합니다. 커널이 이 프로세스를 사용하여 충분한 메모리를 확보할 수 없는 경우 cgroup OOM Killer가 호출됩니다. 이 cgroup 내의 프로세스를 종료하여 리소스 경계를 적용합니다.

컨테이너의 기본 프로세스가 이런 식으로 종료되면 Kubernetes는 이벤트를 관찰하고 컨테이너의 상태를 OOMKilled로 표시합니다. 그런 다음 포드의 구성된 restartPolicy에 따라 결과가 결정됩니다.

  • Always 또는 OnFailure: 컨테이너가 다시 시작됩니다.
  • Never: 컨테이너가 다시 시작되지 않고 종료된 상태로 유지됩니다.

OOM Killer는 문제를 일으킨 컨테이너로 실패를 격리하여 하나의 결함이 있는 포드가 전체 노드를 비정상 종료하는 것을 방지합니다.

cgroup 버전이 OOM Killer 동작에 미치는 영향

OOM 종료 동작은 cgroup 버전 간에 크게 다를 수 있습니다. 사용 중인 cgroup 버전을 잘 모르겠다면 클러스터 노드의 cgroup 모드를 확인하세요.

  • cgroup v1에서 컨테이너의 메모리 cgroup 내 OOM 이벤트로 인해 예기치 않은 동작이 발생할 수 있습니다. OOM Killer는 컨테이너의 기본 프로세스(PID 1)가 아닌 하위 프로세스를 비롯하여 해당 cgroup 내의 모든 프로세스를 종료할 수 있습니다.

    이러한 동작은 Kubernetes에 상당한 문제를 야기합니다. Kubernetes는 기본적으로 기본 컨테이너 프로세스의 상태를 모니터링하므로 이러한 '부분적' OOM 종료는 인식하지 못합니다. 중요한 하위 프로세스가 종료되더라도 기본 컨테이너 프로세스는 계속 실행될 수 있습니다. 이 동작으로 인해 Kubernetes 또는 운영자에게 즉시 표시되지는 않지만 노드의 시스템 저널(journalctl)에는 계속 표시되는 미묘한 앱 실패가 발생할 수 있습니다.

  • cgroup v2는 더 예측 가능한 OOM Killer 동작을 제공합니다.

    cgroup v2 환경에서 워크로드 무결성을 보장하기 위해 OOM Killer는 부분 종료를 방지하고 다음 두 가지 결과 중 하나를 보장합니다. 해당 cgroup 및 그 하위 요소에 속한 모든 태스크가 종료되거나 (Kubernetes에 오류가 표시됨) 워크로드에 메모리를 너무 많이 사용하는 태스크가 없는 경우 워크로드가 그대로 유지되고 예기치 않은 내부 프로세스 종료 없이 계속 실행됩니다.

    단일 프로세스를 종료하는 cgroup v1 동작을 원하는 시나리오의 경우 kubelet은 cgroup v2singleProcessOOMKill 플래그를 제공합니다. 이 플래그를 사용하면 전체 cgroup이 아닌 OOM 이벤트 중에 개별 프로세스를 종료할 수 있도록 더 세부적으로 제어할 수 있습니다.

시스템 수준 OOM 종료

시스템 수준 OOM 종료는 단일 컨테이너뿐만 아니라 전체 노드에서 사용 가능한 메모리가 부족할 때 발생하는 더 심각한 이벤트입니다. 이 이벤트는 모든 프로세스(모든 포드 및 시스템 데몬 포함)의 메모리 사용량 합계가 노드의 용량을 초과하는 경우 발생할 수 있습니다.

이 노드의 메모리가 부족하면 전역 OOM Killer가 노드의 모든 프로세스를 평가하고 프로세스를 종료하여 전체 시스템의 메모리를 회수합니다. 선택된 프로세스는 일반적으로 수명이 짧고 메모리를 많이 사용하는 프로세스입니다.

심각한 OOM 상황을 방지하기 위해 Kubernetes는 노드 압력 제거를 사용하여 노드 리소스를 관리합니다. 이 프로세스에는 메모리 또는 디스크 공간과 같은 리소스가 매우 부족해질 때 노드에서 덜 중요한 포드를 제거하는 작업이 포함됩니다. 시스템 수준 OOM 종료는 이 제거 프로세스가 문제를 방지할 만큼 빠르게 메모리를 확보하지 못했다는 것을 나타냅니다.

OOM Killer가 컨테이너의 프로세스를 종료하면 일반적으로 cgroup 트리거 종료와 동일한 효과가 발생합니다. 컨테이너가 OOMKilled로 표시되고 정책에 따라 다시 시작됩니다. 그러나 중요한 시스템 프로세스가 종료되면(드물게 발생) 노드 자체가 불안정해질 수 있습니다.

OOM 이벤트 조사

다음 섹션에서는 가장 간단한 Kubernetes 도구에서 시작하여 더 자세한 로그 분석으로 이어지는 OOM 이벤트를 감지하고 확인하는 방법을 설명합니다.

포드 상태에서 표시되는 OOM 이벤트 확인

OOM 이벤트를 확인하는 첫 번째 단계는 Kubernetes에서 OOM 이벤트를 관찰했는지 확인하는 것입니다. Kubernetes는 컨테이너의 기본 프로세스가 종료될 때 이벤트를 관찰합니다. 이는 cgroup v2 환경의 표준 동작입니다.

  • 포드 상태를 검사합니다.

    kubectl describe pod POD_NAME
    

    POD_NAME을 조사하려는 포드의 이름으로 바꿉니다.

    표시된 OOM 이벤트가 발생하면 출력은 다음과 비슷합니다.

    ...
      Last State:     Terminated
        Reason:       OOMKilled
        Exit Code:    137
        Started:      Tue, 13 May 2025 19:05:28 +0000
        Finished:     Tue, 13 May 2025 19:05:30 +0000
    ...
    

Reason 필드에 OOMKilled가 표시되면 이벤트가 확인된 것입니다. 137Exit Code는 OOM 종료도 나타냅니다. Reason 필드의 값이 다르거나 앱 오류에도 불구하고 포드가 계속 실행 중인 경우 다음 섹션으로 이동하여 자세히 조사합니다.

로그에서 보이지 않는 OOM 이벤트 검색

하위 프로세스가 종료되었지만 기본 컨테이너 프로세스가 계속 실행되는 경우(cgroup v1 환경의 일반적인 시나리오) OOM 종료는 Kubernetes에 '표시되지 않습니다'. 이러한 이벤트의 증거를 찾으려면 노드의 로그를 검색해야 합니다.

표시되지 않는 OOM 종료를 찾으려면 로그 탐색기를 사용하세요.

  1. Google Cloud 콘솔에서 로그 탐색기로 이동합니다.

    로그 탐색기로 이동

  2. 쿼리 창에서 다음 쿼리 중 하나를 입력합니다.

    • OOM 이벤트가 발생했다고 생각되는 포드가 이미 있는 경우 해당 포드를 쿼리합니다.

      resource.type="k8s_node"
      jsonPayload.MESSAGE:(("POD_NAME" AND "ContainerDied") OR "TaskOOM event")
      resource.labels.cluster_name="CLUSTER_NAME"
      

      다음을 바꿉니다.

      • POD_NAME: 쿼리하려는 포드의 이름입니다.
      • CLUSTER_NAME: 포드가 속한 클러스터의 이름입니다.
    • OOM 이벤트가 발생한 포드 또는 노드를 찾으려면 모든 GKE 워크로드를 쿼리합니다.

      resource.type="k8s_node"
      jsonPayload.MESSAGE:("ContainerDied" OR "TaskOOM event")
      resource.labels.cluster_name="CLUSTER_NAME"
      
  3. 쿼리 실행을 클릭합니다.

  4. 출력에서 TaskOOM 문자열이 포함된 로그 항목을 검색하여 OOM 이벤트를 찾습니다.

  5. 선택사항: 모든 GKE 워크로드의 OOM 이벤트를 검색했으며 OOM 이벤트가 발생한 특정 포드를 식별하려면 다음 단계를 완료하세요.

    1. 각 이벤트에서 이벤트와 연결된 컨테이너 ID를 기록해 둡니다.
    2. ContainerDied 문자열이 포함되어 있고 OOM 이벤트 직후에 발생한 로그 항목을 찾아 컨테이너 중지를 식별합니다. OOM 이벤트의 컨테이너 ID를 상응하는 ContainerDied 행에 일치시킵니다.

    3. container IDs를 일치시키면 일반적으로 ContainerDied 행에 실패한 컨테이너와 연결된 포드 이름이 포함됩니다. 이 포드는 OOM 이벤트의 영향을 받았습니다.

실시간 정보에 journalctl 사용

시스템을 실시간으로 분석해야 하는 경우 journalctl 명령어를 사용하세요.

  1. SSH를 사용하여 노드에 연결합니다.

    gcloud compute ssh NODE_NAME --location ZONE
    

    다음을 바꿉니다.

    • NODE_NAME: 검사하려는 노드의 이름입니다.
    • ZONE: 노드가 속한 Compute Engine 영역입니다.
  2. 셸에서 노드의 시스템 저널에 있는 커널 메시지를 살펴봅니다.

    journalctl -k
    
  3. 출력을 분석하여 이벤트 유형을 구분합니다.

    • 컨테이너 수준 종료: 로그 항목에 cgroup 한도가 적용되었음을 나타내는 memory cgroup, mem_cgroup 또는 memcg와 같은 용어가 포함됩니다.
    • 시스템 수준 종료: 로그 항목은 cgroup을 언급하지 않는 Out of memory: Killed process...와 같은 일반 메시지입니다.

OOM 이벤트 해결

OOM 이벤트를 해결하려면 다음 해결책을 시도해 보세요.

  • 메모리 한도 늘리기: 가장 직접적인 해결책입니다. 포드 매니페스트를 수정하여 앱의 최대 사용량을 수용할 수 있는 더 높은 resources.limits.memory 값을 제공합니다. 한도 설정에 관한 자세한 내용은 Kubernetes 문서의 포드 및 컨테이너의 리소스 관리를 참조하세요.
  • 메모리 요청 추가 또는 조정: 포드의 매니페스트에서 resources.requests.memory 필드가 일반적인 사용량에 적합한 실제 값으로 설정되어 있는지 확인합니다. 이 설정을 사용하면 Kubernetes에서 메모리가 충분한 노드에 포드를 예약하는 데 도움이 됩니다.
  • 워크로드 수평 확장: 트래픽 부하를 분산하고 단일 포드의 메모리 압력을 줄이려면 복제본 수를 늘립니다. Kubernetes에서 워크로드를 사전에 확장하도록 하려면 수평형 포드 자동 확장을 사용 설정하는 것이 좋습니다.
  • 노드의 수직 확장: 노드의 많은 포드가 한도에 가까우면 노드 자체가 너무 작을 수 있습니다. 노드 크기를 늘리려면 메모리가 더 많은 노드 풀로 워크로드를 마이그레이션하세요. Kubernetes에서 노드를 사전에 확장하도록 하려면 수직형 포드 자동 확장을 사용 설정하는 것이 좋습니다.
  • 앱 최적화: 앱을 검토하여 메모리 누수를 식별 및 해결하고 트래픽 급증 시 대량의 메모리를 사용하는 코드를 최적화합니다.
  • 문제가 있는 워크로드 삭제: 중요하지 않은 워크로드의 경우 마지막 수단으로 포드를 삭제하여 클러스터의 부담을 즉시 덜 수 있습니다.

다음 단계

  • 문서에서 문제의 해결 방법을 찾을 수 없는 경우 지원 받기에서 다음 주제에 관한 조언을 포함한 추가 도움을 받으세요.