관리 메뉴

근묵자흑

Kubernetes Pod 배포 시 발생하는 주요 오류와 해결 방법 본문

k8s

Kubernetes Pod 배포 시 발생하는 주요 오류와 해결 방법

Luuuuu 2025. 4. 6. 14:55

Kubernetes에서 Pod을 배포할 때 다양한 오류와 실수가 발생할 수 있습니다. 이러한 문제들은 애플리케이션 가용성, 성능, 그리고 클러스터 전체의 안정성에 영향을 미칠 수 있습니다. 이 글에서는 가장 흔히 발생하는 오류들과 그 해결 방법, 그리고 개발자와 운영자들이 자주 범하는 실수에 대해 살펴보겠습니다.

1. 리소스 관리: Requests와 Limits

리소스 관리는 Kubernetes에서 가장 중요하면서도 가장 자주 실수하는 부분입니다. 리소스 요청(requests)과 제한(limits)을 올바르게 설정하지 않으면 애플리케이션 성능과 클러스터 안정성에 직접적인 영향을 미칩니다.

자주 발생하는 리소스 관련 실수

  1. 리소스 요청을 설정하지 않거나 매우 낮게 설정
resources: {}  # BestEffort QoS - 권장하지 않음

# 또는 CPU를 너무 낮게 설정
resources:
  requests:
    cpu: "1m"  # 권장하지 않음

이는 노드의 오버커밋(overcommit)을 초래하여 높은 부하 시 CPU 병목 현상, 지연 시간 증가, 타임아웃 등의 문제를 일으킵니다. 평상시에는 문제가 없더라도 CPU 자원을 많이 사용하게 되는 경우, 각 애플리케이션은 초기에 요청한 자원만큼만 사용할 수 있어 CPU throttling이 발생하게 됩니다.

  1. CPU 제한 설정의 문제점

CPU 제한을 설정하면 노드의 CPU가 완전히 활용되지 않더라도 파드가 불필요하게 스로틀링(throttling)될 수 있습니다. 이는 Linux 커널의 CPU CFS(Completely Fair Scheduler) 할당량 메커니즘과 관련된 알려진 문제입니다. CPU limits는 문제를 해결하는 것보다 문제를 더 야기시키는 경우가 많습니다.

  1. 메모리 오버커밋

CPU와 달리 메모리 제한에 도달하면 파드가 종료(OOMKilled)됩니다. 이를 최소화하려면 메모리를 오버커밋하지 말고, QoS를 Guaranteed로 설정하세요.

Burstable QoS (OOMKilled 가능성 높음):

resources:
  requests:
    memory: "128Mi"
    cpu: "500m"
  limits:
    memory: "256Mi"
    cpu: 2

Guaranteed QoS (권장):

resources:
  requests:
    memory: "128Mi"
    cpu: 2
  limits:
    memory: "128Mi"
    cpu: 2

리소스 사용량 모니터링 및 최적화

  1. 현재 리소스 사용량 확인
kubectl top pods
kubectl top pods --containers
kubectl top nodes
  1. 장기적인 리소스 사용량 추적
    현재 리소스 사용량만 보는 것은 훌륭하지만, 결국 시간별 리소스 사용량을 확인할 필요가 생깁니다(예: "어제 아침 피크 시간의 CPU 사용량은 얼마였는가?"). Prometheus, DataDog 등의 모니터링 도구를 사용하여 시간 경과에 따른 리소스 사용량을 추적하고 분석하세요. 이런 도구들은 metrics-server로부터 메트릭 정보를 받아 내부적으로 저장하고 사용자들이 쿼리할 수 있게 지원합니다.
  2. VerticalPodAutoscaler 사용
    VerticalPodAutoscaler는 자동으로 Pod의 리소스 사용량을 확장시켜주는 도구입니다. CPU/메모리 사용량을 지켜보고 있다가 새롭게 리소스 제약을 설정해 줍니다.
  3. 저활용 클러스터의 비용 최적화
    최적의 리소스 사용량을 찾는 작업은 쉽지 않습니다. 마치 매번 테트리스를 하는 느낌입니다. 컴퓨팅 리소스 활용도가 낮은 경우(평균 ~10% 미만), AWS Fargate나 Virtual Kubelet 기반 제품과 같은 서버리스/사용량 기반 결제 모델을 고려해보세요. 이러한 제품들은 서버리스의 이점과 사용량에 따른 비용 과금 방식을 활용하여 더 저렴하고 운영 효율적인 솔루션이 될 수 있습니다.

2. OOMKilled (Out of Memory Killed)

오류 의미

OOMKilled는 컨테이너가 메모리 제한을 초과하여 Kubernetes에 의해 강제 종료되었음을 의미합니다. Linux 커널의 OOM(Out of Memory) Killer가 메모리 부족 상황에서 시스템 안정성을 유지하기 위해 프로세스를 종료하는 메커니즘과 유사합니다.

발생 원인

  1. 메모리 제한이 너무 낮게 설정됨
    애플리케이션의 실제 메모리 요구 사항보다 낮은 메모리 제한을 설정하면 애플리케이션이 필요한 메모리를 할당받지 못해 OOMKilled가 발생합니다.
  2. 메모리 누수(Memory Leaks)
    애플리케이션 코드에 메모리 누수가 있으면 시간이 지남에 따라 메모리 사용량이 계속 증가하여 결국 제한을 초과하게 됩니다.
  3. 일시적인 메모리 스파이크
    데이터 처리, 캐시 빌드, GC(가비지 컬렉션) 등으로 인한 일시적인 메모리 사용량 급증이 제한을 초과할 수 있습니다.
  4. JVM 기반 애플리케이션의 설정 문제
    Java 기반 애플리케이션에서 JVM 힙 크기가 잘못 구성되어 컨테이너 메모리 제한과 일치하지 않는 경우가 많습니다.

OOMKilled 문제 해결 방법

  1. 메모리 제한 조정
resources:
  requests:
    memory: "512Mi"
  limits:
    memory: "1Gi"

애플리케이션의 실제 메모리 사용 패턴을 분석하여 적절한 메모리 제한을 설정하세요.

  1. 메모리 사용량 모니터링
# 현재 메모리 사용량 확인
kubectl top pods

# 상세 메모리 사용량 정보
kubectl describe pod <pod_name>

# 컨테이너 로그에서 OOM 메시지 확인
kubectl logs <pod_name>
  1. Guaranteed QoS 클래스 사용

메모리 요청과 제한을 동일하게 설정하여 Guaranteed QoS 클래스를 사용하면 OOM Killer의 우선순위가 낮아져 Pod이 종료될 가능성이 줄어듭니다.

resources:
  requests:
    memory: "512Mi"
  limits:
    memory: "512Mi"

3. Liveness & Readiness Probes

Liveness & Readiness Probes는 자주 오해되거나 잘못 구성되는 영역입니다. 이들은 애플리케이션 가용성과 복원력에 중요한 역할을 합니다.

Liveness & Readiness Probes의 차이

  1. Liveness Probe
    • 프로브가 실패하면 파드가 재시작됩니다.
    • 복구 불가능한 오류가 발생했을 때 파드를 재시작하는 데 사용됩니다.
  2. Readiness Probe
    • 프로브가 실패하면 파드가 서비스 엔드포인트에서 제거되어 트래픽을 받지 않게 됩니다.
    • 프로브가 다시 성공하면 파드가 서비스에 다시 연결됩니다.

중요: 두 프로브 모두 파드 수명 주기 전체에 걸쳐 계속 실행됩니다. Readiness Probe는 시작 시에만 실행되는 것이 아닙니다. 사람들은 주로 Readiness Probe의 경우 Pod 시작 시점에만 검사를 진행하여 트래픽 전달 여부를 결정한다고 생각하지만, 그것은 단지 하나의 사용 사례에 불과합니다.

Readiness Probe의 또 다른 중요한 사용법은 특정 Pod에 너무 많은 트래픽이 몰려 '과열'되는 경우, 더 이상 트래픽이 전송되지 않게 하여 '냉각'시키는 메커니즘을 제공하는 것입니다. 일정 시간 후 Readiness Probe가 성공적으로 검사를 통과하기 시작하면 다시 트래픽을 전송하게 됩니다.

일반적인 프로브 관련 실수

  1. 프로브를 정의하지 않음
    프로브가 없으면 Kubernetes는 컨테이너가 실행 중이기만 하면 건강하다고 간주합니다. 이는 "좀비" 애플리케이션이 트래픽을 계속 수신하게 할 수 있습니다.
  2. Liveness Probe와 Readiness Probe를 동일하게 설정
# 권장하지 않음
livenessProbe:
  httpGet:
    path: /health
    port: 8080
readinessProbe:
  httpGet:
    path: /health
    port: 8080

이렇게 설정하면 Readiness Probe가 실패할 때 Liveness Probe도 실패하여 불필요한 재시작이 발생할 수 있습니다. 이는 굳이 건강한 Pod가 단지 일을 많이 한다고 해서 재시작하는 것이 비생산적일 수 있습니다.

  1. 외부 종속성에 기반한 프로브
    데이터베이스나 다른 서비스와 같은 공유 종속성이 다운되었을 때 프로브가 실패하도록 설정하면 연쇄적인 장애(cascading failure)가 발생할 수 있습니다.

올바른 프로브 설정 권장사항

  1. Readiness Probe부터 시작
    Liveness Probe는 위험할 수 있으므로, 먼저 Readiness Probe만 정의하는 것이 좋습니다. 때로는 차라리 상태 검사를 둘 다 제대로 설정하지 않는 것이 잘못 설정하는 것보다 나은 경우가 있습니다.
  2. Liveness Probe는 신중하게 설정
    Liveness Probe는 애플리케이션 자체의 건강 상태만 확인해야 하며, 외부 종속성에 의존해서는 안 됩니다.
  3. 적절한 타임아웃과 임계값 설정
livenessProbe:
  httpGet:
    path: /liveness
    port: 8080
  initialDelaySeconds: 15
  periodSeconds: 10
  timeoutSeconds: 5
  failureThreshold: 3
readinessProbe:
  httpGet:
    path: /readiness
    port: 8080
  initialDelaySeconds: 5
  periodSeconds: 10
  timeoutSeconds: 5
  failureThreshold: 3

4. CrashLoopBackOff

오류 의미

Pod 내 컨테이너가 시작된 후 반복적으로 충돌하는 현상입니다. kubelet은 컨테이너를 재시작하지만, 컨테이너가 계속 충돌하면 점진적으로 재시작 간격을 늘리는 백오프(back-off) 메커니즘을 적용합니다.

발생 원인

  • 애플리케이션 코드 자체의 버그
  • 구성 오류 (환경 변수, 파라미터 등)
  • 필요한 종속성 누락
  • 리소스 부족 (메모리, CPU)

해결 방법

  1. 로그 확인하기
kubectl logs <pod_name>
kubectl logs <pod_name> --previous  # 이전 컨테이너 인스턴스 로그
  1. Pod 이벤트 확인하기
kubectl describe pod <pod_name>
  1. 리소스 제한 조정하기
apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: mycontainer
    image: myimage
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"

5. ImagePullBackOff & ErrImagePull

오류 의미

Kubernetes가 컨테이너 이미지를 지정된 레지스트리에서 가져오지 못할 때 발생합니다.

발생 원인

  • 잘못된 이미지 이름 또는 태그
  • 레지스트리 인증 문제
  • 네트워크 연결 문제

해결 방법

  1. 이미지 이름과 태그 확인
    배포 구성에서 이미지 이름과 태그가 올바른지 확인합니다.
  2. 레지스트리 인증 확인
kubectl get secret
  1. 네트워크 연결 테스트
# HTTP 연결 테스트
curl -v <registry-url>/v2/

# DNS 해결 테스트
dig <registry-url>
  1. 비공개 레지스트리 시크릿 생성 및 연결
# Docker 레지스트리 시크릿 생성
kubectl create secret docker-registry regcred \
  --docker-server=<registry-server> \
  --docker-username=<username> \
  --docker-password=<password> \
  --docker-email=<email>

# Pod 구성에 시크릿 연결
apiVersion: v1
kind: Pod
metadata:
  name: private-reg
spec:
  containers:
  - name: private-reg-container
    image: <your-private-image>
  imagePullSecrets:
  - name: regcred

참고