목차
- Prometheus의 대략적인 구조 - pulling, exporter
- 결론
- 분석
1. Prometheus의 구조 - exporter, pulling
Prometheus 각종 exporter 들로부터 그들의 매트릭을 pull하여 저장한다. 그 주기는 scrape_config
- rate_interval(이하 scrape interval)
을 참조한다.
docker에 node-exporter
, cadvisor-exporter
등의 이름으로 container 를 돌리고 있다면, 아래처럼 접속해서 prometheus 가 수집하는 매트릭을 확인해볼 수 있다.

http://node-exporter:9100/metric
http://cadvisor-exporter:9100/metric
그렇다면 이들 exporter들의 상태는 언제, 어떻게 변할까? '적절한 수집 주기'는 얼마일까?
2. 결론
- exporter 들은 자체적으로 자신의 상태값을 갱신하지 않는다.
- exporter들은
/metric
http request 를 받을 때 metric을 수집하기 시작한다.- (적어도 prometheus 의 exporter들은 그러할 것이라고 추측할 수 있다)
- Prometheus UI에서 metric 을 수집하는 데에 걸리는 시간, response에 걸리는 시간 등은 손쉽게 확인 가능하다. (target 페이지)
- request -> scrap -> response에 100~500ms 정도 걸렸다.
- (따라서 이론상) exporter 들은 1~2sec 의 scrape config도 충분히 감당 가능할 것으로 보인다.
- 1~2 sec 의
scrape_interval
에 따른 Prometheus 및 Node의 부하는 확인하지 않았다.thanos
등을 운영하며 여러 개의 Prometheus pod를 운영하는 게 좋을 것이다. - 그렇지만 '정확한' 값을 수집하는 게 프로메테우스이 역할은 아니기에, 기존의
scrape_interval
(15sec)를 유지해도 좋을 것이라 판단된다.
3. 분석
1) 구조
main : http 요청을 받아 처리하는 부분 빼고는 특기할 만 한 사항이 없다.
요청을 받을 때마다 newHandler를 생성해 http 요청을 적절히 처리한다.
http handler: 요청에 따라 적절한 값을 반환한다. metric을 요청할 시 요청한 metric을 반환한다.
collector : 실제로 매트릭을 수집한다.
https://github.com/prometheus/client_golang/blob/main/prometheus/collector.go

2) 개괄
Node exporter 의 코드 전문은 아래에서 확인할 수 있다.
https://github.com/prometheus/node_exporter
오픈소스 프로젝트이므로 예고 없이 코드의 내용이 변경될 수 있다.
코드는 Go로 작성되었다.
3) 실제 코드
(1) main 함수
a. main 함수
main 함수는 default 값들을 처리하는 부분과 http 요청을 처리하는 부분 2개로 나뉘어져 있다.
주기적으로 metric을 수집한 뒤 자신에게 저장할거라 생각했으나, 그런 코드는 존재하지 않는다.
http 요청을 받을 때에만 metric을 수집해 반환한다.

b. defaults..
metric path의 기본값은 /metric
이다 .
metricsPath = kingpin.Flag(
"web.telemetry-path",
"Path under which to expose metrics.",
).Default("/metrics").String()
(2) http handler
main 함수에서 http 요청을 처리하는 부분이다.
http.Handle(*metricsPath, newHandler(!*disableExporterMetrics, *maxRequests, logger))
a. Handler
http 요청을 처리한다. /metric
(기본값 기준) 에 요청을 받을 경우 metric을 수집해 반환한다.

b. handler - type / struct
Handler 타입이다. handler에서는는 아래의 내용들을 다룬다.
1) node exporter 자체의 매트릭을 수집할지
2) 수집할 / 수집하지 않을 매트릭들의 목록
3) request 최대 제한
// handler wraps an unfiltered http.Handler but uses a filtered handler,
// created on the fly, if filtering is requested. Create instances with
// newHandler.
type handler struct {
unfilteredHandler http.Handler
// enabledCollectors list is used for logging and filtering
enabledCollectors []string
// exporterMetricsRegistry is a separate registry for the metrics about
// the exporter itself.
exporterMetricsRegistry *prometheus.Registry
includeExporterMetrics bool
maxRequests int
logger *slog.Logger
}
c. handler 생성
(1-2) http handler의 main 함수에서 http 요청을 처리하는 부분이다.
http.Handle(*metricsPath, newHandler(!*disableExporterMetrics, *maxRequests, logger))
http 요청을 받을 때마다 newHandler
메서드를 호출, handler 가 생성된다. handler는 innerHandler
의 메타 정보를 다룬다. exporter 자체의 매트릭을 수집할지, metric 수집 실패 전까지 몇 번 metric 수집을 시도할지 등의 정보를 다룬다.
func newHandler(includeExporterMetrics bool, maxRequests int, logger *slog.Logger) *handler {
h := &handler{
exporterMetricsRegistry: prometheus.NewRegistry(),
includeExporterMetrics: includeExporterMetrics,
maxRequests: maxRequests,
logger: logger,
}
if h.includeExporterMetrics {
h.exporterMetricsRegistry.MustRegister(
promcollectors.NewProcessCollector(promcollectors.ProcessCollectorOpts{}),
promcollectors.NewGoCollector(),
)
}
if innerHandler, err := h.innerHandler(); err != nil {
panic(fmt.Sprintf("Couldn't create metrics handler: %s", err))
} else {
h.unfilteredHandler = innerHandler
}
return h
}
innerHandler가 생성될 때, filter 조건에 맞게 NodeCollector 를 생성한다. 이 nodeCollector 들이 CPU사용량 등의 매트릭을 실제로 수집한다.
// innerHandler is used to create both the one unfiltered http.Handler to be
// wrapped by the outer handler and also the filtered handlers created on the
// fly. The former is accomplished by calling innerHandler without any arguments
// (in which case it will log all the collectors enabled via command-line
// flags).
func (h *handler) innerHandler(filters ...string) (http.Handler, error) {
nc, err := collector.NewNodeCollector(h.logger, filters...)
if err != nil {
return nil, fmt.Errorf("couldn't create collector: %s", err)
}
// Only log the creation of an unfiltered handler, which should happen
// only once upon startup.
if len(filters) == 0 {
h.logger.Info("Enabled collectors")
for n := range nc.Collectors {
h.enabledCollectors = append(h.enabledCollectors, n)
}
sort.Strings(h.enabledCollectors)
for _, c := range h.enabledCollectors {
h.logger.Info(c)
}
}
r := prometheus.NewRegistry()
r.MustRegister(versioncollector.NewCollector("node_exporter"))
if err := r.Register(nc); err != nil {
return nil, fmt.Errorf("couldn't register node collector: %s", err)
}
var handler http.Handler
if h.includeExporterMetrics {
handler = promhttp.HandlerFor(
prometheus.Gatherers{h.exporterMetricsRegistry, r},
promhttp.HandlerOpts{
ErrorLog: slog.NewLogLogger(h.logger.Handler(), slog.LevelError),
ErrorHandling: promhttp.ContinueOnError,
MaxRequestsInFlight: h.maxRequests,
Registry: h.exporterMetricsRegistry,
},
)
// Note that we have to use h.exporterMetricsRegistry here to
// use the same promhttp metrics for all expositions.
handler = promhttp.InstrumentMetricHandler(
h.exporterMetricsRegistry, handler,
)
} else {
handler = promhttp.HandlerFor(
r,
promhttp.HandlerOpts{
ErrorLog: slog.NewLogLogger(h.logger.Handler(), slog.LevelError),
ErrorHandling: promhttp.ContinueOnError,
MaxRequestsInFlight: h.maxRequests,
},
)
}
return handler, nil
}
실제로 매트릭을 가져오는 부분이다. promhttp.HandlerFor
부분이 실제로 collector
의 gather()
메서드를 호출한다.
var handler http.Handler
if h.includeExporterMetrics {
handler = promhttp.HandlerFor(
prometheus.Gatherers{h.exporterMetricsRegistry, r},
promhttp.HandlerOpts{
ErrorLog: slog.NewLogLogger(h.logger.Handler(), slog.LevelError),
ErrorHandling: promhttp.ContinueOnError,
MaxRequestsInFlight: h.maxRequests,
Registry: h.exporterMetricsRegistry,
},
)
// Note that we have to use h.exporterMetricsRegistry here to
// use the same promhttp metrics for all expositions.
handler = promhttp.InstrumentMetricHandler(
h.exporterMetricsRegistry, handler,
)
} else {
handler = promhttp.HandlerFor(
r,
promhttp.HandlerOpts{
ErrorLog: slog.NewLogLogger(h.logger.Handler(), slog.LevelError),
ErrorHandling: promhttp.ContinueOnError,
MaxRequestsInFlight: h.maxRequests,
},
)
}
return handler, nil
handlerFor가 각각의 Gatherer
로부터 .gather()
메서드를 호출한다.
https://github.com/prometheus/client_golang/blob/main/prometheus/promhttp/http.go#L88C25-L165C33
gatherer 메서드가 실제로 매트릭을 수집하여 반환한다.
https://github.com/prometheus/client_golang/blob/v1.20.5/prometheus/registry.go#L140C1-L161C2
각각의 gatherer 로부터 gather()
를 호출한 뒤 metricFamily list를 생성해 append 한다.
// Gather implements TransactionalGatherer interface.
func (r *MultiTRegistry) Gather() (mfs []*dto.MetricFamily, done func(), err error) {
errs := MultiError{}
dFns := make([]func(), 0, len(r.tGatherers))
// TODO(bwplotka): Implement concurrency for those?
for _, g := range r.tGatherers {
// TODO(bwplotka): Check for duplicates?
m, d, err := g.Gather()
errs.Append(err)
mfs = append(mfs, m...)
dFns = append(dFns, d)
}
이 결과값들이 http.handler를 거쳐 prometheus로 반환된다.
기타
Prometheus dcos의 Gatherer 항목
https://pkg.go.dev/github.com/prometheus/client_golang/prometheus#Gatherer
'TIL > Monitoring(k8s, grafana)' 카테고리의 다른 글
[K8s, Grafana] Loki 데이터량 확인 및 유지기간 설정 방법 확인(2.6.1) (0) | 2024.12.10 |
---|---|
[RKE2] Too many open files 로 발생하는 crashLoopBackOff 에러 해결 (0) | 2024.12.01 |
[모니터링, Loki, 로그] Promtail 활용한 syslog 수집 (0) | 2024.11.26 |
[모니터링, k8s, Grafana, Prometheus] Prometheus 쿼리에 '$__rate_interval' 이 포함될 때 return 값이 없는 현상 (3) | 2024.10.31 |
워터컷 기록 및 정보 모음 (0) | 2024.10.31 |