Go로 작성된 HTTP 서버 계측하기

Go로 작성된 HTTP 서버 계측하기 (Instrumenting HTTP server written in Go)

Go로 작성된 간단한 HTTP 서버를 프로메테우스 클라이언트 라이브러리로 계측하는 기초 튜토리얼이에요. 프로메테우스의 공식 Go 클라이언트 라이브러리인 client_golang을 사용해 counter 메트릭을 만들고, 서버가 처리한 요청 수를 세고, /metrics 엔드포인트로 노출한 뒤 프로메테우스가 스크레이프하도록 설정하는 전체 흐름을 따라 해요.

처음부터 끝까지 완성된 코드 예제가 들어 있어서, Go 모르는 사람도 복사해서 실행해 보며 감을 잡을 수 있어요. 계측(instrumentation)이 실제로 어떻게 이뤄지는지 눈으로 확인하고 싶다면 딱 좋은 문서예요.

출처: 문서

본문

이 튜토리얼에서는 간단한 Go HTTP 서버를 만들고, 서버가 처리한 총 요청 수를 세기 위해 counter 메트릭을 추가해 계측할 거예요.

여기에 /ping 엔드포인트가 있고 pong을 응답으로 반환하는 간단한 HTTP 서버가 있어요.

package main

import (
   "fmt"
   "net/http"
)

func ping(w http.ResponseWriter, req *http.Request) {
   fmt.Fprintf(w,"pong")
}

func main() {
   http.HandleFunc("/ping", ping)

   http.ListenAndServe(":8090", nil)
}

컴파일하고 서버를 실행하세요.

go build server.go
./server

이제 브라우저에서 http://localhost:8090/ping을 열면 pong이 보일 거예요.

이제 ping 엔드포인트로 보내진 요청 수를 계측하는 메트릭을 서버에 추가해 봐요. 요청 수는 줄어들지 않고 증가만 하므로 counter 메트릭 타입이 이에 적합해요.

프로메테우스 counter 만들기

type metrics struct {
	pingCounter prometheus.Counter
}

func newMetrics(reg prometheus.Registerer) *metrics {
	m := &metrics{
		pingCounter: promauto.With(reg).NewCounter(
			prometheus.CounterOpts {
				Name: "ping_request_count",
				Help: "No of requests handled by Ping handler",
			}),
	}
	return m
}

다음으로 metrics.pingCounter.Inc()로 counter의 count를 증가시키도록 ping Handler를 업데이트해요.

func ping(m *metrics) func(w http.ResponseWriter, req *http.Request) {
	return func(w http.ResponseWriter, req *http.Request) {
		m.pingCounter.Inc()
		fmt.Fprintf(w, "pong")
	}
}

그런 다음 메트릭(이 경우 counter 하나)을 프로메테우스 레지스트리에 등록하고 메트릭을 노출해요.

func main() {
	reg := prometheus.NewRegistry()
	m := newMetrics(reg)

	http.HandleFunc("/ping", ping(m))
	http.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{}))
	http.ListenAndServe(":8090", nil)
}

prometheus.MustRegister 함수는 pingCounter를 기본 레지스트리에 등록해요. 메트릭을 노출하기 위해 Go 프로메테우스 클라이언트 라이브러리는 promhttp 패키지를 제공해요. promhttp.Handler()는 기본 레지스트리에 등록된 메트릭을 노출하는 http.Handler를 제공해요.

예제 코드는 이제:

package main

import (
	"fmt"
	"net/http"

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

type metrics struct {
	pingCounter prometheus.Counter
}

func newMetrics(reg prometheus.Registerer) *metrics {
	m := &metrics{
		pingCounter: promauto.With(reg).NewCounter(
			prometheus.CounterOpts{
				Name: "ping_request_count",
				Help: "No of request handled by Ping handler",
			}),
	}
	return m
}

func ping(m *metrics) func(w http.ResponseWriter, req *http.Request) {
	return func(w http.ResponseWriter, req *http.Request) {
		m.pingCounter.Inc()
		fmt.Fprintf(w, "pong")
	}
}

func main() {
	reg := prometheus.NewRegistry()
	m := newMetrics(reg)

	http.HandleFunc("/ping", ping(m))
	http.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{}))
	http.ListenAndServe(":8090", nil)
}

예제를 실행하세요.

go mod init prom_example
go mod tidy
go run server.go

이제 localhost:8090/ping 엔드포인트를 몇 번 호출하고, 메트릭을 보기 위해 localhost:8090/metrics에 요청을 보내세요.

여기서 ping_request_count/ping 엔드포인트가 3번 호출됐음을 보여줘요.

기본 레지스트리는 Go 런타임 메트릭용 collector와 함께 제공되므로, go_threads, go_goroutines 같은 다른 메트릭도 보여요.

우리는 첫 번째 메트릭 exporter를 만들었어요. 이제 프로메테우스 구성에서 우리 서버의 메트릭을 스크레이프하도록 업데이트해 봐요.

global:
  scrape_interval: 15s

scrape_configs:
  - job_name: prometheus
    static_configs:
      - targets: ["localhost:9090"]
  - job_name: simple_server
    static_configs:
      - targets: ["localhost:8090"]
prometheus --config.file=prometheus.yml

더 알아보기 (Learn more)