전투에서 실패한 지휘관은 용서할 수 있지만, 경계에서 실패하는 지휘관은 결코 용서할 수 없다.
애플리케이션 운영 환경에서도 마찬가지로 장애가 발생하는 것은 어쩔 수 없다 해도 이를 모니터링하고 빠르게 대응하는 ‘경계 태세’만큼은 항상 갖추고 있어야 한다.
프로덕션 준비 기능이란?
개발자가 애플리케이션을 만들 때는 단순히 기능 요구사항만 만족시키는 것에 그치지 않는다. 서비스가 실제 운영 단계에 올라가면 문제가 발생하지는 않는지, 지표들을 잘 수집하고 있는지, 로그 정보는 정상적으로 쌓이고 있는지 등을 지속적으로 모니터링하고 감시해야 한다.
이처럼 운영 환경에서 애플리케이션을 운용하기 위해 비기능적 요소들을 준비하고 구성하는 과정을 가리켜, 우리는 흔히 프로덕션 준비 기능(Production-Ready Features) 이라고 부른다. 다음과 같은 것들이 있다.
- 지표(Metric): CPU, 메모리, 트래픽, DB 연결 수 등 각종 성능 지표
- 추적(Trace): 애플리케이션 요청 흐름이나 에러 발생 경로 추적
- 감사(Audit): 사용자 활동, 보안 이벤트, 인증/인가 이력 모니터링
- 모니터링: 위와 같은 정보를 종합하여, 장애나 성능 저하를 사전에 파악하고 대응
조금 더 구체적으로 설명하자면, 애플리케이션이 현재 정상 작동중인지, 로그 설정은 적절한지, 커넥션 풀은 얼마나 사용 중인지 등을 쉽게 확인해야 한다는 것이다.
스프링 부트 액츄에이터의 역할
스프링 부트가 제공하는 액츄에이터(Spring Boot Actuator) 는 이러한 프로덕션 준비 기능을 매우 편리하게 사용할 수 있도록 만들어준다. 액추에이터를 사용하면 다음과 같은 작업들이 간단해진다.
- 애플리케이션 상태(Health) 조회
- 환경 정보(Env), 프로퍼티(Properties), 로거(Loggers) 등 내부 설정 확인
- CPU, 메모리, GC 등 모니터링 지표(Metrics) 실시간 확인
- 다양한 엔드포인트(Endpoint)를 통한 심층적인 운영 정보 확인
- 마이크로미터(Micrometer), 프로메테우스(Prometheus), 그라파나(Grafana) 등 모니터링 시스템과의 손쉬운 연동
“액추에이터(Actuator)”는 원래 기계를 움직이거나 제어하는 장치를 의미한다. 이름 그대로 애플리케이션 내부에서 여러 가지 동작 방식을 ‘제어’하고, 동시에 상태 정보를 외부로 노출시키는 역할을 맡고 있다.
운영 환경에 애플리케이션을 배포하기 전 혹은 이미 운영 중이라면 더욱더 우리는 아래 질문들을 꼭 점검해 봐야 한다.
- 장애나 성능 문제는 어떻게 탐지할 것인가?
- 문제가 발생했다면, 어디에서 에러가 발생했는지 어떻게 빠르게 확인할 것인가?
- 리소스 사용량(CPU, 메모리, DB 연결 수 등)을 어떻게 모니터링하고, 임계점을 넘지 않도록 설정할 것인가?
- 애플리케이션 로그 레벨을 동적으로 변경하거나, 상세 정보를 필요할 때 어떻게 더 쉽게 수집할 것인가?
이 모든 것이 바로 프로덕션 준비 기능이며, 이를 더욱 쉽고 체계적으로 구현해주는 것이 스프링 부트 액추에이터다.
엑츄에이터 시작
엑츄에이터가 제공하는 프로덕션 준비 기능을 사용하려면 스프링부트 엑츄에이터 라이브러리를 추가해야한다.
build.gradle
// actuator 추가
implementation 'org.springframework.boot:spring-boot-starter-actuator'
그리고 웹 어플리케이션을 시작하고 아래의 url을 호출해보자
http://localhost:8080/actuator
실행 결과
{
"_links": {
"self": {
"href": "http://localhost:8080/actuator",
"templated": false
},
"health": {
"href": "http://localhost:8080/actuator/health",
"templated": false
},
"health-path": {
"href": "http://localhost:8080/actuator/health/{*path}",
"templated": true
}
}
}
지금 실행된 결과는 헬스 상태를 확인할 수 있는 기능 밖에 없다. 액츄에이터는 헬스 상태 뿐만 아니라 수 많은 기능을 제공 하는데 이런 기능이 웹 환경에서 보이도록 노출해야 한다.
application.properties
management.endpoints.web.exposure.include=*
application.yml
management:
endpoints:
web:
exposure:
include: "*"
다시 "http://localhost:8080/actuator"를 실행해보면 다음과 같은 결과가 나올 것이다.
{
"_links": {
"self": {
"href": "http://localhost:8080/actuator",
"templated": false
},
"beans": {
"href": "http://localhost:8080/actuator/beans",
"templated": false
},
"caches-cache": {
"href": "http://localhost:8080/actuator/caches/{cache}",
"templated": true
},
"caches": {
"href": "http://localhost:8080/actuator/caches",
"templated": false
},
"health": {
"href": "http://localhost:8080/actuator/health",
"templated": false
},
"health-path": {
"href": "http://localhost:8080/actuator/health/{*path}",
"templated": true
},
"info": {
"href": "http://localhost:8080/actuator/info",
"templated": false
},
"conditions": {
"href": "http://localhost:8080/actuator/conditions",
"templated": false
},
"configprops": {
"href": "http://localhost:8080/actuator/configprops",
"templated": false
},
"configprops-prefix": {
"href": "http://localhost:8080/actuator/configprops/{prefix}",
"templated": true
},
"env": {
"href": "http://localhost:8080/actuator/env",
"templated": false
},
"env-toMatch": {
"href": "http://localhost:8080/actuator/env/{toMatch}",
"templated": true
},
"loggers": {
"href": "http://localhost:8080/actuator/loggers",
"templated": false
},
"loggers-name": {
"href": "http://localhost:8080/actuator/loggers/{name}",
"templated": true
},
"heapdump": {
"href": "http://localhost:8080/actuator/heapdump",
"templated": false
},
"threaddump": {
"href": "http://localhost:8080/actuator/threaddump",
"templated": false
},
"metrics-requiredMetricName": {
"href": "http://localhost:8080/actuator/metrics/{requiredMetricName}",
"templated": true
},
"metrics": {
"href": "http://localhost:8080/actuator/metrics",
"templated": false
},
"sbom": {
"href": "http://localhost:8080/actuator/sbom",
"templated": false
},
"sbom-id": {
"href": "http://localhost:8080/actuator/sbom/{id}",
"templated": true
},
"scheduledtasks": {
"href": "http://localhost:8080/actuator/scheduledtasks",
"templated": false
},
"mappings": {
"href": "http://localhost:8080/actuator/mappings",
"templated": false
}
}
}
엑츄에이터가 제공하는 수 많은 기능을 확인할 수 있다. 이렇게 제공하는 기능 하나하나를 엔드포인트라고 한다.
각각의 엔드포인트는 /actuator/{엔드포인트명}과 같은 형식으로 접근할 수 있다.
- http://localhost:8080/actuator/beans: 스프링 컨테이너에 등록된 빈을 보여준다
- http://localhost:8080/actuator/health: 애플리케이션 헬스 정보를 보여준다.
엔드포인트 설정
엔드포인트를 사용하려면 다음 두가지 과정이 모두 필요하다.
- 엔드포인트 활성화
- 엔드포인트 노출
엔드포인트를 활성화 한다는 것은 해당 기능 자체를 사용할지 말지 on, off 를 선택하는 것이다. 엔드포인트를 노출하는 것은 활성화된 엔드포인트를 HTTP에 노출할 것인지, JMX에 노출할 것인지 선택하는 것이다.
대부분의 엔드포인트는 노출이 되어있지 않지만 활성화가 되어있다. 따라서 어떤 엔드포인트를 노출할지 선택하면 된다.
application.properties - 모든 엔드포인트(shutdown 제외) 노출
management.endpoints.web.exposure.include=*
shutdown 엔드포인트는 기본으로 활성화 되지 않기에 활성화 시켜주어야 한다.
application.properties - shutdown 엔드포인트 노출
management.endpoint.shutdown.access=unrestricted
전체 엔드포인트가 아닌 특정 엔드포인트만 노출시키는 방법도 있다.
application.properties - health, info 엔드포인트만 노출
management.endpoints.web.exposure.include=health,info
실행결과
{
"_links": {
"self": {
"href": "http://localhost:8080/actuator",
"templated": false
},
"health": {
"href": "http://localhost:8080/actuator/health",
"templated": false
},
"health-path": {
"href": "http://localhost:8080/actuator/health/{*path}",
"templated": true
},
"info": {
"href": "http://localhost:8080/actuator/info",
"templated": false
}
}
}
다양한 엔드포인트
각각의 엔드포인트를 통해 개발자는 애플리케이션 내부의 수 많은 기능을 관리하고 모니터링할 수 있다.
스프링 부트가 기본으로 제공하는 다양한 엔드포인트에 대해서 알아보자.
- /actuator/health
- 애플리케이션 상태(Health)에 대한 정보를 확인할 수 있다.
- DB 연결 상태, 큐, 캐시 등 다양한 커스텀 구성 요소 상태도 확인 가능하게 설정할 수 있다.
- /actuator/info
- 애플리케이션에 대한 임의의 정보(버전, 작성자 등)를 노출할 수 있다.
- application.properties 또는 application.yml에 info.* 속성을 추가해 노출할 수 있다.
- /actuator/metrics
- CPU 사용량, GC 횟수, 메모리 사용량 등 애플리케이션 모니터링 지표를 확인할 수 있다.
- 특정 metric 이름을 추가로 붙여 호출하면 해당하는 메트릭 값만 상세하게 확인할 수도 있다.
- 예: /actuator/metrics/jvm.memory.used
- /actuator/env
- 애플리케이션이 사용하는 모든 환경 변수(프로퍼티) 정보를 볼 수 있다.
- 프로파일, 시스템 변수, application.properties/application.yml에서 설정한 값 등이 포함된다.
- /actuator/loggers
- 런타임 시점에 로거(Logger) 이름별로 로그 레벨을 조회하고 동적으로 변경할 수 있다.
- /actuator/threaddump
- 쓰레드 덤프를 실행해서 보여준다.
전체 엔드포인트는 여기 서 확인할 수 있다.
이제 자주 사용하는 엔드포인트에 대해 알아보자.
1. 헬스 정보(health)
헬스 정보를 사용하면 애플리케이션 동작에 문제가 발생했을 때 문제를 빠르게 인지할 수 있다.
먼저 다음과 같은 URL을 호출 해보자.
http://localhost:8080/actuator/health
실행 결과
{
"status": "UP"
}
헬스 정보는 단순히 애플리케이션이 정상적으로 살아있는지를 넘어서 애플리케이션이 사용하는 데이터 베이스가 응답하는지, 디스크 사용량에는 문제가 없는지 같은 다양한 정보들을 포함해서 만들어진다.
헬스정보를 더 자세히 보려면 다음과 같은 옵션을 지정하면 된다.
application.properties
management.endpoint.health.show-details=always
실행결과
{
"status": "UP",
"components": {
"db": {
"status": "UP",
"details": {
"database": "H2",
"validationQuery": "isValid()"
}
},
"diskSpace": {
"status": "UP",
"details": {
"total": 1999673716736,
"free": 1841839448064,
"threshold": 10485760,
"path": "C:\\projects\\laboratory\\bbs\\backend_1\\board\\.",
"exists": true
}
},
"ping": {
"status": "UP"
},
"redis": {
"status": "UP",
"details": {
"version": "3.0.504"
}
},
"ssl": {
"status": "UP",
"details": {
"validChains": [],
"invalidChains": []
}
}
}
}
각각의 항목들이 자세히 노출되는 것을 볼 수 있다. 만약 자세히 노출되는게 부담스럽다면 show-details 옵션을 제거하고 다음과 같은 옵션을 사용하면 된다.
#management.endpoint.health.show-details=always #제거
management.endpoint.health.show-components=always
실행결과
{
"status": "UP",
"components": {
"db": {
"status": "UP"
},
"diskSpace": {
"status": "UP"
},
"ping": {
"status": "UP"
},
"redis": {
"status": "UP"
},
"ssl": {
"status": "UP"
}
}
}
만약 헬스 정보중 하나라도 문제가 발생하면 status가 "DOWN"이 된다.
2. 애플리케이션 정보(Info)
info 엔드포인트는 애플리케이션의 기본 정보를 노출한다. 기본으로 제공하는 기능들은 다음과 같다.
- java: 자바 런타임 정보
- os: OS 정보
- env: Environment에서 info.로 시작하는 정보
- build: 빌드 정보, META-INF/build-info.properties 파일이 필요
- git: git 정보, git.properties 파일이 필요
이제 실행 해보자. 다음과 같은 url을 호출하면 된다.
http://localhost:8080/actuator/info
실행결과
{}
처음 실행하면 정보들이 보이지 않을 것이다. 먼저 java, os 기능을 활성화 해보자.
1. java, os 정보 확인
application.properties
management.info.java.enabled=true
management.info.os.enabled=true
실행결과
{
"java": {
"version": "17.0.12",
"vendor": {
"name": "Amazon.com Inc.",
"version": "Corretto-17.0.12.7.1"
},
"runtime": {
"name": "OpenJDK Runtime Environment",
"version": "17.0.12+7-LTS"
},
"jvm": {
"name": "OpenJDK 64-Bit Server VM",
"vendor": "Amazon.com Inc.",
"version": "17.0.12+7-LTS"
}
},
"os": {
"name": "Windows 10",
"version": "10.0",
"arch": "amd64"
}
}
실행해보면 java, os관련 정보를 확인할 수 있다.
2. env 정보 확인
이번에는 env를 사용해보자. Environment애서 info.로 시작하는 정보를 출력한다.
management.info.env.enabled=true
info.app.name=bbs
info.app.company=Company
실행결과
"app": {
"name": "bbs",
"company": "Company"
},
application.properties에서 info로 시작하는 부분의 정보가 노출되는 것을 볼 수 있다.
3. build 정보 확인
이번에는 빌드 정보를 노출해보자 빌드시점에 META-INF/build-info.properties 파일을 만들면 된다.
build.gradle
springBoot{
buildInfo()
}
이렇게 하고 빌드를 해보면 build 폴더 안에 resources/main/META-INF/build-info.properties 파일을 확인할 수 있다.
실행결과
"build": {
"artifact": "board",
"name": "board",
"time": "2025-01-13T05:34:31.635Z",
"version": "0.0.1-SNAPSHOT",
"group": "bbs"
},
3. 로그 정보(loggers)
loggers 엔드포인트를 사용하면 로깅과 관련된 정보를 확인하고 실시간으로 변경할 수 있다.
다음과 같은 엔드포인트를 호출해보자.
http://localhost:8080/actuator/loggers
실행결과
{
"levels": [
"OFF",
"ERROR",
"WARN",
"INFO",
"DEBUG",
"TRACE"
],
"loggers": {
"ROOT": {
"configuredLevel": "INFO",
"effectiveLevel": "INFO"
},
"_org": {
"effectiveLevel": "INFO"
},
"_org.springframework": {
"effectiveLevel": "INFO"
},
"_org.springframework.web": {
"effectiveLevel": "INFO"
:
:
:
}
로그를 별도로 설정하지 않으면 스프링 부트는 기본적으로 INFO를 사용한다. 실행결과를 보면 ROOT의 configuredLevel이 INFO인것을 확인할 수 있다. 따라서 그 하위도 모두 INFO 레벨이 적용된다.
configuredLevel과 effectiveLevel은 무엇일까?
- configuredLevel: 설정되어 있는 로그 레벨을 의미
- effectiveLevel: 실제 적용되어 사용되는 로그 레벨을 의미
더 자세히 조회하기
다음과 같은 엔드포인트로 특정 로거 이름을 기준으로 조회할 수 있다.
패턴: http://localhost:8080/actuator/loggers/{로거이름}
예시: http://localhost:8080/actuator/loggers/bbs.board
실행 결과
{
"effectiveLevel": "INFO"
}
실시간 로그 레벨 변경
개발 서버는 보통 DEBUG 로그를 사용하지만 운영서버는 보통 요청이 많아 로그도 많이 남기 때문에 DEBUG 로그까지 출력하면 성능에 영향을 주게 된다. 따라서 운영서버에는 보통 INFO 레벨을 사용한다.
그런데 만약 서비스 운영중에 문제가 있어서 급하게 DEBUG나 TRACE로 로그를 남겨서 확인하고 싶다면 어떻게 해야할까? 일반적으로 로깅 설정을 변경하고 서버를 다시 시작해야 한다.
여기서 loggers 엔드포인트를 사용하면 애플리케이션을 다시 시작하지 않고 실시간으로 로그 레벨을 변경할 수 있다.
Postman을 사용해서 변경을 진행해보겠다.
Post로 전달하는 내용 JSON
{
"configuredLevel": "TRACE"
}
요청에 성공하면 204 응답이 온다.(Http status가 204이므로 별도의 응답 메시지는 없다.)
다시 GET으로 요청해서 확인해보면 configuredLevel이 TRACE로 변경된 것을 확인할 수 있다.
4. HTTP 요청 응답 기록
HTTP 요청과 응답의 과거 기록을 확인하고 싶다면 httpexchanges 엔드포인트를 사용하면 된다.
HttpExchangeRepository 인터페이스의 구현체를 빈으로 등록해야 httpexchanges 엔드포인트를 사용할 수 있다.
@SpringBootApplication
public class BoardApplication {
public static void main(String[] args) {
SpringApplication.run(BoardApplication.class, args);
}
@Bean
public InMemoryHttpExchangeRepository httpExchangeRepository() {
return new InMemoryHttpExchangeRepository();
}
}
이 구현체는 최대 100개의 HTTP 요청을 제공한다. 최대 요청이 넘어가면 과거 요청을 삭제한다.
다음과 같은 url로 확인할 수 있다.
http://localhost:8080/actuator/httpexchanges
실행결과
{
"exchanges": [
{
"timestamp": "2025-01-13T05:59:55.766081400Z",
"request": {
"uri": "http://localhost:8080/login",
"method": "GET",
"headers": {
"host": [
"localhost:8080"
],
"connection": [
"keep-alive"
],
:
:
:
액츄에이터의 보안
스프링 부트 액추에이터는 애플리케이션의 내부 정보를 노출하여 운영 상태를 진단할 수 있게 해줍니다. 하지만, 이런 강력한 기능은 내부 정보가 외부에 너무 과도하게 노출되는 문제를 야기할 수 있다.
즉, 액추에이터 엔드포인트를 외부 인터넷에 그대로 공개하면 보안 위협에 취약해질 수 있으므로 반드시 접근 제어와 보안 설정이 필요하다.
1. 외부 노출 방지 – 접근 제어 및 내부망 활용
액추에이터 엔드포인트는 외부 인터넷 망이 아닌 내부망을 통해 접근할 수 있도록 설정하는 것이 가장 안전하다.
- 내부망 사용: 액추에이터에 노출되는 정보는 애플리케이션의 핵심 운영 데이터이므로, 외부 인터넷에서 접근할 수 없고 오직 내부 네트워크를 통해서만 접근 가능한 환경을 구성하는 것이 좋다.
- 스프링 시큐리티 적용: 만약 어쩔 수 없이 외부 인터넷 망을 통해 액추에이터에 접근해야 한다면, 스프링 시큐리티를 이용해 인증된 사용자만 접근할 수 있도록 추가적인 개발이 필요하다. 예를 들어, HTTP Basic 인증이나 다른 인증/인가 메커니즘을 도입하여 접근을 제어할 수 있다.
2. 액추에이터를 다른 포트에서 실행하기
액추에이터 엔드포인트에 대한 접근을 분리하는 또 다른 방법은 애플리케이션의 일반 서비스 포트와는 별도의 포트에서 액추에이터 기능을 실행하는 것이다.
예를 들어, 외부에서는 8080 포트를 통해 애플리케이션에 접근하지만, 관리/모니터링을 위한 액추에이터 엔드포인트는 내부망 전용 포트(예: 9292)를 사용하는 방식이다.
액추에이터 포트 분리 설정 방법
application.properties 혹은 application.yml 파일에 아래와 같이 설정한다.
application.properties
management.server.port=9292
이렇게 설정하면, 외부에서 일반 애플리케이션은 8080 포트로 접근할 수 있지만, 액츄에이터 http://localhost:9292/actuator와 같이 별도의 포트에서 접근할 수 있다.
액츄에이터 기본 엔드포인트 변경
만약 포트를 분리하기 어려운 상황이라면, 액추에이터의 기본 엔드포인트 URL 경로 자체를 변경하여 보안성을 높일 수 있다. 예를 들어 기본 경로인 /actuator 대신, 관리용 별도의 경로인 /manage로 변경하는 방법은 다음과 같다.
management.endpoints.web.base-path=/manage
이를 통해 관리자만이 알고 있는 별도의 URL로 관리 기능에 접근하도록 유도할 수 있다.
REFERENCE
'Spring Framework' 카테고리의 다른 글
[Spring] 자바 리플렉션으로 알아보는 JDK 동적 프록시와 CGLIB (0) | 2025.01.14 |
---|---|
[Spring] API 예외 처리 (1) | 2024.11.15 |
[Spring] 예외 처리와 오류 페이지 (0) | 2024.11.12 |
[Spring] Bean Validation (검증) - 2 (0) | 2024.11.04 |
[Spring] Bean Validation (검증) - 1 (4) | 2024.10.31 |