들어가며
Spring Actuator + Micrometer + Prometheus + Grafana 스택을 이용하여 모니터링 구축을 진행하였습니다.
Spring Actuator란?
어플리케이션의 상태를 모니터링하기 위한 Metric을 수집하고 Http Endpoint를 제공해주는 라이브러리입니다.
Micrometer란?
JVM 기반의 어플리케이션에서 다양한 모니터링 도구가 제공하는 클라이언트 라이브러리에 대한 facade를 제공하는 오픈소스 라이브러리입니다.
로깅과 관련된 SLF4J처럼 다양한 모니터링 도구를 추상화하여 어떤 모니터링 도구를 사용할지 고민할 필요 없이 Metric을 수집하는데만 집중할 수 있게 해줍니다.
Prometheus란?
Metric을 수집하고 저장하며 이를 통해 모니터링하거나 경고를 할 수 있게 도와주는 오픈소스 라이브러리입니다.
Prometheus에서 수집하는 Metric은 간단한 숫자 데이터로 TSDB라는 DB에 시계열(Time Series) 데이터로 저장합니다.
일반적인 다른 모니터링 도구와의 다른 점은 클라이언트가 Metric을 수집하여 서버로 보내는 방식이 아닌 서버가 클라이언트에게서 Pull 해오는 방식으로 동작한다는 점입니다.
Grafana란?
시계열 데이터를 시각화하는데 가장 최적화된 대시보드를 제공해주는 오픈소스 라이브러리입니다.
다양한 DataSource를 연결하여 데이터를 가져와 시각화 할 수 있으며 시각화한 데이터가 특정 수치 이상으로 값이 치솟을 때 알림을 전달 받을 수 있는 기능도 제공합니다.
Spring Actuator + Micrometer 설정
dependency
//Spring Actuator
implementation 'org.springframework.boot:spring-boot-starter-actuator'
//Micrometer
runtimeOnly 'io.micrometer:micrometer-registry-prometheus'
Java
복사
spring-boot-starter-actuator를 사용하면 AutoConfiguration을 통해 기본적인 Metric들의 엔드포인트가 활성화됩니다.
Actuator를 통해 수집된 Metric들을 Prometheus가 읽을 수 있는 Metric으로 변환해주기 위해 Prometheus용 Micrometer의 dependency를 추가해줍니다.
이 때, runtimeOnly로 해준 이유는 build 시엔 없어도 build에 영향이 없고 runtime 시에만 필요하기 때문입니다.
ActuatorConfig.java
@Configuration
public class ActuatorConfig {
@Bean
public MeterRegistry meterRegistry() {
return new SimpleMeterRegistry();
}
@Bean
public HttpTraceRepository httpExchangeRepository() {
return new InMemoryHttpTraceRepository();
}
}
Java
복사
Custom Metric들을 정의하기 위한 MeterRegistry의 구현체 중 하나인 SimpleMeterRegistry를 빈으로 등록합니다.
또한 Actuator에서 http 관련된 Metric들을 수집하기 위한 HttpTraceRepository 구현체 중 하나인 InMemoryHttpTraceRepository를 빈으로 등록합니다.
application.yml
#Actuator 설정
management:
server:
port: 9000
metrics:
tags:
application: gocho
endpoints:
enabled-by-default: false
web:
base-path: "/actuator"
exposure:
include: info, health, prometheus, metrics, httptrace
endpoint:
info:
enabled: true
health:
enabled: true
prometheus:
enabled: true
metrics:
enabled: true
httptrace:
enabled: true
YAML
복사
port 설정을 통해 Actuator가 노출될 포트를 8080이 아닌 9000으로 분리하였습니다.
enabled-by-default는 기본적으로 활성화된 엔드포인트들을 사용할건지에 대한 설정으로 보안을 위해 꺼두었습니다.
base-path를 통해 Actuator로 접근하기 위한 엔드포인트의 기본 경로를 설정 해줄 수도 있고 기본 값은 /actuator입니다.
이제 노출되길 원하는 Metric들을 enable 시켜주고 http를 통해 노출시켜야하므로 web의 exposure에 포함시켜줍니다.
#Tomcat Metric 설정
server:
tomcat:
mbeanregistry:
enabled: true
YAML
복사
추가로 tomcat의 metric을 수집하기 위한 설정을 해줍니다.
Actuator 보안 설정
Actuator는 서버를 종료하는 엔드포인트, 힙덤프 등 민감한 부분도 노출할 수 있으므로 사용할 엔드포인트만 활성화하고 노출하는게 중요합니다.
http {
#호스트 설정
server {
#Spring actuator의 URI 매핑 설정
location /actuator {
return 404;
}
}
}
YAML
복사
그래서 저는 로드 밸런서를 통한 Actuator 엔드포인트의 직접적인 접근을 막기 위해 Spring Boot 앞 단에 있는 Nginx에 위와 같은 설정을 추가하였습니다.
Endpoint 확인
이제 지정해둔 포트의 base-path를 통해 접근하면 첫번째 사진처럼 노출을 활성화 해둔 Metric들이 확인가능하고 원하는 Metric으로 접근하면 자세한 내용을 확인할 수 있습니다.
Prometheus + Grafana 구축
Spring Boot는 8080, Actuator는 9000, Prometheus는 9090, Grafana는 3000 포트를 사용하고 있기 때문에 하나의 인스턴스를 Scale-up해서 전부 띄워도 상관없습니다.
하지만 Spring Boot 서버가 여러 개 있기도 하고 인스턴스가 죽었을 때 모니터링 서버가 같이 죽는 이슈를 예방하기 위해 모니터링 서버를 위한 인스턴스를 따로 분리하도록 하겠습니다.
Task-Definition 작성
먼저 모니터링 서버 인스턴스를 만들기 위한 작업 정의서를 만들어줍니다.
작업 정의서 이름을 정하고 인스턴스 유형은 AWS Fargate로 선택해줍니다.
Prometheus 컨테이너를 추가하기 위해 이미지 URI는 prom/prometheus:latest로 지정해줍니다.
이후 해당 컨테이너의 9090 포트를 뚫어주도록 합니다.
Grafana 컨테이너는 이미지 URI를 grafana/grafana:latest로 지정해줍니다.
기본적으로 3000번 포트를 사용하기 때문에 해당 컨테이너의 포트를 3000번으로 뚫어줍니다.
ECS 추가
이제 만든 작업 정의서로 태스크를 생성하기 위해 ECS를 생성해줍니다.
AWS ECS에서 콘솔을 통해 먼저 AWS Fargate 클러스터를 생성합니다.
서비스를 생성할 땐 앞서 만들어뒀던 작업 정의서를 선택합니다.
이 부분이 중요한데 Spring Boot 서버가 있는 곳과 같은 VPC와 Subnet에 위치시켜주셔야합니다.
또한 보안 그룹은 Prometheus와 Grafana가 사용하는 9090번과 3000번 포트를 인바운드 규칙에서 열어주시고 퍼블릭IP를 꺼줍니다.
Prometheus와 Grafana에 접근하기 위해 로드밸런스를 연결해줘야하는데 현재 AWS 콘솔로는 로드 밸런싱 할 컨테이너를 1개 밖에 설정할 수 없습니다.
그래서 우선 콘솔을 통해 1개만 설정 후 서비스를 생성하고 AWS CLI를 통해 컨테이너를 추가해줍니다.
aws ecs update-service \
--cluster gocho_monitoring_cluster \
--service gocho_monitoring_service \
--load-balancers \
targetGroupArn={{TargetGroupARN}},containerName=prometheus,containerPort=9090 \
targetGroupArn={{TargetGroupARN}},containerName=grafana,containerPort=3000
JSON
복사
네임스페이스 설정
ECS Fargate의 태스크는 하나의 인스턴스이고 각 태스크들끼린 서로의 존재를 알지 못합니다.
Docker에선 docker network connect [네트워크] [컨테이너] 를 통해 컨테이너들의 네트워크를 하나로 연결 해줄 수 있습니다.
네트워크를 지정해주지 않은 컨테이너는 기본적으로 docker0이라는 default network로 연결이 되고 네트워크를 분리하고 싶다면 docker network create [네트워크] 를 통해 네트워크를 생성한 후 연결시켜줄 수 있습니다.
ECS의 네임스페이스는 이러한 Docker의 network 같은 기능을 해줍니다.
AWS CloudMap에서 위와 같이 인스턴스 검색 방식이 ‘VPC에서 API 호출 및 DNS 쿼리’ 인 네임스페이스를 생성해줍니다.
Spring Boot 서버와 모니터링 서버가 존재하는 ECS 서비스의 서비스 업데이트에서 ‘서비스 연결 켜기’를 통해 네임스페이스를 지정해줍니다.
이 때, DNS는 해당 태스크를 다른 태스크들이 알 수 있게 해주는 호스트가 되고 포트는 외부로 노출되는 포트를 말합니다.
네임스페이스를 설정하기 위한 ecs-service-connect 태스크가 추가되고 서비스가 재배포에 성공하면 Prometheus에서 타겟들을 정상적으로 연결할 수 있습니다.
EFS 마운트
Prometheus엔 타겟 설정 정보를 저장하는 prometheus.yml이 있고 Grafana에는 대시보드 설정 정보 등을 저장하는 grafana.db가 있습니다.
ECS Fargate는 태스크마다 기본적으로 20GB의 임시 스토리지를 제공합니다.
하지만 태스크를 만들 때마다 새로운 임시 스토리지를 수신하기 때문에 설정 정보들이 초기화되는 이슈가 발생하게 됩니다.
설정 정보들을 포함한 Docker 이미지 파일을 ECR에 Push한 후 해당 이미지 파일을 통해 태스크를 생성해도 되지만 더욱 유연한 관리를 위해 EFS를 사용하기로 하였습니다.
먼저 EFS를 생성해줍니다.
VPC는 마운트하고자 하는 인스턴스인 모니터링 서버가 존재하는 VPC와 동일한 곳으로 설정해줍니다.
EFS의 엑세스 포인트로 prometheus와 grafana를 각각 생성해줍니다.
prometheus는 /etc/prometheus 경로, grafana는 /var/lib/grafana 경로에 설정 파일들이 실제로 저장되고 있었습니다.
이 때 루트 디렉터리 경로는 EFS에서 저장될 디렉토리의 경로를 의미합니다.
EFS는 파일 전송을 위한 NFS라는 유형의 프로토콜을 사용하며 해당 프로토콜은 2049번 포트를 사용합니다
EFS를 위한 보안 그룹을 추가하고 인바운드 규칙에 2049번 포트를 뚫어주는데 사용자는 EFS를 마운트할 다른 인스턴스가 존재하는 보안 그룹으로 지정해줍니다.
이제 작업 정의서를 개정하여 생성했던 EFS와 엑세스 포인트를 지정하여 볼륨을 추가합니다.
이 때, 고급 구성을 누른 후 전송 암호화를 체크해줍니다.
그리고 컨테이너 탑재 지점 설정을 통해 Prometheus와 Grafana 각 컨테이너에 추가한 볼륨을 지정 해준 후 EFS를 마운트할 컨테이너에서의 실제 경로를 지정해줍니다.
이제 EFS로 손쉽게 접근하기 위해 EC2에 마운트하는 작업을 해보겠습니다.
sudo mount \
-t nfs \
-o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport {{EFS-DNS-NAME}}:/ ~/efs-mount
JSON
복사
Bastion용으로 사용하고 있는 EC2에서 위 명령어를 실행하여 EFS를 마운트 시켜줍니다.
EC2로 SSH 접속한 후 확인해보면 정상적으로 마운트되었고 파일도 잘 생성되는걸 확인할 수 있습니다.
prometheus.yml 설정
EFS를 마운트한 인스턴스에서 /efs-mount/bsdata/etc/prometheus로 접근하여 touch prometheus.yml 을 통해 설정 파일을 생성해줍니다.
global:
scrape_interval: 15s
evaluation_interval: 15s
#scrape_timeout: 10s
alerting:
alertmanagers:
- static_configs:
- targets:
# - alertmanager:9093
rule_files:
# - "first.rules"
# - "second.rules"
scrape_configs:
- job_name: prometheus
metrics_path: "/actuator/prometheus"
static_configs:
- targets: ['localhost:9090']
basic_auth:
username: username
password: password
YAML
복사
prometheus의 기본 설정은 위와 같이 되어있습니다. 큰 설정인 global과 scrape_configs만 살펴보겠습니다.
global은 전역에 적용되는 설정으로 타겟으로부터의 수집 주기인 scrape_interval과 alert을 보낼지 판단하는 주기인 evaluation_interval이 있습니다.
scrape_configs는 metric을 수집할 타겟 설정으로 타겟들의 단위는 job입니다.
job_name은 타겟의 이름을 지정하고 actuator의 metric을 micrometer를 통해 변환한 prometheus용 metric으로 수집할 것이기 때문에 metrics_path 를 /actuator/prometheus로 지정해줍니다.
static_configs의 targets엔 타겟의 호스트와 포트번호를 지정해주는데 호스트는 네임스페이스의 DNS로 지정해줍니다.
추가로 저는 Actuator Endpoint에 Basic Authentication을 설정 해놓았기 때문에 basic_auth 설정을 통해 username과 password를 지정해줍니다.
Grafana 설정
Grafana 접속 후 Home - Connections - Data Sources 에 접속합니다.
Add new data source를 통해 Prometheus를 연결해줍니다.
HTTP 에서 Prometheus server URL만 Prometheus가 실행되고 있는 태스크의 호스트와 포트로 지정해줍니다.
이제 Home - Dashboards 에서 원하는 대시보드를 만들 수 있습니다.
또한 New - Import 를 누른 후 다른 사람이 올린 대시보드의 ID를 입력하여 Import 할 수 있습니다.
Grafana Labs를 통해 여러 가지 대시보드를 구경하고 적용할 수 있습니다.