Go 언어에서의 Prometheus 메트릭 수집 🚀📊

콘텐츠 대표 이미지 - Go 언어에서의 Prometheus 메트릭 수집 🚀📊

 

 

안녕하세요, 여러분! 오늘은 Go 언어에서 Prometheus 메트릭을 수집하는 방법에 대해 알아볼 거예요. 이거 완전 꿀팁이에요! 🍯 프로그래밍 세계에서 성능 모니터링은 정말 중요하죠. 그래서 우리는 Prometheus라는 강력한 도구를 사용할 거예요. 이 글을 읽고 나면 여러분도 Go 언어로 Prometheus 메트릭을 수집하는 프로 개발자가 될 수 있을 거예요! 😎

참고: 이 글은 '재능넷'의 '지식인의 숲' 메뉴에 등록될 예정이에요. 재능넷은 다양한 재능을 거래하는 플랫폼인데, 여러분의 Go 언어와 Prometheus skills도 충분히 재능이 될 수 있답니다! 👨‍💻👩‍💻

1. Prometheus란 무엇인가요? 🤔

Prometheus는 오픈소스 모니터링 및 알림 툴킷이에요. 2012년에 SoundCloud에서 시작됐고, 지금은 Cloud Native Computing Foundation의 두 번째 졸업 프로젝트랍니다. 대박이죠? 👏

Prometheus의 주요 특징들을 살펴볼까요?

  • 다차원 데이터 모델 (시계열 데이터는 메트릭 이름과 키/값 쌍으로 식별)
  • PromQL이라는 유연한 쿼리 언어
  • HTTP pull 모델을 통한 시계열 수집
  • 푸시 게이트웨이를 통한 short-lived 작업 지원
  • 서비스 디스커버리 또는 정적 구성을 통한 대상 발견
  • 다양한 그래프 및 대시보드 모드

와! 이렇게 보니까 Prometheus 완전 만능이네요! 😮

2. Go 언어와 Prometheus의 만남 💑

Go 언어와 Prometheus는 찰떡궁합이에요! Go는 성능이 뛰어나고 동시성 처리가 쉬워서, 대규모 시스템 모니터링에 딱이거든요. Prometheus는 Go로 작성되었기 때문에, Go 애플리케이션과의 통합이 아주 smooth해요. 마치 재능넷에서 딱 맞는 재능을 찾은 것처럼요! 😉

Go에서 Prometheus를 사용하려면 'github.com/prometheus/client_golang' 패키지를 사용해야 해요. 이 패키지는 Prometheus 클라이언트 라이브러리의 Go 버전이에요.


import (
    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promauto"
    "github.com/prometheus/client_golang/prometheus/promhttp"
)

이렇게 임포트하면 준비 끝! 이제 메트릭을 수집할 준비가 됐어요. 👍

3. Prometheus 메트릭 타입 알아보기 📊

Prometheus는 네 가지 핵심 메트릭 타입을 제공해요. 각각의 특징을 알아볼까요?

1. Counter (카운터) 🔢

누적되는 값을 표현할 때 사용해요. 예를 들면 요청 수, 완료된 작업 수 등이 있죠. 값은 항상 증가하거나 리셋될 때 0으로 초기화돼요.

2. Gauge (게이지) 🌡️

임의로 오르내릴 수 있는 값을 나타내요. 메모리 사용량, 동시 요청 수 같은 걸 측정할 때 쓰죠.

3. Histogram (히스토그램) 📊

값의 분포를 측정해요. 요청 지속 시간이나 응답 크기 같은 것들을 측정할 때 유용하죠.

4. Summary (요약) 📋

히스토그램과 비슷하지만, 백분위수를 직접 계산해요. 클라이언트 측에서 더 정확한 백분위수를 얻을 수 있어요.

이렇게 네 가지 타입이 있어요. 각각의 상황에 맞게 쓰면 되겠죠? 👌

4. Go에서 Prometheus 메트릭 구현하기 💻

자, 이제 실제로 Go 코드에서 Prometheus 메트릭을 어떻게 구현하는지 알아볼까요? 예제와 함께 설명해드릴게요!

4.1 Counter 구현하기

먼저 Counter를 구현해볼게요. 웹 서버의 요청 수를 세는 카운터를 만들어봐요.


var (
    httpRequests = promauto.NewCounter(prometheus.CounterOpts{
        Name: "http_requests_total",
        Help: "총 HTTP 요청 수",
    })
)

func handleRequest(w http.ResponseWriter, r *http.Request) {
    httpRequests.Inc()
    // 요청 처리 로직
}

여기서 'httpRequests.Inc()'를 호출할 때마다 카운터가 1씩 증가해요. 간단하죠? 😃

4.2 Gauge 구현하기

이번엔 Gauge를 구현해볼게요. 현재 활성 고루틴 수를 측정하는 게이지를 만들어봐요.


var (
    goroutines = promauto.NewGauge(prometheus.GaugeOpts{
        Name: "goroutines_count",
        Help: "현재 실행 중인 고루틴 수",
    })
)

func updateGoroutinesCount() {
    for {
        goroutines.Set(float64(runtime.NumGoroutine()))
        time.Sleep(time.Second)
    }
}

이 코드는 1초마다 현재 고루틴 수를 업데이트해요. 실시간으로 변하는 값을 측정하기에 딱이죠! 👍

4.3 Histogram 구현하기

이제 Histogram을 구현해볼 차례예요. HTTP 요청 처리 시간을 측정하는 히스토그램을 만들어볼게요.


var (
    httpDuration = promauto.NewHistogram(prometheus.HistogramOpts{
        Name:    "http_request_duration_seconds",
        Help:    "HTTP 요청 처리 시간 (초)",
        Buckets: prometheus.DefBuckets,
    })
)

func handleRequestWithTimer(w http.ResponseWriter, r *http.Request) {
    start := time.Now()
    // 요청 처리 로직
    duration := time.Since(start).Seconds()
    httpDuration.Observe(duration)
}

이 코드는 각 HTTP 요청의 처리 시간을 측정하고, 그 분포를 기록해요. 성능 분석에 아주 유용하답니다! 📊

4.4 Summary 구현하기

마지막으로 Summary를 구현해볼게요. 데이터베이스 쿼리 실행 시간을 측정하는 summary를 만들어봐요.


var (
    dbQueryDuration = promauto.NewSummary(prometheus.SummaryOpts{
        Name:       "db_query_duration_seconds",
        Help:       "데이터베이스 쿼리 실행 시간 (초)",
        Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
    })
)

func executeDBQuery() {
    start := time.Now()
    // DB 쿼리 실행 로직
    duration := time.Since(start).Seconds()
    dbQueryDuration.Observe(duration)
}

이 코드는 DB 쿼리 실행 시간의 중앙값(50%), 90%, 99% 백분위수를 계산해요. DB 성능 최적화에 딱이죠! 🎯

5. Prometheus 메트릭 노출하기 🌐

자, 이제 메트릭을 수집했으니 이를 Prometheus 서버에 노출시켜야 해요. Go에서는 이를 위한 간단한 HTTP 핸들러를 제공해요.


import (
    "net/http"
    "github.com/prometheus/client_golang/prometheus/promhttp"
)

func main() {
    http.Handle("/metrics", promhttp.Handler())
    http.ListenAndServe(":8080", nil)
}

이 코드를 실행하면, http://localhost:8080/metrics 엔드포인트에서 모든 등록된 메트릭을 볼 수 있어요. Prometheus 서버는 이 엔드포인트를 주기적으로 스크랩하여 데이터를 수집하죠. 완전 쉽죠? 😎

6. 커스텀 메트릭 만들기 🛠️

Prometheus 클라이언트 라이브러리가 제공하는 기본 메트릭 외에도, 여러분만의 커스텀 메트릭을 만들 수 있어요. 이게 바로 Prometheus의 강력한 점이죠!

예를 들어, 우리 서비스의 사용자 수를 추적하는 커스텀 게이지를 만들어볼까요?


var (
    activeUsers = promauto.NewGauge(prometheus.GaugeOpts{
        Name: "active_users",
        Help: "현재 활성 사용자 수",
    })
)

func updateActiveUsers(count int) {
    activeUsers.Set(float64(count))
}

// 사용 예:
updateActiveUsers(100)  // 활성 사용자가 100명일 때
updateActiveUsers(150)  // 활성 사용자가 150명으로 증가했을 때

이렇게 하면 우리 서비스의 실시간 사용자 수를 추적할 수 있어요. 재능넷 같은 플랫폼에서 이런 메트릭은 서비스의 인기도를 실시간으로 파악하는 데 유용할 거예요! 📈

7. 레이블 사용하기 🏷️

Prometheus의 또 다른 강력한 기능은 레이블이에요. 레이블을 사용하면 같은 메트릭에 대해 다양한 차원의 데이터를 수집할 수 있죠.

예를 들어, HTTP 요청을 경로별로 구분해서 카운트하고 싶다면 이렇게 할 수 있어요:


var (
    httpRequestsTotal = promauto.NewCounterVec(
        prometheus.CounterOpts{
            Name: "http_requests_total",
            Help: "경로별 총 HTTP 요청 수",
        },
        []string{"path"},
    )
)

func handleRequest(w http.ResponseWriter, r *http.Request) {
    httpRequestsTotal.WithLabelValues(r.URL.Path).Inc()
    // 요청 처리 로직
}

이렇게 하면 각 경로별로 요청 수를 따로 추적할 수 있어요. "/home", "/profile", "/settings" 등 각 페이지의 인기도를 한눈에 파악할 수 있겠죠? 완전 꿀팁이에요! 🍯

8. 메트릭 그룹화하기 👥

프로젝트가 커지면 메트릭의 수도 늘어나겠죠? 이럴 때 메트릭을 그룹화하면 관리하기 훨씬 편해져요.

예를 들어, 데이터베이스 관련 메트릭을 하나의 구조체로 그룹화해볼까요?


type DBMetrics struct {
    QueryDuration prometheus.Summary
    ConnectionCount prometheus.Gauge
    ErrorRate prometheus.Counter
}

func NewDBMetrics() *DBMetrics {
    return &DBMetrics{
        QueryDuration: promauto.NewSummary(prometheus.SummaryOpts{
            Name: "db_query_duration_seconds",
            Help: "데이터베이스 쿼리 실행 시간 (초)",
        }),
        ConnectionCount: promauto.NewGauge(prometheus.GaugeOpts{
            Name: "db_connection_count",
            Help: "현재 데이터베이스 연결 수",
        }),
        ErrorRate: promauto.NewCounter(prometheus.CounterOpts{
            Name: "db_error_total",
            Help: "데이터베이스 오류 총 횟수",
        }),
    }
}

// 사용 예:
dbMetrics := NewDBMetrics()
dbMetrics.QueryDuration.Observe(0.5)
dbMetrics.ConnectionCount.Set(10)
dbMetrics.ErrorRate.Inc()

이렇게 그룹화하면 코드도 깔끔해지고, 관리도 쉬워져요. 완전 개이득! 👍

9. 고급 기능: 커스텀 콜렉터 만들기 🚀

때로는 기본 메트릭 타입으로는 부족할 때가 있어요. 이럴 때 커스텀 콜렉터를 만들어 사용할 수 있답니다.

예를 들어, 시스템의 현재 메모리 사용량을 수집하는 커스텀 콜렉터를 만들어볼까요?


type memoryCollector struct {
    memoryUsage *prometheus.Desc
}

func NewMemoryCollector() *memoryCollector {
    return &memoryCollector{
        memoryUsage: prometheus.NewDesc(
            "system_memory_usage_bytes",
            "현재 시스템 메모리 사용량 (바이트)",
            nil, nil,
        ),
    }
}

func (collector *memoryCollector) Describe(ch chan<- *prometheus.Desc) {
    ch <- collector.memoryUsage
}

func (collector *memoryCollector) Collect(ch chan<- prometheus.Metric) {
    var m runtime.MemStats
    runtime.ReadMemStats(&m)
    ch <- prometheus.MustNewConstMetric(
        collector.memoryUsage,
        prometheus.GaugeValue,
        float64(m.Alloc),
    )
}

// 사용 예:
collector := NewMemoryCollector()
prometheus.MustRegister(collector)

이 코드는 Go 런타임의 메모리 통계를 읽어와 Prometheus 메트릭으로 변환해요. 이렇게 하면 시스템의 메모리 사용량을 실시간으로 모니터링할 수 있죠. 완전 프로 개발자 느낌 나지 않나요? 😎

10. 메트릭 테스트하기 🧪

메트릭을 구현했다면, 당연히 테스트도 해야겠죠? Go에서는 Prometheus 메트릭을 쉽게 테스트할 수 있는 방법을 제공해요.

카운터 메트릭을 테스트하는 예제를 볼까요?


func TestHTTPRequestsCounter(t *testing.T) {
    // 테스트를 위한 레지스트리 생성
    registry := prometheus.NewRegistry()
    
    // 테스트할 카운터 생성
    counter := promauto.With(registry).NewCounter(prometheus.CounterOpts{
        Name: "http_requests_total",
        Help: "총 HTTP 요청 수",
    })

    // 카운터 증가
    counter.Inc()
    counter.Inc()

    // 메트릭 수집
    metricFamilies, err := registry.Gather()
    if err != nil {
        t.Fatalf("메트릭 수집 실패: %v", err)
    }

    // 결과 확인
    if len(metricFamilies) != 1 {
        t.Fatalf("예상치 못한 메트릭 수: got %v, want 1", len(metricFamilies))
    }

    mf := metricFamilies[0]
    if len(mf.Metric) != 1 {
        t.Fatalf("예상치 못한 샘플 수: got %v, want 1", len(mf.Metric))
    }

    if got := *mf.Metric[0].Counter.Value; got != 2 {
        t.Errorf("예상치 못한 카운터 값: got %v, want 2", got)
    }
}

이 테스트는 카운터가 제대로 증가하는지 확인해요. 테스트 코드를 작성하면 메트릭이 의도한 대로 동작하는지 확실히 알 수 있죠. 안정성 UP! 👆

11. 성능 고려사항 🏎️

Prometheus 메트릭을 사용할 때는 성능도 고려해야 해요. 메트릭 수집이 애플리케이션의 성능에 영향을 주면 안 되니까요!

몇 가지 팁을 드릴게요:

  • 메트릭은 필요한 것만 수집하세요. 너무 많은 메트릭을 수집하면 오버헤드가 커질 수 있어요.
  • 레이블은 신중하게 사용하세요. 레이블 조합이 너무 많아지면 메모리 사용량이 급증할 수 있어요.
  • 고비용 연산은 피하세요. 메트릭 수집 시 복잡한 계산은 피하는 게 좋아요.
  • 적절한 스크래핑 간격을 설정하세요. 너무 자주 스크래핑하면 서버에 부담이 될 수 있어요.

이런 점들을 고려하면 Prometheus 메트릭을 효율적으로 사용할 수 있어요. 성능과 모니터링, 둘 다 잡는 거죠! 👍

12. 실전 예제: 웹 서버 모니터링 🌐

자, 이제 우리가 배운 내용을 종합해서 실전 예제를 만들어볼까요? Go로 간단한 웹 서버를 만들고, Prometheus로 모니터링하는 예제를 만들어볼게요.


package main

import (
    "net/http"
    "time"

    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promauto"
    "github.com/prometheus/client_golang/prometheus/promhttp"
)

var (
    httpRequestsTotal = promauto.NewCounterVec(
        prometheus.CounterOpts{
            Name: "http_requests_total",
            Help: "총 HTTP 요청 수",
        },
        []string{"method", "endpoint"},
    )

    httpRequestDuration = promauto.NewHistogramVec(
        prometheus.HistogramOpts{
            Name:    "http_request_duration_seconds",
            Help:    "HTTP 요청 처리 시간",
            Buckets: prometheus.DefBuckets,
        },
        []string{"method", "endpoint"},
    )

    activeConnections = promauto.NewGauge(prometheus.GaugeOpts{
        Name: "http_active_connections",
        Help: "현재 활성 연결 수",
    })
)

func instrumentHandler(path string, handler http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        activeConnections.Inc()
        defer activeConnections.Dec()

        handler(w, r)

        duration := time.Since(start).Seconds()
        httpRequestsTotal.WithLabelValues(r.Method, path).Inc()
        httpRequestDuration.WithLabelValues(r.Method, path).Observe(duration)
    }
}

func helloHandler(w http.ResponseWriter, r *http.Request) {
    time.Sleep(time.Duration(100+time.Now().UnixNano()%900) * time.Millisecond) // 랜덤 지연
    w.Write([]byte("Hello, Prometheus!"))
}

func main() {
    http.HandleFunc("/hello", instrumentHandler("/hello", helloHandler))
    http.Handle("/metrics", promhttp.Handler())

    println("서버가 :8080 포트에서 실행 중입니다.")
    http.ListenAndServe(":8080", nil)
}

이 예제는 다음과 같은 기능을 합니다:

  • "/hello" 엔드포인트에 대한 요청을 처리합니다.
  • 각 요청에 대해 총 요청 수, 요청 처리 시간, 현재 활성 연결 수를 측정합니다.
  • "/metrics" 엔드포인트를 통해 Prometheus 메트릭을 노출합니다.

이 서버를 실행하고 Prometheus를 설정하면, 다음과 같은 정보를 모니터링할 수 있어요:

  • 초당 요청 수
  • 요청 처리 시간의 분포
  • 현재 활성 연결 수

이런 정보들을 바탕으로 서버의 성능을 실시간으로 모니터링하고, 필요한 경우 스케일링이나 최적화를 할 수 있겠죠? 완전 프로페셔널한 모니터링 시스템이 된 거예요! 👏

13. Prometheus와 Grafana 연동하기 📊

Prometheus로 수집한 메트릭을 시각화하려면 Grafana를 사용하면 좋아요. Grafana는 데이터 시각화 도구로, Prometheus와 찰떡궁합이랍니다!

Grafana와 Prometheus를 연동하는 방법은 다음과 같아요: