본문으로 건너뛰기
안동민 개발노트 아이콘

안동민 개발노트

본문 시작
15장 : Actuator와 관측

Actuator 엔드포인트

Spring Boot Actuator를 추가해 엔드포인트의 활성화와 노출을 구분하고 탐색·기본 경로·별도 관리 포트·사용자 정의 엔드포인트·가용성을 최소 권한과 실패 신호로 검증합니다.

개발 환경에서는 브라우저에 페이지가 보이면 성공처럼 느껴집니다. 운영 환경에서는 애플리케이션이 살아 있는지, 데이터베이스와 연결되는지, 요청이 얼마나 느린지까지 확인해야 합니다. 이렇게 실행 중인 시스템의 상태를 바깥에서 이해할 수 있게 만드는 것을 관측 가능성(observability)이라고 합니다.

Spring Boot Actuator는 상태·정보·메트릭 같은 운영용 엔드포인트를 제공합니다. 엔드포인트는 운영 정보를 읽거나 일부 설정을 바꾸는 접근 지점이고, Micrometer는 카운터·타이머 같은 메트릭을 여러 관측 시스템에 전달하는 공통 계층입니다. Actuator 스타터를 추가했다고 모든 정보가 자동으로 공개되지는 않습니다. 엔드포인트가 생성되는 것과 HTTP나 JMX에 노출되는 것은 별도 결정입니다.


Actuator 인프라

spring-boot-starter-actuator를 애플리케이션 의존성에 추가하면 상태, 정보, 메트릭 같은 엔드포인트 인프라와 Micrometer 통합이 자동 구성됩니다. 엔드포인트마다 필요한 클래스와 빈 조건이 다르므로 의존성 그래프, 조건 보고서, 엔드포인트 목록을 함께 확인합니다.

src/main/java/board/BoardApplication.java
package board;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class BoardApplication {
    public static void main(String[] args) {
        SpringApplication.run(BoardApplication.class, args);
    }
}
build.gradle 의존성
implementation 'org.springframework.boot:spring-boot-starter-webmvc'
implementation 'org.springframework.boot:spring-boot-starter-actuator'

애플리케이션이 정상 시작하면 /actuator 탐색 문서에서 현재 웹에 노출된 링크를 볼 수 있습니다. 엔드포인트 빈이 없거나 보안이 거부한 결과와 경로가 틀린 404를 구별합니다.

활성화·노출 구분

활성화 설정은 엔드포인트 자체와 관련 빈을 만들지 결정합니다. 노출 설정은 만들어진 엔드포인트를 웹 또는 JMX에 연결할지 결정합니다. metrics가 활성화라도 웹 포함에 없으면 HTTP로 읽을 수 없고, 웹에 포함 이름을 적어도 엔드포인트가 비활성화면 나타나지 않습니다.

기본 노출은 보수적입니다. *로 전부 열고 네트워크 방화벽에만 기대지 말고 실제 운영자가 필요한 ID 허용 목록을 작성합니다. YAML에서 별표는 문법 의미가 있으므로 따옴표로 감싸고, 제외가 포함보다 어떤 결과를 만드는지 시작 테스트에서 확인합니다.

src/main/resources/application.yml
management:
  endpoints:
    web:
      exposure:
        include: "health,info,metrics"
  endpoint:
    health:
      show-details: never

엔드포인트 ID·경로

health, info, metrics, loggers, configprops, env, beans, mappings, threaddump, heapdump, httpexchanges, prometheus, shutdown은 민감도와 비용이 다릅니다. 엔드포인트 이름만 보고 공개하지 말고 응답 내용, 변경 가능성, 페이로드 크기, 호출 빈도를 목록에 기록합니다.

기반 경로를 바꾸면 보안이 생기는 것이 아닙니다. /internal/manage처럼 추측하기 어려운 경로도 인증과 네트워크 정책 없이는 공개 엔드포인트입니다. 리버스 프록시가 접두사를 덧붙이면 준비 상태 점검과 대시보드 대상도 함께 바꿉니다.

src/main/java/board/operations/EndpointPolicy.java
package board.operations;

import java.util.Set;

public record EndpointPolicy(
        Set<String> enabled,
        Set<String> webExposed,
        Set<String> mutating
) {
    public EndpointPolicy {
        enabled = Set.copyOf(enabled);
        webExposed = Set.copyOf(webExposed);
        mutating = Set.copyOf(mutating);
        if (!enabled.containsAll(webExposed)) {
            throw new IllegalArgumentException("exposed endpoint must be enabled");
        }
        if (!webExposed.containsAll(mutating)) {
            throw new IllegalArgumentException("mutating endpoint must be inventoried");
        }
    }

    public Set<String> internalOnly() {
        var result = new java.util.HashSet<>(enabled);
        result.removeAll(webExposed);
        return Set.copyOf(result);
    }
}

정책 파일과 실제 탐색 결과를 배포 후 기본 동작 검증에서 비교하면 속성 오타나 버전 업그레이드로 엔드포인트가 조용히 늘어난 상황을 잡을 수 있습니다.

관리 서버 분리

management.server.port를 애플리케이션 포트와 다르게 두면 별도 커넥터에서 Actuator를 제공합니다. 비공개 인터페이스에 바인딩하고 인그레스·서비스·방화벽을 분리하면 공개 요청 경로에 운영 엔드포인트가 섞이지 않습니다. 그러나 프로세스와 JVM은 공유하므로 애플리케이션이 stop-the-world 일시 중지에 빠지면 관리 포트도 응답하지 못할 수 있습니다.

별도 포트의 TLS, 접근 로그, 단계적 종료, 포트 충돌도 운영 대상입니다. management.server.address=127.0.0.1은 사이드카 수집에는 맞지만 원격 점검에는 닿지 않을 수 있습니다. 배포 구성에 따라 주소를 결정하고 로컬 개발 기본값을 운영 환경에 그대로 쓰지 않습니다.

사용자 정의 엔드포인트

사용자 정의 엔드포인트는 HTTP와 JMX에서 같은 작업 모델을 제공할 때 유용합니다. 주문 생성 같은 업무 명령을 Actuator에 넣지 않고 대기열 상태나 캐시 재생성처럼 호출 빈도가 낮은 운영 제어 기능을 둡니다. 선택기와 파라미터를 자유 텍스트로 받지 말고 값 범위를 제한합니다.

src/main/java/board/operations/PostQueueEndpoint.java
package board.operations;

import java.time.Instant;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.stereotype.Component;

@Component
@Endpoint(id = "postqueue")
public class PostQueueEndpoint {
    private final AtomicInteger pending = new AtomicInteger();

    @ReadOperation
    public Map<String, Object> state() {
        return Map.of(
                "pending", pending.get(),
                "observedAt", Instant.now().toString());
    }

    public void accepted() {
        pending.incrementAndGet();
    }

    public void completed() {
        pending.updateAndGet(value -> Math.max(0, value - 1));
    }
}

동적 타임스탬프는 캐시와 테스트를 어렵게 할 수 있어 실제 코드에서는 시계를 주입합니다. 대기 값에 회원 ID나 대기열 항목 내용을 넣지 않고 집계만 제공합니다.

가용성·인가 관찰

404는 엔드포인트 비활성화, 미노출, 경로 불일치 중 하나일 수 있고, 401은 인증 부재, 403은 권한 부족, 503은 상태가 서비스 사용 불가로 매핑된 결과일 수 있습니다. 자동화 도구는 이 상태를 하나의 “Actuator 실패”로 처리하지 않습니다.

배포 smoke 결과
GET /actuator -> 200, links=[health,info,metrics]
GET /actuator/env -> 404, not exposed
GET /actuator/health -> 200, status=UP
GET /actuator/loggers -> 403, operator role required
GET application-port/actuator -> 404, management port separated

탐색 문서 자체를 숨기거나 보안으로 보호하는 경우 알려진 엔드포인트를 직접 점검합니다. 응답 본문뿐 아니라 콘텐츠 타입, 지연 시간 예산, 인증 경로를 확인합니다.

연습 문제

회원 게시판에 별도 관리 포트를 열고 상태·정보·메트릭만 웹에 노출하세요. postqueue 읽기 엔드포인트를 추가하되 항목 원문은 공개하지 마세요. 비활성화, 미노출, 미인증, 정상이라는 네 분기의 상태와 탐색 링크를 표로 검증하세요.

해설 보기

설정 파일만 검토하지 말고 실행된 서버의 탐색 결과를 정책 허용 목록과 비교합니다. 별도 포트가 공개 인그레스에 라우팅되지 않았는지도 확인합니다.

package board.operations;

public record EndpointObservation(int status, boolean linked, String meaning) {
    public static EndpointObservation classify(int status, boolean linked) {
        return switch (status) {
            case 200 -> new EndpointObservation(200, linked, "AVAILABLE");
            case 401 -> new EndpointObservation(401, linked, "AUTHENTICATION_REQUIRED");
            case 403 -> new EndpointObservation(403, linked, "AUTHORIZATION_DENIED");
            case 404 -> new EndpointObservation(404, linked, "DISABLED_OR_NOT_EXPOSED");
            default -> new EndpointObservation(status, linked, "INVESTIGATE");
        };
    }
}

404 원인은 시작 조건 보고서와 엔드포인트 목록으로 다시 좁힙니다.