개요
여러 서비스들의 배치 수행시간을 컨트롤하고 배치 성공, 실패 로그를 수집해야하는 작업이 주어졌다.
해당 업무를 진행 하기 전 테스트를 위하여 진행하고 있던 웹 포트폴리오에서 테스트를 진행하고자 한다.
구성 및 설계
기존은 @shceduled를 사용하여 특정 시간에 배치를 수행하였지만 배치 수행시간을 변경하려면 소스의 수정 및 배포가 강제되었다. 이를 해결하기위해 해당 배치들을 API 형식으로 수정하고 Jenkins의 스캐줄을 사용하여 배치수행을 진행한다.
또한 단일 서비스가 아닌 여러 서비스의 배치 통합이 이루어져야 하기 때문에 각각의 Batch 수행에 관련된 로그를 수집하여야 한다. 이를 해결하기위해 Elasticsearch와 Logstash를 사용한다.
Elasticsearch, Logstash 설치
Docker를 사용하여 설치
# ELK가 세팅된 git 프로젝트 clone
git clone https://github.com/ksundong/docker-elk-kor.git
# logstash 데이터를 확인하기위한 volum 디렉토리 생성
mkdir logs
logstash데이터 확인을 위한 logs 볼륨 추가
docker-compose.yml
context: logstash/
args:
ELK_VERSION: $ELK_VERSION
volumes:
- type: bind
source: ./logstash/config/logstash.yml
target: /usr/share/logstash/config/logstash.yml
read_only: true
- type: bind
source: ./logstash/pipeline
target: /usr/share/logstash/pipeline
read_only: true
# logstash input을 확인하기위한 볼륨
- type: bind
source: /home/k/ELK/docker-elk-kor/logs
target: /logs
ports:
- "5044:5044"
- "5000:5000/tcp"
- "5000:5000/udp"
- "9600:9600"
environment:
LS_JAVA_OPTS: "-Xmx256m -Xms256m"
networks:
- elk
depends_on:
- elasticsearch
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"
networks:
- elk
depends_on:
- elasticsearch
networks:
elk:
driver: bridge
volumes:
elasticsearch:
logstash 파일 출력 및 기타설정
logstash/pipeline/logstash.conf
input {
# beats 미사용
# beats {
# port => 5044
# }
tcp {
port => 5000
}
}
## Add your filters / logstash plugins configuration here
output {
elasticsearch {
hosts => "elasticsearch:9200"
index => "logs"
user => "username"
password => "password"
ecs_compatibility => disabled
}
# 파일 출력을 위한 설정
file {
path => "/logs/test.log"
}
}
Logstash, Elasticsearch 기동
docker-compose build && docker-compose up -d
키바나 확인
http://localhost:5601/app/home#/
Spring boot의 Logback 세팅
module-common의 build.gradle
dependencies {
// 이미 board에 포함된 의존성임으로 추가하지 않음
implementation project(":module-common")
implementation project(":module-normal-board")
// logback을 사용하여 logstash로 전달하기 위하여 필요한 의존성
implementation 'net.logstash.logback:logstash-logback-encoder:7.4'
implementation group: 'ch.qos.logback', name: 'logback-core', version: '1.5.6'
}
Logback 세팅
module-main의 ressources에 logback.xml 생성
logback.xml
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<!-- 콘솔에 로그를 출력하는 콘솔 앱렌더 설정 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<!-- 로그 출력을 위한 레이아웃 설정 -->
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>%d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n</pattern>
</layout>
</appender>
<!-- 파일에 로그를 저장하는 파일 앱렌더 설정 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/app.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 일별로 로그 파일 롤링 -->
<fileNamePattern>logs/app.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 보관할 로그 파일의 최대 개수 -->
<maxHistory>30</maxHistory>
</rollingPolicy>
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>%d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n</pattern>
</layout>
</appender>
<!-- 로그스태시 세팅, 로컬의 5000포트 사용-->
<appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
<destination>127.0.0.1:5000</destination>
<encoder class="net.logstash.logback.encoder.LogstashEncoder"/>
<layout class="net.logstash.logback.layout.LogstashLayout">
<timestampPattern>yyyy-MM-dd' 'HH:mm:ss.SSS</timestampPattern>
</layout>
</appender>
<!-- 루트 로거 설정 -->
<root level="INFO">
<appender-ref ref="CONSOLE" />
<!-- <appender-ref ref="FILE" />-->
</root>
<!-- 로그스태시 사용 -->
<logger name="LOGSTASH">
<appender-ref ref="LOGSTASH"/>
</logger>
</configuration>
module-main의 @EnableAsync 설정
API를 호출 했을 때 배치가 오래 걸리린다면(예를들어 1시간 돌아가는배치) request에 대한 timeOut이 발생하기 때문에 비동기로 동작할 수 있도록 세팅
package com.portfolio;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
@SpringBootApplication(scanBasePackages = "com.portfolio")
@EnableAsync
public class PortfolioWasApplication {
public static void main(String[] args) {
SpringApplication.run(PortfolioWasApplication.class, args);
}
}
modue-normal-board의 TestController
package com.portfolio.controller;
import com.portfolio.entity.NormalBoard;
import com.portfolio.repo.NormalBoardRepo;
import com.portfolio.service.NormalBoardService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@RequestMapping("/test")
@RestController
@RequiredArgsConstructor
@Slf4j
public class TestController {
private final NormalBoardService normalBoardService;
@PostMapping("/jpaInsertTest")
@Tag(name = "jpa insert Test")
@Operation(summary = "jpa insert Test")
public String jpaInsertTest() {
NormalBoard board = NormalBoard.builder()
.title("testTitle")
.content("testContent")
.firstRegTime(LocalDateTime.now())
.lastUpdTime(LocalDateTime.now())
.firstRegUser("patrache")
.lastUpdUser("patrache")
.build();
log.info("controller start");
normalBoardService.insertNormalBoard(board);
return board.toString();
}
@GetMapping("/jpaSelectTest")
@Tag(name = "jpa select Test")
@Operation(summary = "jpa select Test")
public List<NormalBoard> jpaSelectTest() {
List<NormalBoard> boardList = normalBoardService.selectAllBoardList();
return boardList;
}
}
module-normal-board의 NormalBoardServiceImpl
해당 service는 배치가 아니지만 배치라 가정해고 작성, Async가 동작하는것을 확인하기위해 Thread.sleep으로 테스트
package com.portfolio.serviceimpl;
import com.portfolio.entity.NormalBoard;
import com.portfolio.repo.NormalBoardRepo;
import com.portfolio.service.NormalBoardService;
import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.List;
@Service
@RequiredArgsConstructor
public class NormalBoardServiceImpl implements NormalBoardService {
private final NormalBoardRepo boardRepo;
private Logger logstash = LoggerFactory.getLogger("LOGSTASH");
@Override
@Async
public void insertNormalBoard(NormalBoard board) {
try {
Thread.sleep(30000);
boardRepo.save(board);
} catch (Exception e) {
logstash.error("BAT0001||테스트 배치||FAIL||{}", e.toString());
e.printStackTrace();
}
logstash.info("BAT0001||테스트 배치||SUCCESS||ETC");
}
@Override
public List<NormalBoard> selectAllBoardList() {
return boardRepo.findAll();
}
}
Async 동작 확인
Controller start 로그 30초 이후 Logstash 로그 확인
2024-06-05 16:10:08 INFO c.p.controller.TestController - controller start
2024-06-05 16:10:09 INFO c.p.controller.TestController - controller start
2024-06-05 16:10:38 INFO LOGSTASH - BAT0001||테스트 배치||SUCCESS||ETC
2024-06-05 16:10:39 INFO LOGSTASH - BAT0001||테스트 배치||SUCCESS||ETC
Kibana에서 데이터 확인
http://localhost:5601/
목록 > devtool
GET {index}/_search
Jenkins 설치 및 배포
docker run -p 8088:8080 -p 50000:50000 --restart=on-failure -v jenkins_home:/var/jenkins_home jenkins/jenkins:lts-jdk17
'웹 정리 > 웹 포트폴리오 만들기' 카테고리의 다른 글
WAS 6차 (게시물 생성 ValidationCheck) (0) | 2024.09.03 |
---|---|
WAS 5차 (JPA, Swagger 설정을 사용한 게시물 등록) (0) | 2024.07.26 |
WAS 3차 (docker maria DB 세팅) (0) | 2024.06.01 |
WAS 2차 (멀티모듈 프로젝트 세팅) (0) | 2024.05.28 |
WAS 1차 (프로젝트 생성 및 라이브러리 추가) (0) | 2024.05.08 |