본문 바로가기
Java & Kotlin/Spring

ELK 스택을 사용해 Springboot 로그 수집하기

by heekng 2023. 11. 5.
반응형

ELK 스택을 사용해 Springboot 로그 수집하기

분산환경을 사용해 서버를 운영하고 있다면, 몇 개 또는 수십개의 서버의 로그를 한번에 보기 힘든 상황이 나타납니다.

예를 들면 터미널을 여러 개로 분할해서 각 서버에 접속 후 로그를 찾는다거나…
(제가 그러고 있었죠…)

물론, cloudWatch와 같이 서버의 console에 출력되는 로그의 내용을 한번에 조회할 수 있지만, 이것 또한 불편함이 굉장히 많습니다.

이런 불편함을 해결하고자 미루고 미루던 분산된 Springboot 로그를 ELK스택을 이용해 수집하고, 조회해보고자 합니다.


ELK 구축

각각의 서비스의 로그를 수집하는 방법에는 여러가지 방법이 있습니다.
그중 Logstash로 로그를 수집해 ElasticSearch 에 전달, Kibana를 이용해 로그를 시각화하고자 합니다.

먼저 ELk를 구축하는 방법에는 여러 방법이 있관지만, docker-compose를 이용해 간단하기 구축하고 관리하는 방법을 소개합니다.

Docker-elk 설치

https://github.com/deviantony/docker-elk.git
위 docker-elk 오픈소스를 이용합니다.

git clone https://github.com/deviantony/docker-elk.git 

구축을 원하는 서버에서 docker-elk 레포지토리를 클론합니다.

비밀번호 설정

.env

ELASTIC_VERSION=8.10.4  ## Passwords for stack users #  # User 'elastic' (built-in) # # Superuser role, full access to cluster management and data indices. # https://www.elastic.co/guide/en/elasticsearch/reference/current/built-in-users.html ELASTIC_PASSWORD='changeme'  # User 'logstash_internal' (custom) # # The user Logstash uses to connect and send data to Elasticsearch. # https://www.elastic.co/guide/en/logstash/current/ls-security.html LOGSTASH_INTERNAL_PASSWORD='changeme'  # User 'kibana_system' (built-in) # # The user Kibana uses to connect and communicate with Elasticsearch. # https://www.elastic.co/guide/en/elasticsearch/reference/current/built-in-users.html KIBANA_SYSTEM_PASSWORD='changeme'  # Users 'metricbeat_internal', 'filebeat_internal' and 'heartbeat_internal' (custom) # # The users Beats use to connect and send data to Elasticsearch. # https://www.elastic.co/guide/en/beats/metricbeat/current/feature-roles.html METRICBEAT_INTERNAL_PASSWORD='' FILEBEAT_INTERNAL_PASSWORD='' HEARTBEAT_INTERNAL_PASSWORD=''  # User 'monitoring_internal' (custom) # # The user Metricbeat uses to collect monitoring data from stack components. # https://www.elastic.co/guide/en/elasticsearch/reference/current/how-monitoring-works.html MONITORING_INTERNAL_PASSWORD=''  # User 'beats_system' (built-in) # # The user the Beats use when storing monitoring information in Elasticsearch. # https://www.elastic.co/guide/en/elasticsearch/reference/current/built-in-users.html BEATS_SYSTEM_PASSWORD='' 

ELK 환경에서 사용할 비밀번호를 설정합니다.
위 내용에서 알 수 있듯이 기본 비밀번호는 changeme로 설정되어 있습니다.
원하는 비밀번호로 변경하면 됩니다.

기본 환경 설정

docker-compose.yml

...  setup:     profiles:       - setup     build:       context: setup/       args:         ELASTIC_VERSION: ${ELASTIC_VERSION}     init: true     volumes:       - ./setup/entrypoint.sh:/entrypoint.sh:ro,Z       - ./setup/lib.sh:/lib.sh:ro,Z       - ./setup/roles:/roles:ro,Z     environment:       ELASTIC_PASSWORD: ${ELASTIC_PASSWORD:-}       LOGSTASH_INTERNAL_PASSWORD: ${LOGSTASH_INTERNAL_PASSWORD:-}       KIBANA_SYSTEM_PASSWORD: ${KIBANA_SYSTEM_PASSWORD:-}       METRICBEAT_INTERNAL_PASSWORD: ${METRICBEAT_INTERNAL_PASSWORD:-}       FILEBEAT_INTERNAL_PASSWORD: ${FILEBEAT_INTERNAL_PASSWORD:-}       HEARTBEAT_INTERNAL_PASSWORD: ${HEARTBEAT_INTERNAL_PASSWORD:-}       MONITORING_INTERNAL_PASSWORD: ${MONITORING_INTERNAL_PASSWORD:-}       BEATS_SYSTEM_PASSWORD: ${BEATS_SYSTEM_PASSWORD:-}     networks:       - elk     depends_on:       - elasticsearch       - logstash       - kibana  ...  volumes:   elasticsearch:     driver: local     driver_opts:       type: 'none'       o: 'bind'       device: /{saveDir}/elasticsearch 

docker-compose.yml에서도 변경할 부분이 있습니다.

setup Container은 docker-elk에 필요한 Elasticsearch 사용자 및 그룹을 초기화합니다.
setup과 동시에 elasticsearch, logstatch, kibana를 설정하기 위해 세개 컨테이너 또한 depends_on에 작성합니다.

volumes 설정 또한 기본값과 다른데요.
elasticsearch의 기본 데이터는 elasticsearch 이름의 볼륨에 저장됩니다.
하지만 컨테이너를 내리면서 볼륨이 삭제되었을 때, 기존의 데이터가 삭제됨을 방지하기 위해 로컬 디렉토리에 elasticsearch volume의 데이터를 마운트합니다.

docker-elk/logstash/pipeline/logstash.conf

input {         beats {                 port => 5044         }          tcp {                 port => 50000                 codec => json_lines         } }  ## Add your filters / logstash plugins configuration here  output {         if [app-name] and [profile] {                 elasticsearch {                         hosts => "elasticsearch:9200"                         user => "logstash_internal"                         password => "${LOGSTASH_INTERNAL_PASSWORD}"                         index => "service-log-%{[app-name]}-%{[profile]}-%{+YYYY.MM.dd}"                 }         } else {                 elasticsearch {                         hosts => "elasticsearch:9200"                         user => "logstash_internal"                         password => "${LOGSTASH_INTERNAL_PASSWORD}"                         index => "no-profile-log-%{+YYYY.MM.dd}"                 }         } } 

logstash가 외부에서 전달받는 데이터를 어떻게 elasticsearch에 전달할 지에 대한 설정입니다.

사용자의 환경에 따라 다르지만, 위처럼 수정한 설정은 다음과 같습니다.
input > tcp
50000포트로 json_lines 코덱의 데이터를 받습니다.
output
json으로 전달받은 데이터 중 app-name과 profile key가 존재한다면 service-log-%{[app-name]}-%{[profile]}-%{+YYYY.MM.dd} 형태의 인덱스로 데이터를 전달합니다.
그 외의 경우에는 no-profile-log-%{+YYYY.MM.dd} 형태의 인덱스로 데이터를 전달합니다.

docker-elk/elasticsearch/config/elasticsearch.yml

--- ## Default Elasticsearch configuration from Elasticsearch base image. ## https://github.com/elastic/elasticsearch/blob/main/distribution/docker/src/docker/config/elasticsearch.yml # cluster.name: docker-cluster network.host: 0.0.0.0  ## X-Pack settings ## see https://www.elastic.co/guide/en/elasticsearch/reference/current/security-settings.html # #xpack.license.self_generated.type: trial xpack.license.self_generated.type: basic xpack.security.enabled: true 

elasticsearch에 대한 설정파일입니다.
xpack.license.self_generated.type: basic 값을 설정해 기본 설정인 trial타입에서 basic타입으로 변경합니다.
이는 elasticsearch가 몇가지 기능을 유료화하면서 해당 기능을 체험하는 것을 기본값으로 설정한 것인데, 추후에 다시 설정을 변경하지 않기 위함입니다.

실행

docker-compose up setup 

위에서 설정한 값을 기준으로 setup 컨테이너를 실행해 사용자 계정, 권한을 설정합니다.

docker-compose up -d 

전체 컨테이너를 백그라운드에서 실행합니다.

인덱스 생성 권한 부여

elasticsearch 인덱스는 admin권한으로 미리 생성하고 데이터를 전달해도 되지만, 위에서 설정한 것과 같이 (index => "service-log-%{[app-name]}-%{[profile]}-%{+YYYY.MM.dd}") 날짜별로 인덱스가 생성되게 설정하였기 때문에, logstash_internal 유저에게 인덱스를 생성할 수 있게 권한을 부여해야 합니다.

Stack Managemaent > Users에 접속하면, logstash_internal 계정을 확인할 수 있습니다.

확인해보면 logstash_writer Role이 부여된 것을 확인할 수 있습니다.

Stack Management > Roles 에 진입해 해당 Role의 권한을 확인합니다.

기존에 작성된 indices 에 service-log-*, no-profile-log-* 를 추가해 create_index 권한을 부여합니다.

이 부분이 되게 중요합니다.
해당 패턴의 인덱스를 logstash에서 전달받을 때, 자동생성하지 못 하는 에러로 한참을 찾아보았던 포인트입니다…

Springboot에서 Logstash로 로그정보 전달하기

흔히 사용하는 로그정보를 Logstash로 전달하는 방법에는 Filebeat가 있습니다.
하지만 로그 정보를 파일로 저장하고, 별도의 Filebeat를 설치해야한다는 과정이 번거로워 서버 내에 로그 라이브러리를 이용해 logstash로 로그정보를 전달하려 합니다.

의존성 설치

build.gradle

implementation 'net.logstash.logback:logstash-logback-encoder:7.4' 

logstash-logback-encoder 의존성을 설치합니다.
해당 의존성은 문자열으로 출력되는 로그를 key: value 형태의 json으로 출력하는 encoder을 제공하고, 서버 내에서 logstash로 비동기 로그 전달을 지원합니다.

logback 설정

resources/logback-spring.xml

<?xml version="1.0" encoding="UTF-8"?> <configuration>   <include resource="org/springframework/boot/logging/logback/defaults.xml"/>   <include resource="org/springframework/boot/logging/logback/console-appender.xml"/>    <property name="APP_NAME" value="test-app-name"/>   <property name="SPRING_PROFILE" value="${spring.profiles.active}"/>   <property name="ELK_SERVER_URI" value="{logstash IP}:50000"/>    <appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">     <destination>${ELK_SERVER_URI}</destination>     <encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">       <providers>         <mdc/>         <pattern>           <pattern>             {             "app-name": "${APP_NAME}",             "profile": "${SPRING_PROFILE}"             }           </pattern>         </pattern>         <timestamp/>         <version/>         <context/>         <logLevel/>         <message/>         <loggerName/>         <logstashMarkers/>         <stackTrace/>         <callerData/>         <threadName/>       </providers>     </encoder>   </appender>    <springProfile name="local">     <root level="INFO">       <appender-ref ref="CONSOLE"/>       <appender-ref ref="LOGSTASH"/>     </root>   </springProfile>  </configuration> 

springboot의 기본 로그 라이브러리 logback설정입니다.
resources 패키지 하위에 logback-spring.xml을 생성하여 위처럼 작성합니다.
providers에는 여러가지 태그들이 작성되어 있는데요. 그 중 pattern 태그는 커스텀한 값을 작성 할 수 있습니다.
logstash pipeline에서 로그에서 조회한 app-name과 profile을 여기에 작성해서 어느 서버에서 나타난 로그인지 확인할 수 있습니다.

distination태그에 Springboot에서 output되는 로그를 전달할 logstash의 서버정보를 입력합니다.

로그 수집 확인 및 시각화

이제 설정을 토대로 로그 수집을 확인하고, 간단하게 시각화해보겠습니다.
Springboot에서 임의의 로그를 전달한 이후라 생각하고 진행합니다.

인덱스 확인

Stack Management > Index Management 로 진입합니다.

위 Springboot에서 설정한 대로 service-log-test-app-name-local-{date} 형태로 인덱스가 생성된 것을 확인할 수 있습니다.

특정 패턴의 인덱스 데이터 조회

이제 운영되고 있는 서비스에서는 service-log-test-app-name-local-{date}의 형태로 날짜만 바뀌어 인덱스가 생성되고, 해당 인덱스 안에 로그가 쌓일 것입니다.
service-log-test-app-name-local- 으로 시작하는 모든 인덱스의 데이터들을 한 눈에 확인해보겠습니다.

좌측 메뉴의 Discover을 선택합니다.

Create a data view를 선택합니다.

위와 같이 data view의 이름을 지정하고, 특정 패턴을 입력하여 해당 패턴의 이름을 가진 index를 모두 불러옵니다.

위처럼 특정 시간대의 로그가 잘 조회되는 것을 확인할 수 있습니다.

로그 정보 수명 주기 관리

상시로 수집되는 로그정보는 많이 쌓일수록 더 큰 용량을 차지합니다.
처음에는 얼마 안되는 양으로 부담이 없지만, 날짜가 늘어갈수록 스토리지가 부담될 수 있습니다.
이번에는 로그을 17일 유지하며, 저장된 날짜별 성능을 제한하도록 하겠습니다.

Index Lifecycle Policies 생성

Index Lifecycle Policies 는 인덱스의 수명주기를 정의합니다.

Stack Management > Index Lifecycle Management > Create policy로 접근합니다.

위 policy는 test-ilm 이름으로, 2일동안 Hot phase, 15일동안 Warm phase, 그 이후 Delete 하도록 설정하였습니다.

Index Template 설정

위에서 만든 ilm을 특정 패턴을 가진 인덱스에 모두 적용하도록 Index Template를 생성합니다.

Stack Management > Index Management > Templates > CreateTemplate 으로 접근합니다.

 

위와 같이 Template이름을 지정하고, 해당 템플릿을 적용할 인덱스트 패턴을 지정합니다.
그리고 Index settings에서 해당 템플릿에 적용될 ilm을 지정합니다.

그 외의 설정은 선택으로 상황에 맞추어 작성합니다.

기존에 생성되어있던 index는 적용되지 않습니다.
지정한 인덱스 패턴으로 새로 생성되는 인덱스에 적용됩니다.

Index Mangement로 접근해 인덱스 상세정보 조회시 하단에 해당 인덱스에 적용된 Template를 확인할 수 있습니다.
또한 현재 phase 또한 확인할 수 있습니다.

반응형