Java 애플리케이션 모니터링 툴 Spring Boot Admin 활용기

멀티 서비스를 운영중인 필자의 파트에서 다수의 애플리케이션을 모니터링 하기위한 툴 Spring Boot Admin를 사용하게된 이유, Kubernetes를 사용중인 Cloud환경에서 사용하기 위한 설정법, 그리고 보안설정 과정에 대해 설명합니다.
May 28, 2024
Java 애플리케이션 모니터링 툴 Spring Boot Admin 활용기

Spring Boot Admin을 도입한 이유

로그레벨 변경

springboot

Spring Boot Admin을 사용한 이유는 여러가지가 있지만, 그 중 가장 유용한 기능은 로그레벨 설정을 동적으로 변경할 수 있다는 점입니다.

보통 개발계 환경에서는 Debug 레벨까지 설정하고, 운영계 환경에서는 Error 레벨로 설정을 합니다. 운영계 환경에서 서비스에 버그가 발견되거나, VOC 처리를 위해 Debug 레벨 로그를 살펴볼 일이 생길 때, 재배포 없이 레벨을 변경할 수 있다는 점이 가장 유용했습니다.

환경설정 정보 확인

ktds

또한, 여러가지 애플리케이션 환경설정 정보를 한 눈에 확인할 수 있다는 점이 도움이 됩니다. 특히 Container화 되어 구동되는 Cloud Native한 환경에서 작동되다보니, 서비스마다 생성되는 Pod의 CPU/Memory 리소스 사용량이 다른데 각각 애플리케이션이 얼마만큼의 리소스를 사용하게끔 설정되었는지 확인하는데에 유용했습니다.

그 외에 쓰레드 덤프, 힙 덤프를 다운받을 수 있는 기능과, container 로그 터미널에 접속하지 않아도 로그를 확인할 수 있는 점, 모니터링 중인 애플리케이션이 몇 개인지, 지금까지 구동중인 기간 등을 확인할 수 있습니다.

Spring Boot Admin 작동원리

Spring Boot Admin Server와 Client

Spring Boot Admin Server은 주기적으로 client의 actuator endpoint를 호출해서 client의 정보를 입수합니다. 가져온 정보를 보기좋게 표시하고, 상호 작용할 수 있는 사용자 인터페이스를 제공합니다.

모니터링 하고자 하는 애플리케이션을 Client라고 부릅니다. Client는 Server가 시스템 정보를 획득할 수 있도록 몇가지 설정을 해야합니다.

따라서 Spring Boot Admin을 사용하기 위해서는 모니터링 대상이 될 Client 역할, 그리고 정보를 수집하고 표시하는 Server 역할 이렇게 두 가지가 필요합니다.

Spring Boot Admin Server 설정

Spring Boot Admin Server 설정

Spring Boot Admin Server은 Spring Cloud 프로젝트로 생성하였습니다. 그리고 가장 먼저 디펜던시 설정을 해줍니다. 웹 기반으로 동작하기 위한 Web 디펜던시, Server역할을 주기위한 Spring Boot Admin Server 디펜던시, 쿠버네티스 환경 내 Discovery Client 역할을 주기위한 디펜던시, 그리고 ID/PW를 주기위한 Security 디펜던시가 사용됩니다.

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
	<groupId>de.codecentric</groupId>
	<artifactId>spring-boot-admin-starter-server</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-kubernetes</artifactId>
	<version>1.1.9.RELEASE</version>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-security</artifactId>
</dependency>	

Service Discovery 하기위한 설정으로 Spring-boot-admin-server-cloud 에서 제공하는 ServiceInstanceConverter 인터페이스의 DefaultServiceInstanceConverter를 상속받은 클래스를 생성했습니다. 이 클래스는 생성된 프로젝트의 kubernetes metadata 값으로 설정한 값을 불러올 수 있는 기능을 가집니다. 이 기능을 통해서 actuator의 endpoint 값을 수집할 것입니다.

@Component
public class OpenShiftServiceInstanceConverter extends DefaultServiceInstanceConverter {

    private static final String MANAGEMENT_CONTEXT_PATH = "/actuator";
    private static final String MANAGEMENT_PREFIX = "management.prefix";
    private static final String MANAGEMENT_PREFIX_SUB = "management.prefix.sub";
    private static final String MANAGEMENT_PREFIX_THREE = "management.prefix.three";
    private static final String MANAGEMENT_PREFIX_FOUR = "management.prefix.four"; 

    @Override
    public Registration convert(ServiceInstance instance) {
        return Registration.create(instance.getServiceId(), getHealthUrl(instance).toString())
                .managementUrl(getManagementUrl(instance).toString()).serviceUrl(getServiceUrl(instance).toString())
                .metadata(getMetadata(instance)).build();
    }

    @Override
    protected String getManagementPath(ServiceInstance instance) {
        String managementPath = instance.getMetadata().get(MANAGEMENT_PREFIX);
        String managementPathSub = instance.getMetadata().get(MANAGEMENT_PREFIX_SUB);
        String managementPathThree = instance.getMetadata().get(MANAGEMENT_PREFIX_THREE);
        String managementPathFour = instance.getMetadata().get(MANAGEMENT_PREFIX_FOUR);

        StringBuffer retuernPathBuffer = new StringBuffer();
      
        if(!StringUtils.isBlank(managementPath)) {
        	retuernPathBuffer.append(managementPath);        	
        }
    	if(!StringUtils.isBlank(managementPathSub)) {
    		retuernPathBuffer.append("/").append(managementPathSub);    		
    	}
    	if(!StringUtils.isBlank(managementPathThree)) {
    		retuernPathBuffer.append("/").append(managementPathThree);
    	}
    	if(!StringUtils.isBlank(managementPathFour)) {
    		retuernPathBuffer.append("/").append(managementPathFour);    		
    	}    	
    	
    	retuernPathBuffer.append("/").append(MANAGEMENT_CONTEXT_PATH);
    	return retuernPathBuffer.toString();
    }
}

위 코드는 배포된 리소스의 Service정보인, Service.metadata.management 값 4가지, prefix, prefix.sub, prefix.three, prefix.four을 가져올 수 있도록 만든 클래스입니다.

Management.Prefix 값이 4단계까지 있는 이유는, client 역할을 할 서비스들의 context path depth가 각기 다르기 때문입니다. Client의 설정에 따라 달라지는 값이긴 하지만, context path와 동일하게 맞추었습니다. 보통 4단계 보다 더 길지는 않기 때문에 4단계까지만 설정해 놓았습니다.

Kubernetes 환경에서 사용하기 위한 몇가지 설정이 더 있지만, 아래에 후술하겠습니다.

Spring Boot Admin Client 설정

Spring Boot Admin의 Client로 모니터링 되기 위해서는 spring-boot-starter-actuator 디펜던시가 필요합니다. Actuator 디펜던시를 통해 health check 용도로 많이 사용하곤 합니다. 여기서 사용하는 것은 주기적으로 호출해서 시스템 정보를 획득하기 위함입니다. pom.xml 내에 아래 디펜던시를 추가합니다.

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

Client 및 actuator 환경을 설정하기 위해 application.yml에 아래와 같이 설정하였습니다.

#Spring Boot Admin 관련 설정 추가
management:
  endpoints:
    web:    
      exposure:
        include: "*" // 다양한 actuator endpoint를 모두 포함하기 위함
        exclude: "session" // web endpoint로 제외할 값
  endpoint:
    health:
      show-details: always // health endpoint 정보 노출

Client 설정에도 kubernetes 환경에서 사용하기 위한 몇가지 설정이 더 있지만, 아래에 후술하겠습니다.

Kubernetes RBAC 설정

필자의 파트에서는 멀티 서비스를 관리하기 위해 kubernetes를 사용중입니다. 3개의 worker nodes로 구성되어있고, vi 라는 namespace를 갖습니다. Spring Boot Admin 설정을 이루기 위해서는 Role, RoleBinding, ServiceAccount 3가지 설정을 배포해야 합니다.

Spring Boot Admin Server의 프로젝트명을 sbadm 이라고 설정 하였습니다. sbadm 프로젝트가 client 서비스들의 특정 리소스에 대해 조회, 변경 감지와 같은 행위를 할 수 있는 권한을 주기 위해 Role을 만듭니다.

kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: sbadm
  namespace: vi
rules:
  - verbs:
      - get
      - watch
      - list
    apiGroups:
      - ''
      - extensions
      - apps
    resources:
      - pods
      - configmaps
      - services
      - endpoints
      - secrets

미리 kubernetes 설정으로 vi라는 namespace에 ServiceAccount를 만들어주겠습니다. 이 serviceAccount를 통해 role을 적용시킬 수 있도록 할 것입니다.

kind: ServiceAccount
apiVersion: v1
metadata:
  name: sbadm
  namespace: vi

마지막으로 role을 service account에 적용시키기 위해, role binding을 생성합니다.

kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: sbadm
  namespace: vi
subjects:
  - kind: ServiceAccount
    name: sbadm
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: sbadm

위와 같은 RBAC 설정을 통해 sbadm 프로젝트가 vi namespace 내 pod들의 정보 수집 권한을 얻도록 하였습니다.

Spring Boot Admin Server 배포설정

이제 위에서 후술한다고 하였던 Server와 Client의 추가적인 설정을 하겠습니다.

바로 위에서 serviceAccount의 metadata.name을 sbadm으로 생성하였습니다. 따라서, server 역할을 할 sbadm 프로젝트의 deployments 배포설정 소스에 serviceAccountName: sbadm을 추가합니다.

참고로, 필자의 파트는 kubernetes 배포를 helm을 통해 진행합니다. 따라서 배포 설정값이 전부 담긴 values.yaml을 수정하겠습니다.

deployments:
  - spec:
      serviceAccountName: sbadm

물론 위 설정만 한 것이 아니라 container image 설정, port 설정, resources request/limit, livenessProbe, readinessProbe 설정 등 필요에 따른 설정값을 모두 포함합니다.

추가적으로, spring-cloud-starter-kubernetes 디펜던시가 지원하는 기능을 사용하여, application.yml 내에 kubernetes내 배포된 리소스 중 service의 label.type 값이 “spring-boot” 인 프로젝트만 discover 할 수 있도록 설정합니다. 추후 Client 설정에서 이것과 관련한 설정을 해줄 것입니다.

spring:
  cloud:
    kubernetes:
      discovery:
        service-labels:
          type: spring-boot   

Spring Boot Admin Client 배포설정

위에서, service의 metadata 값을 수집해서 actuator endpoint를 수집한다고 하였습니다. 그래서, client 배포를 할 때 Service 설정 값에 metadata.label 값을 넣어줄 것입니다. 해당 설정을 하는 위치는 helm templates 내 service.yaml 입니다.

그 값은 actuator의 endpoint 가 될 값으로서, context path와 동일하게 넣어주어야 합니다. 만약 application.yml 내에 context-path 값이 :/ 로 root값이라면 /actuator 라는 endpoint만으로 조회가 이루어질 것이기 metadata.labels에 management-prefix 값 설정할 필요 없이 type 설정만 해주면 됩니다.

apiVersion: v1
kind: Service
metadata:
  name: {{ $.Values.app }}
  labels:
    app: {{ $.Values.app }}
    chart: {{ template "merge.chart" $ }}
    release: {{ $releaseName }}
    type: spring-boot

만약 context path가 /fe/app 으로 설정되어있으면, 아래와 같이 설정해야 합니다.

apiVersion: v1
kind: Service
metadata:
  name: {{ $.Values.app }}
  labels:
    app: {{ $.Values.app }}
    chart: {{ template "merge.chart" $ }}
    release: {{ $releaseName }}
    management.prefix: fe
    management.prefix.sub: app
    type: spring-boot	

Actuator endpoint 접근제한을 통한 보안설정

아무나 endpoint에 접근해서 정보를 볼 수 없도록 OncePerRequestFilter을 상속받은 Filter 클래스를 통해 접근제한을 설정했습니다.

Kubernetes 내 Pod들은 각각 랜덤한 IP주소를 갖는데, C클래스 대역의 사설IP가 생성되는 규칙을 이용해서 그러한 규칙을 갖는 IP와 호출 URI에 “actuator”를 포함하는 호출만 응답을 허가하도록 각 client에 설정하였습니다.

Spring Security를 통한 ID/PW 설정

Spring Boot Admin Server 설정 구성하면서 spring-boot-starter-security 디펜던시를 추가했었습니다. Spring Security를 ID/PW 설정을 할 것입니다.

springbootadmin

아주 간단하게 ID/PW를 설정하고 싶다면 application.yml 내에 spring.security 환경변수를 사용해서 name,password 를 설정할 수 있습니다. 처음에는 아래와 같이 임시로 사용했습니다.

spring:          
  security:
    user:
      name: admin
      password: admin

이후 더 다양한 관리자 계정으로 접속 가능하며, application.yml 소스 내에 노출되지 않도록 하기 위해 추가적인 설정이 필요했습니다.

첫째로, SecurityConfig.java 파일 내에 AuthenticationManagerBuilder를 사용하는 configure 메서드를 오버라이드 합니다.

둘째로, UserDetailsService를 상속받은 CustomUserDetailsService 클래스에 비발디 DB를 거쳐서 “ADMIN”권한을 가진 계정만 입력 통과되도록 구현합니다.

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
	 auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
}

맺음말

로그분석을 통한 신속한 대응, 모니터링의 필요성, 장애 예방 목적으로 Spring Boot Admin 도입한 이후 실제로 로그 확인, 메모리 할당량 체크하는 부분에서 상당히 유용히 사용되고 있습니다.

특히 로그분석 하는 과정에서 재배포를 하지 않고도 로그레벨을 변경할 수 있다는 점이 가장 메리트가 컸습니다. 또한, Spring Boot에서 제공하는 라이브러리로 대부분 구현할 수 있기에 접근성도 좋았습니다.

본부 내 장애 제로화 캠패인으로 Java 프로젝트를 사용하는 파트에 Spring Boot Admin의 이점에 대해 알리고 사용을 장려하도록 할 예정입니다.

앞으로도 운영 효율적인 기술, 장애 위험을 배제할 수 있는 수단 등을 발굴해나가면서 기술력을 높이고 탄탄한 운영을 지속할 수 있도록 하겠습니다.

Share article
Subscribe to our newsletter

More articles

See more posts
RSSPowered by inblog