11 KiB
title, source_type, status, confidence, tags, related_projects, last_reviewed, canonical_sources, audience, target_publish, status_label
| title | source_type | status | confidence | tags | related_projects | last_reviewed | canonical_sources | audience | target_publish | status_label | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Runtime 설정 오류를 Startup에서 실패시키기 | blog | verified | high |
|
|
2026-07-03 |
|
backend-engineer | ready |
Runtime 설정 오류를 Startup에서 실패시키기
Parent / 부모 (필수)
- 핵심 canonical: wiki/projects/ca-tmpl/runtime-container-health-migration
- 관련 개념 문서: wiki/concepts/runtime-container-health-migration - 일반 runtime/container/health/migration 개념. 현재 이 글의 구현 사실 근거는 verified project canonical에 둔다.
타깃 독자 / Target reader
- 독자 profile: container readiness, health check, migration, runtime config guard를 skeleton에 넣고 싶은 엔지니어.
- 이미 안다고 가정하는 것: Docker/Kubernetes probe, Flyway, env config, Spring Boot Actuator.
- 처음 듣는다고 가정하는 것: runtime safety를 startup validator, health group, migration strategy, exit code로 묶는 방식.
도입 / Hook
운영 설정 오류는 배포 뒤에 늦게 발견될수록 비쌉니다. pool size가 음수로 들어가거나, open-in-view가 켜지거나, prod profile에서 Flyway safety option이 풀린 상태로 애플리케이션이 올라오면, 문제는 요청을 받기 시작한 뒤에 드러날 수 있습니다.
ca-tmpl은 이런 오류를 startup 단계에서 실패시키는 방향으로 runtime contract를 잡았습니다. env-driven configuration을 쓰되 잘못된 값은 lenient default로 숨기지 않고, health group은 liveness/readiness/startup 역할을 나누며, Flyway migration 실패는 startup failure와 exit code로 드러냅니다. 이 글은 ca-tmpl에 실제로 구현된 runtime/container/health/migration baseline과 아직 운영 검증으로 말하면 안 되는 범위를 정리합니다.
본문 outline / Body outline
- startup fail-fast의 가치.
- runtime numeric bounds와 OSIV/Hikari guard.
- health probe group split과 readiness gate.
- Flyway migration startup contract와 exit code.
- container image/runtime baseline.
- Kubernetes cluster rollout 검증은 없음.
본문 / Body
runtime 설정은 코드보다 덜 중요해 보이지만, 실제로는 애플리케이션의 동작 경계를 바꿉니다. DB pool max size, Tomcat thread count, shutdown timeout, Flyway option, Actuator exposure는 모두 장애 양상을 바꿀 수 있습니다. 그래서 ca-tmpl은 “값이 이상하면 프레임워크 기본값으로 알아서 흘러가게 둔다”보다 “startup에서 실패한다”는 쪽을 택했습니다.
RuntimeNumericBoundsValidator는 대표적인 예입니다. spring.datasource.hikari.maximum-pool-size, server.tomcat.threads.max, server.tomcat.max-connections 같은 값은 1 이상이어야 하고, minimum idle이나 accept count처럼 0을 허용하는 값은 0 이상이어야 합니다. key가 없으면 framework default에 맡기지만, key가 있는데 범위를 벗어나면 IllegalStateException으로 startup을 막습니다. env-driven 설정을 쓰면서도 잘못된 env 값을 조용히 묻지 않는 장치입니다.
OSIV와 Hikari 설정도 별도 guard로 다룹니다. OpenInViewSafetyValidator는 spring.jpa.open-in-view=true를 거부합니다. Hikari validator는 connection-timeout, validation-timeout, keepalive-time, max-lifetime, leak-detection-threshold의 상호 관계를 검사합니다. 예를 들어 validation timeout이 connection timeout보다 길면 pool 동작을 예측하기 어려워집니다. ca-tmpl은 이런 값을 요청 처리 뒤의 증상으로 발견하기보다 startup에서 configuration error로 드러내려 합니다.
health check는 endpoint 하나로 뭉개지 않습니다. application.yml에는 Actuator health group이 liveness, readiness, startup으로 나뉘어 있습니다. liveness는 JVM이 계속 살아갈 수 있는지를 보며 dependency health를 포함하지 않습니다. DB가 잠깐 내려갔다고 pod를 재시작하는 것은 보통 원하는 동작이 아니기 때문입니다. readiness는 traffic을 받아도 되는지를 판단하므로 readinessState,db를 포함합니다. startup은 context initialization과 migration 완료 후 준비 상태를 드러내는 gate로 둡니다.
Flyway도 startup contract의 일부입니다. MigrationStartupRunner는 context refresh 중 Flyway migration을 수행하고, 실패하면 MigrationFailedException 계열로 바꿔 exit code 70에 연결합니다. StartupErrorCode에는 startup validation, migration failure, profile mismatch, required adapter disabled가 각각 다른 exit code와 phase로 정의되어 있습니다. 실패 원인을 process exit status와 structured startup log에서 분리해 보려는 설계입니다.
prod profile의 Flyway safety guard도 들어 있습니다. FlywayProdSafetyValidator는 prod에서 baseline-on-migrate=true, out-of-order=true, clean-disabled=false 같은 위험한 override를 막습니다. 여기서 중요한 것은 “Flyway를 쓰면 안전하다”가 아닙니다. migration 도구를 쓰더라도 prod에서 안전망을 푸는 설정이 들어오면 애플리케이션이 올라오지 않게 만드는 것입니다.
container/runtime baseline도 project canonical에 포함되어 있습니다. src/Dockerfile, graceful shutdown 설정, Actuator health group, startup validator, migration strategy가 묶여 있습니다. 그러나 실제 Kubernetes manifest, rolling update, probe tuning, cluster에서의 rollout incident 검증은 없습니다. 따라서 이 글은 “Kubernetes 운영에서 검증된 lifecycle 설계”가 아니라 “ca-tmpl이 startup fail-fast와 health/migration baseline을 코드와 설정으로 고정하고 로컬 검증했다”까지 말합니다.
코드 예제 / Code samples (있다면)
// 출처: [[wiki/projects/ca-tmpl/runtime-container-health-migration]]
// 실제 파일: app-bootstrap/.../RuntimeNumericBoundsValidator.java, ca-tmpl @f6fbd4e196b4
static final List<Bound> BOUNDS =
List.of(
new Bound("spring.datasource.hikari.maximum-pool-size", "APP_DATASOURCE_POOL_MAX_SIZE", 1),
new Bound("server.tomcat.threads.max", "APP_SERVER_TOMCAT_MAX_THREADS", 1),
new Bound("server.tomcat.max-connections", "APP_SERVER_TOMCAT_MAX_CONNECTIONS", 1),
new Bound("spring.datasource.hikari.minimum-idle", "APP_DATASOURCE_POOL_MIN_IDLE", 0),
new Bound("server.tomcat.accept-count", "APP_SERVER_TOMCAT_ACCEPT_COUNT", 0));
// 출처: [[wiki/projects/ca-tmpl/runtime-container-health-migration]]
// 실제 파일: app-bootstrap/.../StartupErrorCode.java, ca-tmpl @f6fbd4e196b4
public enum StartupErrorCode {
STARTUP_VALIDATION_FAILED(78, StartupPhase.ENV_VALIDATION),
MIGRATION_FAILED(70, StartupPhase.MIGRATION),
PROFILE_MISMATCH(71, StartupPhase.PROFILE_CHECK),
REQUIRED_ADAPTER_DISABLED(72, StartupPhase.ADAPTER_ENABLEMENT);
}
// 출처: [[wiki/projects/ca-tmpl/runtime-container-health-migration]]
// 실제 파일: app-bootstrap/.../FlywayProdSafetyValidator.java, ca-tmpl @f6fbd4e196b4
if (isTrue(BASELINE_ON_MIGRATE_KEY)) {
violations.add(BASELINE_ON_MIGRATE_KEY + "=true (removes the missing-migration safety net)");
}
if (isTrue(OUT_OF_ORDER_KEY)) {
violations.add(OUT_OF_ORDER_KEY + "=true (breaks migration ordering consistency)");
}
if (isFalse(CLEAN_DISABLED_KEY)) {
violations.add(CLEAN_DISABLED_KEY + "=false (re-arms destructive Flyway clean)");
}
// 출처: [[wiki/projects/ca-tmpl/runtime-container-health-migration]]
// 실제 파일: app-bootstrap/.../MigrationStartupRunner.java, ca-tmpl @f6fbd4e196b4
try {
MigrateResult result = flyway.migrate();
int executed = (result != null) ? result.migrationsExecuted : 0;
log.info("startup phase {}: migration complete, {} migration(s) applied",
kv("startup.phase", StartupPhase.MIGRATION.wireName()), executed);
} catch (FlywayException e) {
throw StartupFailures.migrationFailed("Flyway forward-only migration failed during startup", e);
}
# 출처: [[wiki/projects/ca-tmpl/runtime-container-health-migration]]
# 실제 파일: app-bootstrap/src/main/resources/application.yml, ca-tmpl @f6fbd4e196b4
management:
endpoint:
health:
probes:
enabled: true
group:
liveness:
include: livenessState
readiness:
include: readinessState,db
startup:
include: readinessState
Sources / 근거 (canonical 인용 필수, derived layer 의무)
- wiki/projects/ca-tmpl/runtime-container-health-migration - 이 글의 1차 canonical. startup validators, Actuator health group, Flyway startup migration/safety guard, exit code mapping, local verification, 운영 미검증 경계를 따른다.
- wiki/concepts/runtime-container-health-migration - 관련 개념 문서. container lifecycle, health probe, migration strategy 일반 배경으로만 둔다.
사실 vs 의견 / Fact vs opinion 구분
- 사실: ca-tmpl에는 runtime numeric bounds validator, OSIV/Hikari safety validator, Actuator health group 설정, Flyway startup migration strategy, prod safety validator, startup exit code mapping이 존재한다. 근거: wiki/projects/ca-tmpl/runtime-container-health-migration
- 사실: startup validation과 migration failure는 local/dev verification 범위로 기록되어 있다. 근거: wiki/projects/ca-tmpl/runtime-container-health-migration
- 사실: Kubernetes manifest, rolling update, production probe tuning, cluster-level incident 검증은 없다. 근거: wiki/projects/ca-tmpl/runtime-container-health-migration
- 의견: skeleton에서는 잘못된 runtime env를 lenient default로 흘리는 것보다 startup에서 실패시키는 쪽이 학습과 운영 설명에 유리하다.
- 알지 못하는 것: 실제 orchestrator rollout behavior, migration lock contention, production shutdown latency.
답할 수 있는 범위 / Answer boundary
- 자신 있게 답할 수 있는 후속 질문:
- ca-tmpl은 어떤 runtime config 오류를 startup에서 막는가?
- liveness와 readiness health group을 왜 나누는가?
- Flyway migration 실패가 어떻게 startup failure와 exit code로 연결되는가?
- 다음 글로 넘길 부분:
- Kubernetes production probe tuning.
- rolling update와 graceful shutdown 실측.
- DB migration 운영 runbook과 장애 복구 사례.
게시 체크리스트 / Publish checklist
- 모든 사실 주장에 canonical 링크 있음
- 사실 vs 의견 분리 명시됨
- 금지 마케팅 표현 없음
- 코드 예제 출처 명시
- 타깃 독자 가정과 톤 일치
/lint통과- 게시 URL 기록 (게시 후):