Spring, Apache, Java

[프로덕션 준비] 모니터링 Part.1 Actuator 사용하기

cwchoiit 2024. 6. 30. 21:32
728x90
반응형
SMALL

이번 포스팅에서는 스프링 부트에서 제공하는 액츄에이터 기능을 사용해서 모니터링을 효율적으로 하는 방법을 알아보자.

이 액츄에이터를 사용하려면 우선 다음과 같은 의존성이 필요하다.

 

build.gradle

implementation 'org.springframework.boot:spring-boot-starter-actuator'

이 의존성을 추가한 후 스프링 부트의 웹 서버를 실행해서 다음 URL로 접속해보자.

 

`http://yourbaseurl/actuator`

 

그러면 다음과 같은 화면이 보인다.

이렇게 여러개가 아니라 3개만 보인다면 application.yml 파일에 다음과 같이 추가해주면 된다.

application.yml

management:
  endpoints:
    web:
      exposure:
        include: "*"

이러면 액츄에이터가 제공해주는 모든 것들을 다 보여주겠다는 뭐 그런 의미가 된다.

그리고 다시 위 URL로 접속해보면 저렇게 보여진다.

 

그래서 실제 저기서 알려주는 health, caches, conditions 등등 여러 종류의 URL로 접속해보면 현재 스프링 부트의 서버 정보 관련된 내용이 나온다.

 

엔드포인트 설정하기

엔드포인트를 사용하려면 다음 2가지 과정이 모두 필요하다.

  • 엔드포인트 활성화
  • 엔드포인트 노출

엔드포인트를 활성화 한다는 것은 해당 기능 자체를 사용할지 말지 on, off를 선택하는 것이다.

엔드포인트를 노출하는 것은 활성화된 엔드포인트를 HTTP에 노출할지 아니면 JMX에 노출할지 선택하는 것이다. 엔드포인트를 활성화하고 추가로 HTTP를 통해서 웹에 노출할지, 아니면 JMX를 통해서 노출할지 두 위치에 모두 노출할지 노출 위치를 지정해주어야 한다.

 

물론 활성화가 되어 있지 않으면 노출도 되지 않는다. 그런데 엔드포인트는 대부분 기본으로 활성화 되어 있다. (shutdown 제외) 노출이 되어 있지 않을 뿐이다. 따라서 어떤 엔드포인트를 노출할지 선택하면 된다. 참고로 HTTP와 JMX를 선택할 수 있는데, 보통 JMX는 잘 사용하지 않으므로 HTTP에 어떤 엔드포인트를 노출할지 선택하면 된다.

 

application.yml - 모든 엔드포인트를 웹에 노출

management:
   endpoints:
     web:
       exposure:
        include: "*"

 

"*" 옵션은 모든 엔드포인트를 웹에 노출하는 것이다. 참고로 shutdown 엔드포인트는 기본으로 활성화되지 않기 때문에 노출도 되지 않는다. 엔드포인트 활성화 + 엔드포인트 노출이 둘 다 적용되어야 사용할 수 있다.

 

엔드포인트 활성화

application.yml - shutdown 엔드포인트 활성화

management:
   endpoint:
	  shutdown:
    	enabled: true
   endpoints:
      web:
        exposure:
          include: "*"

특정 엔드포인트를 활성화 하려면 management.endpoint.{엔드포인트명}.enabled=true를 적용하면 된다.

 

엔드포인트 노출

스프링 공식 메뉴얼이 제공하는 예제를 통해서 엔드포인트 노출 설정을 알아보자.

management:
   endpoints:
     jmx:
       exposure:
         include: "health,info"
  • JMXhealth, info를 노출한다.
management:
   endpoints:
     jmx:
       exposure:
         include: "*"
         exclude: "env,beans"
  • web에 모든 엔드포인트를 노출하지만 env, beans는 제외한다.

다양한 엔드포인트

각각의 엔드포인트를 통해서 개발자는 애플리케이션 내부의 수 많은 기능을 관리하고 모니터링 할 수 있다.

스프링 부트가 기본으로 제공하는 다양한 엔드포인트에 대해서 알아보자. 다음은 자주 사용하는 기능 위주로 정리했다.

엔드포인트 목록

  • beans: 스프링 컨테이너에 등록된 스프링 빈을 보여준다.
  • conditions: condition을 통해서 빈을 등록할 때 평가 조건과 일치하거나 일치하지 않는 이유를 표시한다.
  • configprops: @ConfigurationProperties를 보여준다.
  • health: 애플리케이션 헬스 정보를 보여준다.
  • httpexchanges: HTTP 호출 응답 정보를 보여준다. HttpExchangeRepository를 구현한 빈을 별도로 등록해야 한다.
  • info: 애플리케이션 정보를 보여준다.
  • loggers: 애플리케이션 로거 설정을 보여주고 변경도 할 수 있다.
  • shutdown: 애플리케이션을 종료한다. 이 기능은 기본으로 비활성화 되어 있다.
전체 엔드포인트는 공식 메뉴얼을 참고
 

Endpoints :: Spring Boot

If you add a @Bean annotated with @Endpoint, any methods annotated with @ReadOperation, @WriteOperation, or @DeleteOperation are automatically exposed over JMX and, in a web application, over HTTP as well. Endpoints can be exposed over HTTP by using Jersey

docs.spring.io

 

Health 정보

이 정보가 은근히 아주 쏠쏠하게 도움이 많이 되는데 예를 들면 DB 상태, 디스크 상태 등 여러 유용한 정보를 보여주기 때문에 이 기능을 잘 사용하면 좋다. 기본으로는 별 정보가 안나온다. 근데 다음과 같이 show-details 옵션을 always로 변경하면 더 자세한 정보를 출력해준다.

application.yml

management:
  endpoint:
    health:
      show-details: always
  endpoints:
    web:
      exposure:
        include: "*"

 

이렇게 설정해 둔 채로 `/actuator/health` 로 이동해보면 다음과 같이 보여진다.

우선, 첫번째 statusUP 또는 DOWN을 표시할 수 있는데, 아래 components 목록 중 하나라도 DOWN이라면 저 statusDOWN이 된다. DB의 헬스 상태를 확인을 어떻게 할까? 예전에는 실제로 디비에 더미 쿼리를 날려서 날린 쿼리를 잘 응답하는지 알아봤는데 요새는 디비의 상태 체크를 해주는 옵션 자체가 디비마다 있다. 그래서 그 방식으로 헬스 체크를 하고, 정상 응답을 받으면 다음과 같이 UP 상태로 띄워지게 된다. 

 

그래서 만약에 이 정보를 보고 DB가 DOWN인 상태라면 어? 이 애플리케이션 또는 이 서버의 디비가 현재 맛이 갔네? 라는 사실을 빠르게 인지할 수 있고 그에 따른 대응도 당연히 빨라질 수 밖에 없다. 이 DB상태가 DOWN이 되면 알림을 보내는 기능을 구현할수도 있고 여러 방법을 통해서 말이다.

 

저런 세부적인 내용까지 볼 필요없고 그냥 상태가 UP인지 DOWN인지만 보고 싶으면 다음과 같이 show-componentsalways로 설정하면 된다.

application.yml

management:
  endpoint:
    health:
      show-components: always
  endpoints:
    web:
      exposure:
        include: "*"

이렇게 딱 깔끔하게 상태체크만 볼수도 있다. 원하는대로 설정이 가능하다.

 

그래서, 이 JSON 데이터를 대시보드로 이쁘게 꾸밀수도 있고, 알림 설정을 해놔서 상태가 DOWN이 되면 곧바로 담당자에게 알림을 보내는 기능을 통해 애플리케이션의 장애를 빠르게 대응할 수 있게 된다. 일단, 어디서 어떤 문제가 생겼는지를 바로 체크할 수 있다는 것 자체가 대응의 시간을 전폭적으로 줄여주기 때문에 상당히 유용한 기능이라고 볼 수 있다.

 

info: 애플리케이션 정보 

이번엔 info에 대해 알아보자. 이 info는 애플리케이션 정보를 알려준다. 예를 들면 OS 정보, JVM정보, 환경 변수 정보, Git 정보등을 말이다.

마찬가지로 application.yml 파일에 추가해줄 설정이 있다.

application.yml

management:
  info:
    java:
      enabled: true
    os:
      enabled: true
    env:
      enabled: true
    git:
      mode: full

주의할 점은 이 infomanagement 바로 하위에 있다. 이 점 주의! 

java, os, env, git을 모두 enabled 시킨다.

 

그리고 envinfo 하위에 사용자가 직접 작성한 변수나 설정값을 말하는데 다음 설정값을 보자.

application.yml

management:
  info:
    java:
      enabled: true
    os:
      enabled: true
    env:
      enabled: true
    git:
      mode: full
  endpoint:
    shutdown:
      enabled: true
    health:
      show-components: always
  endpoints:
    web:
      exposure:
        include: "*"

info:
  app:
    name: hello-actuator
    company: cw

이 파일을 보면 하단에 info아래 app아래 name, company와 같은 사용자가 직접 정의한 설정값이 있다. 이런 값들을 보여준다.

그래서 위 사진을 보면 app 하위에 name, company 환경 변수 값이 잘 보여지고 있고 java 버전이라던가 jvm 버전, os정보가 잘 표시된다. gitbuild는 일단 닫아놨다. 이건 따로 설정이 필요하다.

 

우선, build 정보는 build/resources/main/META-INF/build-info.properties 파일이 필요하다. 근데 이걸 직접 만드는게 아니라, 알아서 만들어준다. 어떻게 만드느냐? 

 

build.gradle

springBoot {
    buildInfo()
}

build.gradle 파일에 다음과 같이 넣어주면 알아서 빌드 시 만들어준다. 파일이 만들어지면 저 위 사진에서 보여지는 build가 다음과 같이 보여진다.

그래서 빌드 버전과 빌드된 시간이 보여진다. 이젠 git 정보다. git 정보는 어떻게 보일 수 있을까?

우선 플러그인 하나가 필요하다. 

 

build.gradle

id 'com.gorylenko.gradle-git-properties' version '2.4.1'

이 라인을 추가해준다. 근데 중요한 건 해당 프로젝트가 당연히 git에 의해 관리되는 프로젝트여야 한다. 아니면 에러가 발생한다.

그렇게 한 후에 위 application.yml 파일에 git 설정을 똑같이 해주면 다음과 같이 보여진다.

커밋 정보와, 커밋 메시지, 브랜치 정보, 누구에 의해 커밋됐는지 등 아주 자세하게 알려준다. 이 정보가 은근 유용하다. 그래서 info는 이러한 정보들을 알려준다고 보면 된다. 

 

Logger

이 내용은 로그 레벨에 관련된 설정 정보를 확인하고 변경도 가능한 내용이다. 이건 개인적으로 많이 유용하다고 생각된다.

우선, 다음 URL로 요청을 날려보자.

`http://yourbaseURL/actuator/loggers`

 

그러면 다음과 같은 화면이 보인다.

현재 애플리케이션이 다루고 있는 패키지들에 대한 로그 레벨을 전부 보여준다. 

그나저나, 로그 레벨의 단계는 다음과 같다.

  • TRACE
  • DEBUG
  • INFO
  • WARN
  • ERROR

만약 내가 INFO로 로그 레벨을 특정 패키지에 설정했다면 해당 패키지는 INFO, WARN, ERROR 관련 로그만 출력된다.

만약 내가 DEBUG로 로그 레벨을 특정 패키지에 설정했다면 해당 패키지는 DEBUG, INFO, WARN, ERROR 관련 로그만 출력된다.

 

실제로 그런지 확인해보자.

LogController

package hello.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
public class LogController {

    @GetMapping("/log")
    public String log() {
        log.trace("trace log");
        log.debug("debug log");
        log.info("info log");
        log.warn("warn log");
        log.error("error log");
        return "ok";
    }
}

다음과 같이 hello.controller 패키지에 LogController라는 컨트롤러 하나를 만들었다.

여기서 모든 레벨에 대한 로그를 찍고 이 컨트롤러를 호출하면 결과는 다음과 같다.

실행결과

2024-07-01T21:10:35.972+09:00  INFO 32099 --- [nio-8080-exec-1] hello.controller.LogController           : info log
2024-07-01T21:10:35.972+09:00  WARN 32099 --- [nio-8080-exec-1] hello.controller.LogController           : warn log
2024-07-01T21:10:35.972+09:00 ERROR 32099 --- [nio-8080-exec-1] hello.controller.LogController           : error log

INFO레벨부터 로그가 찍혔다. 이 이유는 해당 패키지에 대한 로그 레벨이 INFO이기 때문이다. 실제로 그런지 액츄에이터로 확인해보자.

이는 ROOT의 기본 로그 레벨이 INFO라서 그 하위 패키지들은 따로 변경하지 않는 이상 전부 ROOT의 로그 레벨을 따라간다.

근데 이 액츄에이터는 이렇게 로그 레벨을 확인하는 기능도 있지만 실행중인 애플리케이션의 로그 레벨을 변경할 수도 있다.

 

예를 들어보자. 만약 운영중인 실제 서버가 어떤 장애가 났는데 해당 장애를 알기 위해 DEBUG로 찍은 로그를 확인하고 싶다. 보통은 로컬 또는 개발 서버에는 TRACE, DEBUG로 로그 레벨을 잡고 운영 중인 서버는 INFO부터 로그 레벨을 잡는게 일반적이다. 그럼 운영 중인 서버에서는 DEBUG 로그는 출력되지 않기 때문에 디버깅을 하기 어려운 환경이다. 이러한 상황일때 방법은 두가지가 있다. 

  • 로그 레벨을 바꾸고 다시 서버를 실행한다.
  • 액츄에이터를 이용해서 일시적으로 로그 레벨을 변경한다.

로그 레벨을 바꾸고 다시 서버를 실행한다는 것은 이렇게 하면 된다. 

application.yml

management:
  info:
    java:
      enabled: true
    os:
      enabled: true
    env:
      enabled: true
    git:
      mode: full
  endpoint:
    shutdown:
      enabled: true
    health:
      show-components: always
  endpoints:
    web:
      exposure:
        include: "*"

info:
  app:
    name: hello-actuator
    company: cw

logging:
  level:
    hello.controller: debug

이 파일에 logging.level.{원하는 패키지}: 로그레벨을 설정하면 된다. 그러나 이건 어떤 불편함이 있냐면, 이렇게 하면 로그 레벨을 바꾸고 다시 실행해야 하는 부분과 실행해서 원하는 작업을 다 끝내면 다시 로그 레벨을 원래대로 돌려놓고 또 다시 실행해야 하는 이런 단계를 거쳐야하고 그 단계를 거치면서 서버 다운 타임이 생기게 된다. 보통은 이런 경우를 원하지는 않을 것이다.

 

그럼 이럴땐 액츄에이터를 이용하면 된다. 액츄에이터로 로그 레벨을 확인하는 방법은 저렇게 전역으로 확인하는 방법도 있지만 딱 특정 패키지만을 확인하는 방법도 있다. 다음과 같이 path 마지막에 원하는 패키지명을 적어주면 된다.

`http://yourbaseURL/actuator/loggers/hello.controller`

이렇게 적어주면 해당 패키지의 로그 레벨만을 보여준다.

 

그리고 변경을 하려면? 그렇다. POST로 요청을 날려서 바디에 변경하고자 하는 레벨을 넣어주면 된다.

위 사진과 같이 POST로 요청을 날리고 바디엔 "configuredLevel"의 값을 원하는 로그 레벨로 지정해주면 된다. 그럼 응답은 204로 떨어지는데 204로 떨어지면 잘 변경된 것이다. 실제로 잘 변경됐는지 다시 GET으로 날려보면 그 결과를 알 수 있다.

 

그리고 다시 저 LogController로 요청을 날려보면 이젠 TRACE부터 로그가 출력된다.

2024-07-01T21:19:38.890+09:00 TRACE 32099 --- [io-8080-exec-10] hello.controller.LogController           : trace log
2024-07-01T21:19:38.890+09:00 DEBUG 32099 --- [io-8080-exec-10] hello.controller.LogController           : debug log
2024-07-01T21:19:38.890+09:00  INFO 32099 --- [io-8080-exec-10] hello.controller.LogController           : info log
2024-07-01T21:19:38.890+09:00  WARN 32099 --- [io-8080-exec-10] hello.controller.LogController           : warn log
2024-07-01T21:19:38.890+09:00 ERROR 32099 --- [io-8080-exec-10] hello.controller.LogController           : error log

 

이렇게 실시간으로 특정 패키지의 로그 레벨을 변경할 수 있다. 아주 유용할 것 같다. 그리고 당연히 REST API로 요청을 날린거라서 이 서버를 다시 띄우면 원래 로그 레벨인 INFO로 설정된다. 

 

HTTP 요청 응답 기록 

이 기능은 요청이 들어오고 응답에 대한 결과를 기록하는 액츄에이터의 기능이다. 기능은 매우 단순하기 때문에 개발 단계에서는 종종 쓰지만 운영단계에서는 더 좋은 모니터링 툴이나 네이버에서 만든 핀포인트라는 툴을 사용하는것을 추천한다.

 

이 기능을 사용하려면 HttpExchangeRepository를 구현한 구현체가 필요하다. 그리고 이것을 우리 대신 구현해둔 InMemoryHttpExchangeRepository라는 클래스를 빈으로 등록해서 간단하게 실행해보자.

 

ActuatorApplication

package hello;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.actuate.web.exchanges.InMemoryHttpExchangeRepository;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class ActuatorApplication {

    public static void main(String[] args) {
        SpringApplication.run(ActuatorApplication.class, args);
    }

    @Bean
    public InMemoryHttpExchangeRepository httpExchangeRepository() {
        return new InMemoryHttpExchangeRepository();
    }
}

InMemoryHttpExchangeRepository를 빈으로 등록한다. 그러고 실행하면 액츄에이터 목록 중에 다음과 같은게 있다.

이 URL로 들어가면 이제 이 서버에 들어온 요청과 그에 대한 응답을 기록해둔다.

위에서 만들어 둔 LogController에 요청을 한 후 들어가 보면 다음과 같이 기록이 되어있다.

URL, Header 정보들이 요청 기록에 있고, 하단에 내려보면 응답 기록도 있다. 이런 간단한 기능이다.

참고로 이 InMemoryHttpExchangeRepository는 내부로 들어가보면 최대 100개까지 기록한다. 그리고 100개가 넘으면 과거의 것을 지우고 하나씩 추가가 된다.

 

액츄에이터 보안 관련

액츄에이터가 알려주는 내용들은 서비스를 운영하고 개발할 때 굉장히 도움이 되지만, 외부에 공개됐을때 보안적으로 위험한 정보들이 많다. 그래서 외부에는 이 정보들을 공개하지 않는게 좋다. 방법은 2가지 정도가 있다.

 

  • 외부망과 내부망으로 분리된 네트워크라면 내부망에서만 접근할 수 있는 포트로 변경
  • 분리된 네트워크가 아니고 포트를 변경할 수 없다면, `/actuator` 경로로 진입하는 사용자가 권한이 있는 사용자인지 스프링 시큐리티나 인터셉터로 인증 단계를 거치는 개발 필요

위 1번의 방법을 따른다면 포트를 변경하는 방법은 간단하다.

 

application.yml

management.server.port=9292

이 값을 원하는 값으로 교체하면 된다. 위 예시는 9292로 변경했다.

결론은 어떤 방법으로든 액츄에이터는 관련 인물만 접속하고 조회할 수 있어야 한다.

728x90
반응형
LIST