Files
llm-wiki/wiki/projects/ca-tmpl/runtime-container-health-migration.md
T

150 lines
12 KiB
Markdown

---
title: ca-tmpl - Runtime / Container / Health / Migration 결정
source_type: project
status: verified
confidence: high
tags: [ca-tmpl, runtime, container, kubernetes, flyway, actually-implemented, locally-verified]
related_projects: [ca-tmpl]
last_reviewed: 2026-07-02
---
# ca-tmpl - Runtime / Container / Health / Migration 결정
> Layer: `wiki/projects/` — 내 프로젝트 사실. 일반 개념은 [[wiki/concepts/runtime-container-health-migration]] 참조.
## 프로젝트 컨텍스트
ca-tmpl은 Clean Architecture 기반 백엔드 skeleton 프로젝트다. **운영 계약(operational contract)** 단계에서 JVM 서비스의 runtime baseline을 세 축으로 묶어 단일 운영 계약으로 통합하는 결정을 했다.
- **Container**: Eclipse Temurin (Adoptium) **JRE slim** + JVM ergonomics (`-XX:MaxRAMPercentage=75`, `-XX:+UseContainerSupport`, `-XX:+ExitOnOutOfMemoryError`) + **UTC / UTF-8** locale 고정.
- **Health**: Kubernetes Probes 3종 (**liveness / readiness / startup**) 분리 + Spring Boot Actuator Health Groups + **Required / Optional Dependency Matrix**.
- **Migration**: Flyway forward-only migration을 **readiness-gated**로 실행 + `repair` / `baselineOnMigrate` / `outOfOrder` 모두 **prod forbidden** + 표준 startup **exit code 78 / 70 / 71 / 72** 매핑.
- **Graceful shutdown budget**: app **20s** + preStop **5s** + terminationGracePeriodSeconds **35s** (10s margin).
현재 진행 상태:
- **C2 구현 + 로컬 검증 완료** — `src/Dockerfile`, runtime safety/startup validators, Actuator health group contract, Flyway prod safety guard, startup exit-code mapping, graceful shutdown settings가 코드화되어 있다. 2026-07-02 `./gradlew check` 통과로 로컬 검증했다. Kubernetes manifest와 운영 rolling update 실측은 없다.
문서/설계 산출물만 존재하며, 코드/검증/측정은 전무하다.
## 실제 구현 내용 (`actually-implemented`)
- `src/Dockerfile`과 runtime settings가 존재한다.
- `app-bootstrap``RuntimeSafetyConfig`, `RuntimeSafetySettings`, `RuntimeNumericBoundsValidator`, `OpenInViewSafetyValidator`, `HikariPoolConstraintValidator`가 startup/runtime guard를 구성한다.
- `MigrationStartupConfig`, `MigrationStartupRunner`, `FlywayProdSafetyValidator`, `StartupFailureException`, `StartupErrorCode`가 migration readiness-gate와 exit code mapping을 구성한다.
- `adapter-web``HealthcheckController``app-bootstrap` health group contract가 liveness/readiness/startup 구분을 검증한다.
## 로컬/dev 검증 (`locally-verified`)
- `./gradlew check` 통과(2026-07-02, `BUILD SUCCESSFUL`, 114 tasks).
- `RuntimeHealthLifecycleContractTest`가 liveness/readiness/startup group membership과 readiness-vs-liveness 분리를 검증한다.
- `FlywayProdSafetyValidatorTest`, `MigrationStartupRunnerTest`, `RequiredEnvironmentValidatorTest`, `StartupErrorCodeTest`, `StartupFailureExceptionTest`가 migration/startup failure contract와 exit code를 검증한다.
- `ContainerRuntimeOomContractTest`, `OperationalContractRuntimeTest`, `RuntimeNumericBoundsValidatorTest`, `OpenInViewSafetyValidatorTest`, `HikariPoolConstraintValidatorTest`가 runtime/container/startup guard를 검증한다.
## 운영 검증 (`prod-verified`)
**없음.** 운영 환경 검증 없음. K8s rolling update 동작, graceful shutdown 실측, cold start latency, migration 실패 복구 모두 없음.
## 문서/계획만 존재 (`documented-only` / `planned`)
운영 계약 문서(canonical §15 Runtime / Lifecycle Contract, §29 G-D)와 3 branch-note에 다음이 **설계 수준**으로만 기록되어 있다.
### Container (`actually-implemented` / `locally-verified`)
- Base image: **Eclipse Temurin JRE slim** 채택 (distroless / alpine+musl / GraalVM native 대안 모두 검토 후 보류).
- JVM ergonomics: `-XX:+UseContainerSupport` (JDK 10+ default 명시) + `-XX:MaxRAMPercentage=75` + `-XX:+ExitOnOutOfMemoryError` + `-XX:HeapDumpPath`.
- Locale: **UTC / UTF-8** 고정 (env `TZ=UTC`, `LANG=C.UTF-8`).
- 대안 검토: distroless (보안 surface 축소 vs 디버깅 손실), alpine+musl (image 크기 vs glibc 호환성 risk), GraalVM native-image (cold start vs reflection/peak throughput 손실, hybrid 사례) — branch-note `feature-container-runtime-contract`.
### Health (`actually-implemented` / `locally-verified`)
- K8s Probes **3 endpoint 분리**: `/livez`, `/readyz`, `/startupz` (or Actuator `/actuator/health/{liveness,readiness}` + startup variant).
- Spring Boot Actuator Health Groups로 endpoint별 HealthIndicator set 분리.
- **Required / Optional Dependency Matrix** — DB·broker는 readiness 필수, 외부 cache는 optional 등 dependency 범위 명시.
- 대안 검토: single `/health` (legacy, restart loop risk), custom HealthIndicator only (default readiness 외부 dependency 미포함), Istio mesh-based health (sidecar/app 구분 모호) — branch-note `feature-runtime-health-lifecycle-contract`.
### Migration (`actually-implemented` / `locally-verified`)
- **Flyway forward-only** + **readiness-gated**: migration 완료 전 readiness probe `false`.
- **prod forbidden**: `flyway.repair`, `flyway.baselineOnMigrate`, `flyway.outOfOrder` 모두 prod에서 사용 금지.
- 표준 startup **exit code 매핑** (sysexits.h 관례):
- `78` — config error (env / property 누락·잘못된 값)
- `70` — internal software error (예상 외 application failure)
- `71` — OS error (system call / resource 실패)
- `72` — critical OS file missing
- 대안 검토: Liquibase (DB-agnostic + rollback, XML/YAML verbose), Hibernate `hbm2ddl=update` (anti-pattern), Atlas (declarative, JVM 외부), K8s Init Container (replica race) vs Job + migration lock — branch-note `feature-migration-startup-contract`.
### Graceful Shutdown (`partially-implemented`)
- App SIGTERM 수신 후 in-flight 처리 **20s** + preStop hook **5s** drain + K8s terminationGracePeriodSeconds **35s** (10s margin).
- Spring Boot `server.shutdown=graceful` + `spring.lifecycle.timeout-per-shutdown-phase` 설정 예정.
Kubernetes manifest와 실제 rolling update/drain 실측은 아직 없다. 따라서 local/runtime guard와 운영 가정의 경계를 분리해서 말해야 한다.
## 면접에서 말할 수 있는 범위
### 자신 있게 답할 수 있는 (개념·설계 의도)
- **JRE slim vs distroless** 선택 근거 — 운영/디버깅 친숙도 vs 보안 surface trade-off.
- **liveness / readiness / startup 3 probe 분리** 이유 — single `/health`로 묶으면 dependency 일시 outage가 container restart loop를 유발하고, startup 단계 liveness 오판이 긴 migration/warmup을 죽일 수 있다.
- **Graceful shutdown 단계** — SIGTERM → app drain 20s → preStop 5s → grace 35s. 각 timeout이 sync되지 않으면 SIGKILL로 inflight 요청 유실.
- **Flyway `repair`가 prod에서 위험한 이유** — 실제 schema 변경 없이 metadata만 수정. 공식이 직접 위험성 경고. `baselineOnMigrate`는 누락 migration skip, `outOfOrder`는 협업 일관성 깨짐.
- **Exit code 78/70/71/72 의미** — sysexits.h 관례. config error / internal / OS / critical OS file missing 진단 분리.
### 적당히 답할 수 있는
- **GraalVM native-image trade-off** — cold start/메모리 우위 vs reflection·dynamic proxy build-time metadata 비용, peak throughput 손실. 우아한형제들도 hybrid 채택.
- **`-XX:MaxRAMPercentage=75`** vs 절대값 `-Xmx` — container memory limit 변경에 따라가는 비율 방식이 안전한 이유.
### 답하면 안 되는 (실측·운영 경험 없음)
- "K8s rolling update를 운영하면서…" — 운영 경험 없음.
- "cold start latency를 측정해보니…" — 측정 없음.
- "DB migration이 prod에서 실패해서 복구한 경험" — 없음.
- "liveness probe 오판으로 restart loop가 발생했을 때…" — 운영 incident 없음.
- "graceful shutdown 35s budget이 실제로 충분했다" — 실측 없음.
## 과장 금지 지점
- **"GraalVM native-image가 곧 표준"** → ❌. reflection-heavy 코드와 peak throughput 손실은 실측 trade-off. ca-tmpl은 채택하지 않았고 hybrid 사례만 참조했다.
- **"K8s probe 동작을 운영에서 확인했다"** → ❌. health group contract는 로컬 테스트로 검증했지만 Kubernetes manifest/cluster 검증은 없다.
- **"Flyway readiness-gated migration이 운영에서 동작한다"** → ❌. startup guard와 prod forbidden option은 로컬 테스트로 검증했지만 prod migration 복구 경험은 없다.
- **"graceful shutdown 35s가 충분히 검증되었다"** → ❌. graceful shutdown 설정은 존재하지만 운영 drain 실측은 없다.
- **"distroless가 보안상 우월하다고 채택했다"** → ❌. ca-tmpl은 **JRE slim 채택**. distroless는 대안으로 검토만 했고 디버깅 손실을 이유로 보류.
- **"exit code 78/70/71/72가 표준이다"** → ❌. sysexits.h는 BSD 관례. POSIX 강제 표준 아님. 조직 enum 명시가 필요.
### Blog-topic ingest: runtime 묶음 (2026-07-02)
[[raw/blog-topics/jvm-oom-vs-container-oomkill-exit-137-2026-07-02]] 는 JVM OOM과 container OOMKill이 비슷한 종료 신호로 보일 때 heap dump/native stderr/runtime signal을 어떻게 구분할지 정리하기 위한 raw seed다.
- **canonical 반영 범위**: container/JVM runtime failure 해석을 runtime/container 결정 문서의 blog-topic 후보로 연결했다.
- **blogify 전 조건**: 충족. 이 문서는 2026-07-02 기준 코드와 `./gradlew check`로 검증됨.
- **블로그 전 과장 방지**: Kubernetes 운영 장애 대응 경험처럼 쓰지 않고, local/container evidence와 운영 가정을 분리한다.
- [[raw/blog-topics/spring-actuator-health-probe-group-split-2026-07-02]]: Spring Actuator health group을 liveness/readiness/startup으로 분리하고 startup guard/shutdown lifecycle을 같은 운영 계약으로 보는 글감. Kubernetes end-to-end readiness 보장처럼 쓰지 않는다.
- [[raw/blog-topics/java21-context-propagation-strategy-virtual-threads-2026-07-02]]: Java 21에서 request/security/tenant context를 ThreadLocal, Micrometer Context Propagation, ScopedValue 중 어디까지 다룰지 선택 기준을 정리하는 글감. ScopedValue 채택 경험처럼 쓰지 않고 후보/기준으로 제한한다.
- [[raw/blog-topics/spring-boot-startup-exit-code-propagation-2026-06-10]]: startup failure exit code를 `ExitCodeGenerator`/Spring Boot uncaught exception path와 sysexits 관례로 분리해 설명하는 글감. POSIX 표준처럼 쓰지 않고, ca-tmpl 내부 convention과 local 검증 경계를 구분한다.
- [[raw/blog-topics/spring-async-taskdecorator-bounded-executor-saturation-shutdown-2026-06-13]]: executor await timeout과 app shutdown / Kubernetes grace period의 계층 부등식을 다루는 글감. executor sizing 숫자를 측정값으로 쓰지 않는다.
## 관련 개념
- [[wiki/concepts/runtime-container-health-migration]]
## Sources
- [[raw/project-notes/ca-skeleton-operational-contract]] — ca-tmpl 운영 계약 SSOT (§15 Runtime / Lifecycle Contract, §29 G-D).
- [[raw/branch-notes/feature-container-runtime-contract]] — Temurin JRE slim + JVM ergonomics + UTC/UTF-8 계약.
- [[raw/blog-topics/jvm-oom-vs-container-oomkill-exit-137-2026-07-02]] — JVM OOM vs container OOMKill 블로그 글감 raw seed. canonical 반영 범위: runtime/container failure interpretation + 과장 금지 항목.
- [[raw/branch-notes/feature-runtime-health-lifecycle-contract]] — liveness/readiness/startup 3-endpoint 분리 + Required/Optional Dependency Matrix.
- [[raw/blog-topics/spring-actuator-health-probe-group-split-2026-07-02]] — Actuator health probe group split 블로그 글감 raw seed.
- [[raw/branch-notes/feature-runtime-context-propagation-contract]] — Java 21 context propagation 선택 기준 parent branch.
- [[raw/blog-topics/java21-context-propagation-strategy-virtual-threads-2026-07-02]] — Java 21 context propagation strategy 블로그 글감 raw seed.
- [[raw/branch-notes/feature-migration-startup-contract]] — Flyway readiness-gated + prod forbidden 옵션 + exit code 78/70/71/72 매핑.
- [[raw/blog-topics/spring-boot-startup-exit-code-propagation-2026-06-10]] — startup exit code propagation 블로그 글감 raw seed.
- [[raw/blog-topics/spring-async-taskdecorator-bounded-executor-saturation-shutdown-2026-06-13]] — async executor shutdown budget 블로그 글감 raw seed.
## Cluster / 묶음
<!-- GENERATED: derived-blogs:start -->
- [[wiki/blog/ca-tmpl-runtime-container-health-migration-2026-07-02]]
<!-- GENERATED: derived-blogs:end -->