☘ ANONI Chat - ELK Stack setting

ELK란 무엇인가?

  • ELK는 Elasticsearch, Logstash, Kibana의 약자로, 로그 수집, 저장, 분석, 시각화를 위한 오픈소스 로그 플랫폼 스택이다.
  • 최근에는 Beats까지 포함한 "Elastic Stack" 이라고도 부른다.

do-messenger_screenshot_2025-06-04_13_38_39.png

ELK Stack의 구성 요소

구성 요소 역할 요약 상세 설명
Elasticsearch 로그 저장 & 검색 엔진 고속 검색 가능한 NoSQL 기반의 분산형 문서 데이터베이스
Logstash 로그 수집 & 가공 도구 다양한 형식의 로그를 수집하고, 필터링 및 변환 후 Elasticsearch로 전달
Kibana 로그 시각화 UI Elasticsearch의 데이터를 시각화해주는 웹 인터페이스
(Beats) 로그 전달 경량 에이전트 Filebeat, Metricbeat 등. 각 시스템에서 데이터를 Logstash/ES로 보냄

ELK 장점

상황 ELK 적합 여부
서버가 많아 로그가 분산되어 있는 경우 ✅ 중앙집중화
에러, 경고, 사용자 행위를 빠르게 추적하고 싶은 경우 ✅ 실시간 검색
로그에서 통계 분석 및 대시보드를 구성하고 싶은 경우 ✅ 시각화
JSON 형식의 로그를 수집 및 필터링하고 싶은 경우 ✅ 구조화된 로그 처리
DevOps, SRE, 보안팀이 로그 기반 모니터링/알림이 필요한 경우 ✅ 필수 도구

ELK 단점

  • 고성능을 위해 많은 메모리가 필요하다.
  • infra를 셋팅하는데 있어서 러닝커브가 높다..


SpringBoot 프로젝트 세팅


Spring dependencies 추가

build.gradle

dependencies {  
    implementation 'net.logstash.logback:logstash-logback-encoder:7.4'  
}
  • logstash 의존성을 추가해준다.

logback-spring.xml 설정 추가

  • spring boot용 Logback 로깅 사용자 정의 설정파일

logback-spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration> <!--모든 로그를 콘솔에 출력-->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder> <!--예: `12:30:15.321 [main] INFO AuctionService - Started`-->
    </appender>

    <appender name="MAIN_LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">

        <destination>logstash:5000</destination> <!--컨테이너 포트 5000으로 전송-->

        <encoder class="net.logstash.logback.encoder.LogstashEncoder" /> <!--JSON 형식 로그로 인코딩-->
        <keepAliveDuration>5 minutes</keepAliveDuration> <!--TCP연결 5분간 유지-->
    </appender>

<!-- 추가적으로 로그 분기 가능 (ex) 
    <appender name="CUSTOM_LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">

        <destination>logstash:5001</destination>

        <encoder class="net.logstash.logback.encoder.LogstashEncoder" />
        <keepAliveDuration>5 minutes</keepAliveDuration>
    </appender>
-->


	<!--전체 시스템 로그 중 INFO 이상만 콘솔 출력 (별도 logger 설정 없는 경우에 해당)-->
    <root level="info">
	    <appender-ref ref="MAIN_LOGSTASH" />
        <appender-ref ref="CONSOLE" />
    </root>

	<!--
	클래스 또는 패키지 이름이 `MainServiceLogger`인 로거에 적용
	DEBUG 이상 로그
    additivity="false" : 루트로 로그 전파 X (CONSOLE + MAIN_LOGSTASH만 사용)
    + 콘솔 동시 출력
      
		--java-- 다음 코드로 사용 가능
		Logger logger = LoggerFactory.getLogger("MainServiceLogger");
        logger.info("{}", bidLogDTO);
	-->
    <logger name="MainServiceLogger" level="debug" additivity="false">

        <appender-ref ref="MAIN_LOGSTASH" />
        <appender-ref ref="CONSOLE" />
    </logger>

<!--
    <logger name="CustomServiceLogger" level="debug" additivity="false">
        <appender-ref ref="CUSTOM_LOGSTASH" />
        <appender-ref ref="CONSOLE" />
    </logger>
-->
</configuration>

TEST용 log 생성

mainController

@GetMapping(GlobalURL.MAIN_URL)  
public ModelAndView mainView()  
{  
    log.info("[MainController Log] mainView 접속 TEST");  
    return new ModelAndView("main");  
}


Ubuntu server Kibana 사용 셋팅


docker-compose.yml 생성

  • DockerComposeTool 설정파일.
  • 여러개의 컨테이너(서비스)를 하나의 애플리케이션 처럼 정의하고 실행하도록 도움.
  • 컨테이너 환경을 코드화/자동화
  • 컨테이너를 띄울 서버(필자는 Ubuntu)에 생성하여 준다.
  1. Elasticsearch
  2. Logstash
  3. Kibana
  4. Spring Boot 애플리케이션

주석 제외코드 ▶ anoniChat-docker-compose.yml

# Docker Compose 파일 스펙 버전 3 사용
version: '3' 

services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.11.1
    environment:
      - discovery.type=single-node # 단일 노드 구성
    ports:
      - "9200:9200"
    networks:
      - elk # `elk`키워드 네트워크로 구성 (다른 서비스와 내부 통신)
    volumes:
      - esdata:/usr/share/elasticsearch/data

  logstash:
	  image: docker.elastic.co/logstash/logstash:7.12.0
	  ports:
	    - "5044:5044" # Filebeat 등 input으로 사용하는 포트
	    - "5000:5000" # TCP 또는 JSON 로그 input 용 포트 (Spring에서 이 포트를 사용)
	  volumes:
	    - ./logstash.conf:/usr/share/logstash/pipeline/logstash.conf
	  networks:
	    - elk # `elk`키워드 네트워크

  kibana:
    image: docker.elastic.co/kibana/kibana:7.11.1
    ports:
      - "5601:5601" # 웹 UI 접근용 포트
    networks:
      - elk # `elk`키워드 네트워크
    environment:
      - ELASTICSEARCH_HOSTS=http://elasticsearch:9200 # Elasticsearch 주소 연결
      - server.host=0.0.0.0 # 모든 IP 바인딩 허용

  spring:
    image: ghcr.io/anonichat/app/anonichat
    ports:
      - "8081:8080"
    environment:
      - ELASTICSEARCH_HOST=elasticsearch:9200 # Elasticsearch의 내부 주소를 환경변수로 주입
    depends_on:
      - elasticsearch # Elasticsearch가 먼저 실행되도록 보장
    networks:
      - elk # 내부 ELK 네트워크로 연결

volumes:
  esdata:
    driver: local # Elasticsearch 데이터 저장소를 호스트 볼륨에 영구 저장

networks:
  elk: # 모든 서비스가 하나의 공용 네트워크 `elk`에서 통신
    driver: bridge # `elasticsearch`, `logstash`, `spring`, `kibana`는 서로 이름으로 접근 가능

logstash.conf 생성

  • Logstash의 데이터 처리 파이프라인을 정의하는 설정 파일이다.
  • docker-compose.yml파일을 생성한 같은 디렉토리에 생성한다.

주석 제외 코드 ▶ anoniChat-logstash.conf

input { // 데이터 수신 설정

	beats {
	    port => 5044
	}
	  
	tcp {
	    port => 5000
	    codec => json_lines // json형식으로 한줄씩 파싱
	    type => "main_log" // 수산 로그에 type필드로 "auction_log" 부여
	}
	
	//tcp {
	//    port => 5001
	//    codec => json_lines
	//    type => "custom_log"
	//}
}

filter { // 수신된 로그를 처리하기위한 전처리
	if [type] == "main_log" { // 로그 타입이 `main_log`일 때만 처리.
		grok { // 정규식으로 메세지 파싱
			match => { "message" => "%{TIMESTAMP_ISO8601:timestamp} \[%{DATA:thread}\] %{LOGLEVEL:loglevel} %{DATA:logger} - %{GREEDYDATA:logmessage}" }
		}
	}

// 필터 사용 예시
//	if [type] == "custom_log" {
//		if "TestLogDTO" in [message] {
//		    grok {
//		        match => {
//		           "message" => "TestLogDTO\(userId=%{NUMBER:user_id}, exchangeAmount=%{NUMBER:exchange_amount}, payType=%{WORD:pay_type}, payStatus=%{WORD:pay_status}\)"
//		        }
//		    }
//		    mutate { // 필드 타입 변환 및 메시지 필드 제거
//			    remove_field => ["message"]  
//			}
//		} else {
//			drop { } // `TestLogDTO`가 포함되지 않으면 해당 로그 삭제(drop).
//		}
//	}
}

output { // 로그 출력 설정 시작
	if [type] == "main_log" {
	    elasticsearch {
	      hosts => ["http://elasticsearch:9200"] // Elasticsearch로 전송
	      index => "main_log" // 인덱스 이름: `main_log`.
		}
	}
//  if [type] == "custom_log" {
//    elasticsearch {
//      hosts => ["http://elasticsearch:9200"]
//      index => "custom_log"
//   }
//  }
}



적용 및 확인


server에 파일 생성하기


임의의 폴더를 지정한 후,

nano anoniChat-docker-compose.yml
nano logstash.conf

파일을 생성한다.

Pasted image 20250605172214.png

DockerComposeTool이 없다면?
sudo apt install -y docker-compose

해당 명령어로 다운로드 받기

이후,

docker-compose up -d
  • -d : 백그라운드로 실행

http://{‘IP주소‘}:5610 (Kibana port) 로 접속확인

do-messenger_screenshot_2025-06-09_11_03_39.png

  • 로그 출력 정상 확인


Jenkins file 셋팅


위 과정을 거치며, Spring Server가 Docker Compose로 묶이게 되었다.

기존의 Jenkins파이프라인으로 Spring Server를 띄울 시 네트워크 연결이 되지 않으므로, 새로 셋팅을 해주어야 한다.

  • 기존 Spring Sever 배포 파이프라인
try {
	sh "docker stop ${CONTAINER_NAME} || true"
	sh "docker rm ${CONTAINER_NAME} || true"
	sh "docker rmi ${DOCKER_HUB_REPO}:${DOCKER_LATEST_TAG} || true"
	sh "docker pull ${DOCKER_HUB_REPO}:${DOCKER_LATEST_TAG}"
	sh """
	docker run -d \
	  --name ${CONTAINER_NAME} \
	  --restart always \
	  -p ${CONTAINER_PORT} \
	  ${DOCKER_HUB_REPO}:${DOCKER_LATEST_TAG}
	"""
	sh "sleep 10"
	sh "docker ps | grep ${CONTAINER_NAME}"
} catch (Exception e) {
	echo "❌ 배포 실패: ${e.getMessage()}"
	throw e
}
  • 변경 후 파이프라인

environment {
	COMPOSE_PATH = '/home/hello/Desktop/AnoniChat/elk-stack'
	SPRING_SERVICE_NAME = 'spring'
}
    
...

try {
	dir("${COMPOSE_PATH}") {
		sh """
			docker-compose stop ${SPRING_SERVICE_NAME} || true
			docker-compose rm -f ${SPRING_SERVICE_NAME} || true
			docker-compose pull ${SPRING_SERVICE_NAME}
			docker-compose up -d ${SPRING_SERVICE_NAME}
			docker-compose ps
		"""
	}
} catch (Exception e) {
	echo "❌ 배포 실패: ${e.getMessage()}"
	throw e
}




테스트


Docker-Compose 파일을 찾지 못하는 모습...

🚀 최신 이미지로 배포 중...
[Pipeline] script
[Pipeline] {
[Pipeline] dir
Running in /home/hello/Desktop/AnoniChat/elk-stack
[Pipeline] {
[Pipeline] sh
+ docker-compose stop spring
/home/hello/Desktop/AnoniChat/elk-stack@tmp/durable-7c44adc8/script.sh.copy: 2: docker-compose: not found
+ true
+ docker-compose rm -f spring
/home/hello/Desktop/AnoniChat/elk-stack@tmp/durable-7c44adc8/script.sh.copy: 3: docker-compose: not found
+ true
+ docker-compose pull spring
/home/hello/Desktop/AnoniChat/elk-stack@tmp/durable-7c44adc8/script.sh.copy: 4: docker-compose: not found
[Pipeline] }
[Pipeline] // dir
[Pipeline] echo
❌ 배포 실패: script returned exit code 127

답 :

Ubuntu서버에는 docker-compose를 설치했지만,

Jenkins서버(컨테이너)에 설치가 되지 않았다.

이를 해결하기 위해 직접 설치해도 되지만, Jenkins docekrfile을 수정하도록 하겠다.


Jenkins-dockerfile

  • 도커 컴포즈 다운로드 코드 추가
# 3. Docker CLI 설치 (DooD 방식)  
RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker.gpg && \  
    echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" \  
    > /etc/apt/sources.list.d/docker.list && \  
    apt-get update && apt-get install -y docker-ce-cli && \  
    # Docker Compose 설치 (v2)    DOCKER_COMPOSE_VERSION=2.24.0 && \  
    curl -SL https://github.com/docker/compose/releases/download/v${DOCKER_COMPOSE_VERSION}/docker-compose-linux-$(uname -m) \  
    -o /usr/local/bin/docker-compose && \  
    chmod +x /usr/local/bin/docker-compose && \  
    ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose

실행결과

Running in /home/hello/Desktop/AnoniChat/elk-stack
[Pipeline] {
[Pipeline] sh
+ docker-compose stop spring
no configuration file provided: not found
+ true
+ docker-compose rm -f spring
no configuration file provided: not found
+ true
+ docker-compose pull spring
no configuration file provided: not found
  • 이번에는 docker-compose.yml파일을 찾을수가 없다고 한다...

jenkins server에서 해당 경로로 진입시 docker-compose파일을 찾을 수 없다.

당연한건가??

Pasted image 20250609165033.png

+ docker-compose파일이 있는 디렉토리 마운트

docker run -d \
  --name jenkins-dood \
  -p 8080:8080 \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v jenkins_home:/home/hello \
  -v /home/hello/Desktop/AnoniChat/elk-stack:/home/hello/Desktop/AnoniChat/elk-stack \
  ghcr.io/anonichat/app/jenkins-dood:v0.06



최종

기본적인 ELK CI/CD 셋팅 완료.

Pasted image 20250609170633.png

🎉 CI/CD 파이프라인이 성공적으로 완료되었습니다!
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // withEnv
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS


다음단계