506 lines
61 KiB
Markdown
506 lines
61 KiB
Markdown
---
|
||
title: branch / feature-runtime-health-lifecycle-contract
|
||
source_type: branch-note
|
||
status: raw
|
||
branch: feature-runtime-health-lifecycle-contract
|
||
parent_branch:
|
||
related_projects: [ca-skeleton]
|
||
governing_docs: [wiki/projects/ca-tmpl/runtime-container-health-migration]
|
||
tags: [branch, ca-skeleton, runtime, health, lifecycle]
|
||
created: 2026-05-21
|
||
target_merge:
|
||
status_label: in-progress
|
||
id: BR-CA-SKELETON-OPERATIONAL-CONTRACT-013
|
||
kind: project-work-item
|
||
project: ca-skeleton-operational-contract
|
||
work_item: WI-CA-SKELETON-OPERATIONAL-CONTRACT-013
|
||
inherits: [DEC-CA-SKELETON-OPERATIONAL-CONTRACT-MIGRATION-001@1]
|
||
refines: []
|
||
overrides: []
|
||
depends_on: []
|
||
contract_packet: 1
|
||
contract_packet_sha256: 3924b8c0f447ff7dd65b9e109da6dadaac2055bb945f0db17844ea22b8e8e0fd
|
||
---
|
||
|
||
# branch: feature-runtime-health-lifecycle-contract
|
||
|
||
> Layer: `raw/branch-notes/` — runtime health와 application lifecycle 실패 계약을 정의합니다.
|
||
|
||
<!-- section-id: branch-parent -->
|
||
## 부모 (필수)
|
||
|
||
- **Parent project (canonical SSOT)**: [[raw/project-notes/ca-skeleton-operational-contract]]
|
||
|
||
> ca-skeleton 은 별도 root branch 없이 project-note 가 SSOT 역할. 본 feature branch 는 project-note 의 운영 계약 중 해당 영역 (§<관련 섹션>) 의 결정/근거/금지 사항을 정제한다.
|
||
|
||
<!-- GENERATED: branch-contract:start -->
|
||
<!-- section-id: branch-contract-packet -->
|
||
## 브랜치 계약 패킷
|
||
|
||
- **생성 시 프로젝트 개정**: `1`
|
||
- **패킷 스키마**: `contract_packet: 1`
|
||
- **완료 조건**: startup·readiness·shutdown lifecycle test가 통과한다
|
||
|
||
<!-- section-id: inherited-project-decisions -->
|
||
### 상속한 프로젝트 결정
|
||
|
||
| 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]] |
|
||
|
||
<!-- section-id: branch-local-decisions -->
|
||
### 브랜치 지역 결정
|
||
|
||
> 기존 branch-local 결정은 아래 `## Decision Evidence Map / 결정-근거 매핑`의 D-row가 소유하며 이 packet에서 복제하지 않는다.
|
||
|
||
| Decision ID | Decision | Relation | Supporting Claims | Status |
|
||
|---|---|---|---|---|
|
||
|
||
<!-- section-id: declared-overrides -->
|
||
### 선언한 예외
|
||
|
||
| Override ID | Overrides | Reason | Approval | Status |
|
||
|---|---|---|---|---|
|
||
<!-- GENERATED: branch-contract:end -->
|
||
|
||
<!-- section-id: branch-goal -->
|
||
## 목표
|
||
|
||
서비스는 요청 처리 중에만 실패하지 않습니다. startup, migration, readiness, graceful shutdown, scheduler, async executor, resource exhaustion 같은 lifecycle 표면도 skeleton 기본 기준에 포함되어야 합니다.
|
||
|
||
- 이슈:
|
||
- PR:
|
||
|
||
<!-- section-id: branch-scope -->
|
||
## 범위
|
||
|
||
### 포함 범위
|
||
|
||
- 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_ENABLED` flag. 본 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 코드에는 현재 custom `GET /healthcheck` (`HealthcheckController:16`, `{"status":"UP"}`) 만 존재하며, 위 3개 actuator probe endpoint + Spring Boot Actuator Health Groups 설정은 미작성이다. 상세 + reconcile 권고는 §Audit `HEALTH_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 매핑은 sibling `feature-migration-startup-contract` 가 SSOT (§Audit `OWNERSHIP_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 + async `stop(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` 유지. 상세 §Audit `NTP_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` — `distributedLockProvider` bean; 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 + 코드상 management `SecurityFilterChain` 부재 → `/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-15 `src/.env` interim 으로 로컬/런타임 해소**(probe 200 / 집계 401). 정식 owner 는 sibling. (D2 위임 — probe shape 는 본 branch, exposure 는 interim 후 sibling 이관. 상세 §Audit `PROBE_AUTH_BLOCKER`.)
|
||
|
||
## 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-contract` worktree 에서 `spring-boot-starter-actuator` 추가 + `management.endpoint.health.probes.enabled=true` + 3개 group include 설정 완료 (`actually-implemented`). custom `GET /healthcheck` (`HealthcheckController:16`) 는 **공존** — task 명세가 retire 금지를 명시함. actuator probe 는 별도 경로(`/actuator/health/{liveness,readiness,startup}`)로 추가됨. exposure/auth policy 는 parallel `feature-management-actuator-security-contract` 소유 (unchanged).
|
||
- **`SHUTDOWN_BUDGET_DRIFT`** (정합 권고): D6/§결정사항 의 "app shutdown timeout = 20s" 가 코드와 어긋남 — 코드 실측은 (a) executor await `setAwaitTerminationSeconds(19)` (`AsyncExecutorConfig:46`, "container 20s budget − 1s cleanup margin; 25s forbidden"), (b) server phase timeout `APP_SERVER_SHUTDOWN_TIMEOUT` default **30s**. 노트가 executor-await(19s)와 server-phase-timeout(30s) 두 축을 "20s" 하나로 뭉갬. → 권고: D6 를 *executor await 19s / server phase 30s / terminationGracePeriod 35s(manifest)* 세 축으로 분리. (사용자 결정 영역 — auto-rewrite 안 함.)
|
||
- **`OWNERSHIP_DRIFT`** (정합 — 위임 확인): 본 노트가 표로 다루는 일부 계약값의 registry `owner_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` 의 `distributedLockProvider` multi-instance 주석은 [[raw/branch-notes/feature-distributed-lock-contract]] (D1/D3) 로 reassign 됨.
|
||
→ 조치: 이 코드/키들을 본 branch 가 *소유*한다고 주장하지 않음. §구현 가이드 의 위임 포인터(R3) 유지.
|
||
- **`GOVERNING_DOC_STALE`** (Advisory): governing `wiki/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 모니터링은 인프라 계층(Prometheus `node_timex_offset_seconds` + K8s NodeProblemDetector `NTPProblem` NodeCondition)에 위임. → **권고(사용자 결정 영역 — 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 401 `AUTH_TOKEN_MISSING`** (`/api/healthcheck` 만 200). 원인: `SECURITY_PUBLIC_PATHS=/api/healthcheck` + 코드에 management/actuator `SecurityFilterChain` 부재(`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` 에서 처리되므로 8080 `SECURITY_PUBLIC_PATHS` 에 추가하는 것이 의미상 잘못됨). → **revert 완료(2026-06-15)**: `SECURITY_PUBLIC_PATHS=/api/healthcheck` 단일값으로 복원. `verifyPublicPathSnapshot` PASS. `src/.env` = HEAD~1 identical. **현재 상태**: probe shape `actually-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 registered` WARN 2건(cardinality filter ordering — 일부 early meter 에 미적용 가능). → **확인(metrics 브랜치)**: (a) metrics dormant→active 가 의도된 통합 시점인지, (b) `MetricsCardinalityMeterFilter`/`MetricsContractConfig` filter 설치를 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 문서(frontmatter `governing_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-lifecycle` worktree (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_PATHS` 3개 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 미스테이지).
|
||
|
||
### 구현 중 마주친 기술적 문제
|
||
|
||
1. **`AvailabilityHealthContributorAutoConfiguration` 조건 오인**: `livenessState`/`readinessState` 기여자는 K8s 환경 감지 조건(`@ConditionalOnBooleanProperty("management.health.livenessstate.enabled")`) 뒤에 있음. `ApplicationContextRunner` 에서 이 속성을 명시적으로 `true` 로 설정해야 하고 `ApplicationAvailabilityAutoConfiguration` 도 함께 등록해야 함.
|
||
2. **`HealthEndpointGroupMembershipValidator`**: 그룹 `include` 에 명시된 기여자가 컨텍스트에 없으면 startup fail. `db` 기여자를 `DownDbContributorConfig` @Bean 으로 등록해 해소.
|
||
3. **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` 속성 명시적 추가.
|
||
|
||
## 묶음
|
||
|
||
<!-- GENERATED: sources:start -->
|
||
- [[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]]
|
||
<!-- GENERATED: sources:end -->
|
||
|
||
<!-- GENERATED: blog-topics:start -->
|
||
- [[raw/blog-topics/spring-actuator-health-probe-group-split-2026-07-02]]
|
||
<!-- GENERATED: blog-topics:end -->
|
||
|
||
> 본 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 이 되는 이유.
|
||
- `ApplicationContextRunner` vs `@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):
|