1. Prometheus
Prometheus는 SoundCloud라는 글로벌 음원 스트리밍 회사에서 2012년에 오픈소스로 공개한 시스템 모니터링 툴입니다. Prometheus는 타겟 서비스를 주기적으로 스크래핑하여 시스템 메트릭을 수집합니다. 여기서 메트릭이란 시계열 데이터를 의미하는 단어로 시간적 순서를 나타내는 데이터입니다. 시계열 데이터로는 예를 들어 주식 가격, 기상 정보, 웹 사이트 사용자 트래픽 등이 있습니다.
이번 연동 테스트에서는 Prometheus를 통해 수집된 메트릭 정보를 Grafana로 시각화해 볼 예정입니다.
2. Grafana
Grafana는 Grafana Labs에서 개발한 시각화 웹 애플리케이션입니다. Grafana는 Prometheus 뿐만 아니라 ElasticSearch, InfluxDB, MySQL, MongoDB 등과 연결하여 사용할 수 있습니다. 연동 테스트에서는 Prometheus의 메트릭 정보를 시각화 하는 역할을 담당할 예정입니다.
3. 연동 테스트 시나리오
Prometheus와 Grafana의 연동 테스트 구조는 아래 그림과 같습니다.
먼저 각 나라별로 금광을 채굴하는 회사가 <Client> 입니다. 실시간으로 채굴되는 금의 양을 세계 금관리 협회 서버 <API> 에서 수집한다고 가정했습니다. 그리고 <Prometheus> 는 세계 금관리 협회 서버의 상태 메트릭을 수집합니다. 마지막으로 <Grafana> 는 이를 시각화해서 대시보드로 보여주는 역할을 합니다. 프로젝트 전체 소스는 여기서 보실 수 있습니다.
4. Client 애플리케이션 구현
금광을 채굴하는 Client 애플리케이션의 프로젝트 구조는 아래와 같습니다.
그리고 금광 채굴 클라이언트는 SpringBoot, WebClient를 사용해서 개발했습니다. 채굴된 금광의 데이터를 세계 금관리 협회로 보내는 주요 코드 부분을 살펴보겠습니다. 먼저, ApplicationRunner 인터페이스를 구현한 GoldApplicationRunner 빈을 이용해 스프링 부트가 시작될 때 제가 만든 서비스의 메서드가 실행되도록 구현했습니다.
@Component
@RequiredArgsConstructor
public class GoldApplicationRunner implements ApplicationRunner {
private final ScenarioService scenarioService;
@Override
public void run(ApplicationArguments args) {
scenarioService.miningForGold();
}
}
ScenarioService 클래스의 구현은 아래와 같고 miningForGold 메서드는 7초의 대기 시간을 가진 후 각 회사별로 금광 채굴을 시작하도록 함수를 호출합니다.
@Service
@RequiredArgsConstructor
public class ScenarioService {
private final MineralMint mineralMint;
private final NuggetVentures nuggetVentures;
private final OreProsEnterprises oreProsEnterprises;
private final TreasureVein treasureVein;
public void miningForGold() {
try {
Thread.sleep(7000);
mineralMint.startMining();
nuggetVentures.startMining();
oreProsEnterprises.startMining();
treasureVein.startMining();
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
(도커 컴포즈로 Client, API, Prometheus, Grafana를 동시에 실행시키다보니 모든 서비스가 정상적으로 올라오기도 전에 Client 가 실행되는 문제가 있었습니다. 그래서 Client에 7초의 대기시간을 주었습니다.)
아래는 각 회사 클래스에서 금광을 채굴하는 메서드입니다.
@Override
public void startMining() {
ExecutorService executor = Executors.newFixedThreadPool(this.goldMinerCount);
for (int i = 0; i < this.goldMinerCount; i++) {
executor.execute(() -> {
while (true) {
try {
Thread.sleep(1000);
synchronized (this) {
if(this.goldReservesKilograms < 1) {
break;
}
this.goldReservesKilograms = Miner.act(GoldMiningActVo.of(this), webClient);
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
}
executor.shutdown();
}
5. API 애플리케이션 구현
각 나라에서 보내오는 금 채굴 데이터를 수집하는 세계 금관리 협회 서버의 프로젝트 구조는 아래와 같습니다.
세계 금관리 협회 서버는 SpringBoot, JPA, QueryDSL, H2를 사용해서 구현했습니다. 아래는 금광 데이터를 받는 GoldStatusRequest와 GoldStatusController 입니다.
@Getter
@Setter
public class GoldStatusRequest {
private String name;
private String country;
private int goldMiningKilograms;
}
@PostMapping
public ResponseEntity saveWorldGoldStatus(@RequestBody GoldStatusRequest goldStatusRequest) {
goldStatusService.saveGoldStatus(goldStatusRequest);
return new ResponseEntity(HttpStatus.OK);
}
컨트롤러에서 호출된 서비스 메서드를 통해 금광 데이터는 DB에 저장됩니다.
public void saveGoldStatus(GoldStatusRequest goldStatusRequest) {
goldStatusRepo.save(GoldStatus.of(goldStatusRequest));
}
그리고 Prometheus와의 연동을 위해 build.gradle과 application.yml에 각각 아래 설정을 추가해주었습니다.
[build.gradle - dependencies]
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'io.micrometer:micrometer-registry-prometheus'
[application.yml]
management:
endpoints:
web:
exposure:
include: health, prometheus
위 설정을 통해 actuator 엔트포인트에서 health와 prometheus 만 노출되도록 설정해주었습니다. 설정을 마쳤으니 동작에 이상이 없는지 확인해보겠습니다.
/actuator에 health와 prometheus가 노출된 것을 확인할 수 있습니다. /actuator/prometheus 를 호출하면 prometheus 가 수집하는 메트릭 정보를 볼 수 있습니다.
6. API와 Prometheus 연동
그러면 이제 Client와 API 구성이 모두 끝났으니 API와 Prometheus를 연동해보겠습니다. Prometheus는 Docker를 이용해서 구동할 계획이라 docker-compose.yml에 아래와 같이 prometheus 설정을 추가해주었습니다.
version: '1.0'
services:
api:
image: gold-mining-api
container_name: compose-api
ports:
- 8080:8080
prometheus:
image: prom/prometheus
container_name: compose-prometheus
volumes:
- ./prometheus:/etc/prometheus
ports:
- 9090:9090
restart: always
그리고 Prometheus의 설정파일인 prometheus.yml 파일은 아래와 같이 작성했습니다.
global:
scrape_interval: "2s"
evaluation_interval: "2s"
rule_files:
# - "first.rules"
# - "second.rules"
scrape_configs:
- job_name: "springboot"
metrics_path: "/actuator/prometheus"
static_configs:
- targets:
- "host.docker.internal:8080"
- job_name: "prometheus"
static_configs:
- targets:
- "localhost:9090"
Prometheus 설정은 크게 global, rule_files, scrape_configs 세 부분으로 나뉩니다. 여기서 각 설정 옵션이 의미하는 바는 아래와 같습니다.
- global : prometheus의 전역 설정과 관련한 블록
- scrape_interval : 타겟 서비스 메트릭 수집 주기로 기본값은 1분이다. 여기선 2초로 설정함.
- evaluation_interval : rules를 평가하는 주기로 기본값은 1분이다. 여기선 2초로 설정함.
- rule_files : 수집 메트릭 정보를 평가하고 등급을 나누는 규칙을 기술하는 블록
- scrape_configs : 메트릭 수집 설정과 관련한 블록
- job_name : 메트릭 수집 구분 이름
- metrics_path : 메트릭 수집 엔트포인트 정의, 기본값은 /metrics
- static_configs.targets : 메트릭 수집 서버주소 정의
여기서 한 가지 주의해야할 부분이 docker compose를 사용하는 경우에 static_configs.targets에 값을 host.docker.internal:8080 로 지정해야한다는 것입니다.
(저는 이걸 몰라서 한 시간동안 해맸네요😵💫 이 글을 읽으시는 분들은 이런 시행착오는 안 겪으시길 바래요.)
이렇게 설정을 마친 후 prometheus 를 띄워보면 아래와 같이 Status - Targets 화면에서 세계 금관리 협회 API와의 연동이 정상인 것을 확인할 수 있습니다.
7. Prometheus와 Grafana 연동
이제 연동의 마지막인 Prometheus와 Grafana를 연동해보겠습니다. Grafana도 Prometheus와 동일하게 Docker로 띄울 예정이라 docker-compose.yml에 아래와 같이 설정을 추가해주었습니다.
version: '1.0'
services:
api:
image: gold-mining-api
container_name: compose-api
ports:
- 8080:8080
prometheus:
image: prom/prometheus
container_name: compose-prometheus
volumes:
- ./prometheus:/etc/prometheus
ports:
- 9090:9090
restart: always
grafana:
image: grafana/grafana
container_name: compose-grafana
ports:
- 3000:3000
volumes:
- ./grafana:/var/lib/grafana
restart: always
depends_on:
- prometheus
그리고 프로젝트 루트에 Grafana 전용 폴더를 만들어주었습니다.
Grafana를 도커로 띄우면 아래와 같이 grafana 폴더에 필요한 파일들이 자동으로 생성됩니다.
설정을 마쳤으니 Grafana를 띄워보겠습니다. Grafana 구동 후 http://localhost:3000/login 로 접속해보시면 아래와 같은 화면을 만나게 됩니다.
여기서 아이디, 패스워드를 각각 admin 으로 입력하고 로그인하시면 됩니다. 그러면 홈 화면이 나오는데 여기서 좌측 네비게이션 바에서 Connections - Data sources 를 클릭해줍니다. 그런 다음 Add new data source 버튼을 클릭해줍니다.
data source 를 선택하는 화면에서 Prometheus 를 클릭해줍니다.
그런 다음 Prometheus 서버의 주소(http://host.docker.internal:9090)를 입력해주고 하단의 Save & test 버튼을 클릭하면 data source 생성이 완료됩니다.
이제 data source 를 생성을 완료했으니 dashboard를 만들어 보겠습니다.
좌측의 Dashboards 버튼을 클릭해줍니다. 그런 다음 New 버튼을 누르고 Import 를 클릭해줍니다.
그러면 아래처럼 dashboard URL 이나 ID 를 입력하는 Input Box가 나오는데 이 부분에 Spring Boot 2.1 System Monitor 의 ID 를 입력해줍니다.
그런 다음 Load 버튼을 눌러주면 아래와 같이 data source 선택 창이 나옵니다. 이 부분에 앞에서 만들었던 prometheus의 data source 를 선택해줍니다.
그리고 Import를 해주면 아래와 같은 대시보드 화면이 나타납니다.
이렇게 해서 Prometheus와 Grafana 연동까지 모두 완료됐습니다.
8. Docker Desktop으로 시뮬레이션 실행
이제 Docker Desktop으로 모든 서비스를 띄우고 시뮬레이션을 실행해보겠습니다. docker compose up 명령을 통해 도커 컨테이너를 실행시킵니다. compose-client의 경우에 실행은 바로 되지만 Thread.sleep(7000) 코드로 인해 7초 뒤에 세계 금관리 협회 API를 호출하게 됩니다.
로그를 통해 세계 금관리 협회 API 서버에서 client의 요청을 받아 DB에 금광 데이터를 저장하는 것을 확인할 수 있습니다. Grafana의 대시보드를 보면 CPU와 Request가 올라간 것을 볼 수 있습니다.
여기까지 Prometheus와 Grafana를 연동하여 서버를 모니터링하는 테스트를 모두 마쳤습니다. 직접 시뮬레이션 환경을 구성하고 Grafana를 통해 대시보드 모니터링까지해보니 나름 뿌듯하면서 많이 신기했습니다. 이번 연동 테스트는 정말 하기 잘했다는 생각이 듭니다. 👏👏👏
9. 어려웠던 점
연동 테스트를 하면서 어려움을 겪었던 부분은 두 가지였습니다.
첫 번째는 Docker URL 세팅 부분입니다.
일반적으로 로컬에서 통신할 때 localhost를 사용하기 때문에 저는 도커 데스크탑에서 컨테이너간 통신을 할 때에도 localhost를 쓰면 되겠다고 생각했습니다. 그런데 이게 웬걸 Client에서 API를 호출할 때마다 통신에 실패했습니다. 그래서 구글링을 해보니 Docker Desktop 으로 컨테이너를 띄운 경우에는 URL을 http://host.docker.internal:8080 로 설정해줘야한다는 것을 알았습니다. 바뀐 URL로 테스트해보니 Client와 API간에 정상적으로 통신이 되는 것을 확인했습니다.
두 번째는 Grafana 대시보드 세팅 부분입니다.
대시보드 템플릿의 존재를 몰랐던 저는 직접 대시보드를 구성해야하는 줄 알고 이 부분에서 작업이 잠시 멈췄습니다. 대시보드의 패널을 어떻게 추가해야하는지 몰랐기 때문인데요. 그래서 문제 해결 방법을 찾아가던 중에 Grafana 대시보드 템플릿의 존재에 대해 알게 되었습니다. Grafana 대시보드 템플릿 사이트에서 템플릿 ID만 가져오면 손쉽게 Import가 가능했습니다. 저처럼 대시보드 구성부분에서 막히셨을 때에는 이 템플릿 사이트를 찾아보시면 좋을 것 같습니다.
10. 아쉬웠던 점
이 글을 마무리하는 시점에서 생각해봤을 때 조금 아쉬움이 남는 부분은 로컬 환경에서 연동 테스트를 진행했다는 부분입니다. 실제 서비스가 이루어지는 환경이 아니라 로컬에서 테스트를 진행했기 때문에 반쪽짜리 테스트가 아니었나 하는 아쉬움이 남습니다. 그래서 다음 글에서는 이를 보완하여 로컬 환경이 아닌 쿠버네티스를 사용하는 클라우드 환경에서 실제 서비스 운영환경과 비슷하게 구성을 하고 테스트를 해볼 예정입니다. 다음 글은 연동 테스트의 클라우드 버전으로 이어지겠습니다.
긴 글 끝까지 읽어주셔서 감사합니다. 🙇
다음 글. 쿠버네티스 클러스터 구축해보기