Azure 상식

AKS의 이슈상황 원인과 대응방안 + Pod Lifecycle

ktzzang0601 2025. 8. 25. 22:31

1. POD FAILED 현상

  • OOMKilled / Memory cgroup 초과
  • Evicted(DiskPressure/EphemeralStorage/MemoryPressure)
  • DeadlineExceeded (Job activeDeadlineSeconds 초과)
  • Exit code ≠ 0 (프로세스 조기 종료, entrypoint 오타, 권한 문제)

2. POD FAILED 해결 방안

  • OOMKilled: 🧭 resources.requests/limits 합리화, 메모리 leak 점검, VPA/HPA 도입, GC/힙 옵션 조정
  • Evicted: 노드 DiskPressure해소(이미지/컨테이너/워킹디렉 정리), ephemeral-storage requests/limits 및 emptyDir.sizeLimit 설정, 노드풀 디스크/사이즈 증설
  • Job 실패: backoffLimit, activeDeadlineSeconds, 재시도 간격 재설계. 반복 실패면 InitContainer로 프리체크, 종속 서비스 준비(health/endpoint) 확인
  • Exit code ≠ 0 : 권한·엔트리포인트: securityContext, command/args 검증, 실행 비사용자(shell) 문제 해결

3. BackOff 계열(CrashLoopBackOff, ImagePullBackOff, ErrImagePull 등등..)

  1. CrashLoopBackOff
    a. 원인 : 어플리케이션 예외/의존 미준비/잘못된 CMD, liveness probe 오탐 등
    b. 대응방법 : 
      - Startup, Readness, Liveness Probe 분리
      - InitContainer로 의존 리소스(DB 등) 준비 확인
      - 애플리케이션 종료 신호 처리(SIGTERM)와 graceful shutdown 구성
  2. ImagePullBackOff / ErrImagePull
    a. 원인 : 레지스트리 인증, 네트워크 혹은 이미지 태그 오타
    b. 대응방법 : 
      - A
    CR 사용 시 AKS-ACR 연결(Managed Identity attach-acr), 또는 imagePullSecrets 설정
      - 프록시/NSG/DNS 확인, 사설 레지스트리면 프라이빗 엔드포인트/방화벽 예외
      - 태그 고정(immutable), imagePullPolicy: IfNotPresent 적절히 사용
  3. CreateContainerConfigError / CreateContainerError
    a.  pod pending과 동일한 대응을 통해 예방 가능

4. (추가) Nodegroup Scale-in시 고려할 Pod Lifecycle 설정

  • Scale-in 시 연결 드레이닝, 데이터 무결성 보장이 중요합니다.
  • 핵심 체크리스트는 아래와 같습니다.
    - Pod Disruption Budget(PDB): 과도한 동시 축출 방지Copy
    preStop hook + terminationGracePeriodSecondsCopy
      ->
    preStop에서 LB 등록 헤제/세션 종료/큐 flush
      -> SIGTERM 후 readiness가 자동 false되지만, 어플리케이션 자체 graceful 필요
  • readinessProbe 엄격화: 종료 직전 트래픽 유입 차단 속도를 높이고 정상 동작 중에만 Ready 유지
  • PDB + HPA 조합: 축출 가능한 파드 수와 자동 확장 균형 맞추기
  • safe-to-evict 어노테이션: 스케일인 시 지우면 안되는 파드에 다음과 같이 safe-to-evict 어노테이션 추가
  • 빈약한 스테이트풀 방지: StatefulSet은 PodManagementPolicy=Parallel와 PodAntiAffinity로 분산, 스토리지는 RWO/PV 바인딩 존 일치.

5. 참고 문서

 

6. 추가 검색 내용

① OOMKilled (메모리 초과로 프로세스 KILL)

원인
  • 컨테이너 memory.limit 초과 → 커널 OOMKiller가 프로세스를 SIGKILL(137)로 종료.
  • 애플리케이션 메모리 누수, JVM/Node/Go 등 런타임 힙/네이티브 메모리/DirectBuffer 과다.
  • 캐시/버퍼(파일 캐시)·tmpfs 사용 등도 cgroup 메모리 사용량에 포함되어 limit에 닿음.
진단
  • kubectl describe pod <pod>: Last State: Terminated, Reason: OOMKilled 확인.
  • kubectl logs <pod> --previous로 직전 크래시 직전 로그 확인.
  • 메트릭: kubectl top pod, APM/프로파일러(Heap/Alloc 곡선이 지속 증가하면 누수 의심).
  • JVM은 -XX:+HeapDumpOnOutOfMemoryError로 덤프 확인, Node.js는 –max-old-space-size와 heap snapshot, Go는 pprof(net/http/pprof) 등.
해결/예방
  • 리소스 합리화
    • requests는 실제 9599퍼센타일 사용량 근거로 잡고, limits는 requests의 1.21.5배(메모리는 너무 타이트하게 잡지 않기).
    • 메모리는 스로틀링이 없고 바로 OOM이므로 충분한 헤드룸(20~30%) 확보.
  • VPA/HPA
    • HPA는 보통 CPU 중심(또는 외부 지표), 메모리는 변동/버스트가 크면 HPA 신호로 쓰기 위험.
    • VPA는 최소 추천(“Off” 모드) 으로 켜서 요청값을 주기적으로 조정. HPA와 동시에 “자동 적용”은 충돌 우려 → 한쪽은 추천/관측용으로 운용.
  • 런타임/GC 튜닝(언어별 핵심)
    • JVM: -XX:+UseContainerSupport(JDK 8u191+), -XX:MaxRAMPercentage=60 등으로 힙이 컨테이너 제한을 존중. -Xms/-Xmx를 limit보다 여유 있게. -XX:+ExitOnOutOfMemoryError로 조기 실패, -XX:MaxDirectMemorySize도 관리.
    • Node.js: --max-old-space-size=<MB> 지정(컨테이너 limit 기준 여유 있게).
    • Go: GOGC 조정(예: 100→150), 메모리 재사용 패턴 점검, pprof로 hot path 추적.
    • Python: tracemalloc, MALLOC_ARENA_MAX=2 등으로 arena 폭주 억제.
  • 코드 측면: 캐시 상한, large object 재분할, 스트리밍 처리, 연결/버퍼 누수, goroutine/쓰레드 누수 제거.

② Evicted (노드 DiskPressure로 퇴거)

원인
  • 노드의 nodefs(루트 디스크) 혹은 imagefs(컨테이너 레이어/이미지 디스크)가 임계치 이하로 남음 → kubelet이 Eviction 정책에 따라 Pod 축출.
  • 주범: 컨테이너 로그 과다, emptyDir 무제한 사용, 이미지 누적, 워킹 디렉터리 대용량 쓰기.
진단
  • kubectl describe pod <pod> 이벤트에 Evicted + DiskPressure 메시지.
  • kubectl describe node <node>에서 Conditions: DiskPressure=True.
  • 노드 진입(예: kubectl debug node/<node> --image=nicolaka/netshoot) 후 /var/lib/containerd, /var/log/containers, /var/lib/kubelet 용량 확인.
해결/예방 (우선순위순)
  1. 즉시 압력 해소
    • 불필요 이미지/컨테이너/죽은 Pod 로그 정리(운영 표준 작업으로 스크립트화).
    • 로그 레벨 하향, 애플리케이션 로그 로테이션(파일에 쓸 경우) 적용.
  2. 리소스 한도 명시
    • ephemeral-storage requests/limits로 컨테이너 임시 저장소 상한을 명확히:
    • emptyDir 용량 제한:
  3. 노드/런타임 레벨 설정
    • kubelet eviction 임계값(기본값 유지 권장, 필요시 완화는 신중): 메모리/디스크 가용률 임계치.
    • 컨테이너 로그 로테이션(kubelet):
      containerLogMaxSize, containerLogMaxFiles를 적정선으로(예: 10Mi, 5).
  4. 용량 확장/구조 개선
    • 노드풀 디스크/사이즈 증설(특히 /var/lib/containerd가 작을 때).
    • 이미지 다이어트(멀티스테이지, alpine/distroless, 불필요 레이어 제거).
    • 대용량 임시파일은 오브젝트 스토리지/외부 볼륨으로 오프로딩.

③ Job 실패 (backoffLimit, activeDeadlineSeconds 등)

원인
  • 작업 자체 실패(종속 서비스 준비 전 시작, 입력 데이터 불완전, 권한/네트워크 문제).
  • 재시도/시간 제한 정책이 워크로드 특성에 맞지 않음 → 불필요한 반복 실패, 장시간 점유.
진단
  • kubectl describe job <job>: .status.failed, conditions 확인.
  • kubectl get pods -l job-name=<job> -o wide + 실패한 Pod 로그/exit code 확인.
  • 실패 패턴(즉시 실패 vs 오래 걸리다 timeout vs 간헐 성공)을 분리.
해결/설계 팁
  • 재시도/시간 제한 재설계
  • 종속성 프리체크(InitContainer)
    • DB/큐/HTTP 엔드포인트 준비 확인 후 본 컨테이너 실행:
  • PodFailurePolicy(고급)
    • 특정 exit code는 즉시 실패로 간주하거나 재시도 제외 등 세밀 제어(지원 버전에서).
  • Idempotency & 체크포인트
    • 같은 입력 재처리에도 안전하도록 설계, 중간 산출물은 외부 스토리지에 체크포인트.
  • 리소스/네트워크
    • Job이 CPU/메모리/IO 부족으로 지연되면 요청 상향.
    • 대량 출력은 PVC/오브젝트 스토리지 사용, stdout 과다로그 금지.
  • 스케줄링
    • 많은 Job 동시 제출 시 parallelism 제한, 큐잉 시스템 도입.
    • startingDeadlineSeconds(CronJob)로 지연 시작 방지.

④ Exit code ≠ 0 (권한/엔트리포인트/커맨드 등)

원인
  • 실행 파일 권한/소유자 문제, 잘못된 엔트리포인트/인자, 셸 기능 필요하지만 sh -c 없이 실행, 잘못된 아키텍처 이미지, shebang 누락·CRLF 라인엔딩 등.
  • securityContext로 비루트 사용자 실행 시, 파일/포트/디렉터리 권한 미흡.
  • 마운트한 볼륨이 바이너리를 가려버리는 경로 충돌.
진단
  • Pod 로그의 에러 메시지 + 종료 코드 표:
    • 126 실행 권한/실행 불가, 127 명령 없음, 137 OOM/SIGKILL, 139 세그폴트, 1 일반 에러.
  • kubectl exec로 컨테이너 진입 → ls -l, file ./app, ldd ./app, 권한/의존 확인.
  • kubectl describe pod의 command/args, securityContext 확인.
해결/예방
  • 엔트리포인트/인자 검증
    • 스크립트는 #!/bin/sh + chmod +x + LF 줄바꿈 유지.
  • securityContext 정렬
    • 애플리케이션이 쓰기 필요한 경로(/tmp, /app/data 등)는 writable 볼륨으로 마운트:
  • 아키텍처/의존성
    • ARM 노드에 amd64 이미지(또는 반대)면 exec format error → 멀티아키 이미지 사용.
    • 네이티브 라이브러리 의존(예: glibc vs musl) 맞추기.
  • 경로 충돌 방지
    • volumeMount가 /app을 덮어 이미지 내 바이너리를 가리지 않는지 확인.

① CrashLoopBackOff

컨테이너가 시작하자마자 비정상 종료(exit≠0) → kubelet이 지수적 back-off(수초→수분 상한)로 재시도하는 상태.

주된 원인

  • 앱 예외/환경 값 누락/잘못된 command / args/엔트리포인트 스크립트 오류
  • 의존 서비스(DB, MQ, API) 준비 전 시작 → 초기화 실패 후 즉시 종료
  • livenessProbe 오탐(초기 기동이 긴 서비스인데 너무 이른 검사)
  • 종료 시그널(SIGTERM) 미처리 → 다음 기동에서 락/캐시/포트 충돌로 재크래시

진단 포인트

  • kubectl describe pod <pod> 이벤트: Back-off restarting failed container
  • 직전 크래시 로그: kubectl logs <pod> -c <container> --previous
  • 종료 코드/시그널: Last State: Terminated (ExitCode, Reason)
  • 프로브 실패 이력: describe 이벤트에 Liveness probe failed / Readiness probe failed

해결/예방

A. 프로브 분리/튜닝
  • startupProbe 통과 전에는 livenessProbe가 동작하지 않음(초기 오탐 방지).
  • readiness는 트래픽 유입 제어, liveness는 진짜 장애만 재시작.
B. 의존성 프리체크(InitContainer)
C. 정상 종료/그레이스풀 셧다운
  • 앱이 SIGTERM 수신 시 커넥션 종료, 워커 중단, 임시파일 정리하도록 구현.
D. 커맨드/엔트리포인트 가드
E. 기타
  • 컨테이너가 너무 빨리 죽는 경우 재현을 위해 liveness 임시 비활성화 후 원인 파악
  • 로그/메트릭 수집기(APM, 프로파일러)로 초기화 경로 예외 포착
  • Job성 작업이면 Deployment 대신 Job + backoffLimit 사용(잘못된 재시작 루프 방지)

② ImagePullBackOff / ErrImagePull

이미지 풀 실패. ErrImagePull은 즉시 오류, 그 다음부터 back-off로 재시도하며 ImagePullBackOff.

주된 원인

  • 인증 실패(프라이빗 레지스트리/ACR 권한 없음)
  • 네트워크/DNS/프록시/방화벽(ACR Private Endpoint/DNS 설정 누락 포함)
  • 이미지 참조 오류(리포지토리/태그 오타, 삭제된 태그)
  • Docker Hub 레이트 리밋/미러 필요

진단 포인트

  • kubectl describe pod 이벤트의 정확한 에러 메시지:
    • unauthorized: authentication required
    • manifest unknown
    • dial tcp ... i/o timeout(네트워크)
  • 서비스어카운트에 연결된 imagePullSecrets 유효성:
    kubectl get sa <sa> -o yaml
  • 레지스트리 FQDN DNS/443 연결성: 디버그 파드에서 nslookup, curl -I https://<registry>

해결/예방

A. 인증 구성
  • AKS ↔ ACR(Managed Identity)
    AKS 클러스터 MI에 ACR AcrPull 권한을 부여(예: --attach-acr).
    운영 점검 포인트: 클러스터(혹은 노드풀) MI의 AcrPull 역할 바인딩과 ACR 방화벽/네트워크 규칙.
  • 이미지 풀 시크릿(기타 레지스트리)
B. 네트워크/DNS
  • 프록시 환경이면 노드(컨테이너 런타임) 에 프록시 설정 필요(파드 내부 프록시 설정과 별개).
  • ACR Private Endpoint 사용 시:
    • 노드 VNet에 privatelink.azurecr.io(및 데이터/토큰 FQDN)용 Private DNS 링크
    • NSG/방화벽에서 443 허용, 라우팅이 사설 IP로 향하는지 확인
C. 이미지 참조 안정화
  • latest 지양, 태그 고정 + 불변(tag immutability) 또는 digest 고정:
  • Docker Hub는 조직 계정/미러/캐시 레지스트리 검토
D. 빠른 확인
  • 같은 네임스페이스에서 작은 테스트 파드로 pull 시도해 에러 메시지 비교
  • 이벤트 나열: kubectl get events --sort-by=.lastTimestamp

③ CreateContainerConfigError / CreateContainerError

스케줄링은 됐는데 컨테이너 생성 단계에서 실패.
  • CreateContainerConfigError: 컨테이너 설정이 잘못됨(볼륨/CM/Secret/환경 참조 오류 등)
  • CreateContainerError: 런타임가 컨테이너 생성 자체를 실패(OCI runtime, 보안 프로필, 마운트 문제 등)

주된 원인

  • 존재하지 않는 ConfigMap/Secret/PVC 참조(이름 오타/네임스페이스 불일치/키 누락)
  • volumeMounts ↔ volumes 불일치, subPath 대상이 파일/디렉터리와 안 맞음
  • PodSecurity(Restricted)/Seccomp/AppArmor/Capabilities 정책으로 거부
  • 읽기 전용 루트(readOnlyRootFilesystem: true)인데 앱이 /tmp 등 쓰기 시도
  • 잘못된 UID/GID로 권한 부족, 또는 존재하지 않는 사용자로 실행

진단 포인트

  • kubectl describe pod 이벤트의 상세 메시지:
    예) configmap "app-cm" not found, secret key "password" not found,
    MountVolume.SetUp failed, Error: container has runAsNonRoot and image has non-numeric user
  • 리소스 존재 확인:
    • kubectl get cm/secret/pvc <name> -n <ns>
    • 키 확인: kubectl get secret <name> -o yaml(base64 decode)
  • 보안 거부 로그: 이벤트에 denied by policy/seccomp/apparmor 표기

해결/예방 스니펫

A. CM/Secret 정확 매칭
B. subPath/마운트 정합
C. 보안 컨텍스트/쓰기 경로
D. PodSecurity 정책과의 합의
  • Restricted 수준 네임스페이스면 hostPath/특권/NET_RAW 등 차단.
    → 매니페스트에서 금지 기능 제거/대체, 혹은 정책-네임스페이스 레벨 조정.
E. PVC 관련(경계)
  • PVC 미바인드면 보통 Pod가 Pending으로 멈춥니다.
    CreateContainerConfigError라면 마운트/경로/권한 구성 오류 가능성에 초점.
F. 사전 검증/디버깅
  • 로컬 스키마 검증: kubeconform/kubectl apply --dry-run=client -f ...
  • 이벤트 타임라인: kubectl get events --sort-by=.lastTimestamp
  • 에페메럴 디버그 컨테이너로 마운트/권한 확인:
    kubectl debug -it pod/<pod> --target=<container> --image=nicolaka/netshoot