kube-prometheus-stack helm chart는 Grafana 설치시에 가장 많이 이용하는 helm 이다.

배포를 하면 총 6가지의 POD가 올라온다. Alertmanager는 옵션이지만 나머지는 필수적으로 설치해야한다.

Pod 역할 배포방식
Prometheus 시계열 DB, 메트릭 저장 StatefulSet
Prometheus Operator Prometheus 자동 관리 Deployment
Node Exporter 노드 레벨 메트릭(CPU, Memory, Disk) 수집 DaemonSet
Grafana 대시보드 시각화 Deployment
kube-state-metrics K8s 리소스 레벨 상태 메트릭 Deployment
Alertmanager 알림 관리/발송 (enabled 시) StatefulSet

 

📌 Prometheus Operator 역할 보충설명

1. Prometheus 설정 자동 갱신
2. ServiceMonitor 추가하면 자동 반영
3. PrometheusRule 추가하면 알림 규칙 자동 반영
4. Prometheus Pod 장애 시 자동 복구
5. 버전 업그레이드 관리

 

📌 kube-state-metrics 역할 보충설명

1. Pod 상태 (Running, Pending, Failed)
2. Deployment replica 수
3. Service 정보
4. PVC 상태
5. Node 상태 (Ready, NotReady)

 

 

helm이 설치되어있지않다면 helm 설치부터 진행하면 된다. 난 helm 설치는 되어있어서 건너뛰었다. promethues repo를 추가하고 압축을 해제하면 구조를 확인할 수 있다.

# 레포 추가
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update

# 다운로드
helm pull prometheus-community/kube-prometheus-stack

# 압축 해제
tar -xzf kube-prometheus-stack-*.tgz

# 구조 확인
ls kube-prometheus-stack/

# values.yaml 파일 수정
kube-prometheus-stack/
├── values.yaml
├── charts/
│   ├── grafana/
│   ├── ...

# install (kube-prometheus-stack 폴더 안에서 진행)
helm upgrade --install prometheus . \
  --namespace monitoring \
  --create-namespace \
  -f ./values.yaml

 

values.yaml 파일 내에서 꼭 수정이 필요한 부분들을 정리하고자 한다.

 

1. Prometheus PV, PVC 및 보관주기 설정 

Prometheus는 기본적으로 Pod가 재시작되거나 노드가 교체되어도 메트릭 데이터를 보존하려면 PV,PVC 설정이 꼭 필요하다.

재시작이 없다면 기본적으로 로그를 보관주기 설정에 맞게 보관하지만 재시작이 되거나 노드가 교체된다면 로그 보관주기와 상관없이 데이터가 날라간다. 그래서 retention을 유지하려면 PV, PVC 설정이 필수이다.

# 로그 보관주기 변경
prometheus:
  prometheusSpec:
    retention: 20d    <- 수정
    
# prometheus PV, PVC 설정 
prometheus:
  prometheusSpec:
    storageSpec:
      volumeClaimTemplate:
        spec:
          storageClassName: nfs-sc    <- 환경에 맞게 변경
          accessModes: ["ReadWriteOnce"]
          resources:
            requests:
              storage: 10Gi
          selector: {} <- 미지원 시 삭제 필요 (AWS EBS는 미지원)

 

2. Grafana PV, PVC 및 admin ID/PW 설정

kube-prometheus-stack은 Grafana 대시보드를 ConfigMap으로 관리해서 PV,PVC 설정이 필수는 아니지만, 해당 설정을 안해주면 Grafana pod가 재시작되거나 노드가 교체되었을때 Grafana 커스텀 대시보드가 날라간다. Grafana 커스텀 대시보드를 사용한다면 PV 설정은 필수이다.

storageSpec: {}  이 기본으로 되어있는데 {} 삭제하고 작성해주면 된다. {} 삭제하지 않고 storageSpec: 설정 시에는 Pod 오류가 발생한다. 기본적으로 아래 코드가 주석처리 되어있어서 주석을 풀고 storageClassName과 storage만 작성하면 된다.

# sc명 확인
kubectl get storageclass

# grafana pv, pvc 설정
grafana:
  persistence:
    enabled: true
    type: pvc
    storageClassName: nfs-sc    # 환경에 맞게 변경
    accessModes: ["ReadWriteOnce"]
    size: 5Gi
          
# admin ID/PW 설정
grafana:
  adminUser: admin
  adminPassword: "your-password"

3. Grafana ingress.yaml 작성

grafana 대시보드는 외부에서 접속이 필요하기 때문에 ingress 리소스가 필요하다. values.yaml 내에서 추가하는 방식도 있지만 추가적으로 yaml 파일을 작성해서 관리하는것이 편해서 그렇게 했다. 나는 nginx ingress controller와 HTTPS를 사용하여 그 기준으로 작성하였다.

# values.yaml 파일 외 별도로 ingress.yaml 파일 작성
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: nginx
  name: grafana-dashboard
  namespace: monitoring
spec:
  rules:
  - host: grafana.co.kr <- 수정필요
    http:
      paths:
      - backend:
          service:
            name: kube-prometheus-stack-grafana
            port:
              number: 80
        path: /
        pathType: Prefix
  tls:
  - hosts:
    - grafana.co.kr <- 수정필요
    secretName: tls

 

loki-stack을 통해서 loki, promtail, grafana, Prometheus 등을 한번에 패키지로 설치할 수 있다. Helm으로 빠르고 간편하게 설치 가능하며, 로그 시스템만 추가적으로 구축하는 경우에는 Loki와 promtail 단독 helm chart를 사용하는것이 더 좋다.
 

Loki-stack 패키지 다운시 아래 리스트가 다운되며, loki와 Promtail은 필수적으로 설치해야한다.

filebeat: ELK 스택(Elasticsearch)에서 사용 (promtail 대체)
fluent-bit: promtail을 대신하는 가볍고 빠른 수집기
grafana
logstash: ELK 스택(Elasticsearch)에서 사용 (promtail 대체)
loki:   Log Engine (저장/분석)
prometheus
promtail: Log Forwarder (수집기)로 각 노드에서 로그를 읽어서 loki로 push해주는 agent이고 데몬셋으로 띄워진다. 

Loki와 promtail은 필수적으로 설치해야 로그 수집 시스템을 구성할 수 있다. grafana와 prometheus는 이미 설치되어있어서 설치하지 않았지만 시각화를 위해서 필요하다.
 

로그 전달 과정 (Promtail → Loki → Grafana)

1. Promtail (수집기): 로그 파일을 읽으면서 설정된 규칙(relabel_configs)에 따라 라벨(Key:Value)을 붙인다. 이때 JSON 형식이 아니라 Loki 전용 프로토콜로 라벨과 로그 라인을 묶어서 보낸다. relabel_configs에 라벨을 추가할 수 있지만 너무 많은 라벨 추가 시 Loki 인덱싱이 너무 많아질 수 있기에 조심해야한다.
2. Loki (저장소): 받은 라벨을 인덱싱하여 저장한다. 예를 들어서 namespace=airflow 이런식으로 인덱싱하여 로그를 저장한다.
3. Grafana (시각화): Loki에게 LogQL을 통해서 Log Selectors로 필터링해서 로그를 가져온다.
 

설치 시 필수 확인사항

모두 loki chart의 values.yaml 파일내에서 수정해야한다.
 

1. loki의 pod가 재시작되어도 데이터를 보전할 수 있게 PVC 구성
loki는 엔진으로써 데이터를 저장하고 분석하기에 Pod가 재시작되어도 데이터가 날라가지 않게 PVC 구성을 해줘야한다.

persistence:
    enabled: true        # false를 true로 덮어쓰기
    size: 100Gi          # 10Gi를 100Gi로 덮어쓰기
    storageClassName: "nfs-client" # 빈 값을 "nfs-client"로 덮어쓰기

 
2. 데이터 보관기간 설정
reject_old_samples_max_age와 retention_period은 동일한 값을 설정해주는것이 좋다. 보관은 90일 하는데, 유입은 7일 전 것만 받으면 과거 데이터를 백업해서 넣고 싶을 때 넣을수가없다. 기본값은 7일이였다.

limits_config:
    reject_old_samples: true
    reject_old_samples_max_age: 2160h  # 90일 이전 로그 유입 차단
    retention_period: 2160h            # 90일 지나면 삭제

 
3. 이전 데이터 삭제 활성화
Loki가 실제로 1번에서 할당한 PVC로 설정해서 Loki Pod에 마운트해준 디스크에서 오래된 데이터를 찾아 지우는 작업을 수행한다. Compactor가 실제로 삭제 명령을 내리고 삭제 작업을 하기에, true로 주지 않는다면 2번에서 설정한 90일 지나면 삭제가 먹지 않는다. 운영 환경에서 NAS 디스크 용량 고갈을 방지하기 위해서는 필수적이다.

config:
    compactor:
      retention_enabled: true

 
 
Grafana와 loki 설치가 완료됐다면 아래 링크에서 대시보드 템플릿을 json 파일로 다운받거나 url로 grafana에 import 가능하다.
시간도 절약되고 다양한 대시보드가 존재해서 추천한다.
대시보드 모음 페이지: https://grafana.com/grafana/dashboards/

 

Grafana dashboards | Grafana Labs

No results found. Please clear one or more filters.

grafana.com

 
나는 저 중에서 아래 링크로 만들어진 대시보드를 선택했다. 여기서 grafana Variables 설정만 수정하여 node > namespace > pod 계층으로 조회할 수 있도록 시각화했다.
https://grafana.com/grafana/dashboards/24574-logging-dashboard-via-loki-v3/

 

Logging Dashboard via Loki v3 | Grafana Labs

Big update of https://grafana.com/grafana/dashboards/18042-logging-dashboard-via-loki-v2/ By default queries of variables and panels supposed that you have this loki labels (if not, you should change them in variables dashboard settings section or in json

grafana.com

 

트러블슈팅

 
1. Grafana에서 loki connection error
Save & Test 시 오류가 발생했다. Grafana pod에 들어가서 오류 메세지( Unexpected < )가 아래와 같이 나오는걸 확인했다. 
Loki: v2.6.1 (구형) 이였고 Grafana는 v11.2.2 (최신) 버전이여서 발생한 문제였다. grafana와 loki의 각 pod 들어가서 curl을 다양한 종류로 날려봤는데 서로 통신이 너무 잘된다... Loki 엔진을 업그레이드하면 문제는 해결되지만, 결론적으로 Explore에서 로그가 나온다면 그냥 쓰면 된다!!
 
2. Grafana Dashboard Tag 값 불일치
Loki version이 3인 대시보드를 import 했더니, 나는 Loki version이 2라서 라벨 호환이 되지 않아서 json 파일을 수정하고 import했더니 문제가 없었다. 라벨 호환이 되지 않은 이유는 3버전부터 Promtail의 OTel(OpenTelemetry)의 Semantic Conventions(시맨틱 컨벤션) 규칙을 따르기 때문이다. 2버전에서는 차트 제작자 마음대로 라벨링을 했지만, 3버전부터는 전세계 엔지니어들이 정한 OTel 규칙을 따라서 만들어졌다. 그래서 key가 달랐고 key명으로 패널 쿼리와 Variables 설정값의 name과 label을 수정했다.
 
3. Variables의 Chained Variables(종속 변수) 미작동
node > namespace > pod로 종속 변수를 설정했는데 자꾸 전체 레이블이 보이면서 필터링이 무시됐다. 아래와 같이 수정했는데도 되지 않아서 트러블슈팅에 오랜 시간이 걸렸다. 알고보니 loki 2.6.1 버전에서는 Grafana 드롭다운 종속변수가 잘 적용되지 않았다. 그래서 Loki 엔진을 2.9.3으로 업그레이드 + 최신 인덱스 포맷(TSDB)로 변경했더니 그라파나와의 통신 병목이 해결됐다. Loki 버전을 업그레이드 한 이유는 2.8 버전부터 TSDB가 적용가능하기 때문이다. 

#수정해도 적용이 안됨

#수정 전
label_values({node_name=~\"$node_name\"}, namespace)"

# 수정 후
label_values({node_name=~\"${node_name:regex}\"}, namespace)"

 

loki chart의 values.yaml의 아래 부분을 수정했다.

schema_config:
  configs:
    # 1. 기존 boltdb-shipper 설정
    - from: "2024-01-01" 
      store: boltdb-shipper
      object_store: s3
      schema: v11
      index:
        prefix: index_
        period: 24h

    # 2. 신규 TSDB 설정
    - from: "2026-05-13" 
      store: tsdb # 수정
      object_store: s3
      schema: v13 # 수정 (tsdb 사용 시 v13 권장)
      index:
        prefix: index_
        period: 24h

storage_config:
  tsdb_shipper: # 수정
    active_index_directory: /loki/index 
    cache_location: /loki/index_cache
    shared_store: s3


인덱스 스토리지 엔진(Index Storage Engine) 파헤쳐보기

1. boltdb-shipper (기존 방식)

  • 알고리즘: B+ Tree (Key-Value store) 기반
  • 구조: 인덱스를 BoltDB라는 파일(KV 저장소)에 담아서 관리함, "이 레이블 조합은 이 로그 위치에 있다"라는 정보를 키-값 쌍으로 저장하는 방식
  • 특징: 구현이 단순하고 안정적이지만, 레이블이 많아지면(High Cardinality) 인덱스 파일이 너무 커지고, 여러 레이블을 조합해서 필터링할 때 속도가 급격히 떨어진다. ('필터링 무시' 현상을 일으킨 원인)

2. tsdb (신규 방식)

  • 알고리즘: Inverted Index (역색인) 기반
  • 구조: 프로메테우스(Prometheus)의 TSDB 구조를 그대로 가져옴. 특정 레이블 값(예: node="worker-1")이 포함된 모든 데이터 블록의 ID를 리스트(Postings list)로 관리한다.
  • 특징: 검색 조건이 node="worker-1" AND namespace="prod"라면, 각각의 리스트를 가져와서 교집합만 슥 뽑아내면 돼서 알고리즘적으로 훨씬 효율적이다.

 
airflow, kubeflow, trino의 cpu, memory 데이터는 Prometheus로 가져와서 telegraf를 통해서 postgresql db에 저장한다.
db에 저장하는 이유는 분기마다 데이터를 누적해서 보고싶기 때문이다. Prometheus는 기본 데이터 보관기간이 15일이다. 

 

Prometheus의 데이터 보관기간을 늘리면 되는데, 왜 Telegraf를 통해서 Postgresql에 데이터를 보관하는 것일까?

그 이유는 스토리지 용량 증가로 오래된 쿼리 조회 시 속도가 느려지고 디스크I/O 부하가 발생할 수 있다. 또한 Prometheus는 데이터를 영구적으로 쌓아두고 분석하는 데이터 웨어하우스(DW) 목적으로 설계되지 않았기에 장기보관에 적합하지 않다. Prometheus 로컬 스토리지는 백업과 복구가 DB만큼 유연하지 않아서 안정성을 위해서도 Postgresql를 선택했다. 나중에 여러 매트릭 데이터와 비즈니스 데이터를 조인해서 엑셀로 추출하기에도 DB에 저장해야 쿼리 유연성을 보장할 수 있다.
 
Trino에 테이블에 있는 airflow 내 특정한 dag와 kubeflow의 모델의 cpu, memory와 사용자 데이터를 postgre로 저장하기 위해서 sync-worker를 사용했다. 5분에 한번씩 데이터를 가져오게 하였고 postgre로 데이터를 insert하는 파이썬 코드다.
 
Grafana에서는 postgresql DB만 커넥션으로 연결하면 된다.
 

아키텍쳐 설계 시 고민한 점

1. 메트릭 데이터가 Postgre에 많이 쌓이면 grafana에서 조회 속도가 느려질텐데 이걸 어떻게 해결할 수 있을까?
-> postgre extension인 Timescale 형식으로 변경하면 된다. postgre config 파일에 추가만 하면 돼서 변경은 어렵지 않지만, 다만 Timescale로 변경하는 건 DB 재시작이 필요하다.
 
2. NAS 용량 데이터를 가져올때 왜 cronjob을 사용했을까?
-> telegraf sidecar로 스토리지를 가져오지 않고 cronjob을 선택한 이유는 스토리지 용량 계산에 순간적으로 메모리를 많이 쓰면서 I/O와 메모리 부하 문제가 발생할 경우,  telegraf sidecar는 telegraf pod에도 영향을 주기 때문이다. 보통 스토리지 용량 계산하는데 시간이 오래 걸린다. Trino와 연동된 스토리지 용량 확인은 자주 바뀌는 상태 데이터도 아니고 배치 이후에 용량 체크를 위해서 k8s 크론잡으로 딱 하루 한번 실행되게 하였고 shell 파일(mc du)로 용량을 influxdb 형식으로 telegraf에게 전달한다.
 

트러블 슈팅

1. trino의 cordinator와 woker를 구분해서 메트릭 데이터를 가져오려면 어떻게 해야할까??
-> Trino JMX Exporter의 k8s service의 셀렉터를 구분해서 띄워야한다. service를 각각 띄우기 위해서는 pod에 lable이 구분되어져야한다. 그래야 telegraf의 tag 필드에서 구분해서 cordinator와 woker를 구분해서 값이 들어온다. Trino는 기본적으로 JMX 형태로 메트릭을 제공하는데, 이를 프로메테우스가 pull 할 수 있도록 HTTP로 변환해주는 JMX Exporter가 필요하다.
 
2. 메트릭 이름을 지정해서 특정 메트릭만 가져오도록 telegraf.conf 파일을 수정하였는데 왜 메트릭을 안가져올까??
-> 프로메테우스에서 telegraf가 데이터를 가져올때 메트릭 이름과 태그로 분리한다. 그래서 딱 일치하는 값이 아니면 아예 수집이 안된다. Telegraf는 fieldpass에 적힌 이름과 100% 일치하는 필드만 가져오기 때문에, 글자 하나만 틀려도 데이터를 안가지고 온다. 그래서 fieldpass를 전부 없애서 Telegraf를 띄운 다음에 Postgresql에 데이터가 어떻게 들어오는지 확인하고 fieldpass를 다시 넣어줬다.
 

+ Recent posts