61 KiB
title, source_type, status, branch, parent_branch, related_projects, governing_docs, tags, created, target_merge, status_label, id, kind, project, work_item, inherits, refines, overrides, depends_on, contract_packet, contract_packet_sha256
| title | source_type | status | branch | parent_branch | related_projects | governing_docs | tags | created | target_merge | status_label | id | kind | project | work_item | inherits | refines | overrides | depends_on | contract_packet | contract_packet_sha256 | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| branch / feature-runtime-health-lifecycle-contract | branch-note | raw | feature-runtime-health-lifecycle-contract |
|
|
|
2026-05-21 | in-progress | BR-CA-SKELETON-OPERATIONAL-CONTRACT-013 | project-work-item | ca-skeleton-operational-contract | WI-CA-SKELETON-OPERATIONAL-CONTRACT-013 |
|
1 | 3924b8c0f447ff7dd65b9e109da6dadaac2055bb945f0db17844ea22b8e8e0fd |
branch: feature-runtime-health-lifecycle-contract
Layer:
raw/branch-notes/— runtime health와 application lifecycle 실패 계약을 정의합니다.
부모 (필수)
- Parent project (canonical SSOT): raw/project-notes/ca-skeleton-operational-contract
ca-skeleton 은 별도 root branch 없이 project-note 가 SSOT 역할. 본 feature branch 는 project-note 의 운영 계약 중 해당 영역 (§<관련 섹션>) 의 결정/근거/금지 사항을 정제한다.
브랜치 계약 패킷
- 생성 시 프로젝트 개정:
1 - 패킷 스키마:
contract_packet: 1 - 완료 조건: startup·readiness·shutdown lifecycle test가 통과한다
상속한 프로젝트 결정
| Decision Ref | Project Summary | Branch Application | Source |
|---|---|---|---|
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-MIGRATION-001@1 |
Flyway는 startup에서 실행하고 migration 완료 후에만 readiness를 healthy로 전환한다 | Work Item 완료 조건에 적용 | raw/project-notes/ca-skeleton-operational-contract |
브랜치 지역 결정
기존 branch-local 결정은 아래
## Decision Evidence Map / 결정-근거 매핑의 D-row가 소유하며 이 packet에서 복제하지 않는다.
| Decision ID | Decision | Relation | Supporting Claims | Status |
|---|
선언한 예외
| Override ID | Overrides | Reason | Approval | Status |
|---|
목표
서비스는 요청 처리 중에만 실패하지 않습니다. startup, migration, readiness, graceful shutdown, scheduler, async executor, resource exhaustion 같은 lifecycle 표면도 skeleton 기본 기준에 포함되어야 합니다.
- 이슈:
- PR:
범위
포함 범위
- actuator health/readiness/liveness 기준.
- graceful shutdown 기준.
- startup validation 기준.
- scheduled job 실패 기준.
- async executor/thread pool rejection 기준.
- resource exhaustion 분류.
- system clock/timezone 기준.
제외 범위
- Kubernetes manifest 작성.
- cloud provider specific health check.
- scheduler business job 구현.
근거 (필수, 최소 1개+)
본 branch의 결정 근거. 상세 비교는 §외부 근거 / 대안 조사 (있다면) 참조.
| Source | 정당화하는 결정 |
|---|---|
| raw/official-docs/runtime-health-k8s-probes-official | K8s liveness/readiness/startup probe 공식 |
| raw/official-docs/runtime-health-spring-actuator-groups | Spring Boot Actuator Health Groups 공식 (ca-tmpl 결정과 정합 |
| raw/official-docs/runtime-health-istio-mesh-health-check | mTLS 환경 편의성 vs sidecar/app 살아있음 구분 불명확 |
| raw/company-tech-blogs/runtime-health-datadog-engineering-graceful-shutdown | Datadog preStop 5s + drain 20s + grace 35s 비율 보강 |
| raw/official-docs/k8s-configure-probes-task-page | D5 startup probe budget 산식 (failureThreshold × periodSeconds) verbatim + D11 startup validation scope (legacy / slow-starting 분리) 정당화 |
| raw/official-docs/k8s-pod-lifecycle-probes-concept | D7 timeoutSeconds vs periodSeconds 의미 구분 — 4가지 probe 메커니즘이 "단일 호출" 단위임을 정의 + probe outcome 정의 |
| raw/official-docs/rfc3339-datetime-utc | D12 UTC 강제의 IETF Standards Track 근거 ("Z" suffix 의미 + UTC interoperability 권고) |
| raw/official-docs/spring-smartlifecycle-reference | D4 graceful shutdown 의 phase ordering (ascending start / descending stop) + stop(Runnable) async + DefaultLifecycleProcessor phase-level timeout 메커니즘 |
외부 근거 / 대안 조사 (2026-05-22 — Group G-D: Runtime Health Lifecycle)
본 branch의 liveness/readiness/startup probe 3-endpoint 분리 + Required vs Optional Dependency Matrix + UTC + NTP drift >5s readiness fail 결정에 대한 외부 source.
- 채택 결정 (3-endpoint 분리 + Dependency Matrix):
- raw/official-docs/runtime-health-k8s-probes-official — K8s liveness/readiness/startup probe 공식
- raw/official-docs/runtime-health-spring-actuator-groups — Spring Boot Actuator Health Groups 공식 (ca-tmpl 결정과 정합)
- 검토한 대안:
- 대안 1: Single /health endpoint (legacy) — K8s 공식이 분리 권장
- 대안 2: Custom HealthIndicator beans — Spring 기본, 단 default readiness는 외부 dependency 미포함이라 ca-tmpl이 명시적으로 readiness group에 DB/broker 묶음
- 대안 3: Service mesh-based health (Istio) — raw/official-docs/runtime-health-istio-mesh-health-check (mTLS 환경 편의성 vs sidecar/app 살아있음 구분 불명확)
- 사례: raw/company-tech-blogs/runtime-health-datadog-engineering-graceful-shutdown — Datadog preStop 5s + drain 20s + grace 35s 비율 보강
- 비교 핵심: ca-tmpl 3-endpoint 분리 + 150s startup budget은 K8s 공식 + Spring Actuator Groups와 정합. Spring default readiness가 외부 dependency 미포함이라 ca-tmpl이 명시적 readiness group으로 보강. Istio mesh health는 sidecar 살아있음/app 살아있음 구분 어려움.
TODO
TODO drained 2026-05-22 — 결정은 아래 "결정 사항" / "Health Endpoint Contract" / "Required vs Optional Dependency Matrix" / "Startup Validation Scope" / "Decisionized Work Items" 참조. actuator endpoints/graceful shutdown/startup validation/scheduler/executor/resource exhaustion/timezone-clock 모두 표 또는 결정 라인으로 반영됨. 잔존 TODO 없음.
Work Item Contract
각 TODO는 아래 판정 단위로 재작성되어야 canonical 승급 가능합니다. TODO가 단순히 기준 작성으로 남아 있으면 이 branch는 완료로 보지 않습니다.
| field | required | rule |
|---|---|---|
| Decision | yes | 구현자가 선택해야 하는 기본값 |
| Allowed | yes | 허용되는 예외와 조건 |
| Forbidden | yes | 절대 금지되는 구현/문서 상태 |
| Required registry update | conditional | error/env/header/log/metric/capability 변경 시 필수 |
| Required contract test | yes | 계약 위반 시 실패해야 하는 테스트 |
| Failure condition | yes | review/build에서 실패로 판정할 상태 |
| Canonical extraction target | yes | wiki/projects 승급 위치 |
진행 중 메모
- readiness 실패와 liveness 실패는 운영 의미가 다릅니다.
결정 사항
- 2026-05-21: runtime lifecycle도 non-business operational contract에 포함.
- 2026-05-22: endpoint shape owner는 이 branch. management actuator security branch는 exposure/auth policy만 소유.
- 2026-05-22: startup probe를 별도로 두고 migration/startup validation 중 readiness/liveness 오판을 막음.
- 2026-05-22: graceful shutdown timeout은 app runtime과 deployment manifest sync table에서 같은 값을 사용.
- 2026-05-22: startup probe timeout =
initialDelaySeconds=10,periodSeconds=5,failureThreshold=30(최대 150s, migration 포함). 초과 시 K8s가 SIGKILL. - 2026-05-22: graceful shutdown total budget = 35s (
terminationGracePeriodSeconds). app shutdown timeout = 20s, preStop sleep = 5s, safety margin = 10s. - 2026-05-22: startup probe single-call timeout 30s × failureThreshold 30 × periodSeconds 5s = total budget 150s. container-runtime의 single timeout 30s는 single probe call 한도. 150s는 startup 전체 한도(migration 포함). 두 수치는 다른 축.
- 2026-05-22: multi-instance claim parsing SSOT는
feature-env-driven-runtime-configuration의APP_MULTI_INSTANCE_ENABLEDflag. 본 branch는 readiness probe 시 이 flag와 distributed lock contract test 결과의 일치 verify (consume only).
결정-근거 매핑
각 결정이 어떤 raw source claim 으로 뒷받침되는지 명시한다.
Decision ID는 이 branch-note 안에서 안정적으로 유지한다.
| Decision ID | Decision | Supporting Claims | Evidence Strength | Open Risk |
|---|---|---|---|---|
| D1 | runtime lifecycle 도 non-business operational contract 에 포함 | UNSUPPORTED_DECISION (scope 결정은 내부 운영 정책) | N/A | branch scope 결정 — 외부 표준 인용 대상 아님 |
| D2 | endpoint shape owner = 본 branch, management actuator security branch 는 exposure/auth policy 만 소유 | UNSUPPORTED_DECISION (SSOT ownership 분할) | N/A | branch ownership 정책 |
| D3 | startup probe 별도 endpoint — migration/startup validation 중 readiness/liveness 오판 방지 | raw/official-docs/runtime-health-k8s-probes-official.md#K8S-PROBE-C4, raw/official-docs/runtime-health-k8s-probes-official.md#K8S-PROBE-C5 (startup probe 가 성공할 때까지 liveness/readiness 실행 안 함 + startup 실패 시 kubelet kill) |
official-vendor-doc (K8s 공식 — startup probe 가 느린 초기화 보호) |
K8S-PROBE-C4 Usage Boundary: startup probe 미설정 시 동작은 본 인용 범위 밖. Spring Boot 가 startup 전용 group 을 default 제공하는지는 SB-HEALTH-C1 에 명시 없음 (liveness + readiness 만) |
| D4 | graceful shutdown timeout = app runtime ↔ deployment manifest sync table 동일값 | Mechanism SUPPORTED: raw/official-docs/spring-smartlifecycle-reference.md#SPRING-SMARTLC-C3 (startup ascending / shutdown descending phase 순서 — web server 보다 outbound 컴포넌트가 먼저 stop 되는 mechanism), raw/official-docs/spring-smartlifecycle-reference.md#SPRING-SMARTLC-C7 (stop(Runnable) async + DefaultLifecycleProcessor 의 phase-level timeout 대기 mechanism). Quantitative stays UNSUPPORTED: app runtime ↔ deployment manifest 의 동일값 강제 + 35s/20s/5s/10s 조합 자체는 인용 자료에 직접 spec 없음. company-tech-blog RH-DD-C1~C4 는 needs-confirmation (verbatim 미확보) |
official-vendor-doc (Spring Framework — mechanism only) + UNSUPPORTED (quantitative sync 값) |
SPRING-SMARTLC-C7 Does not prove: DefaultLifecycleProcessor 의 timeout default 값 (30s) 은 본 인용 범위 밖. company-tech-blog 자체가 needs-confirmation — official best practice 표현 금지. 35s/20s/5s/10s 가 "Datadog 권장 범위 내" 진술은 검증 실패. CODE DRIFT: ca-tmpl 실측값은 executor await 19s + server phase timeout 30s — §Audit SHUTDOWN_BUDGET_DRIFT 참조 |
| D5 | startup probe timeout = initialDelaySeconds=10, periodSeconds=5, failureThreshold=30 (최대 150s, migration 포함) |
Mechanism SUPPORTED: raw/official-docs/k8s-configure-probes-task-page.md#K8S-PROBE-TASK-C2 (startup probe maximum budget = failureThreshold × periodSeconds 의 단일 문장 verbatim — "30 * 10 = 300s" 예시로 산식 직접 명시) + raw/official-docs/runtime-health-k8s-probes-official.md#K8S-PROBE-C6 (periodSeconds default 10s — ca-tmpl 은 5s 로 override). Quantitative stays UNSUPPORTED: ca-tmpl 의 구체 값 initialDelaySeconds=10 / periodSeconds=5 / failureThreshold=30 자체는 내부 운영 가정 — 인용 자료의 예시는 30 × 10 = 300s 이며 ca-tmpl 의 30 × 5 = 150s 가 Spring Boot 콜드스타트를 cover 한다는 실측 부재 (Claims To Verify 참조) |
official-vendor-doc (산식 mechanism) + UNSUPPORTED (정량 10/5/30) |
K8S-PROBE-TASK-C2 Does not prove: initialDelaySeconds 가 budget 에 포함되는지는 본 인용 단독으로 명시 안 됨 (C4 권고와 조합 필요). failureThreshold / timeoutSeconds / initialDelaySeconds default 값도 본 capture 에서 직접 증명 안 됨 — ca-tmpl 의 10/5/30 은 외부 표준이 아닌 ca-tmpl 운영 가정 |
| D6 | graceful shutdown total budget = 35s (terminationGracePeriodSeconds), app shutdown timeout = 20s, preStop sleep = 5s, safety margin = 10s | UNSUPPORTED_DECISION (인용 자료에 35s/20s/5s/10s 정량 spec 직접 근거 없음 — RH-DD-C2 의 "5–10s preStop + 10–30s drain + 30–60s grace" 도 verbatim 미확인, needs-confirmation) |
company-case-study (Datadog blog — verbatim 미확인) |
정량 값은 ca-tmpl 운영 가정. company-tech-blog 의 "5–10s/10–30s/30–60s" 도 needs-confirmation — official 권장 아님. CODE DRIFT: 코드는 app shutdown 20s 가 아니라 executor await 19s (AsyncExecutorConfig:46 "container 20s budget − 1s cleanup margin") + server phase timeout 30s (APP_SERVER_SHUTDOWN_TIMEOUT default) — §Audit SHUTDOWN_BUDGET_DRIFT |
| D7 | startup probe single-call timeout 30s vs total budget 150s = 다른 축 명시 | Mechanism SUPPORTED: raw/official-docs/k8s-pod-lifecycle-probes-concept.md#K8S-POD-LC-C4 (httpGet probe = 단일 HTTP GET 호출 — timeout 적용 단위), K8S-POD-LC-C5 (exec probe = 단일 명령 실행), K8S-POD-LC-C6 (tcpSocket probe = 단일 TCP 연결), K8S-POD-LC-C7 (grpc probe = 단일 RPC 호출). 4가지 probe 메커니즘 모두 "단일 호출의 결과를 평가" 하므로 timeout 은 호출 단위, period 는 반복 주기라는 두 축 구분이 메커니즘 정의로부터 함의됨 + raw/official-docs/runtime-health-k8s-probes-official.md#K8S-PROBE-C6 (periodSeconds default 10s — period 축 근거). Quantitative stays UNSUPPORTED: single-call timeout 30s 의 정량 값은 본 branch 인용 자료에 직접 verbatim 없음 — container-runtime spec 별도 필요 |
official-vendor-doc (timeout vs period 축 구분 mechanism) + UNSUPPORTED (30s 단일 값) |
K8S-POD-LC-C4~C7 Does not prove: periodSeconds / timeoutSeconds 의 단일 문장 verbatim 정의 — 본 capture 의 configuration fields 섹션이 truncate (concept 페이지 NOTE 참조). 의미 구분은 메커니즘 정의로부터 간접 정당화 |
| D8 | multi-instance claim parsing SSOT = feature-env-driven-runtime-configuration consume only |
UNSUPPORTED_DECISION (SSOT 분할은 내부 운영 정책) | N/A | branch ownership 분할. CODE 정합: StartupSafetyValidator:59 validateMultiInstance() 가 APP_MULTI_INSTANCE_ENABLED=true 시 5개 coordination bean (distributedLockProvider 등) 존재를 assert — owner 는 feature-env-driven-runtime-configuration + feature-distributed-lock-contract (§엣지·실패·의존) |
| D9 | liveness = JVM process can continue, readiness = traffic + required deps ready, startup = startup/migration validation 완료 | raw/official-docs/runtime-health-spring-actuator-groups.md#SB-HEALTH-C1, raw/official-docs/runtime-health-spring-actuator-groups.md#SB-HEALTH-C2, raw/official-docs/runtime-health-spring-actuator-groups.md#SB-HEALTH-C3, raw/official-docs/runtime-health-spring-actuator-groups.md#SB-HEALTH-C6, raw/official-docs/runtime-health-k8s-probes-official.md#K8S-PROBE-C1, raw/official-docs/runtime-health-k8s-probes-official.md#K8S-PROBE-C3 |
official-vendor-doc (Spring Actuator + K8s 공식) |
SB-HEALTH-C3 Does not prove: readiness 가 자동으로 외부 의존성 실패에 반응하는 것 아님 — application code 가 publish 해야 함. ca-tmpl 의 "readiness 에 외부 dependency 포함" 은 SB-HEALTH-C8 (needs-confirmation) — default 모델과 어긋날 가능성 |
| D10 | Required vs Optional Dependency Matrix (primary DB required / primary cache conditional / message broker optional·fail-open / notification adapter optional) | raw/official-docs/runtime-health-spring-actuator-groups.md#SB-HEALTH-C7 (health group 의 CompositeHealthContributor include/exclude 메커니즘 존재) |
official-vendor-doc (부분) |
SB-HEALTH-C7 Does not prove: 외부 dependency 를 readiness 에 포함시키는 권장/비권장 정책은 본 인용 범위 밖. SB-HEALTH-C8 가 needs-confirmation — Spring 의 "default readiness 는 외부 의존성 미포함" verbatim 부재 |
| D11 | Startup validation = env var presence + DB schema migration history + required adapter bean — external endpoint reachability 는 startup-time 검사하지 않음 | Mechanism SUPPORTED: raw/official-docs/k8s-configure-probes-task-page.md#K8S-PROBE-TASK-C1 (startup probe 의 일차 use case = legacy / slow-starting 워크로드 보호 — startup validation 의 외부 dependency 는 startup probe 가 cover 한다는 분리 정당화), K8S-PROBE-TASK-C4 ("If your container usually starts in more than initialDelaySeconds + failureThreshold × periodSeconds, you should specify a startup probe that checks the same endpoint as the liveness probe" — runtime probe 로 외부 reachability 위임하는 메커니즘 정당화). Scope decision stays partially UNSUPPORTED: env var presence / DB migration history / adapter bean 의 각 항목이 startup validation 에 포함되어야 한다는 공식 spec 없음 — ca-tmpl 운영 가정 |
official-vendor-doc (startup probe ↔ runtime probe 분리 mechanism) + UNSUPPORTED (validation 항목 구성) |
K8S-PROBE-TASK-C1 Does not prove: startup probe 가 모든 워크로드 default 라는 뜻 아님 — 본 인용은 "legacy applications" 한정. K8S-PROBE-TASK-C4 의 "should... the same endpoint" 는 권고 — startup probe endpoint 가 liveness 와 반드시 같아야 하거나 달라야 한다는 강제 아님. "startup-time 외부 endpoint 검사 anti-pattern" 의 공식 경고 자체는 본 capture 에 없음. CODE 정합: 실 구현은 sibling feature-migration-startup-contract (RequiredEnvironmentValidator/MigrationStartupRunner/StartupSafetyValidator, exit 78/70/71/72) — 본 branch 는 scope policy owner, 코드/에러코드는 위임 (§Audit OWNERSHIP_DRIFT) |
| D12 | JVM timezone UTC 강제 + NTP drift > 5초 시 readiness fail 검토 | UTC part SUPPORTED: raw/official-docs/rfc3339-datetime-utc.md#RFC3339-C1 ("Z" suffix = UTC offset 00:00, ICAO "Zulu" 정의), RFC3339-C2 ("true interoperability is best achieved by using Coordinated Universal Time (UTC)" — local timezone rule 의 daylight saving 복잡성으로 인한 IETF Standards Track 권고). 5s drift stays UNSUPPORTED: NTP drift > 5초 threshold 의 정량 값은 RFC 3339 범위 밖 — NTP (RFC 5905) / NIST 별도 raw 필요. health endpoint timestamp 가 readiness 에 미치는 영향의 mechanism 도 본 RFC 범위 밖 |
official-standard (UTC 권고 — IETF RFC 3339 Standards Track) + UNSUPPORTED (5s threshold + readiness 연동) |
RFC3339-C2 Does not prove: "UTC 만 허용" strict MUST 아님 — best achieved by 는 권고 (numeric offset 도 syntactically valid). NTP drift threshold 의 정량 spec 자체는 본 RFC 범위 밖 — Claims To Verify 의 NTP 5초 threshold 검증 항목 참조. CODE 정합: Clock.systemUTC() 는 IdempotencyConfig:27 에 실재 (UTC clock actually-implemented). JVM -Duser.timezone=UTC / TZ=UTC 는 container env (owner feature-container-runtime-contract). NTP-drift readiness check 는 코드 부재 = planned |
| D13 | Service mesh-based health (Istio) 대안 거부 | raw/official-docs/runtime-health-istio-mesh-health-check.md#RH-IST-C1, raw/official-docs/runtime-health-istio-mesh-health-check.md#RH-IST-C2, raw/official-docs/runtime-health-istio-mesh-health-check.md#RH-IST-C3 (mTLS + httpGet probe 실패 / probe rewrite default 활성화 / sidecar 가 response body strip) |
official-vendor-doc (Istio 공식) |
RH-IST-C2 Does not prove: probe rewrite 가 application 자체의 deadlock 을 감지한다는 뜻 아님 — sidecar→app HTTP probe 통과만 확인. ca-tmpl 의 "sidecar/app 살아있음 구분 불명확" 평가 와 정합 |
Health Endpoint Contract
| endpoint | shape owner | default meaning | failure condition |
|---|---|---|---|
/actuator/health/liveness |
runtime-health | JVM process can continue | dependency outage alone fails liveness |
/actuator/health/readiness |
runtime-health | can receive traffic and required deps ready | migration/startup validation 중 healthy |
/actuator/health/startup |
runtime-health | startup/migration validation completed | absent startup gate in deployable profile |
⚠️ 구현 상태 =
planned: ca-tmpl 코드에는 현재 customGET /healthcheck(HealthcheckController:16,{"status":"UP"}) 만 존재하며, 위 3개 actuator probe endpoint + Spring Boot Actuator Health Groups 설정은 미작성이다. 상세 + reconcile 권고는 §AuditHEALTH_ENDPOINT_NOT_IMPLEMENTED, 구현 절차는 §구현 가이드 1 참조.
Required vs Optional Dependency Matrix
이 branch는 dependency taxonomy 표만 owns. 실제 dependency 분류는 integration-adapter-templates와 cross-link.
| dependency type | required | startup validation | readiness 영향 |
|---|---|---|---|
| primary DB | yes | connection + migration history | unavailable → readiness fail |
| primary cache (Redis enabled 시) | conditional | ping | unavailable → degraded ready (cache-aside fallback) |
| message broker (Kafka, outbox publish) | no — fail-open | none (producer lazy) | unavailable → degrade (outbox 가 DB 보존 후 retry; readiness 미반영) |
| notification adapter (Slack/Email) | no | none | unavailable → degrade |
dependency taxonomy 표의 owner는 본 branch. 실제 adapter별 분류(Kafka/Redis/Slack/Email 등)와 fail-open/closed 정책 SSOT는 integration-adapter-templates branch consume. 양방향 cross-link.
Startup Validation Scope
- env var presence + type/range 검증.
- DB schema migration history 일치 확인.
- required adapter bean 등록 확인.
- external endpoint reachability는 startup-time에 검사하지 않음 (runtime probe로 대체).
- JVM timezone UTC 강제. NTP drift > 5초 시 readiness fail 검토 (테스트 계약 항목).
Decisionized Work Items
| item | Decision | Allowed | Forbidden | Required test |
|---|---|---|---|---|
| graceful shutdown | stop readiness first, drain inflight, then exit | force stop after timeout | accept new traffic while draining | lifecycle smoke |
| scheduler failure | structured error log + retry/DLQ owner mapping | fail-fast for critical jobs | swallow exception | job failure test |
| executor rejection | map to operational error/log with executor name | shed load with 503 | generic internal without context | rejection test |
| resource exhaustion | memory/disk/temp classified separately | platform alert first | raw OOM only | resource failure mapping |
구현 가이드
결정 이 "무엇" 이라면 본 §는 "어디에 어떻게" 의 사전 명세. 본 branch 는 ca-tmpl 운영 계약의 runtime health + lifecycle 표면을 owns — 단, 구체 error-code / env-key / executor 설정 / 메트릭은 sibling branch 가 SSOT (registry
owner_branch기준). 따라서 아래 sub-section 은 본 branch 가 정하는 것 (endpoint shape, dependency taxonomy, startup validation scope, shutdown ordering, clock readiness policy) 만 명세하고, sibling-owned 메커니즘은 위임 포인터(R3) 로 남긴다. ca-tmpl 코드 anchor 는/home/donghyeon/workspace/ca-tmpl/src(read-only 대조 2026-06-14).
1. Health probe endpoint shape + readiness group membership
Trace: D3 (
K8S-PROBE-C4/C5) + D9 (SB-HEALTH-C1/C2/C3/C6,K8S-PROBE-C1/C3) + D10 (SB-HEALTH-C7). Health Endpoint Contract 표가 owner.
- UNSUPPORTED_IMPL_DECISION: (a) readiness group
include멤버의 정확한 indicator 이름 집합 —SB-HEALTH-C8이needs-confirmation이라 "Spring default readiness 가 외부 dependency 미포함"의 verbatim 미확보 → 어떤 indicator 를 명시 include 할지는 구현자 trade-off. (b) 기존 custom/healthcheck(HealthcheckController:16) 를 retire 할지 actuator 와 공존할지 — 두 endpoint 공존 시 운영 혼선 vs migration 비용 trade-off.
| 구현 항목 | 명세 | 상태 | Anchor |
|---|---|---|---|
| actuator probe 활성화 | management.endpoint.health.probes.enabled=true + management.endpoint.health.group.{liveness,readiness,startup}.include=... |
actually-implemented |
2026-06-15 worktree ca-tmpl-runtime-health-lifecycle. spring-boot-starter-actuator 추가 + application.yml management 블록 |
| startup group/probe | startup gate 를 readiness 와 분리해 migration 중 readiness/liveness 오판 방지 | actually-implemented |
management.endpoint.health.group.startup.include=readinessState |
| liveness 멤버 | livenessState 만 — 외부 dependency 미포함 (outage 시 restart loop 방지) |
actually-implemented |
management.endpoint.health.group.liveness.include=livenessState |
| readiness 멤버 | readinessState + db (primary DB — REQUIRED) — optional 의존성 제외 |
actually-implemented |
management.endpoint.health.group.readiness.include=readinessState,db |
| 기존 endpoint | custom GET /healthcheck → {"status":"UP"} (actuator 미사용) |
actually-implemented |
adapter-web/.../HealthcheckController.java:16 |
| exposure/auth policy | OUT_OF_BRANCH_SCOPE (R3) — actuator 노출/인증은 raw/branch-notes/feature-management-actuator-security-contract (D2) | 위임 ⚠️ §Audit PROBE_AUTH_BLOCKER (현재 probe 401) |
governing security-baseline-jwt-actuator-secrets |
2. Required-dependency → readiness wiring (taxonomy → group membership)
Trace: D10 + §Required vs Optional Dependency Matrix. CompositeHealthContributor include/exclude 메커니즘 =
SB-HEALTH-C7.
- UNSUPPORTED_IMPL_DECISION: "degraded ready" (primary cache conditional) 를 Spring HealthStatus 로 어떻게 표현할지 (UP-with-detail vs custom status) — Spring status enum 매핑은 구현자 선택. 인용 자료에 spec 없음.
| dependency | readiness 멤버십 | 위임 owner (R3) |
|---|---|---|
| primary DB (required) | readiness group include → unavailable=DOWN | adapter 분류는 feature-integration-adapter-templates |
| message broker (Kafka, outbox publish) | readiness 제외 — Kafka 기본 비활성(DisabledMessagePublisher) + publish 실패는 outbox retry, broker HealthIndicator 부재 (2026-06-15 런타임 확인: readiness body 에 broker component 없음) |
mechanism feature-domain-event-outbox-contract + fail-open/closed feature-integration-adapter-templates |
| primary cache (conditional) | readiness 제외 → cache-aside fallback = degraded ready | feature-cache-consistency-contract |
| notification (Slack/Email, optional) | readiness 제외 → degrade only | feature-integration-adapter-templates (fail-open/closed) |
| multi-instance 일치 | readiness 시 APP_MULTI_INSTANCE_ENABLED flag ↔ distributed-lock contract test 결과 일치 verify (consume only) |
D8 — flag SSOT feature-env-driven-runtime-configuration, lock feature-distributed-lock-contract |
3. Startup validation scope (policy owner here, 코드 위임)
Trace: D11 (
K8S-PROBE-TASK-C1/C4). 본 branch = startup validation 에 무엇이 포함되는가 의 scope policy owner. 코드 + exit-code 매핑은 siblingfeature-migration-startup-contract가 SSOT (§AuditOWNERSHIP_DRIFT).
- UNSUPPORTED_IMPL_DECISION: env presence / migration history / adapter bean 3항목 구성 자체는 ca-tmpl 운영 가정 (D11 partially-unsupported) — 공식 spec 없음.
| validation 항목 (scope) | 위임 구현 (sibling) | exit code | Anchor |
|---|---|---|---|
| env var presence (datasource) | RequiredEnvironmentValidator |
78 STARTUP_VALIDATION_FAILED |
app-bootstrap/.../runtime/startup/RequiredEnvironmentValidator.java |
| DB schema migration history | MigrationStartupRunner (readiness-gated) |
70 MIGRATION_FAILED |
.../runtime/startup/MigrationStartupRunner.java |
| prod-forbidden flyway flags | FlywayProdSafetyValidator |
71 PROFILE_MISMATCH |
.../runtime/startup/FlywayProdSafetyValidator.java |
| required adapter bean 등록 + prod-unsafe toggle | StartupSafetyValidator |
72 REQUIRED_ADAPTER_DISABLED |
.../runtime/StartupSafetyValidator.java:57-100 |
| StartupPhase 라벨 (구조화 로그) | StartupPhase enum: env-validation / migration / adapter-enablement / profile-check |
— | .../runtime/startup/StartupPhase.java |
| external endpoint reachability | 금지 — startup-time 검사 안 함, runtime probe 로 위임 | — | D11 (K8S-PROBE-TASK-C4) |
4. Graceful shutdown ordering + budget sync
Trace: D4 (
SPRING-SMARTLC-C3/C7— descending stop phase + asyncstop(Runnable)+ phase-level timeout). 본 branch = shutdown ordering invariant + budget ≤ terminationGracePeriod sync 요구 owner. 정량 값은 sibling SSOT.
- UNSUPPORTED_IMPL_DECISION: 35s/20s/5s/10s 조합은 ca-tmpl 운영 가정 (D6 UNSUPPORTED_DECISION).
RH-DD-C1~C4는needs-confirmation.- OUT_OF_BRANCH_SCOPE (R3):
terminationGracePeriodSeconds=35s+preStop sleep=5s는 K8s manifest 값 → §범위 Out of scope.feature-container-runtime-contract가 owner.
| 항목 | 명세 | 위임/상태 | Anchor |
|---|---|---|---|
| ordering invariant | SIGTERM → readiness DOWN (신규 traffic 차단) → server inflight drain → outbound 컴포넌트 descending stop → exit | 본 branch owns (D4) | SPRING-SMARTLC-C3 |
| spring 설정 | server.shutdown=graceful + spring.lifecycle.timeout-per-shutdown-phase=${APP_SERVER_SHUTDOWN_TIMEOUT} |
actually-implemented (config) |
app-bootstrap/.../application.yml:203-205, 211 |
| server phase timeout 값 | APP_SERVER_SHUTDOWN_TIMEOUT default 30s |
위임 feature-env-driven-runtime-configuration |
env-keys.yaml (validation: ≤ k8s terminationGracePeriod) |
| executor await | setWaitForTasksToCompleteOnShutdown(true) + setAwaitTerminationSeconds(19) ("20s budget − 1s margin; 25s forbidden") |
위임 feature-background-job-async-contract |
app-bootstrap/.../async/AsyncExecutorConfig.java:46,72-73 |
| budget sync 요구 | app shutdown budget ≤ terminationGracePeriodSeconds — 초과 시 SIGKILL → inflight 유실 | 본 branch invariant + container-runtime 값 | §엣지·실패·의존 |
5. executor / resource) — taxonomy owns here, mechanism 위임
Trace: §Decisionized Work Items. 본 branch = 실패 표면 분류 policy owner. 구체 error-code / executor 설정 / scheduler 코드 / 메트릭은 sibling SSOT (§Audit
OWNERSHIP_DRIFT).
- UNSUPPORTED_IMPL_DECISION: resource exhaustion (memory/disk/temp) 분류는 본 branch policy 지만 대응 registry error-code 가 부재 (error-codes.yaml
NOT FOUND) →RESOURCE_*코드는 "신규 제안" /planned.
| 실패 표면 | policy (본 branch) | 위임 mechanism (sibling) | Anchor |
|---|---|---|---|
| scheduler failure | structured error log + retry/DLQ owner mapping; critical=fail-fast; swallow 금지 | OutboxRelayScheduler.relay() — 모든 Exception catch + ERROR 로그 + 다음 tick 재시도 (thread 생존) |
app-bootstrap/.../outbox/OutboxRelayScheduler.java:66-87 (feature-domain-event-outbox-contract) |
| executor rejection | executor name 포함 operational error/log + 503 shed; context 없는 generic internal 금지 | LoggingAbortPolicy → OperationalError.JOB_EXECUTOR_REJECTED (TRANSIENT_DEPENDENCY / 503 / retryable) + 메트릭 executor.rejected.total·executor.saturation |
AsyncExecutorConfig.java:71, shared-contract/.../OperationalError.java:148, error-codes.yaml (feature-background-job-async-contract) |
| resource exhaustion | memory/disk/temp 별도 분류; platform alert first; raw OOM only 금지 | planned — 대응 RESOURCE_* error-code 미존재 (신규 제안 필요) |
error-codes.yaml NOT FOUND |
6. Clock / timezone readiness
Trace: D12 (
RFC3339-C1/C2— UTC interoperability 권고).
- UNSUPPORTED_IMPL_DECISION: NTP drift > 5초 threshold + readiness-gating 메커니즘은 무출처 (RFC 3339 범위 밖). 2026-06-14 자동조사 결론: 어떤 공식 표준(RFC 5905/7519, NIST SC-45, K8s)도 app-readiness 의 NTP-drift 임계값을 정의하지 않으며, readiness 를 clock skew 로 gating 하면 동일 노드 모든 pod 의 동시 readiness fail(cascade) 위험 → Alt 2(clock-agnostic readiness + 인프라 계층 모니터링 위임) 권고. 본 sub-section 의 "NTP readiness" 행은 사용자 D12 개정 확정 전까지
planned유지. 상세 §AuditNTP_READINESS_ANTIPATTERN.- OUT_OF_BRANCH_SCOPE (R3): JVM
-Duser.timezone=UTC/TZ=UTC는 container env →feature-container-runtime-contract(governing doc:TZ=UTC,LANG=C.UTF-8).
| 항목 | 명세 | 상태 | Anchor |
|---|---|---|---|
| UTC clock | Clock.systemUTC() bean (timestamp 생성 UTC 고정) |
actually-implemented |
app-bootstrap/.../idempotency/IdempotencyConfig.java:27 |
| JVM timezone | TZ=UTC container env 강제 (production) + -Duser.timezone=UTC test JVM arg (test pinning) |
container env 위임 feature-container-runtime-contract; test arg actually-implemented 2026-06-15 (app-bootstrap/build.gradle tasks.named('test')) |
RuntimeHealthLifecycleContractTest#jvm_default_timezone_is_utc 로 검증 |
| NTP drift readiness | drift > 5초 시 readiness fail | planned (무출처, 코드 부재) |
D12 / Claims To Verify / §Audit 자동조사 |
엣지·실패·의존
R4(깊이 게이트) 캡처용. 정상 경로 외 구현 중 부딪힐 실패/엣지/타 계약 의존.
- 실패·엣지 경로:
- readiness flip race: migration 진행 중 startup probe 통과 전까지 readiness 는 DOWN 이어야 함 (D3). readiness 가 migration 완료 전 UP 되면 un-migrated 인스턴스로 traffic 유입.
- liveness ≠ dependency outage: DB outage → liveness 200 / readiness 503 (D9,
K8S-PROBE-C1). liveness 가 외부 의존성 실패로 죽으면 cascading restart loop. - graceful shutdown race: app shutdown budget > terminationGracePeriodSeconds → SIGKILL → inflight 유실 (D4/D6 budget sync invariant). executor await 19s + server phase 30s 가 grace 35s 안에 drain 완료해야 함.
- executor rejection under load: queue capacity 200 초과 →
LoggingAbortPolicy→ 503 (TRANSIENT_DEPENDENCY). executor-name context 없이 shed 하면 금지 (Decisionized Work Items). - scheduler 침묵 swallow:
OutboxRelayScheduler가 모든 Exception catch + 생존 — business 실패가 조용히 삼켜지면 안 됨 (status 전이는 use case 에서 로깅). - clock skew 미감지: NTP drift 미감지 시 JWT exp 검증 / distributed-lock TTL / idempotency timestamp 왜곡 (ca-tmpl audit report 의 "clock drift 노드가 readiness UP 유지" 격리 갭).
- startup-time 외부 reachability 미검사: 필수 외부 의존성이 boot 시 down 이어도 인스턴스는 ready 가 됨 (D11) → runtime readiness probe 가 잡아야 함.
- 다른 계약 의존:
- raw/branch-notes/feature-env-driven-runtime-configuration
D8—APP_MULTI_INSTANCE_ENABLED/APP_SERVER_SHUTDOWN/APP_SERVER_SHUTDOWN_TIMEOUT(consume; readiness 가 flag↔lock-test 일치 verify). - raw/branch-notes/feature-container-runtime-contract —
terminationGracePeriodSeconds=35s/preStop=5s/TZ=UTC/ JVM ergonomics (K8s manifest + container env; 본 branch budget 은 ≤ grace 로 sync). - raw/branch-notes/feature-migration-startup-contract — startup validators + exit code 78/70/71/72 +
StartupErrorCode/StartupPhase(본 branch 가 scope 정의, 해당 branch 가 코드 구현). - raw/branch-notes/feature-background-job-async-contract —
applicationTaskExecutor+LoggingAbortPolicy+JOB_EXECUTOR_REJECTED+ executor 메트릭 + awaitTermination 19s (본 branch 가 rejection policy 의도, 해당 branch 가 구현). - raw/branch-notes/feature-domain-event-outbox-contract —
OutboxRelayScheduler(scheduler 실패 mechanism). - raw/branch-notes/feature-distributed-lock-contract
D1/D3—distributedLockProviderbean; multi-instance readiness 일관성. - raw/branch-notes/feature-integration-adapter-templates — adapter→dependency 분류 + fail-open/closed SSOT.
- ⚠️ BLOCKER raw/branch-notes/feature-management-actuator-security-contract
D2— actuator endpoint exposure/auth. 2026-06-15 런타임 검증: probe shape 는 정확하나SECURITY_PUBLIC_PATHS=/api/healthcheck만 public + 코드상 managementSecurityFilterChain부재 →/actuator/health/{liveness,readiness,startup}가 JWT 인증 뒤 → kubelet(토큰 없음) 401 → liveness=restart loop / readiness=never-ready / startup=kill. 이 sibling 이 probe 경로를 unauthenticated 허용(또는 별도 management port)하기 전까지 probe end-to-end 비동작 → 2026-06-15src/.envinterim 으로 로컬/런타임 해소(probe 200 / 집계 401). 정식 owner 는 sibling. (D2 위임 — probe shape 는 본 branch, exposure 는 interim 후 sibling 이관. 상세 §AuditPROBE_AUTH_BLOCKER.)
- raw/branch-notes/feature-env-driven-runtime-configuration
Audit & Findings (ca-tmpl ground-truth 대조 2026-06-14)
/branch-spec§2 — branch-note 의 명칭/매핑이 registry/코드 enum 과 어긋날 때 surface. 사용자 작성 결정 영역은 auto-rewrite 하지 않고 정합 권고만 기록.
HEALTH_ENDPOINT_NOT_IMPLEMENTED(정합 권고)→ 2026-06-15 해소:feature-runtime-health-lifecycle-contractworktree 에서spring-boot-starter-actuator추가 +management.endpoint.health.probes.enabled=true+ 3개 group include 설정 완료 (actually-implemented). customGET /healthcheck(HealthcheckController:16) 는 공존 — task 명세가 retire 금지를 명시함. actuator probe 는 별도 경로(/actuator/health/{liveness,readiness,startup})로 추가됨. exposure/auth policy 는 parallelfeature-management-actuator-security-contract소유 (unchanged).SHUTDOWN_BUDGET_DRIFT(정합 권고): D6/§결정사항 의 "app shutdown timeout = 20s" 가 코드와 어긋남 — 코드 실측은 (a) executor awaitsetAwaitTerminationSeconds(19)(AsyncExecutorConfig:46, "container 20s budget − 1s cleanup margin; 25s forbidden"), (b) server phase timeoutAPP_SERVER_SHUTDOWN_TIMEOUTdefault 30s. 노트가 executor-await(19s)와 server-phase-timeout(30s) 두 축을 "20s" 하나로 뭉갬. → 권고: D6 를 executor await 19s / server phase 30s / terminationGracePeriod 35s(manifest) 세 축으로 분리. (사용자 결정 영역 — auto-rewrite 안 함.)OWNERSHIP_DRIFT(정합 — 위임 확인): 본 노트가 표로 다루는 일부 계약값의 registryowner_branch는 sibling 임 (D2/D8 의 "shape/scope/policy 만 owns" 와 정합):JOB_EXECUTOR_REJECTED(TRANSIENT_DEPENDENCY/503/retryable) →feature-background-job-async-contract(error-codes.yaml,OperationalError.java:148).STARTUP_VALIDATION_FAILED(78)/MIGRATION_FAILED(70)/PROFILE_MISMATCH(71)/REQUIRED_ADAPTER_DISABLED(72)→feature-migration-startup-contract.executor.saturation/executor.rejected.total→feature-background-job-async-contract(metrics.yaml).APP_SERVER_SHUTDOWN/APP_SERVER_SHUTDOWN_TIMEOUT/APP_MULTI_INSTANCE_ENABLED→feature-env-driven-runtime-configuration(env-keys.yaml).StartupSafetyValidator:35의distributedLockProvidermulti-instance 주석은 raw/branch-notes/feature-distributed-lock-contract (D1/D3) 로 reassign 됨. → 조치: 이 코드/키들을 본 branch 가 소유한다고 주장하지 않음. §구현 가이드 의 위임 포인터(R3) 유지.
GOVERNING_DOC_STALE(Advisory): governingwiki/projects/ca-tmpl/runtime-container-health-migration.md(last_reviewed 2026-05-22) 은 "C2 미진입 / 코드 없음" 으로 기술하나, startup validators + async executor + outbox scheduler 는 현재 코드 존재 (sibling-owned). Health endpoint 슬라이스는 여전히planned(정합). → 본 branch health 슬라이스 착수 시 governing doc refresh 권고. 비차단.RESOURCE_CODE_ABSENT(planned): resource-exhaustion 분류(memory/disk/temp)에 대응하는 registry error-code 가error-codes.yaml에 없음. 별도 operational code 가 필요하면 owner_branch=본 branch 로 "신규 제안" row 등록 (§구현 가이드 5).NTP_READINESS_ANTIPATTERN(정합 권고 — 자동조사 2026-06-14): D12 의 "NTP drift > 5초 시 readiness fail" 은wiki-decision-researcher조사 결과 어떤 공식 표준에도 근거 없음 — RFC 5905(STEPT 125ms / PANICT 1000s, app readiness 임계값 아님)·RFC 7519(JWT leeway "a few minutes", 숫자 없음)·NIST SP 800-53 SC-45(org-defined 위임)·K8s 공식(클럭을 readiness 사유로 미정의). "5초" 는 무출처 운영 가정으로 확정. 또한 readiness 를 clock-skew 로 gating 하면 동일 노드의 모든 pod 이 동시에 readiness fail → cascade failure 위험(AWS EKS prescriptive guidance). 조사 권고 = Alt 2: readiness 는 clock-agnostic, clock-skew 모니터링은 인프라 계층(Prometheusnode_timex_offset_seconds+ K8s NodeProblemDetectorNTPProblemNodeCondition)에 위임. → 권고(사용자 결정 영역 — auto-rewrite 안 함): D12 의 readiness-gating 부분을 제거하고 (a) UTC 강제(유지,RFC3339-C1/C2+Clock.systemUTC()), (b) clock-skew = 인프라 위임으로 분리. 채택 시 raw 4건 archive(RFC 5905 / RFC 7519 / K8s NPD / node-exporter mixin) 후 §Sources·§Decision Evidence Map 갱신. 미채택(Alt 3 startup-only sanity check) 선택지도 조사에 포함 — 결정 전 확인 필요: ca-tmpl 의 실제 JWT leeway / 분산락 TTL(허용 드리프트 역산), NPD·node-exporter 배포 여부.PROBE_AUTH_BLOCKER(⚠️ 차단 의존 — 2026-06-15 런타임 검증; 2026-06-15 sentinel BLOCKED): worktree 부팅 후 unauthenticated curl 결과/actuator/health+/actuator/health/{liveness,readiness,startup}전부 HTTP 401AUTH_TOKEN_MISSING(/api/healthcheck만 200). 원인:SECURITY_PUBLIC_PATHS=/api/healthcheck+ 코드에 management/actuatorSecurityFilterChain부재(EndpointRequest/toAnyEndpoint검색 0건). K8s kubelet 은 JWT 없이 probe 를 호출하므로 liveness 401=restart loop / readiness 401=never-ready / startup 401=kill → probe end-to-end 비동작. → 조치(sibling 코드):feature-management-actuator-security-contract가/actuator/health/liveness·/actuator/health/readiness를 unauthenticated 허용(EndpointRequest.to("health")permitAll 또는 별도management.server.port). 본 branch 코드 변경 아님(D2 exposure/auth 위임). → 2026-06-15 interim 시도 후 revert:src/.env의SECURITY_PUBLIC_PATHS에 3개 sub-path 를 interim 추가했으나ca-architect-sentinel가 not-ready(blocking:1) 판정 —verifyPublicPathSnapshot스냅샷 미갱신 + 이 branch scope 밖(actuator 인증/노출은feature-management-actuator-security-contract+ 별도management.server.port=9001에서 처리되므로 8080SECURITY_PUBLIC_PATHS에 추가하는 것이 의미상 잘못됨). → revert 완료(2026-06-15):SECURITY_PUBLIC_PATHS=/api/healthcheck단일값으로 복원.verifyPublicPathSnapshotPASS.src/.env= HEAD~1 identical. 현재 상태: probe shapeactually-implemented, probe auth = 여전히 sibling BLOCKER —feature-management-actuator-security-contract정식 구현(별도management.server.port=9001또는EndpointRequest.to("health").permitAll()) 전까지 kubelet probe 401 은 expected in this branch.BROKER_READINESS_DRIFT(정합 — 2026-06-15 코드 대조 후 노트 정정 완료): §Dependency Matrix(D10) 가 broker 를 "required(publish) → readiness fail" 로 기술했으나 구현은 broker 를 readiness 에서 제외(readiness.include=readinessState,db). 코드 ground truth: Kafka 기본 비활성(DisabledMessagePublisher) + fail-open(publish 실패는 outbox 흡수) + broker HealthIndicator 부재. transactional outbox 설계상 broker 가용성이 readiness 를 gating 하면 안 됨 → 코드가 옳음, 노트가 stale. → 본 세션에서 D10 + §Matrix + §구현 가이드 2 를 broker=optional·fail-open 으로 정정. 코드 변경 불필요.ACTUATOR_METERREGISTRY_SIDEEFFECT(확인 필요 — 2026-06-15): 본 branch 가spring-boot-starter-actuator를 classpath 에 추가 → 여태 "no Actuator → no-op" 이던MeterRegistry가 actuator autoconfiguration 으로 활성화(tracing/metrics/outbox/lock 의ObjectProvider<MeterRegistry>no-op fallback 이 실제 등록으로 전환). 부팅 로그에SimpleMeterRegistry — A MeterFilter is being configured after a Meter has been registeredWARN 2건(cardinality filter ordering — 일부 early meter 에 미적용 가능). → 확인(metrics 브랜치): (a) metrics dormant→active 가 의도된 통합 시점인지, (b)MetricsCardinalityMeterFilter/MetricsContractConfigfilter 설치를 meter 등록 이전 으로 당겨 WARN 해소.feature-metrics-alerting-contract소유 — 본 branch 코드 변경 아님(actuator 의존은 health probe 에 필수).
테스트 계약
- required dependency가 unavailable이면 readiness가 실패해야 함.
- graceful shutdown 중 신규 요청 처리 정책이 명시되어야 함.
- scheduler failure가 조용히 삼켜지면 실패.
- async executor rejection이 INTERNAL without context로 뭉개지면 실패.
- startup probe 없이 migration/readiness race가 가능하면 실패.
- JVM timezone이 UTC가 아니면 실패.
- NTP drift > 5초 상태에서 readiness가 ready를 유지하면 실패 (검토 대상).
검증해야 할 주장
| Claim | Why uncertain | How to verify | Status |
|---|---|---|---|
Spring Boot 의 default readiness group 이 외부 의존성 (DB/Kafka) 을 포함하지 않음 → ca-tmpl 이 명시적 management.endpoint.health.group.readiness.include 필요 |
SB-HEALTH-C8 는 needs-confirmation — verbatim 미확보 |
Spring Boot reference 의 actuator.endpoints.health.groups 페이지 별도 fetch + application.yml config 검증 |
needs-confirmation |
startup probe total budget = failureThreshold × periodSeconds 산식의 K8s 공식 verbatim |
K8S-PROBE-C7 는 needs-confirmation — task 페이지 truncate |
task 페이지 #define-startup-probes sub-URL 직접 fetch |
needs-confirmation |
| ca-tmpl 의 startup 30 × 5s = 150s 가 Spring Boot 콜드스타트 + JVM warmup + 외부 의존성 wiring 시간 cover | 실측 부재 | k8s deployment 실측 (startup 시간 분포 + p99) | planned |
| readiness fail → EndpointSlice 제거 → drain → preStop sleep → SIGTERM → shutdown timeout 의 e2e timing 이 ca-tmpl 의 PreStop 5s + grace 35s 와 정합 | K8S-PROBE-C3 Does not prove: EndpointSlice 제거 propagation delay 본 인용 범위 밖 |
chaos test — readiness fail 시 inflight request loss rate 측정 | planned |
| liveness probe 가 dependency outage 로 인해 실패하지 않음 (cascading restart 방지) | K8S-PROBE-C1 Usage Boundary: liveness 가 모든 hang 검출하지 않음. ca-tmpl 의 "JVM process can continue" 정의 와 정합 검증 필요 |
contract test: DB outage fixture → liveness 200 / readiness 503 | planned |
| Spring Boot graceful shutdown 시 readiness 자동 DOWN 전환 메커니즘 | 본 branch 인용 자료에 verbatim 부재 (SB-HEALTH Usage Boundary) |
Spring Boot features/graceful-shutdown.html 별도 fetch |
needs-confirmation |
| Datadog 의 preStop 5s + drain 20s + grace 35s 비율이 실제 Datadog 공식 권장 | RH-DD-C1~C4 모두 needs-confirmation — verbatim 미확보 |
Datadog Engineering blog 원본 URL 재 fetch 또는 ca-tmpl 정책으로만 표현 | needs-confirmation |
| Istio probe rewrite 환경에서도 ca-tmpl 의 3-endpoint 분리 가 동작 | RH-IST-C2 는 probe rewrite 가 sidecar→app HTTP 만 — group 별 endpoint 가 sidecar 에서 어떻게 보이는지 별도 |
Istio sandbox 환경 통합 test | planned |
| NTP drift > 5초 readiness fail 의 정량 threshold (5초) 출처 + readiness-gating 이 anti-pattern 인지 | UNSUPPORTED_DECISION — 외부 spec 인용 없음 |
조사 완료 (2026-06-14 wiki-decision-researcher): RFC 5905(STEPT 125ms/PANICT 1000s)·RFC 7519(JWT leeway "a few minutes")·NIST SP 800-53 SC-45(org-defined)·K8s 공식 어디에도 app readiness 의 NTP-drift 임계값 정의 없음 → "5초" 는 무출처 운영 가정 확정. readiness-gating 은 cascade-failure 위험(AWS EKS guidance) — Alt 2 권고: readiness 는 clock-agnostic, clock-skew 는 인프라 계층(Prometheus node_timex_offset_seconds + K8s NodeProblemDetector NTPProblem)에 위임. 채택 시 별도 raw 4건(RFC 5905·RFC 7519·K8s NPD·node-exporter mixin) archive. §Audit NTP_READINESS_ANTIPATTERN |
resolved (no authoritative standard) — D12 readiness-gating 부분은 사용자 확정 후 Alt 2 로 개정 권고 |
| ca-tmpl 실 코드의 graceful shutdown 정량값 (executor await 19s + server phase 30s) 이 terminationGracePeriod 35s 안에서 inflight drain 완료 | AsyncGracefulShutdownBehaviorTest 는 behaviour test (19s-vs-25s 정확한 수치는 증명 안 함) |
k8s 실측 또는 통합 lifecycle test 로 drain 완료 시간 측정 | planned |
3-endpoint actuator config (management.endpoint.health.probes.enabled + group include) 가 실제로 /actuator/health/{liveness,readiness,startup} 노출 |
2026-06-15 actually-implemented — application.yml management 블록 추가 + spring-boot-starter-actuator 의존성. HTTP-level endpoint 노출 검증은 feature-management-actuator-security-contract 가 exposure/security config 완료 후 통합 테스트 가능 |
locally-verified (group config shape 레벨) |
관심사 커버리지 (coverage-auditor 자동 생성 — 있을 때)
/coverage가 채우는 생성물 — 손으로 유지하지 않는다. governing 문서(frontmattergoverning_docs)가 요구하는 관심사를 이 브랜치가 빠짐없이 덮는지의 결과. 기준:rules/coverage-gate.md. governing_docs:wiki/projects/ca-tmpl/runtime-container-health-migration(§Health + §Graceful Shutdown 슬라이스). 마지막 감사: 2026-06-14 (coverage-auditor) → Covered (Blocking 0 / Should-fix 2 = 위임 링크 보강으로 해소 / Advisory 1). Container 슬라이스(base image·JVM ergonomics·locale)와 Migration 슬라이스(Flyway·exit code)의 4개 관심사는 본 슬라이스 범위 밖 — 각각feature-container-runtime-contract/feature-migration-startup-contract소유(dropped, governing doc §Container·§Migration).
| 관심사 | 상태 | owner | 심각도 | 근거 |
|---|---|---|---|---|
| liveness/readiness/startup 3-probe 분리 | covered-here | — | — | D3, D9 |
| Spring Boot Actuator Health Groups | covered-here | — | — | D9, D10 |
| Required vs Optional Dependency Matrix | covered-here | — | — | D10 + §Dependency Matrix |
| graceful shutdown ordering | covered-here | — | — | D4 (§구현 가이드 4) |
graceful shutdown 정량값 (APP_SERVER_SHUTDOWN* / executor await) |
delegated | raw/branch-notes/feature-env-driven-runtime-configuration, raw/branch-notes/feature-background-job-async-contract, raw/branch-notes/feature-container-runtime-contract (grace=35s manifest) | OK | §Audit OWNERSHIP_DRIFT + §엣지·실패·의존 |
| startup validation scope | covered-here | — | — | D11 |
| startup validators 코드 + exit code 78/70/71/72 | delegated | raw/branch-notes/feature-migration-startup-contract | OK | §구현 가이드 3 + §Audit OWNERSHIP_DRIFT |
| scheduled job 실패 정책 | covered-here | — | — | §Decisionized Work Items |
scheduler 실 mechanism (OutboxRelayScheduler) |
delegated | raw/branch-notes/feature-domain-event-outbox-contract | OK | §구현 가이드 5 |
| async executor rejection 정책 | covered-here | — | — | §Decisionized Work Items |
executor 설정·JOB_EXECUTOR_REJECTED·메트릭 |
delegated | raw/branch-notes/feature-background-job-async-contract | OK | §구현 가이드 5 + §Audit OWNERSHIP_DRIFT |
| resource exhaustion 분류 | covered-here | — | ⚪ Advisory | §Decisionized Work Items — RESOURCE_* code 부재(planned, §Audit RESOURCE_CODE_ABSENT) |
| system clock/timezone (UTC) | covered-here | — | — | D12 (Clock.systemUTC()) |
JVM timezone TZ=UTC (container env) |
delegated | raw/branch-notes/feature-container-runtime-contract | OK | §구현 가이드 6 |
| NTP drift readiness | covered-here | — | 🟡 Should-fix→해소 | D12 planned — 2026-06-14 조사: 무출처, Alt 2(clock-agnostic readiness + 인프라 모니터링 위임) 권고. §Audit NTP_READINESS_ANTIPATTERN |
| actuator endpoint exposure/auth | delegated | raw/branch-notes/feature-management-actuator-security-contract | 🟡 Should-fix (probe 401 — §Audit PROBE_AUTH_BLOCKER) |
D2 + §구현 가이드 1 |
| multi-instance readiness 일관성 | delegated | raw/branch-notes/feature-env-driven-runtime-configuration (D8), raw/branch-notes/feature-distributed-lock-contract (D1/D3) | OK | §구현 가이드 2 |
구현 진행 기록 (2026-06-15 — CA Implementer)
작업 트리:
ca-tmpl-runtime-health-lifecycleworktree (develop 에서 fork된 격리 환경). 구현된 범위: health probe SHAPE (liveness/readiness/startup 3-group split + readiness dependency taxonomy + JVM UTC timezone pinning). management port/exposure/security 는 parallel worktree 소유.
구현 사실 (actually-implemented, locally-verified 2026-06-15)
| 구현 항목 | 파일 | 상태 | 비고 |
|---|---|---|---|
spring-boot-starter-actuator 의존성 추가 |
src/app-bootstrap/build.gradle |
actually-implemented |
implementation 스코프 |
-Duser.timezone=UTC test JVM arg |
src/app-bootstrap/build.gradle (tasks.named('test') 블록) |
actually-implemented |
RuntimeHealthLifecycleContractTest 의 JVM TZ 어설션 핀 |
management.endpoint.health.probes.enabled=true |
src/app-bootstrap/src/main/resources/application.yml |
actually-implemented |
management: 블록 신규 추가 |
management.endpoint.health.group.liveness.include=livenessState |
동상 | actually-implemented |
|
management.endpoint.health.group.readiness.include=readinessState,db |
동상 | actually-implemented |
primary DB = REQUIRED 분류 |
management.endpoint.health.group.startup.include=readinessState |
동상 | actually-implemented |
startup gate |
RuntimeHealthLifecycleContractTest (6개 테스트) |
src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/runtime/RuntimeHealthLifecycleContractTest.java |
locally-verified |
ApplicationContextRunner 기반 — HTTP 없음, SecurityFilterChain 없음 |
ca-quality-reviewer 수정 (2026-06-15 — test quality + comment accuracy)
행동 변경 없음. 테스트 품질 + 주석 정확성 수정만.
| 수정 항목 | 파일 | 상태 | 비고 |
|---|---|---|---|
health_probes_enabled_is_bound → startup_group_includes_readiness_state (메서드 리네임 + 어설션 교체) |
RuntimeHealthLifecycleContractTest.java |
locally-verified |
중복 어설션(liveness/readiness isNotNull 재확인) 제거 → startup 그룹 멤버십(isMember("readinessState")) 어설션으로 교체. startup 그룹을 liveness/readiness 수준의 커버리지 동등성으로 맞춤 |
jvm_default_timezone_is_utc 주석 정정 — "aligns with production" 제거 |
RuntimeHealthLifecycleContractTest.java |
locally-verified |
이 테스트는 UTC timezone POLICY 핀 + test-JVM 결정론 보장만. 프로덕션 UTC 강제는 feature-container-runtime-contract (TZ=UTC Dockerfile) 소유임을 명시 |
tasks.named('test') 블록 주석 정정 — "aligns with production" / "logging timezone default" 과장 제거 |
src/app-bootstrap/build.gradle |
locally-verified |
-Duser.timezone=UTC 는 TEST JVM 전용(결정론적 타임스탬프 산술). 프로덕션 UTC 는 container-runtime-contract 위임 |
java.util.Set / java.util.TimeZone FQN → import + 단순명 |
RuntimeHealthLifecycleContractTest.java |
locally-verified |
기존 파일 나머지 코드와 일관성 맞춤 |
검증 명령 및 결과
./gradlew :app-bootstrap:test --tests '*RuntimeHealthLifecycleContractTest'→ BUILD SUCCESSFUL (7/7 pass —startup_group_includes_readiness_state포함)./gradlew :app-bootstrap:test→ BUILD SUCCESSFUL (전체 모듈 테스트 — 회귀 없음)
검증 명령 및 결과
./gradlew :app-bootstrap:test --tests '*RuntimeHealthLifecycleContractTest'→ BUILD SUCCESSFUL (6/6 pass)./gradlew :app-bootstrap:test→ BUILD SUCCESSFUL (전체 모듈 테스트 — 회귀 없음)./gradlew verifyCleanArchitectureDependencies→ BUILD SUCCESSFUL./gradlew verifyEnvKeys→ BUILD SUCCESSFUL (97 env keys — application.yml 에 새 env placeholder 없음)
사후 revert (2026-06-15 sentinel BLOCKED → 수정)
ca-architect-sentinel판정: not-ready, blocking:1 —src/.env의SECURITY_PUBLIC_PATHS3개 actuator 경로 추가가 스냅샷 미갱신 + 이 branch scope 밖.- 조치:
SECURITY_PUBLIC_PATHS=/api/healthcheck로 revert (HEAD~1 identical). ./gradlew verifyPublicPathSnapshot→ BUILD SUCCESSFUL ("1 public path(s) unchanged")../gradlew :app-bootstrap:test --tests '*RuntimeHealthLifecycleContractTest'→ BUILD SUCCESSFUL (revert 후에도 6/6 pass — test 는 public paths 에 의존하지 않음).src/.env현재 = HEAD~1 (working tree 미스테이지).
구현 중 마주친 기술적 문제
AvailabilityHealthContributorAutoConfiguration조건 오인:livenessState/readinessState기여자는 K8s 환경 감지 조건(@ConditionalOnBooleanProperty("management.health.livenessstate.enabled")) 뒤에 있음.ApplicationContextRunner에서 이 속성을 명시적으로true로 설정해야 하고ApplicationAvailabilityAutoConfiguration도 함께 등록해야 함.HealthEndpointGroupMembershipValidator: 그룹include에 명시된 기여자가 컨텍스트에 없으면 startup fail.db기여자를DownDbContributorConfig@Bean 으로 등록해 해소.- Package 오인: 자동 완성 없이
org.springframework.boot.autoconfigure.actuate.health(잘못됨) →org.springframework.boot.actuate.autoconfigure.health(올바름) 로 수정.
마주친 문제
- Spring Boot 3.5.x 에서
AvailabilityHealthContributorAutoConfiguration의 조건 구조 (Kubernetes 환경 감지 + 속성 explicit enable) 가ApplicationContextRunner슬라이스와 상호작용하는 방식을 확인해야 했음. 해결책:management.health.livenessstate.enabled=true/management.health.readinessstate.enabled=true속성 명시적 추가.
묶음
- raw/company-tech-blogs/runtime-health-datadog-engineering-graceful-shutdown
- raw/official-docs/adapter-spring-boot-autoconfig-custom-starter
- raw/official-docs/config-spring-cloud-kubernetes-configmap-reload
- raw/official-docs/k8s-configure-probes-task-page
- raw/official-docs/k8s-pod-lifecycle-probes-concept
- raw/official-docs/migration-flyway-official-concepts-and-repair
- raw/official-docs/migration-k8s-init-container-job-pattern
- raw/official-docs/rfc3339-datetime-utc
- raw/official-docs/runtime-health-istio-mesh-health-check
- raw/official-docs/runtime-health-k8s-probes-official
- raw/official-docs/runtime-health-spring-actuator-groups
- raw/official-docs/spring-smartlifecycle-reference
본 feature branch 는 leaf — Phase C2 실 코드 작성 완료 (health probe SHAPE 슬라이스).
오류 기록 (본 feature 작업 중 발생)
- Spring actuator autoconfig package 오인:
org.springframework.boot.autoconfigure.actuate.health.*는 존재하지 않음. 올바른 패키지는org.springframework.boot.actuate.autoconfigure.health.*(actuate 와 autoconfigure 순서 반전).ApplicationContextRunner사용 시 jar tf 로 확인 필요. - AvailabilityHealthContributor 조건 gap: K8s 자동감지 없는
ApplicationContextRunner에서livenessState/readinessState기여자는 비활성.management.health.livenessstate.enabled=true+management.health.readinessstate.enabled=true+ApplicationAvailabilityAutoConfiguration등록으로 해소.
면접 준비 (이 작업에서 나올 수 있는 면접 질문)
- Spring Boot Actuator health probe group split (liveness/readiness/startup) — 각각의 의미와 K8s 연동.
StatusAggregator.getDefault()— DOWN 하나가 포함되면 전체 DOWN 이 되는 이유.ApplicationContextRunnervs@SpringBootTest차이 — actuator health 테스트에서 왜 runner 를 선택했는가.HealthEndpointGroupMembershipValidator가 startup 에 실패하는 조건과 해결 패턴.
관련 일일 노트
이 브랜치를 작업한 날짜들. 양방향 nav 유지.
- (없음 — Phase E 운영 계약 설계 단계. C2 실 구현 착수 시 daily-note 연결)
완료 후 정리
- PR 링크:
- 리뷰 메모:
- 머지 결과 / 배포 환경:
- wiki 추출 대상 (verified만,
wiki/projects/로만 추출):actually-implemented항목:locally-verified항목:prod-verified항목:
- 추출하지 않을 항목 (planned / documented-only / abandoned):