Prometheus

개요

|300
프로메테우스는 처음 사운드클라우드에서 만든 모니터링, 알람 시스템이다.
많은 회사들이 프메를 사용하면서 점차 관측 가능성 툴쪽에서는 거의 defacto가 돼버렸다.

이 친구.. 문서가 꽤나 잘 돼있다..!
쿠버네티스 환경에서는 쉽게 관련되는 리소스들을 한꺼번에 설치할 수 있다.[1]
자세한 건 Prometheus Operator 참고.

특징

프로메테우스는 여러 특징을 가지고 있다.

몇 가지 안 좋은 특징도 가지고 있다.

Pull의 장점

데이터를 중앙에서 긁어오는 Pull 방식은 여러 장점이 있다.
중앙에서 주체적으로 데이터를 가져오기에 대상에 문제가 생겼을 때 즉각적으로 알아차릴 수 있다.
중앙에서 긁어올 대상을 추가하는 식으로 편하게 수집 대상을 조절할 수 있다.
어떤 데이터가 오는지 궁금하다면 관리자가 직접 프로메테우스 서버가 긁어오는 경로에 요청을 날려 데이터를 받아볼 수 있다.

구조


기본적으로 이런 구성으로 되어 있다.

설정

프로메테우스는 yaml 형식으로 설정을 적용한다.

global:
  scrape_interval:     15s
  evaluation_interval: 15s

rule_files:
  # - "first.rules"
  # - "second.rules"

scrape_configs:
  - job_name: prometheus
    static_configs:
      - targets: ['localhost:9090']

global에는 전역적으로 적용될 설정을 지정한다.
rule을 통해 몇가지 조건과 규칙을 설정할 수 있다.
scrape_configs가 실제로 데이터를 가져올 익스포터의 경로를 설정하는 필드이다.
데이터를 긁어오는 각 작업의 단위는 job으로 규정되어 여디에서 긁어올지를 명시해주면 된다.
일반적으로 익스포터들은 9090포트에 /metrics 경로로 데이터를 노출하도록 돼있다.

데이터를 쿼리할 때, 기본 단위는 잡이다.
이 잡들을 묶어 또 그룹을 만들 수 있다.

관련 문서 참고하자.[2]

데이터

프로메테우스의 데이터 형식이 거의 표준화가 된 지금, 이 형식을 잘 아는 것이 또 하나의 관건이라고 할 수 있겠다.

형식

http_requests_total{method="GET", status="200"} 100 @ 1609746000.0
http_requests_total{method="GET", status="200", region="us-east-1"} 50 @ 1609746000.0
http_requests_total{method="POST", status="200"} 80 @ 1609746000.0

이게 프로메테우스 서버에 저장되는 데이터의 예시이다.
image.png
실제로 익스포터에서 데이터를 긁어올 때는 이런 식으로 타임스탬프가 빠져있는데, 프로메테우스 서버가 데이터를 긁어오는 시점에 타임스탬프가 지정되기 때문이다.

메트릭 이름 {라벨1="ㅇㅇ", 라벨2 = "ㅇㄹㅇㄹ"} 값 @ 타임스탬프

단순하게 보면 key value가 메트릭이름:값인 거고, 거기에 특정 시간이 붙어있는 형태이다.
라벨은 해당 메트릭을 쉽게 분류하고 필터링하기 위해 붙는 값이다.
http_requests_total이란 메트릭을 여러 개 수집하는데, 이때 어떤 것은 POST 데이터만 모은 놈일 수도 있고 어떤 놈은 status가 200인 놈들만 모았을 수도 있다.
이런 것들을 라벨로서 구체적으로 특정할 수 있게 해주는 것이다.

이름은 중복되고 라벨이 다른 메트릭들?

메트릭 이름을 나누어 표현하지 않고 같은 이름으로 표현하는 것인가?

  • 다채롭고 유연하게 쿼리를 하고 유의미한 데이터를 뽑아내는데 도움이 된다.
  • 유지보수와 확장성에 용이하다.
  • 메트릭 이름이 많아지면 메트릭 자체가 늘어나고, 그만큼 부하가 커진다고 한다(이건 잘 모르겠다).

유형

일단 예시의 데이터는 Counter, 사진의 데이터는 Histogram인데, 이건 유형을 말한다.
프로메테우스에서 정의되는 지표에는 아래 4가지 유형이 존재한다.

Counter

계속 누적되면서 카운팅되는 데이터.
가령 서버가 받은 http 요청 총량 같은 것은 계속 늘어나기만 하는 데이터이다.
이런 것이 counter에 해당한다.

Gauge

단순 수치 데이터.
counter와 다르게 이 값은 오르내릴 수 있다.
대표적으로는 순간 메모리 사용량 같은 것이 있다.

Histogram

버킷이란 설정 단위를 여러 개 두고, 이로부터 분포를 구한 데이터.
분포뿐 아니라 총합을 나타낼 수도 있다.
image.png
가령 http 요청 처리에 걸린 시간을 이렇게 단위 별로 구분한다.
이 단위들이 버킷이 되며, 각 버킷은 개수가 누적된다.
이를 통해 히스토그램을 나타내는 것이다.

Summary

histogram과 유사한데, 조금 더 시간 단위로 요약된 데이터.
가령 전체 http 요청 중에서 중간 정도에 해당하는 값이라던가, 90분위수라던가.
아직 관련한 이해가 깊지 않아 여길 참고해야겠다.[3]

규칙(Rule)

프로메테우스는 두 가지 타입의 규칙을 지정할 수 있다.
룰이 뭔가 하니, 그냥 설정하는 개념 중 하나이다.
이 두가지 규칙을 보면 이해하기 편할 것이다.

Recording Rule

쿼리를 짜다보면 구문이 어마무시하게 길어질 수도 있고, 이로 인해 가독성이 떨어지게 될 수 있다.
또한 복잡한 쿼리를 매번 계산하는 것은 리소스 낭비이기도 하다.
중복되는 연산이라면, 캐시해두듯, 변수로 지정하듯 미리 연산 결과를 저장해서 활용하면 연산 수가 줄어들지 않겠는가?
이럴 때 일종의 alias처럼 사용할 수 있으면서 연산 결과를 TSDB에 저장하는 기능을 제공하는 것이 바로 Recording Rule이다.[4]

groups:
  - name: example
    rules:
    - record: code:prometheus_http_requests_total:sum
      expr: sum by (code) (prometheus_http_requests_total)

작성방식은 이렇게 된다.
중요하게 볼 지점은 record, expr인데, 전자는 이름, 후자는 실제 계산이라고 보면 되겠다.
<레벨>:<메트릭 이름>:<연산자>와 같은 식으로 record 이름을 작성하라고 권장된다.

여기에 웰 프렉티스가 있으니 참고한다.[5]

- record: instance_path:requests:rate5m
  expr: rate(requests_total{job="myjob"}[5m])

- record: path:requests:rate5m
  expr: sum without (instance)(instance_path:requests:rate5m{job="myjob"})

이런 식으로 위의 룰을 아래에서 쓸 수도 있다.

Alerting Rule

이건 알람을 전송하는 규칙에 대한 설정이다.
많이 쓰지 않는다고 하여 구태여 정리하지 않았다.

서비스 디스커버리(Service Discovery)

보통은 scrape 설정에 기본적으로 메트릭을 긁어올 경로와 대상을 지정해야 한다.
그러나 서비스가 점점 많아지고 복잡해진다면, 이마저도 어렵고 불편한 과정이 될 것이다.
이를 위해 프로메테우스에서는 특정한 설정을 잡아두면 그 설정에 맞게 알아서 메트릭을 긁어올 대상을 물색하는, 서비스 탐색 기능을 지원한다.[6]
기본적으로는 두 가지 형태의 디스커버리가 있다.

기본적인 이 두 방식을 기반으로, 다양한 시스템으로부터 데이터를 편하게 받을 수 있도록 여러 세부적인 설정을 지원하고 있다.
image.png
이런 식으로 여러 가지 대상이 가능하다.[7]
scrape 부분에 이 설정을 넣어주면 된다.

scrape_configs:
  - job_name: "kubernetes-apiservers"
    kubernetes_sd_configs:
      - role: endpoints
    scheme: https
    tls_config:
      ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
      # insecure_skip_verify: true
    authorization:
      credentials_file: /var/run/secrets/kubernetes.io/serviceaccount/token
    relabel_configs:
      - source_labels:
          [
            __meta_kubernetes_namespace,
            __meta_kubernetes_service_name,
            __meta_kubernetes_endpoint_port_name,
          ]
        action: keep
        regex: default;kubernetes;https
		
  - job_name: "kubernetes-cadvisor"
    kubernetes_sd_configs:
      - role: node
    scheme: https
    metrics_path: /metrics/cadvisor
    tls_config:
      ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
    authorization:
      credentials_file: /var/run/secrets/kubernetes.io/serviceaccount/token
    relabel_configs:
      - action: labelmap
        regex: __meta_kubernetes_node_label_(.+)

내가 좋아하는 쿠버네티스 관련 설정만 잠시 보자.
쿠버네티스 관련해서는 크게 아래 타입을 지정하여 탐색을 할 수 있다.

각각은 메타 라벨을 가지고 있는데, 이들을 relabel_configs.source_labels에 넣고, 매칭되는 값들을 대상으로 삼는다.
위의 예시에서는 endpoints 중 default 네임스페이스에 위치한 kubernetes 서비스의 https 엔드포인트를 대상으로 삼는 것이다.
이렇게 설정하면 해당 경로를 통해 http 요청을 날리고 /metrics에 경로로 노출된 값을 읽어오게 된다.
두번째 예시에서는 메트릭 경로까지 커스텀하는 모습이 보이며, action이 labelmap인데, 이것은 매칭되는 이름들을 다른 이름으로 변경시킬 때 쓴다.
relabel_configs에서 액션에 따라 설정이 달라진다는 것을 참고하자.

PromQL

그렇게 수집된 데이터를 효과적으로 쿼리할 때는 Prometheus Query Language를 사용한다.
SQL처럼 엄청 복잡한 것은 아니고, 조회하는 것에만 특화된 언어라고 보면 되겠다.

기본적으로 차원에 따라 쿼리는 두 가지 유형을 가진다.
일단 하나의 메트릭을 스칼라(보통 샘플이라 한다)라고 쳤을 때,
Instant Query는 한 타임의 메트릭(인스턴트 벡터)들을 쿼리하는 것을 말하며, 기본적인 사용방법이다.
Range Query는 단위 시간 내의 메트릭을 쿼리하는 것을 말한다.

왜 행렬이라 부르지 않고?

각 행이 시간 단위로 쪼개진 행렬이라 봐도 되지 않을까 싶었는데, 항상 같은 수의 샘플이 들어온다는 보장이 없다.
선형대수적 연산도 불가하기에 그냥 시계열 집합으로서 range vector로 표현하는 듯하다.

instant vector

http_requests_total{job="prometheus",group="canary", env!~"staging|testing"}

기본적으로 쿼리할 때는 이런 식으로 한다.
어떤 메트릭을 잡고, 그 뒤에는 {}을 이용해 라벨 필터링을 건다.

{job=~".*"} # Bad!

참고로 이런 식으로 빈 문자열이 들어가도록 잡을 수는 없다.
최소한 하나의 문자라도 들어가게 +라도 넣어야 한다.

{__name__=~"job:.*"}

메트릭 이름은 __name__이란 예약어를 가지고 있어 이를 활용할 수 있다.

range vector

http_requests_total{job="prometheus"}[5m]

인스턴트 벡터에서 기간을 잡으면 range 벡터가 된다.
이때는 []를 이용해 기간을 넣어주면 된다.
이건 현재 시점으로부터 5분전까지의 데이터를 모아서 출력한다.

변형자

sum(http_requests_total{method="GET"}[5m] offset 5m)

offset 변형자를 넣으면 잡히는 기간의 범위를 수정할 수 있다.
여기에서는 offset이 5분이므로, 5분 전부터 10분 전까지의 메트릭을 모으는 것이라 보면 되겠다.
참고로 -을 붙이는 게 가능한데, 이건 미래를 쿼리하는 꼴이다.
특정 과거시점에서 이걸 사용해서 메트릭을 내도록 설정하는 것이다.

sum(http_requests_total{method="GET"} @ 1609746000)

@를 사용하면 unix 시간 기준 특정 시점부터의 데이터를 모을 수 있게 된다.
2021-01-04T07:40:00+00:00 시점에서부터의 데이터를 뽑는 방식이다.

http_requests_total @ start()
rate(http_requests_total[5m] @ end())

start와 end는 내가 확인하고자 하는 시간 범위를 지정한 경우 그 시작점과 끝점을 나타낸다.

서브쿼리

<instant_query> '[' <range> ':' [<resolution>] ']' [ @ 변형 ] [ offset 변형 ]

서브쿼리는 하나의 쿼리 내용을 여러 시간에 걸쳐서 시행하는 것을 말한다.
[ : ]가 서브쿼리를 하는 문법이며, range에는 전체 시간 범위, resolution은 간격을 의미한다.

rate(http_requests_total[5m])[30m:1m]

이렇게 하면 5분간의 초당 http 요청 평균을, 1분 간격으로 30번 쿼리하는 것이다.

연산자

image.png
비교 연산자는 크게 4가지가 있는데, 정규식을 쓸때는 뒤에 ~을 붙인다.
image.png
논리 연산자에서 unless는 차집합이다.
image.png
지피티한테 물어봤다.
이 경우 unless는 중간값만 뺀 결과를 보여주게 된다.

image.png
집계 함수이다.
라벨을 통해 빼고 싶은 값들을 필터링할 수 있는데, 이때는 without|by를 사용한다.

<aggr-op>([parameter,] <vector expression>) [without|by (<label list>)]

parameter는 count_values, topk 등에 필요한 추가 인자이다.
without은 특정 라벨로 제외를 시킬 때, by는 특정 라벨이 있는 놈만 고를 때 사용한다.

함수

https://prometheus.io/docs/prometheus/latest/querying/functions/

관련 문서

이름 noteType created
2024-04-07(일) - 2024-06-13
Prometheus knowledge 2025-02-26
Prometheus-Adapter knowledge 2025-03-04
Prometheus Operator knowledge 2025-03-30
4주차 - 관측 가능성 project 2025-02-23
4W - 프로메테우스 스택을 통한 EKS 모니터링 published 2025-02-28
4W - 이스티오 메트릭 커스텀, 프로메테우스와 그라파나 published 2025-05-03
6W - 이스티오 컨트롤 플레인 성능 최적화 published 2025-05-18
7W - 이스티오 메시 스케일링 published 2025-06-09
E-이스티오 컨트롤 플레인 성능 최적화 topic/explain 2025-05-18
E-이스티오 메시 스케일링 topic/explain 2025-06-08

참고


  1. https://artifacthub.io/packages/helm/prometheus-community/kube-prometheus-stack ↩︎

  2. https://prometheus.io/docs/prometheus/latest/configuration/configuration/ ↩︎

  3. https://prometheus.io/docs/practices/histograms/ ↩︎

  4. https://prometheus.io/docs/prometheus/latest/configuration/recording_rules/ ↩︎

  5. https://prometheus.io/docs/practices/rules/ ↩︎

  6. https://prometheus.io/docs/prometheus/latest/http_sd/ ↩︎

  7. https://prometheus.io/docs/prometheus/latest/configuration/configuration/#kubernetes_sd_config ↩︎