본문 바로가기
SpringBoot

[ELK] ELK 스택 구축으로 로그를 수집하고 시각화하기(1/3) (feat. Redis)

by 띵앤띵 2025. 6. 11.
728x90
서브타이틀 : Spring Boot 로그를 ELK 스택과 함께 실시간으로 분석하고 시각화하는 인프라 구성기

 

🧐 왜 이걸 딥다이빙하게 되었을까요?

서비스 운영 중 발생하는 수많은 로그를 단순 저장만 하는 건 이제 너무 아쉽죠. 로그에서 유의미한 정보를 추출하고, 에러 발생 시 신속하게 대응하기 위해선 수집, 저장, 분석, 시각화까지 한 번에 가능한 ELK 스택을 제대로 써보는 게 중요하다고 생각했어요. 특히 Redis와 함께 연동하면 더욱 안정적으로 로그 버퍼링이 가능하다는 점이 끌렸고요!



🔍 ELK 스택 구성 및 이해

  1. ELK 스택이란?
    • Elasticsearch: 강력한 검색 및 분석 엔진
    • Logstash: 다양한 소스로부터 데이터 수집 및 가공
    • Kibana: 데이터 시각화 및 탐색 툴
    • 이렇게 셋을 합쳐 ELK 스택이라고 불러요!
  2. Grafana vs Kibana
    • Grafana는 시계열 메트릭 시각화에 강점, 다양한 소스 연동 가능
    • Kibana는 Elasticsearch 데이터에 대한 검색, 시각화에 최적화
    • 텍스트 기반 쿼리는 Kibana가 유리, 알림(Alert) 기능은 Grafana가 더 편리해요

🚀 시스템 구성 목표

WAS Log → Redis(Buffer) → Logstash(가공) → Elasticsearch(저장) → Kibana(시각화)

## 참고: beats는 현재 구성에선 제외했어요.

 


🫵 설치 및 설정 방법

1. docker-elk (v8.7.0) 설치

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

 

2. docker-compose.yml 설정

vi docker-compose.yml

services:
  setup:
    container_name: 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
      - setup:/state: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:-}
    depends_on:
      - elasticsearch

  elasticsearch:
    container_name: elasticsearch
    build:
      context: elasticsearch/
      args:
        ELASTIC_VERSION: ${ELASTIC_VERSION}
    volumes:
      - ./elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml:ro,Z
      - elasticsearch:/usr/share/elasticsearch/data:Z
    ports:
      - 9200:9200
      - 9300:9300
    environment:
      node.name: elasticsearch
      ES_JAVA_OPTS: -Xms512m -Xmx512m
      ELASTIC_PASSWORD: ${ELASTIC_PASSWORD:-}
      discovery.type: single-node
    restart: unless-stopped

  logstash:
    container_name: logstash
    build:
      context: logstash/
      args:
        ELASTIC_VERSION: ${ELASTIC_VERSION}
    volumes:
      - ./logstash/config/logstash.yml:/usr/share/logstash/config/logstash.yml:ro,Z
      - ./logstash/pipeline:/usr/share/logstash/pipeline:ro,Z
    ports:
      - 5044:5044
      - 20000:20000/tcp
      - 20000:20000/udp
      - 9600:9600
    environment:
      LS_JAVA_OPTS: -Xms256m -Xmx256m
      LOGSTASH_INTERNAL_PASSWORD: ${LOGSTASH_INTERNAL_PASSWORD:-}
    depends_on:
      - elasticsearch
    restart: unless-stopped

  kibana:
    container_name: kibana
    build:
      context: kibana/
      args:
        ELASTIC_VERSION: ${ELASTIC_VERSION}
    volumes:
      - ./kibana/config/kibana.yml:/usr/share/kibana/config/kibana.yml:ro,Z
    ports:
      - 5601:5601
    environment:
      KIBANA_SYSTEM_PASSWORD: ${KIBANA_SYSTEM_PASSWORD:-}
    depends_on:
      - elasticsearch
    restart: unless-stopped

# 해당 docker 네트워크를 docker-elk-redis_elk 로 외부와 공유한다.
networks:
  docker-elk-redis_elk:
    external: true

volumes:
  setup:
  elasticsearch:

 

3. .env 파일에서 비밀번호 및 버전 관리

docker-compose.yml 파일안에 ${} 이런식으로 변수 설정이 되어있는데,
이건 폴더의 .env 파일을 열어보면 값을 확인하실 수 있습니다. 아래와 같은 구조로 작성되어있습니다.
(버전 및 패스워드를 관리하는 환경 파일)
vi .env

ELASTIC_VERSION=8.7.0

## 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=''

 

4. elasticsearch.yml 설정 변경

이제 저희는 basic 버전을 사용할 것이기 때문에 설정파일을 수정해야합니다.
vi docker-elk/elasticsearch/config/elasticsearch.yml 파일을 열어 수정합시다.
(default : trial로 되어 있을꺼에요!)
## 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
#
#security
xpack.license.self_generated.type: basic
xpack.security.enabled: true
xpack.monitoring.collection.enabled: true
xpack.security.authc.api_key.enabled: true #apiKey 사용설정.

 

 

5. logstash.conf 수정 (Slack 알림 설정)

Redis를 이용하여 Log를 쌓을 것이기 때문에 logstash의 pipeline도 수정해줘야합니다.
vi docker-elk/logstash/pipeline/logstash.conf 파일을 아래처럼 수정합시다.

Slack 알림 설정 : ERROR 로그 발생 시 Slack 채널로 자동 전송되도록 설정

 

vi docker-elk/logstash/pipeline/logstash.conf

input {
        redis {
                host => "THINKANDTHING" # 192.168.0.123
                port => 6379
                codec => "json"
                data_type => "list"
                key => "logstash"
        }
}

filter {
  # timestamp 필드를 ISO8601 형식으로 변환 및 타임존 설정
  date {
    match => [ "timestamp", "ISO8601" ]
    timezone => "Asia/Seoul"
  }
  
  mutate {
    rename => { "host" => "[host][name]" }
  }

  # 시간 계산을 위한 ruby 필터 추가
  ruby {
    code => "
      timestamp = event.get('@timestamp')
      event.set('from_time', (timestamp - 300).strftime('%Y-%m-%dT%H:%M:%S.%LZ')) # 5분(300초) 전
      event.set('to_time', (timestamp + 300).strftime('%Y-%m-%dT%H:%M:%S.%LZ'))   # 5분(300초) 후
    "
  }
  
  if [throwable] {
    ruby {
      code => '
        throwable = event.get("throwable")
        if throwable.is_a?(String)
          lines = throwable.split("\n") # 줄바꿈 기준으로 분리
          short_trace = lines[0..4].join("\n") # 처음 5줄만 가져옴
          event.set("short_trace", short_trace)
        else
          event.set("short_trace", "No stack trace available.") # 기본 메시지 설정
        end
      '
    }
  } else {
    mutate {
      add_field => { "short_trace" => "No stack trace available." } # 기본 메시지 설정
    }
  }
}

output {
  stdout { codec => rubydebug }
  elasticsearch {
    hosts => "elasticsearch:9200"
    index => "%{[type]}-%{+YYYY.MM.dd}"
    user => "logstash_internal"
    password => "${LOGSTASH_INTERNAL_PASSWORD}"
    ecs_compatibility => "disabled"
    manage_template => false
    #document_id => "%{[@metadata][_id]}"
    action => "index"
    retry_on_conflict => 5
  }
  # Slack 알림: log.level이 ERROR인 경우
  if [level] == "ERROR" {
    slack {
      url => "https://hooks.slack.com/services/THINKANDTHING~~~~~"
      channel => "THINKANDTHING-alert"
      username => "ELK-Logstash-Bot"
      icon_emoji => ":rotating_light:"
      format => ""
      attachments => [
      {
        "color" => "danger"
        "fields" => [
          {"title" => "service" "value" => "%{source}" "short" => true},
          {"title" => "url" "value" => "<http://192.168.0.211:5601/app/discover#/?_g=(time:(from:'%{from_time}',to:'%{to_time}'))&_a=(index:'%{[type]}-%{+YYYY.MM.dd}',query:(language:kuery,query:'level:ERROR%20AND%20logger:\"%{[logger]}\"'))|*Kibana URL*>" "short" => true},
          {"title" => "thread" "value" => "%{thread}" "short" => true},
          {"title" => "level" "value" => "%{level}" "short" => true},
          {"title" => "logger" "value" => "%{logger}"},
          {"title" => "message" "value" => "%{message}" },
          {"title" => "Stack Trace" "value" => "%{short_trace}"} # 처음 5줄만 포함
        ]
      }
      ]
    }
  }
}

 

6. kibana 설정

기본 설정 유지 (별도 수정 없음)

 


 

🔚 마무리 멘트

ELK 스택은 단순 로그 저장을 넘어, 서비스 품질과 운영 효율을 높이는 핵심 인프라 구성 요소예요. 특히 Redis를 이용한 버퍼링과 Slack 연동으로 실시간 대응까지 갖춘다면 더 이상 로그는 단순 참고자료가 아니라, 강력한 운영 무기가 된답니다! 🙌

 


 

🙇‍♀️ 다음 글 (많관부)

이번 글에서는 설정하는 방법에 대해서 작성했는데요, 다음 글에서는 ELK 설정하고 대시보드 확인하는 방법에 대해 작성해볼게요 ! 

다음글 : https://thinkandthing.tistory.com/122

 

[ELK] ELK 스택 구축으로 로그를 수집하고 시각화하기(2/3) (feat. Redis)

서브타이틀 : 로그를 모으고 보는 것까지! Kibana UI 확인과 Spring Boot 로그 설정법 🙇‍♂️ 이전 글 : https://thinkandthing.tistory.com/121 🧐 왜 이걸 딥다이빙하게 되었을까요?앞선 글에서 ELK 스택을 구

thinkandthing.tistory.com

 

반응형

댓글