Files
llm-wiki/raw/official-docs/kubernetes-exit-code-observability-termination.md
T

115 lines
12 KiB
Markdown

---
title: "Kubernetes Exit Code Observability — lastState.terminated.exitCode, terminationMessagePolicy, and failure cause discrimination"
source_type: official-doc
url: https://kubernetes.io/docs/tasks/debug/debug-application/determine-reason-pod-failure/
archive_url:
related_branches: [feature-migration-startup-contract]
related_projects: [ca-skeleton]
tags: [kubernetes, exit-code, observability, startup-failure, terminationMessage, pod-lifecycle]
created: 2026-06-09
---
# Kubernetes Exit Code Observability — lastState.terminated.exitCode, terminationMessagePolicy, and failure cause discrimination
> Layer: `raw/official-docs/` — Kubernetes 공식 문서 + API reference + GitHub 이슈 교차 확인.
## Parent / 활용 branch (필수)
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| [[raw/branch-notes/feature-migration-startup-contract]] | D7: startup exit code 78/70/71/72가 Kubernetes 환경에서 실제로 관측 가능한지, per-cause 구분이 운영상 의미 있는지 |
## 출처 / Source
- 원본 URL (주): https://kubernetes.io/docs/tasks/debug/debug-application/determine-reason-pod-failure/
- Kubernetes API reference (ContainerStateTerminated): https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#PodStatus
- Kubernetes issues: github.com/kubernetes/kubernetes/issues/78570 (terminationMessagePolicy FallbackToLogsOnError)
- komodor.com/learn/exit-codes-in-containers-and-kubernetes-the-complete-guide/ (exit code 범위 정리)
- 저자 / 조직: Kubernetes project (CNCF)
- 마지막 확인일: 2026-06-09
## 왜 저장했는지 / Why archived
D7 결정(distinct numeric exit code per startup failure cause)이 Kubernetes orchestrator에서 실제로 관측 가능한지 확인. exit code가 사실상 1로 collapse되는지, 아니면 70/71/72/78 같은 custom code가 `lastState.terminated.exitCode`에 보존되는지가 핵심 질문. 또한 structured log (D8)이 exit code (D7)을 실질적으로 대체할 수 있는지 확인.
## 핵심 인용 / Key quotes (verbatim)
> [Kubernetes API reference, ContainerStateTerminated] "exitCode — integer — * — Exit status from the last termination of the container."
> [Kubernetes docs] "Kubernetes retrieves termination messages from the termination message file specified in the `terminationMessagePath` field of a Container, which has a default value of `/dev/termination-log`."
> [Kubernetes docs, terminationMessagePolicy] "FallbackToLogsOnError will use the last chunk of container log output if the termination message file is empty and the container exited with an error. The log output is limited to 2048 bytes or 80 lines, whichever is smaller."
> [komodor guide] "Exit codes between 1-128 typically indicate the container terminated due to an internal error, such as a missing or invalid command in the image specification."
> [komodor guide] "If the Exit Code was `exit(-1)` or another value outside the 0-255 range, `kubectl` translates it to a value within the 0-255 range."
> [komodor guide] "Exit Codes 129-255 — the container was stopped as the result of an operating signal, such as SIGKILL or SIGINT."
## Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| K8S-EXIT-C1 | Kubernetes API의 `lastState.terminated.exitCode` 필드는 컨테이너의 마지막 종료 exit status를 그대로 저장한다 | [Kubernetes API ref] "exitCode — integer — * — Exit status from the last termination of the container" | `official-vendor-doc` | Kubernetes 모든 버전. 애플리케이션이 System.exit(N)으로 종료하면 N이 저장됨 | Kubernetes가 exit code를 기반으로 자동 분기 처리(특정 코드 = 특정 동작)를 한다는 뜻은 아님. 저장만 함. |
| K8S-EXIT-C2 | 0-255 범위의 커스텀 exit code (예: 70, 71, 72, 78)는 Kubernetes가 변환하지 않고 그대로 보존된다 | [komodor guide] "If the Exit Code was `exit(-1)` or another value outside the 0-255 range, `kubectl` translates it to a value within the 0-255 range." — 역설적으로, 0-255 범위 내의 코드는 변환되지 않음을 시사 | `engineering-blog` (komodor 직접 테스트 기반, 공식 문서 아님) | 0-255 범위 내 exit code. 70, 71, 72, 78 모두 이 범위 내. | 공식 Kubernetes 문서에서 이 사실을 명시적으로 확인한 것은 아님 — komodor engineering blog 수준의 evidence |
| K8S-EXIT-C3 | Kubernetes 자체는 exit code 137(SIGKILL/OOM), 143(SIGTERM) 같은 시그널 기반 코드에만 특별한 reason/label을 부여한다. 1-128 범위의 애플리케이션 exit code는 all "Error" reason으로 표시됨 | [komodor guide] "Exit codes between 1-128 typically indicate the container terminated due to an internal error"; [Kubernetes API] terminated.reason = "Error" for non-signal exits | `engineering-blog` + `official-vendor-doc` | Kubernetes 클러스터. Reason 필드는 자동 분류되지 않음. | 운영자가 kubectl로 `lastState.terminated.exitCode` 필드를 직접 쿼리하면 구분 가능. 자동 알림/라우팅에는 추가 설정 필요. |
| K8S-EXIT-C4 | terminationMessagePolicy: FallbackToLogsOnError를 설정하면 컨테이너 종료 시 마지막 2048 bytes / 80 lines의 stderr log를 kubectl describe에서 직접 확인할 수 있다 | [Kubernetes docs] "FallbackToLogsOnError will use the last chunk of container log output if the termination message file is empty and the container exited with an error. The log output is limited to 2048 bytes or 80 lines, whichever is smaller." | `official-vendor-doc` | Kubernetes 1.5+. 컨테이너가 /dev/termination-log에 직접 쓰지 않을 때 유용 | 전체 startup failure log를 캡처하는 것이 아님. 마지막 2048 bytes만 캡처됨. 긴 stack trace는 잘릴 수 있음. |
| K8S-EXIT-C5 | terminationMessagePath의 기본값은 /dev/termination-log이며, 컨테이너가 이 파일에 직접 쓴 내용이 kubectl describe pod에서 termination message로 표시된다 | [Kubernetes docs] "The default termination message path is `/dev/termination-log`. You cannot set the termination message path after a Pod is launched." | `official-vendor-doc` | Kubernetes. 컨테이너가 의도적으로 이 경로에 쓰는 경우에만 유용. Spring Boot는 기본적으로 이 경로에 쓰지 않음. | Spring Boot 앱이 이 파일에 startup failure 원인을 자동으로 쓰지 않음 — 추가 구현 필요 |
| K8S-EXIT-C6 | Kubernetes는 exit code를 기반으로 재시작 정책(restartPolicy)을 실행하지만, 특정 exit code에 따른 차별적 재시작 동작은 없다. 0 = 성공, nonzero = 실패 (restartPolicy에 따라 재시작) | [Kubernetes pod lifecycle] "Containers that fail in a pod with restartPolicy Always or OnFailure are restarted by the kubelet." — exit code N에 관계없이 동일 재시작 정책 적용 | `official-vendor-doc` | Kubernetes 모든 버전 | Kubernetes init container에서 특정 exit code (0/1 구분)는 의미가 다름. 일반 컨테이너에서는 0 외의 모든 코드가 실패로 동일하게 취급됨 |
| K8S-EXIT-C7 | kubectl로 `lastState.terminated.exitCode`를 programmatic하게 조회할 수 있다 | [Kubernetes docs] `kubectl get pod -o jsonpath='{range .status.containerStatuses[*]}{.name}{"\t"}{.lastState.terminated.reason}{"\t"}{.lastState.terminated.exitCode}{"\n"}{end}'` | `official-vendor-doc` | kubectl + Kubernetes API 접근 가능한 환경 | 이 쿼리가 alert rule이나 runbook automation으로 자동화되어 있어야 실질적으로 유용. 사람이 수동으로 kubectl 실행 시에만 의미있는 경우 운영 효율 낮음 |
## Usage Boundaries / 적용 경계
- 이 자료가 직접 증명하는 것:
- `K8S-EXIT-C1`: `lastState.terminated.exitCode` 필드는 실제 프로세스 exit status를 저장함
- `K8S-EXIT-C2`: 0-255 범위 내 커스텀 코드(70, 71, 72, 78)는 k8s가 변환하지 않고 보존됨 (engineering-blog 수준)
- `K8S-EXIT-C3`: Kubernetes는 1-128 범위 코드를 모두 "Error"로 reason 처리함 — per-cause 자동 분기 없음
- `K8S-EXIT-C4`: FallbackToLogsOnError를 설정하면 마지막 2048B 로그를 kubectl describe로 직접 확인 가능
- `K8S-EXIT-C6`: Kubernetes restartPolicy는 exit code 값과 무관하게 0/nonzero만 구분함
- 이 자료가 증명하지 않는 것:
- Kubernetes가 exit code 70/71/72/78에 자동으로 의미있는 동작을 취한다는 것 (자동 분기 없음)
- 운영자가 실제로 exit code로 startup failure 원인을 구분하는 practice가 확립되어 있다는 것
- exit code가 structured log보다 startup failure 원인 파악에 더 유용하다는 것
- 내 프로젝트에 적용하려면 추가 확인이 필요한 것:
- ca-tmpl 클러스터에서 `terminationMessagePolicy: FallbackToLogsOnError` 설정 여부
- Prometheus/Grafana alert rule이 `lastState.terminated.exitCode`를 기반으로 구성되어 있는지 — 아니라면 exit code 구분의 운영적 가치가 제한됨
- structured startup failure log (D8)이 이미 `startup.phase`, `error.code` 필드를 포함하면 exit code 대비 어느 것이 더 쉽게 조회/알림 가능한지
## Kubernetes Exit Code 관측성 실전 분석
### Per-cause exit code (D7: 78/70/71/72)의 Kubernetes에서의 실제 관측성
**보존 여부**: 0-255 범위 내 커스텀 exit code는 `lastState.terminated.exitCode`에 보존됨 (FACT, K8S-EXIT-C2, K8S-EXIT-C1).
**자동 분기 없음**: Kubernetes는 exit code 값에 따라 다른 동작(다른 재시작, 다른 알림)을 자동으로 취하지 않음. 모든 nonzero code = 동일하게 실패 취급 (FACT, K8S-EXIT-C6).
**수동 조회는 가능**: kubectl로 `lastState.terminated.exitCode`를 직접 쿼리하면 78/70/71/72 구분 가능. 그러나 이것은 사람이 수동 triage 시에만 유용하며, 자동화된 alert/runbook에는 별도 Prometheus label 추출 설정 필요 (K8S-EXIT-C7).
**실질적 가치 판단**:
- exit code discriminator가 의미있으려면: Prometheus kube_pod_container_status_last_terminated_exit_code 메트릭으로 alert rule 구성 + per-exit-code runbook 연결이 있어야 함.
- 이 설정 없이는: exit code 78과 70을 kubectl 수동 조회로만 구분 가능 → 실질적으로 "nonzero = startup failed, 원인은 로그 확인" 수준.
### Structured log (D8)과의 비교
| 항목 | D7 Exit Code | D8 Structured Log |
|---|---|---|
| Kubernetes가 자동 처리 | 없음 (저장만) | 없음 (별도 log aggregator 필요) |
| kubectl describe에서 즉시 확인 | `lastState.terminated.exitCode` 필드 (1개 숫자) | `terminationMessagePolicy: FallbackToLogsOnError`로 마지막 log 확인 가능 |
| 원인 상세 | 숫자 코드만 (lookup table 필요) | `startup.phase` + `error.code` + `error.category` 직접 포함 |
| 자동 alert 구성 용이성 | Prometheus label 추출 필요 | log aggregator (ELK/Loki) alert rule 필요 |
| 운영자 즉시 가독성 | 낮음 (78이 뭔지 알아야 함) | 높음 (startup.phase=migration, error.code=MIGRATION_FAILED) |
**결론 (INFERENCE)**: D8 structured log가 실질적 failure cause discriminator이고, D7 exit code는 "빠른 재시작 정책 분기"가 아닌 "coarse-grained triage signal" 역할. exit code와 structured log는 중복이 아니라 보완적이지만, structured log 없이 exit code만으로는 불충분하고, exit code 없이 structured log만으로도 대부분의 discriminator 역할이 가능함.
## 메모 / Notes
- Kubernetes가 exit code를 기반으로 자동 동작 분기를 하지 않으므로, D7의 per-cause 숫자(78/70/71/72)의 주된 가치는 수동 triage 일관성과 runbook lookup key임.
- `terminationMessagePolicy: FallbackToLogsOnError` + D8 structured log가 조합되면, kubectl describe pod만으로 startup failure 원인 파악이 가능 — exit code 없이도 운영 가능.
- D7과 D8은 상호 보완적이나, 둘 중 하나만 택해야 한다면 D8 structured log가 더 풍부한 정보를 제공함.
## Related / 관련
- [[raw/official-docs/spring-boot-exit-code-generator-startup-failure]] — Spring Boot에서 exit code 반환 메커니즘
- [[raw/official-docs/sysexits-bsd-exit-code-convention]] — 숫자 선택 근거 (BSD 컨벤션)
- [[raw/branch-notes/feature-migration-startup-contract]] — D7 (exit code) + D8 (structured log) 결정