개요
이번 포스팅에서는 k8s에서 모니터링 시스템을 구축하기 위해 prometheus stack과 lok stack을 사용해보려고 합니다.
구축할 모니터링 시스템은 다음과 같습니다.
- k8s cluster 내부 메트릭 수집
- 스프링 부트 Pod 내부 에러 로그 수집
본격적인 monitoring system 구축에 앞서 다음 개념에 대해 알아보고자 합니다.
- Prometheus
- Grafana Loki
- Promtail
Prometheus란?
Prometheus는 SoundCloud에서 개발한 오픈소스 시스템 모니터링및 알림 툴킷입니다.
- metric 이름과 key, value로 식별할 수 있는 시계열 데이터 저장
- PromQL이라는 쿼리 언어 사용
- 분산 스토리지에 의존하지 않음
- HTTP pull model을 통해 시계열 데이터 수집
다음은 Prometheus의 아키텍처와 Prometheus와 함께 사용되는 서비스들에 대한 아키텍처입니다.

Prometheus Server
Promethues의 핵심 구성 요소로 메트릭 수집, 저장 및 제공을 담당합니다.
데이터 분석을 위한 쿼리 언어인 PromQL을 제공합니다.
Prometheus는 자체적으로 분산 스토리지를 제공하지는 않고 단일 노드 로컬 디스크의 TSDB에 데이터를 짧은 기간 동안 저장합니다.
만약 데이터를 장기간 보관하고 싶다면 Thanos와 같은 확장 솔루션을 사용해야 합니다.
Prometheus Targets
Prometheus Target은 Prometheus가 모니터링하는 대상을 의미합니다.
보통 HTTP endpoint이며 이 endpoint를 사용하여 prometheus가 데이터를 수집합니다.
여기서 Exporter는 Nginx나 Apache와 같은 다양한 시스템에서 제공받은 metric을 Prometheus가 읽을 수 있는 형태로 변환해주는 역할을 담당합니다.
다양한 종류의 exporter가 존재하며 원한다면 커스텀 exporter를 만들 수도 있습니다.
Push Gateway
Prometheus는 일반적으로 모니터링 타겟이 되는 서비스의 HTTP 엔드포인트를 통해 데이터를 수집합니다.
그러나 Push Gateway를 사용하면 외부에서 데이터를 push하는 방식으로 데이터를 수집할 수 있습니다.
Service Discovery
Service Discovery는 새로운 모니터링 대상을 자동으로 찾고 모니터링 할 수 있게 해주는 컴포넌트입니다.
k8s처럼 기존 Pod가 사라지고 새로운 Pod가 생기는 동적 환경에서 중요한 컴포넌트입니다.
Alert Manager
Alert Manager는 Prometheus에서 전송된 알림을 email, slack과 같은 다양한 서비스 전송하는 역할을 담당합니다.
미리 지정해 둔 alert rule의 조건을 만족하면 Alert Manager를 통해 알림을 보낼 수 있습니다.
Kube-Prometheus-Stack이란?
Kube-Prometheus-Stack은 Prometheus 운영에 필요한 다양한 컴포넌트들을 k8s에 쉽게 배포할 수 있게 도와주는 Helm Charts 패키지입니다.
Kube-Prometheus-Stack을 사용하면 Prometheus를 kubernetes 환경에 최적화된 형태로 구축하고 운영할 수 있습니다.
Kube-Prometheus-Stack의 아키텍처는 다음과 같습니다.

Kube-Prometheus-Stack 패키지는 다음과 같은 component들을 포함하고 있습니다.
- Prometheus Operator
- HA Prometheus
- HA AlertManager
- prometheus-node-exporter
- kube-state-metrics
- Grafana
여기서 Prometheus Operator는 k8s CRD(사용자 정의 리소스)를 사용하여 Prometheus와 Alert Manager의 배포와 구성을 담당합니다.
Kube-Prometheus-Stack에서 사용하는 k8s CRD는 다음과 같은 종류가 있습니다.
- Prometheus, AlertManager CRD: k8s 클러스터에서 실행될 Prometheus/AlertManager 설정을 선언적으로 정의
- ServiceMonitor, PodMonitor, Probe, ScrapeConfig CRD: prometheus의 Service Discovery 설정을 정의
- PrometheusRules CRD: prometheus의 알림이나 기록 rule을 정의
- AlertManagerConfig CRD: AlertManager 설정을 정의
kube-prometheus-stack의 helm charts 구조는 다음과 같습니다.
./kube-prometheus-stack/
├── Chart.lock
├── Chart.yaml
├── README.md
├── charts
│ ├── crds
│ ├── grafana
│ ├── kube-state-metrics
│ ├── prometheus-node-exporter
│ └── prometheus-windows-exporter
├── templates
│ ├── NOTES.txt
│ ├── _helpers.tpl
│ ├── alertmanager
│ ├── exporters
│ ├── extra-objects.yaml
│ ├── grafana
│ ├── prometheus
│ ├── prometheus-operator
│ └── thanos-ruler
└── values.yaml
Grafana Loki란?
Grafana는 Grafana Labs에서 개발한 시각화 시스템으로 수집한 다양한 데이터를 시각화하여 dashboard를 제공해주는 오픈소스입니다.
Prometheus가 수집한 데이터를 Prometheus가 제공해주는 UI를 사용해도 되지만 Grafana와 같은 오픈 소스를 연동하여 사용할 수도 있습니다.
Grafans는 Prometheus의 PromQL이라는 쿼리 언어를 사용하여 데이터를 가져와 dashboard를 구성합니다.
Loki는 Grafana Labs에서 개발한 중앙 로그 수집 시스템으로 Grafana와 연동하여 로그를 시각화할 수 있습니다.

Loki의 아키텍처는 다음과 같습니다.
storage
Loki는 모든 데이터를 S3, GCS와 같은 단일 객체 스토리지 백엔드에 저장합니다.
데이터 형식에는 index와 chunk가 있습니다.
- index: 로그를 검색하기 위한 색인으로 label의 집합으로 이루어져 있습니다.
- chunk: 특정 label 집합의 log entry들을 저장한 컨테이너입니다.
Grafana Loki가 log 데이터를 처리하고 저장하는 과정은 다음과 같습니다.

- 로그 전체를 인덱싱하지 않고 메타데이터(label)만 인덱싱한다.
- log entry는 label마다 존재하는 chunk에 모아서 압축해서 저장한다.
- 저장 용량을 크게 줄일 수 있다.
Loki의 특징
Loki의 특징은 다음과 같습니다.

Kubernetes 환경에서 로깅 시스템으로 Grafana Loki를 채택하면 Prometheus와 Grafana 기반의 모니터링 및 알림 시스템과 자연스럽게 통합된 LMA(Log-Monitoring-Alerting) 환경을 구축할 수 있습니다.
Loki-Stack이란?
Loki-Stack은 Grafana Loki 운영에 필요한 다양한 컴포넌트들을 k8s에 쉽게 배포할 수 있게 도와주는 Helm Charts 패키지입니다.
다음과 같은 컴포넌트들이 존재합니다.
- Loki: 로그 집계
- Promtail: DaemonSet으로 존재하며 로그를 수집하여 Loki에 전송
- Grafana: 데이터 시각화

loki-stack의 helm charts 구조는 다음과 같습니다.
./loki-stack/
├── Chart.yaml
├── README.md
├── charts
│ ├── filebeat
│ ├── fluent-bit
│ ├── grafana
│ ├── logstash
│ ├── loki
│ ├── prometheus
│ └── promtail
├── requirements.lock
├── requirements.yaml
├── templates
│ ├── NOTES.txt
│ ├── _helpers.tpl
│ ├── datasources.yaml
│ └── tests
└── values.yaml
모니터링 시스템 구축
다음과 같은 환경에 세팅되어 있다는 가정 하에 진행하겠습니다.
- k8s cluster
- helm
- 에러 로그를 수집할 대상 Pod
배포는 monitoring namespace 안에서 수행하겠습니다.
kubectl create namsespace monitoring
1. kube-Prometheus-Stack 배포
helm charts의 저장소를 추가합니다.
helm repo add prometheus-community https://prometheus-community.github.io/
helm repo update
values.yaml을 다운받습니다.
mkdir kube-prometheus-stack
helm show values prometheus-community/kube-prometheus-stack > ./kube-prometheus-stack/override-values.yaml
편의를 위해 UI가 존재하는 prometheus와 grafana의 Service type을 NodePort로 변경하겠습니다.


또한 prometheus.prometheusSpec.serviceMonitorSelectorNilUsesHelmValues의 값을 false로 바꿔줍니다.
default는 true인데 true로 설정하면 helm values를 통해 배포한 service monitor만 등록합니다.
다음 명령어로 prometheus custom resource의 설정을 보면 service monitor selector를 확인할 수 있습니다.
kubectl -n monitoring describe prometheus kube-prometheus-stack-prometheus
false로 바꾸어서 label을 자유롭게 설정할 수 있게 합니다.

이제 변경한 values.yaml을 통해 kube-prometheus-stack helm charts를 배포합니다.
helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack -f ./kube-prometheus-stack/override-values.yaml -n monitoring
spec.service monitor select.match labels을 보면 선택하는 service monitor의 label을 확인할 수 있습니다.
spec:
.
.
.
Service Monitor Selector:
Match Labels:
Release: kube-prometheus-stack
임시 방편으로 이 label을 service monitor에 붙여 service monitor로 인식하게 할 수도 있습니다.
2. Loki-Stack 배포
helm charts의 저장소를 추가합니다.
helm repo add grafana https://grafana.github.io/helm-charts
helm repo update
values.yaml을 다운받습니다.
mkdir loki-stack
helm show values grafana/loki-stack > ./loki-stack/override-values.yaml
loki stack의 values.yaml은 비교적 간단합니다. 아래처럼 다양한 컴포넌트들이 있고 default 설정은 loki와 promtail만 활성화 되어 있습니다.
<override-values.yaml>
test_pod:
enabled: true
...
loki:
enabled: true
...
promtail:
enabled: true
...
fluent-bit:
enabled: false
...
grafana:
enabled: false
...
prometheus:
enabled: false
...
filebeat:
enabled: false
...
logstash:
enabled: false
...
로그 수집을 위한 promtail scrape config설정을 합니다.
promtail:
enabled: true
config:
logLevel: info
serverPort: 3101
clients:
- url: http://{{ .Release.Name }}:3100/loki/api/v1/push
scrape_configs:
- job_name: springboot-logs
kubernetes_sd_configs:
- role: pod
relabel_configs:
- source_labels: [__meta_kubernetes_namespace]
action: keep
regex: test
- source_labels: [__meta_kubernetes_pod_label_app]
action: keep
regex: monitoring-test
grafana와 prometheus는 prometheus-stack을 통해 배포했기 때문에 loki와 promtail만 활성화 한 뒤 배포합니다.
helm install loki-stack grafana/loki-stack -f ./loki-stack/override-values.yaml -n monitoring
3. test용 서버 구축
간단하게 테스트를 위해 error가 발생했을 때 로그를 남기고 에러를 throw하는 spring boot 서버 코드를 작성했습니다.
dependency는 다음 두 가지가 필요합니다.
- Actuator: metric 제공을 위한 dependency
- micrometer-registry-prometheus: prometheus metric 제공을 위한 dependency
springboot 설정은 다음과 같이 진행했습니다.
- prometheus에서 메트릭을 수집하기 위해 actuator 설정 (health, metrics, prometheus, info)
- loki에서 error log를 수집하기 위해 에러가 발생했을 때 로깅
간단하게 id 값이 1이나 2이면 예외 상황 발생 로그를 출력하고 3은 에러 발생, 그렇지 않으면 success를 반환하는 API를 만들었습니다.
<spring boot server>
package com.example.monitoring_test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
public class MonitoringTestApplication {
public static void main(String[] args) {
SpringApplication.run(MonitoringTestApplication.class, args);
}
}
@RestController
class TestController {
@GetMapping("/test")
public ResponseEntity<String> testApi(@RequestParam("id") int id) {
if (id == 1) {
System.err.println("internal server error 1 occurred");
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("internal server error 1 occurred");
} else if (id == 2) {
System.err.println("internal server error 2 occurred");
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("internal server error 2 occurred");
} else if (id == 3){
throw new RuntimeException("Unexpected Error occurred");
}
return ResponseEntity.ok("Success");
}
}
<application.properties>
actuator 설정을 통해 health, metrics, prometheus, info API를 열어두었습니다.
spring.application.name=monitoring-test
management.endpoints.web.exposure.include=health,metrics,prometheus,info
management.endpoint.health.show-details=always
k8s pod로 실행할 것이기 때문에 Dockerfile도 작성했습니다.
<Dockerfile>
# Java 19 JDK 베이스
FROM eclipse-temurin:19-jdk-alpine
WORKDIR /app
# 프로젝트 전체 복사
COPY . .
# 권한 부여 (gradlew 실행 가능하게)
RUN chmod +x ./gradlew
# Gradle 빌드
RUN ./gradlew clean bootJar --no-daemon
# 포트 노출
EXPOSE 8080
# 컨테이너 실행 시 jar 실행
CMD ["java", "-jar", "build/libs/monitoring-test-0.0.1-SNAPSHOT.jar"]
다음 명령어로 docker image를 생성합니다.
docker build -t monitoring-test:latest .
만약 k8s cluster의 container runtime이 다를 경우 docker hub에 push 한 뒤 pull 하는 방식을 사용하면 됩니다.
k8s manifest를 정의합니다.
<monitoring-test.yaml>
apiVersion: v1
kind: Pod
metadata:
name: monitoring-test-pod
namespace: test
labels:
app: monitoring-test
spec:
containers:
- name: monitoring-test
image: monitoring-test:latest
ports:
- containerPort: 8080
restartPolicy: Always
---
apiVersion: v1
kind: Service
metadata:
name: monitoring-test-service
namespace: test
spec:
selector:
app: monitoring-test
ports:
- protocol: TCP
port: 8080
targetPort: 8080
type: ClusterIP
다음 명령어로 k8s manifest를 적용합니다.
kubectl create ns test
kubectl apply -f ./monitoring-test.yaml
다음은 ServiceMonitor를 생성해서 prometheus가 위에서 생성한 서비스의 metric을 수집하게 합니다.
endpoint를 보면 actuator를 통해 열어둔 prometheus metric을 가져오는 모습을 확인할 수 있습니다.
<service-monitor.yaml>
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: monitoring-test-servicemonitor
namespace: test
spec:
selector:
matchLabels:
app: monitoring-test
namespaceSelector:
matchNames:
- test
endpoints:
- port: prometheus
path: /actuator/prometheus
scheme: http
interval: 3s
k8s cluster에 적용합니다.
kubectl apply -f ./service-monitor.yaml
prometheus UI에 접속해서 target에 접속했을 때 기본적으로 생성되는 k8s 관련 service monitor 말고 우리가 만든 service monitor가 생성되었다면 성공입니다.

4. prometheus, loki datasource 등록
Connections -> Add new Connection -> prometheus 검색
따로 kube-prometheus-stack을 건드리지 않았다면 prometheus url은 다음과 같습니다.
http://kube-prometheus-stack-prometheus:9090

Connections -> Add new Connection에서 loki 검색

connection에는 loki 주소를 입력하면 됩니다. grafana, loki 모두 k8s에서 배포했기 때문에 규칙에 맞게 작성하면 됩니다.

5. grafana dashboard 생성
datasource 등록을 완료했으면 dashboard를 생성합니다.
Dashboards -> New dashboard -> Import dashboard

직접 custom해도 되지만 기존에 존재하는 spring boot observability라는 dashboard를 사용하려고 합니다.
https://grafana.com/grafana/dashboards/17175-spring-boot-observability/
Spring Boot Observability | Grafana Labs
Import the dashboard template Copy ID to clipboard or Download JSON
grafana.com
위 사이트에서 구한 id 값을 입력하거나 다운로드받은 JSON 값을 넣어주면 됩니다.

대쉬보드를 생성하고 spring boot 서버로 요청을 계속 보내보면 아래와 같이 dashboard에 데이터를 시각화 한 상태로 볼 수 있습니다.

참고 자료
https://grafana.com/docs/loki/latest/get-started/architecture/
https://devocean.sk.com/blog/techBoardDetail.do?ID=163964