안녕하세요. 저는 상품시스템팀에서 개발업무를 맡고 있는 이중석입니다. 최근 팀 내에서 로그엔트리를 ELK 로 전환하고 필요한 알람을 셋팅하는 작업을 진행했습니다. 이 과정중 알람을 설정하는 부분이 어렵진 않지만 쉽지도 않았습니다. 그 과정에서 알게된 것들이 다른분께 도움이 될까 싶어 글을 남깁니다.

ELK로 로그 쌓아보기 (feat. 스프링 배치)

Local 에서 ELK 띄우기

ELK 는 Docker Compose 를 사용하여 실행할 것이기 때문에 Docker 가 설치 안되신 분들은 설치해서 진행해 주세요. Docker 홈페이지
DOCKER_ELK 에서 git clone 명령어를 사용하여 프로젝트를 받습니다.

mkdir ~/workspace
cd ~/workspace
git clone https://github.com/deviantony/docker-elk.git

docker-elk/logstash/pipeline/logstash.conf 파일을 다음과 같이 수정합니다.

input {
	tcp {
		port => 5000
		codec => json_lines
	}
}

## Add your filters / logstash plugins configuration here

output {
	elasticsearch {
		hosts => "elasticsearch:9200"
		index => "logstash-20191218"
		user => "elastic"
		password => "changeme"
	}
}

그후 docker-compose up 명령어를 통하여 ELK 를 실행합니다.

cd ~/workspace/docker-elk
docker-compose up

Spring Batch 로그 ELK 로 전송하기

이제 어디에선가 ELK 로 로그를 전송해야 kibana 에서 확인이 가능할텐데요. 로그를 생성하고 ELK 로 전송할 배치를 만들도록 하겠습니다.

Spring Initializr 으로 접속하여 Dependency 에 Spring Batch, Lombok, H2 Database 를 추가하고 Generate 버튼을 눌러 프로젝트를 생성합니다.

DemoApplication class 에 @EnableBatchProcessing 을 추가해줍니다.

@EnableBatchProcessing
@SpringBootApplication
public class DemoApplication {

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

}

HelloBatchConfig.java 파일을 생성후 아래와 같이 작성합니다. Hello Job Batch Log {} info 로그를 100번 출력 하는 배치 입니다.

@Slf4j
@Configuration
public class HelloBatchConfig {

	private final JobBuilderFactory jobBuilderFactory;
	private final StepBuilderFactory stepBuilderFactory;

	@Autowired
	public HelloBatchConfig(JobBuilderFactory jobBuilderFactory,
	                        StepBuilderFactory stepBuilderFactory) {
		this.jobBuilderFactory = jobBuilderFactory;
		this.stepBuilderFactory = stepBuilderFactory;
	}

	@Bean
	public Job helloJob() {
		return jobBuilderFactory.get("HelloJob")
				.start(step())
				.build();
	}

	@Bean
	public Step step() {
		return stepBuilderFactory.get("HelloJobTaskletStep").tasklet(
				(StepContribution contribution, ChunkContext chunkContext) -> {
					log.info("==== Tasklet called....");

					for(int i = 0 ; i <100; i ++) {
						log.info("Hello Job Batch Log {}", i);
					}

					return RepeatStatus.FINISHED;
				}).build();
	}
}

다음은 배치에서 출력되는 로그를 Logstash 로 보내도록 설정하겠습니다. 이곳 을 보면 tcp 통신을 통하여 바로 logstash 로 전송할 수 있는 appender 가 있습니다.

해당 appender 를 사용하기 위하여 build.gradle 파일에 다음과 같이 dependency 를 추가해줍니다.

compile 'net.logstash.logback:logstash-logback-encoder:6.3'

src/main/resources 디렉토리에 logback.xml 파일을 추가해 줍니다.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>

    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>%-5level %d{HH:mm:ss.SSS} [%thread] %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <appender name="stash" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
        <destination>127.0.0.1:5000</destination>

        <!-- encoder is required -->
        <encoder class="net.logstash.logback.encoder.LogstashEncoder" />
    </appender>

    <root level="INFO">
        <appender-ref ref="console"/>
        <appender-ref ref="stash"/>
    </root>

</configuration>

마지막으로 같은 위치인 src/main/resources 디렉토리에 application.yml 파일을 추가하고 다음과 같이 수정합니다.

logging.config: classpath:logback.xml

이제 배치를 실행하면 logstash로 로그를 전송합니다.

Open Distro 설치하기

Open Distro Alert 기능을 사용하여 알람을 구성해보도록 하겠습니다.

Open Distro Standalone_Install 문서를 참고하여 설치했습니다.

Elasticsearch 에 Open Distro Alert 설치

docker-elk/elasticsearch/Dockerfile 을 다음과 같이 수정합니다.

ARG ELK_VERSION

FROM docker.elastic.co/elasticsearch/elasticsearch:${ELK_VERSION}
RUN elasticsearch-plugin install -b https://d3g5vo6xdbdb9a.cloudfront.net/downloads/elasticsearch-plugins/opendistro-alerting/opendistro_alerting-1.3.0.1.zip

Kibana 에 Open Distro Alert 설치

docker-elk/kibana/Dockerfile 을 다음과 같이 수정합니다.

ARG ELK_VERSION

FROM docker.elastic.co/kibana/kibana:${ELK_VERSION}
RUN kibana-plugin install https://d3g5vo6xdbdb9a.cloudfront.net/downloads/kibana-plugins/opendistro-alerting/opendistro-alerting-1.3.0.0.zip

ELK 랑 Open Distro Alert 버전 맞추기

현재 opendistro_alert의 최신버전인 1.3.0.1 버전 에서 지원하는 elasticsearch 버전은 7.3.2 버전입니다. docker-elk/.env 파일에서 elk 버전을 7.3.2 로 수정합니다.

ELK_VERSION=7.3.2

X-PACK 설정 Off 하기

docker-elk/elastic/config/elasticsearch.yml 파일에서 xpack 관련 설정을 false 로 변경합니다. (해당 설정을 false 로 변경하지 않으면 opendistro_alert 을 사용할때 인증 관련 에러가 발생합니다)

xpack.security.enabled: false
xpack.monitoring.collection.enabled: false

새로운 이미지 Build 하고 재실행하기

새로 Docker 이미지를 빌드한 후, 이전 버전에서 사용된 elasticsearch volume 을 제거하고
다시 컨테이너를 실행합니다.

docker-compose build
docker volume rm docker-elk_elasticsearch
docker-compose up


Kibana 가 정상 실행되지 않는다면?

혹시 Open Distro 설치 후, 다음과 같은 로그가 올라오면서 kibana 가 정상적으로 실행되지 않는다면
메모리 용량을 증가시키고 다시 실행해 보시기 바랍니다.

로그

 {"type":"log","@timestamp":"2020-01-13T05:55:05Z","tags":["info","optimize"],"pid":1,"message":"Optimizing and caching bundles for space_selector, dashboardViewer, apm, code, maps, canvas, infra, siem, uptime, opendistro-alerting, kibana, stateSessionStorageRedirect, status_page and timelion. This may take a few minutes"}
Browserslist: caniuse-lite is outdated. Please run next command `npm update caniuse-lite browserslist`
Browserslist: caniuse-lite is outdated. Please run next command `npm update caniuse-lite browserslist`
Browserslist: caniuse-lite is outdated. Please run next command `npm update caniuse-lite browserslist`


docker-compose.yml 파일 에서 kibana 메모리 용량 지정(environment: NODE_OPTIONS 추가)

  kibana:
    build:
      context: kibana/
      args:
        ELK_VERSION: $ELK_VERSION
    volumes:
      - type: bind
        source: ./kibana/config/kibana.yml
        target: /usr/share/kibana/config/kibana.yml
        read_only: true
    ports:
      - "5601:5601"
    environment:
      NODE_OPTIONS: "--max-old-space-size=2048"
    networks:
      - elk
    depends_on:
      - elasticsearch


아래 화면과 같이 kibana 좌측 메뉴에 A라 표시된 메뉴가 보인다면 성공입니다.

0


우리팀은 이렇게 사용합니다.

배치 관련 알람 설정

제가 속한 상품시스템팀에서는 광고데이터 생성, 결제 데이터 생성, 결제 시도 등등 매일 정해진 시간에 수행되어야 하는 배치들이 있습니다. 이 수많은 배치들이 정해진 시간에 정상적으로 수행했는지 매번 확인하기 힘들어서 알람을 설정해 두었습니다.
알람을 설정할때는 Monitor, Trigger, Destination 를 설정해주셔야 합니다.

배치가 실패했습니다!(배치 에러 로그 모니터링하기)

Destination 설정

배치 실행중 Exception 이 발생하면 Slack 으로 메시지를 보내도록 Destination 을 먼저 셋팅합니다. 아래 화면에서 보이는 Add Destination 버튼을 눌러 Destination 생성 화면으로 이동합니다. 0

아래 화면에서 처럼 destination 이름, type, slack webhook url 을 입력하여 생성하시면 됩니다.
0

Monitor 설정

아래 화면에서 Create Monitor 버튼을 눌러서 Monitor 생성 화면으로 이동합니다. 0


모니터 생성 화면으로 들어가서 Monitor 이름을 입력하고 스크롤을 조금 내리면 Define Monitor 화면이 중간쯤에 나타납니다. 이 화면에서 Define using extraction query 를 선택하고, 로그를 조회할 index를 선택한 후, 어떤 쿼리로 조회할지 입력합니다.

스크린샷의 우측 상단에 보이는 RUN 버튼을 누르면 쿼리조회결과를 확인 할 수 있습니다.
저는 현재시간부터 2분 이내에 들어온 Log Level 이 ERROR 인 로그를 조회하도록 쿼리를 입력했습니다. 0


마지막으로 제일 하단에 있는 Monitor Schedule 을 셋팅하고 Create 버튼으로 Monitor 를 생성합니다.
화면에서는 By interval 로 1분마다 조회하도록 셋팅하였습니다. 이 외에도 Daily Weekly Monthly Custom Cron Expression 등으로 셋팅할 수도 있습니다. 0

Trigger 설정

Monitor 를 생성하면 바로 Trigger 생성 화면으로 넘어옵니다.
Trigger Name 을 입력하고 Trigger Condition 항목을 셋팅합니다. 기본셋팅은

ctx.results[0].hits.total.value > 0

이렇게 입력되어 있습니다. 저는 에러로그가 1개 이상 발생하면 알람을 받고 싶기 때문에 따로 수정하지 않겠습니다.

마지막으로 trigger 생성 화면 제일 하단에 있는 Configure Action 부분을 셋팅합니다. Action Name 을 입력하고 아까 생성한 Destination 을 선택합니다. Message 와 Message Preview 필드 중간에 있는 Send test Message 버튼을 눌러서 Slack 으로 메시지가 오는지 확인후 Trigger를 생성합니다. 0



셋팅이 완료되었습니다!
배치에서 Exception을 발생하도록 수정하고 실행해보세요. Slack 으로 알람이 온다면 성공입니다.

배치 실행은 했니?(정해진 시간에 배치 실행여부 모니터링 하기)

배치가 실패하면 알람이 오도록 셋팅을 했지만, 배치가 어떠한 이유에서든 실행이 되지 않으면 인지할 수가 없습니다. 배치가 정상적으로 실행되지 않았을때 알람이 오도록 셋팅을 하겠습니다.

Destination 설정

Destination 설정은 위와 같습니다.

Monitor 설정

Monitor 설정 전에, 배치가 실행을 완료하면 “테스트 배치 완료!” 라는 로그를 남기도록 코드를 추가합니다.
Monitor Name 을 “Hello Batch 실행여부 확인” 으로 입력하고, Define Monitor 의 Define extraction query 부분을 다음과 같이 작성합니다.

{
    "size": 1,
    "query": {
        "bool": {
            "filter": [
                {
                    "range": {
                        "@timestamp": {
                            "from": "||-1h",
                            "to": "",
                            "include_lower": true,
                            "include_upper": true,
                            "format": "epoch_millis",
                            "boost": 1
                        }
                    }
                },
                {
                    "match": {
                        "message": "테스트 배치 완료!"
                    }
                }
            ],
            "adjust_pure_negative": true,
            "boost": 1
        }
    }
}

그리고 Monitor Schedule 을 배치가 완료되는 시간보다 미래시간을 cron expression 으로 셋팅합니다.
저는 7시 20분으로 설정했습니다. 0

Trigger 설정

모니터링한 시간에 조회되지 않으면 알람을 보내도록 하기 위해서 Trigger condition 조건을

ctx.results[0].hits.total.value < 1

으로 셋팅합니다. 나머지는 배치 에러 트리거 설정과 같습니다.

이런식으로 해당 시간에 배치가 실행되지 않았을때, 알람이 오도록 셋팅할 수 있습니다.

슬랙메시지에 링크추가해서 바로 이동하기

슬랙 메시지에 에러 로그를 바로 확인할 수 있는 링크를 추가하면 좀더 편리하게 에러로그를 확인할 수 있습니다. kibana 우측 메뉴중 가장 상단에 있는 Discover 메뉴로 이동합니다.

filter 에 “level:ERROR” 을 추가하고 시간을 “Last 2 minutes” 으로 설정하고 조회합니다. 그러면 상단의 url 이 변경되는데 이 url 복사합니다. 0

아까 생성한 “배치에 에러로그가 올라온 경우”를 확인하는 Monitor 의 Trigger 수정화면으로 이동합니다.
Configure actions 부분의 Message 에 아래와 같이 추가합니다.

- Link: <위에서 복사한 url|로그보러 가기>

이렇게 하면 슬랙으로 메시지가 왔을때, 바로 링크를 눌러서 kibana 조회 화면으로 이동할 수 있습니다.

링크 길이 줄이기

위에서는 조회 조건이 얼마 없어서 링크가 길지 않습니다. 하지만 조건에 조금만 추가되어도 링크가 매우 길어지게 되서 셋팅할때 은근히 방해가 됩니다. 이때 조회 조건을 저장하면 링크를 줄일 수 있습니다.
다시 Discover 메뉴로 이동한 후에 조회하고 싶은 조건들을 입력합니다. 그 후에 filter 위에 있는 save 버튼을 눌러서 조회 조건을 저장합니다. 저장된 값은 Open 버튼을 누르면 확인할 수 있습니다. 이 상태에서 url 을 확인하면 길이가 줄어든 것을 확인할 수 있습니다.

조회결과 저장 이전화면 0

저장 이전 Url
http://localhost:5601/app/kibana#/discover?_g=(filters:!(),refreshInterval:(pause:!t,value:0),time:(from:now-2m,to:now))&_a=(columns:!(_source),filters:!(),index:ad592d20-368e-11ea-86a8-3790198afce8,interval:auto,query:(language:kuery,query:'level:ERROR'),sort:!('@timestamp',desc))

조회결과 저장 이후화면 0

저장 이후 Url
http://localhost:5601/app/kibana#/discover/5da5dab0-3785-11ea-91df-1764d109c41c

이뿐만 아니라 자주보는 조건을 저장하고 조회하기에도 유용합니다.



지금까지 로컬에서 elk 를 셋팅해보고 팀에서 알람을 셋팅하면서 알게된 소소합 팁들을 정리해봤습니다. 알람을 셋팅하려는 분들께 도움이 되기를 바라며 더욱 멋지게 사용할 수 있는 방법을 알게되면 추가하도록 하겠습니다.

읽어주셔서 감사합니다!