Files
llm-wiki/raw/blog-topics/spring-boot-startup-exit-code-propagation-2026-06-10.md
T

6.8 KiB
Raw Blame History

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
feature-migration-startup-contract
ca-tmpl
blog-topic
ca-tmpl
spring-boot
exit-code
startup
kubernetes
flyway
sysexits
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 후보)

  1. 두 가지 메커니즘과 동작 시점

    • 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 가 전파된다.
  2. System.exit(SpringApplication.exit(run(...))) 의 함정

    • 많은 글이 "custom exit code 를 쓰려면 main 을 이렇게 감싸라"고 처방한다.
    • 그러나 SpringApplication.exit(context, ...) 의 구현은 finally { close(context); } — context 를 닫고, 정상 부팅이면 ExitCodeGenerator bean 이 없으니 0 을 반환한다.
    • 결과: web 서버처럼 계속 떠 있어야 하는 프로세스를 부팅 직후 종료시킨다. 이 패턴은 batch/CLI(러너 완료 후 종료)용이지 long-running server 용이 아니다.
    • 교훈: "startup 실패 exit code" 와 "정상 종료 exit code" 는 다른 문제다. 전자는 예외 + ExitCodeGenerator 로 충분.
  3. 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 는 0255 exit code 를 lastState.terminated.exitCode 에 보존하지만 숫자별 자동 분기는 없음 → 실질 discriminator 는 structured log(startup.phase/error.code).
  4. migration 을 readiness 이전에 — FlywayMigrationStrategy vs ApplicationRunner

    • FlywayMigrationStrategy 는 context refresh 단계(Flyway bean 초기화)에 실행 → readiness(=ApplicationReadyEvent 이후 UP) 이전에 완료/실패. 반쯤 migrate 된 schema 가 트래픽을 받지 못한다.
    • 같은 일을 ApplicationRunner 로 하면 ready 이후 실행되어 순서 보장이 깨진다.

왜 글로 쓸 만한가

  • "startup exit code" 검색 시 나오는 다수 처방이 long-running 서버에 부적합하다는 점은 실제로 코드를 까봐야 드러난다 (SpringApplication.exitfinally close).
  • sysexits 를 빌려 쓰되 71/72 처럼 의미가 안 맞는 코드를 "표준" 이라 부르지 않는 정직한 컨벤션 설계 사례.

검증 상태

  • locally-verified: 예외별 getExitCode() = 78/70/71/72 단위 테스트, structured log 필드 단위 테스트, refresh-time 전략 구조 테스트 (app-bootstrap, 전체 check green).
  • planned: 실제 k8s pod lastState.terminated.exitCode e2e 단언, testcontainers 기반 migration 실패 로그 단언.

트리거 / Trigger

글감 / 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

  • 사실 후보:
    • ExitCodeExceptionMapperExitCodeGenerator는 동작 시점이 다르다.
    • sysexits 숫자는 POSIX 표준이 아니라 BSD 관례/조직 convention으로 다뤄야 한다.
  • 의견/해석 후보:
    • startup failure exit code와 정상 종료 exit code는 다른 문제다.

Outline seed

  1. startup failure의 exit code 전파 경로를 구분한다.
  2. SpringApplication.exit(run(...)) 패턴이 long-running server를 닫는 함정을 설명한다.
  3. 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 / 근거 후보

미해결 / Unknown

  • 아직 확인해야 할 사실: k8s pod termination exit code e2e 검증 여부.
  • 과장하면 안 되는 부분: sysexits를 POSIX 표준이라고 쓰지 않는다.

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한다.