상태·정보·로그 제어
생존 상태와 준비 상태 그룹, 사용자 정의 HealthIndicator, 빌드·Git 정보, 런타임 로그 수준 변경, 제한된 HTTP 교환 기록을 민감 정보·부하·복구 절차까지 포함해 설계합니다.
Actuator 엔드포인트 네 개는 모두 JSON을 돌려주지만 목적이 다릅니다.
상태는 트래픽을 받아도 되는지 판단하고, 정보는 어떤 산출물이 배포됐는지 식별하며, loggers는 실행 중 진단 수준을 바꾸고, HTTP 교환은 최근 요청의 흔적을 보여 줍니다.
같은 노출과 보존 기간 정책을 적용하면 준비 상태가 비밀을 노출하거나 디버그 기록이 메모리를 잠식합니다.
상태 기반 트래픽 제어
상태 지표는 의존성을 확인해 UP, DOWN, OUT_OF_SERVICE, UNKNOWN 같은 상태를 제공합니다.
개별 상태를 집계한 결과는 우선순위와 HTTP 상태 매핑을 거쳐 최종 점검 응답이 됩니다.
모든 의존성 장애를 DOWN으로 만들면 선택 기능 하나 때문에 전체 인스턴스가 로드 밸런서에서 빠질 수 있습니다.
생존 상태는 프로세스가 스스로 회복할 수 없는 상태인지, 준비 상태는 지금 새 요청을 받을 준비가 됐는지 답합니다. 데이터베이스의 일시적 지연을 생존 상태 실패로 연결하면 오케스트레이터가 정상 프로세스를 반복 재시작해 장애를 키울 수 있습니다. 의존성별로 어느 그룹에 포함할지 서비스 성능 저하 정책과 함께 정합니다.
package board.operations;
import java.util.function.IntSupplier;
import org.springframework.boot.health.contributor.Health;
import org.springframework.boot.health.contributor.HealthIndicator;
import org.springframework.stereotype.Component;
@Component("postBacklog")
public class BacklogHealthIndicator implements HealthIndicator {
private final IntSupplier pendingCount;
public BacklogHealthIndicator(IntSupplier pendingCount) {
this.pendingCount = pendingCount;
}
@Override
public Health health() {
int pending = pendingCount.getAsInt();
if (pending > 10_000) {
return Health.outOfService()
.withDetail("pendingBucket", "over-10000")
.build();
}
return Health.up()
.withDetail("pendingBucket", pending > 1_000 ? "high" : "normal")
.build();
}
}상세에 대기열 항목, SQL 예외 원문, 인증 정보를 넣지 않습니다. 원격 의존성 점검에는 짧은 타임아웃과 격벽을 적용해 상태 호출이 새 부하 원인이 되지 않게 합니다.
상태 그룹
시작 동안 준비 상태는 아직 요청을 받지 않는 상태일 수 있습니다. 마이그레이션과 캐시 준비 작업이 끝난 뒤 트래픽 수용 상태로 전환하고, 단계적 종료 시작 시 먼저 준비 상태를 거둔 후 처리 중인 요청을 마칩니다. 점검 간격과 실패 임계값이 전환 시간보다 너무 짧지 않은지 확인합니다.
사용자 정의 그룹으로 데이터베이스 쓰기, 메트릭 외부 전송, 대기량을 나눌 수 있습니다.
핵심 데이터 쓰기가 실패하면 준비 상태를 내리지만 선택적 분석용 메트릭 전송 실패는 UP 상세 또는 별도 메트릭으로 경보할 수 있습니다.
상태만 초록색으로 만들려고 실패를 숨기지 않고 트래픽 제거가 올바른 복구인지를 기준으로 정합니다.
빌드·출처 정보
빌드 정보에는 산출물 이름, 버전, 빌드 시간을, Git 정보에는 커밋 ID와 브랜치 같은 출처 정보를 제공할 수 있습니다. Java와 OS 정보는 런타임 불일치 진단에 유용합니다. 환경 변수 전체를 정보에 복사하면 비밀 정보와 내부 구성이 새어 나가므로 허용 목록만 사용합니다.
package board.operations;
import java.util.Map;
import org.springframework.boot.actuate.info.Info;
import org.springframework.boot.actuate.info.InfoContributor;
import org.springframework.stereotype.Component;
@Component
public class BoardInfoContributor implements InfoContributor {
@Override
public void contribute(Info.Builder builder) {
builder.withDetail("board", Map.of(
"curriculumRevision", "2026-07",
"schemaGeneration", 4,
"runtime", Runtime.version().feature()));
}
}인스턴스마다 달라지는 요청 ID나 타임스탬프를 정보에 넣지 않습니다. 배포 컨트롤러는 기대한 커밋과 정보 엔드포인트에 표시된 커밋을 비교해 오래된 파드가 남았는지 확인합니다.
로그 수준 변경 기한
런타임 로그 수준 변경은 재배포 없이 문제를 좁히는 강력한 도구지만 페이로드·SQL·토큰이 급증할 수 있습니다. 쓰기 작업은 운영자 역할에만 허용하고 누가 어떤 로거를 어느 수준으로 바꿨는지 감사 로그에 기록합니다. 자동 복귀 기한을 두고 장애가 끝난 뒤 원래 수준으로 돌아왔는지 확인합니다.
패키지 로거를 디버그로 올리기 전에 예상 이벤트 비율과 저장소 예산을 계산합니다. 루트 로거 전체를 트레이스로 바꾸지 않고 가장 좁은 패키지를 선택합니다. 애플리케이션 재시작 뒤 영구 설정으로 돌아가는지, 중앙 로깅 제어가 다시 덮는지 운영 문서에 씁니다.
package board.operations;
import java.time.Duration;
import java.time.Instant;
public record TemporaryLogLevel(
String loggerName,
String originalLevel,
String temporaryLevel,
Instant expiresAt
) {
public TemporaryLogLevel {
if (loggerName.isBlank() || expiresAt.isBefore(Instant.now())) {
throw new IllegalArgumentException("invalid temporary logger change");
}
}
public static TemporaryLogLevel forWindow(
String loggerName,
String originalLevel,
String temporaryLevel,
Instant now,
Duration window
) {
return new TemporaryLogLevel(
loggerName, originalLevel, temporaryLevel, now.plus(window));
}
}실제 구현은 시계를 주입해 생성자의 현재 시각 의존성을 제거하고 스케줄러 실패도 감사 기록에 남깁니다.
HTTP 교환 보존 정책
HTTP 교환 기록은 최근 요청/응답의 메서드, URI, 상태, 시간 측정, 일부 헤더를 진단합니다. 리포지토리 빈이 없으면 엔드포인트가 기록을 갖지 않을 수 있습니다. 메모리 기반 리포지토리는 제한된 최근 항목만 두고 프로세스 재시작 때 사라지는 진단 버퍼로 사용합니다.
인가, Cookie, Set-Cookie, 쿼리의 토큰, 요청 본문을 수집하지 않습니다. 모든 트래픽을 무기한 저장하는 감사 로그가 아니며 고부하 환경에서는 표본 추출이나 전용 트레이싱 백엔드가 적합합니다. URI에 ID가 그대로 포함되면 카디널리티와 개인정보가 생기므로 경로 템플릿 기준으로 집계합니다.
엔드포인트별 실패 해석
liveness DOWN -> process deadlock 진단, restart 후보
readiness OUT_OF_SERVICE -> traffic drain, dependency recovery 대기
info commit mismatch -> stale rollout instance
logger DEBUG expired but still active -> LOG_LEVEL_RESTORE_FAILED
httpexchanges retained=10000 -> DIAGNOSTIC_BUFFER_UNBOUNDED상태 상세를 볼 권한과 상태만 볼 공개 점검을 분리합니다. 로거 변경 성공 상태만 믿지 말고 이후 실제 적용 수준과 만료 감사 기록을 확인합니다.
연습 문제
회원 게시판 대기량 지표를 준비 상태 그룹에만 넣고, 커밋·스키마 생성을 정보에 제공하세요.
board.import 로거를 15분 동안 디버그로 올렸다 자동 복귀시키는 변경 기록을 설계하고, HTTP 교환에서 인가와 쿼리 토큰이 남지 않는 테스트를 작성하세요.
해설 보기
지표가 실패할 때 재시작이 해결책인지 트래픽 차단이 해결책인지 먼저 적습니다. 운영 엔드포인트의 변경은 반드시 기존 값, 변경자, 만료 시각을 함께 남깁니다.
package board.operations;
public record ProbeDecision(boolean live, boolean ready, String action) {
public static ProbeDecision backlog(boolean processHealthy, int pending) {
if (!processHealthy) {
return new ProbeDecision(false, false, "RESTART_AFTER_DIAGNOSIS");
}
if (pending > 10_000) {
return new ProbeDecision(true, false, "DRAIN_AND_RECOVER");
}
return new ProbeDecision(true, true, "SERVE");
}
}선택적 메트릭 전송 기능의 장애는 이 결정과 별도 메트릭으로 표현합니다.