6.8 KiB
6.8 KiB
title, source_type, status, related_branches, related_projects, tags, created, status_label, target_audience, inspiration_url, archive_url
| title | source_type | status | related_branches | related_projects | tags | created | status_label | target_audience | inspiration_url | archive_url | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| blog-topic / spring-boot-startup-exit-code-propagation-2026-06-10 | blog-topic | raw |
|
|
|
2026-06-10 | ready-for-canonical | backend-engineer |
blog-topic: spring-boot-startup-exit-code-propagation-2026-06-10
Layer:
raw/blog-topics/— 채용공고가 아닌 작업·학습·트러블슈팅에서 나온 블로그 글감 원석. canonical 정제 전 raw 후보이며,wiki/blog/직접 생성 근거가 아니다.
Parent / 부모
글감 한 줄
서버가 뜨기 전에 죽는 실패(env 누락 / migration 실패 / profile mismatch / required adapter disabled)에서 원인별 JVM exit code 를 안전하게 전파하는 Spring Boot 메커니즘과, 흔히 처방되는 System.exit(SpringApplication.exit(run(...))) 패턴이 장기 실행 서버에서는 오히려 버그인 이유.
핵심 포인트 (draft 후보)
-
두 가지 메커니즘과 동작 시점
ExitCodeExceptionMapper(bean) — context 가 active 일 때만 동작. context refresh 실패(env/profile/adapter 검증이SmartInitializingSingleton에서 throw)는context.isActive()==false라 mapper 가 호출되지 않음.ExitCodeGenerator(예외가 직접 구현) —SpringApplication.run()이 실패를 re-throw 하면, 부팅 스레드에 설치된SpringBootExceptionHandler(uncaught exception handler)가 실패 예외 체인에서getExitCode()를 읽어System.exit(code)호출. main() 을 건드리지 않아도 custom exit code 가 전파된다.
-
System.exit(SpringApplication.exit(run(...)))의 함정- 많은 글이 "custom exit code 를 쓰려면 main 을 이렇게 감싸라"고 처방한다.
- 그러나
SpringApplication.exit(context, ...)의 구현은finally { close(context); }— context 를 닫고, 정상 부팅이면ExitCodeGeneratorbean 이 없으니 0 을 반환한다. - 결과: web 서버처럼 계속 떠 있어야 하는 프로세스를 부팅 직후 종료시킨다. 이 패턴은 batch/CLI(러너 완료 후 종료)용이지 long-running server 용이 아니다.
- 교훈: "startup 실패 exit code" 와 "정상 종료 exit code" 는 다른 문제다. 전자는 예외 +
ExitCodeGenerator로 충분.
-
exit code 숫자 선택 — sysexits(3) 정합/불일치
78 EX_CONFIG(env 누락/malformed),70 EX_SOFTWARE(migration 실패) 는 BSD sysexits 의미와 정합.71 EX_OSERR("cannot fork/pipe"),72 EX_OSFILE("system file missing") 는 profile mismatch / adapter disabled 와 의미가 어긋남 → 외부 표준으로 방어 불가, 조직 internal convention 으로만 성립. 글에서 "POSIX 표준" 이라 과장하지 말 것.- k8s 는 0–255 exit code 를
lastState.terminated.exitCode에 보존하지만 숫자별 자동 분기는 없음 → 실질 discriminator 는 structured log(startup.phase/error.code).
-
migration 을 readiness 이전에 —
FlywayMigrationStrategyvsApplicationRunnerFlywayMigrationStrategy는 context refresh 단계(Flyway bean 초기화)에 실행 → readiness(=ApplicationReadyEvent 이후 UP) 이전에 완료/실패. 반쯤 migrate 된 schema 가 트래픽을 받지 못한다.- 같은 일을
ApplicationRunner로 하면 ready 이후 실행되어 순서 보장이 깨진다.
왜 글로 쓸 만한가
- "startup exit code" 검색 시 나오는 다수 처방이 long-running 서버에 부적합하다는 점은 실제로 코드를 까봐야 드러난다 (
SpringApplication.exit의finally close). - sysexits 를 빌려 쓰되 71/72 처럼 의미가 안 맞는 코드를 "표준" 이라 부르지 않는 정직한 컨벤션 설계 사례.
검증 상태
locally-verified: 예외별getExitCode()= 78/70/71/72 단위 테스트, structured log 필드 단위 테스트, refresh-time 전략 구조 테스트 (app-bootstrap, 전체checkgreen).planned: 실제 k8s podlastState.terminated.exitCodee2e 단언, testcontainers 기반 migration 실패 로그 단언.
트리거 / Trigger
- 트리거 유형:
branch-work - 트리거 날짜: 2026-06-10
- 트리거 연결 노트: raw/branch-notes/feature-migration-startup-contract
글감 / Topic seed
- 한 문장 요지: startup failure exit code는 long-running server를
SpringApplication.exit(run(...))로 감싸는 문제가 아니라 실패 예외와 Boot exit-code propagation 경로를 이해하는 문제다. - 예상 제목 후보:
- Spring Boot startup 실패 exit code를 안전하게 전파하기
SpringApplication.exit(run(...))가 서버에서 위험한 이유
핵심 주장 후보 / Claim candidates
- 사실 후보:
ExitCodeExceptionMapper와ExitCodeGenerator는 동작 시점이 다르다.- sysexits 숫자는 POSIX 표준이 아니라 BSD 관례/조직 convention으로 다뤄야 한다.
- 의견/해석 후보:
- startup failure exit code와 정상 종료 exit code는 다른 문제다.
Outline seed
- startup failure의 exit code 전파 경로를 구분한다.
SpringApplication.exit(run(...))패턴이 long-running server를 닫는 함정을 설명한다.- sysexits 관례와 ca-tmpl 내부 convention의 경계를 분리한다.
Canonical 전환 후보 / Canonical extraction candidates
wiki/projects/ca-tmpl/runtime-container-health-migration.md후보:- startup failure exit code propagation 글감.
- 필요한 추가 검증:
- 현재 ca-tmpl 코드의 exit code exception/test 존재 여부.
Sources / 근거 후보
- raw/branch-notes/feature-migration-startup-contract
- raw/official-docs/spring-boot-exit-code-generator-startup-failure
- raw/official-docs/sysexits-bsd-exit-code-convention
- raw/official-docs/kubernetes-exit-code-observability-termination
미해결 / Unknown
- 아직 확인해야 할 사실: k8s pod termination exit code e2e 검증 여부.
- 과장하면 안 되는 부분: sysexits를 POSIX 표준이라고 쓰지 않는다.
Related
- raw/official-docs/spring-boot-exit-code-generator-startup-failure
- raw/official-docs/sysexits-bsd-exit-code-convention
- raw/official-docs/kubernetes-exit-code-observability-termination
- raw/blog-topics/hikaricp-inter-knob-constraints-startup-guard-2026-06-09 — 같은
SmartInitializingSingletonfail-fast startup-guard 패턴.
Decision / 처리 결정
- 액션:
promote-to-canonical - 이유:
wiki/projects/ca-tmpl/runtime-container-health-migration.md에 startup failure exit code propagation 글감으로 반영했다. - 다음 단계: target canonical이 아직
draft이므로blogify전 sysexits 관례와 ca-tmpl 내부 convention 경계를 분리해 review한다.