☘ ANONI Chat - ELK Stack setting
구성 요소 | 역할 요약 | 상세 설명 |
---|---|---|
Elasticsearch | 로그 저장 & 검색 엔진 | 고속 검색 가능한 NoSQL 기반의 분산형 문서 데이터베이스 |
Logstash | 로그 수집 & 가공 도구 | 다양한 형식의 로그를 수집하고, 필터링 및 변환 후 Elasticsearch로 전달 |
Kibana | 로그 시각화 UI | Elasticsearch의 데이터를 시각화해주는 웹 인터페이스 |
(Beats) | 로그 전달 경량 에이전트 | Filebeat, Metricbeat 등. 각 시스템에서 데이터를 Logstash/ES로 보냄 |
상황 | ELK 적합 여부 |
---|---|
서버가 많아 로그가 분산되어 있는 경우 | ✅ 중앙집중화 |
에러, 경고, 사용자 행위를 빠르게 추적하고 싶은 경우 | ✅ 실시간 검색 |
로그에서 통계 분석 및 대시보드를 구성하고 싶은 경우 | ✅ 시각화 |
JSON 형식의 로그를 수집 및 필터링하고 싶은 경우 | ✅ 구조화된 로그 처리 |
DevOps, SRE, 보안팀이 로그 기반 모니터링/알림이 필요한 경우 | ✅ 필수 도구 |
build.gradle
dependencies {
implementation 'net.logstash.logback:logstash-logback-encoder:7.4'
}
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>
mainController
@GetMapping(GlobalURL.MAIN_URL)
public ModelAndView mainView()
{
log.info("[MainController Log] mainView 접속 TEST");
return new ModelAndView("main");
}
주석 제외코드 ▶ 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`는 서로 이름으로 접근 가능
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"
// }
// }
}
임의의 폴더를 지정한 후,
nano anoniChat-docker-compose.yml
nano logstash.conf
파일을 생성한다.
sudo apt install -y docker-compose
해당 명령어로 다운로드 받기
이후,
docker-compose up -d
-d
: 백그라운드로 실행http://{‘IP주소‘}:5610 (Kibana port)
로 접속확인
위 과정을 거치며, Spring Server가 Docker Compose로 묶이게 되었다.
기존의 Jenkins파이프라인으로 Spring Server를 띄울 시 네트워크 연결이 되지 않으므로, 새로 셋팅을 해주어야 한다.
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
}
🚀 최신 이미지로 배포 중...
[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을 수정하도록 하겠다.
# 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파일을 찾을 수 없다.
당연한건가??
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 셋팅 완료.
🎉 CI/CD 파이프라인이 성공적으로 완료되었습니다!
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // withEnv
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS