使用 Prometheus 監控 Golang 應用程式

語言: CN / TW / HK

C T O

   

 

Go Rust Python Istio containerd CoreDNS Envoy etcd Fluentd Harbor Helm Jaeger Kubernetes Open Policy Agent Prometheus Rook TiKV TUF Vitess Arg o Buildpacks CloudEvents CNI Contour Cortex CRI-O Falco Flux gRPC KubeEdge Linkerd NATS Notary OpenTracing Operator Framework SPIFFE SPIRE     Thanos

使用 Prometheus 監控 Golang 應用程式

為了確保我們的應用程式的質量,需要執行某種質量監控和質量檢查。這些質量檢查通常會比較從應用程式捕獲的給定指標,例如吞吐量或錯誤率,以及一些定義的值,例如錯誤率 < 0.1%

Prometheus 是一個開源監控和警報工具,可幫助我們以簡單可靠的方式從應用程式中收集和公開這些指標。

在本文中,您將瞭解 Prometheus 的基礎知識,包括什麼是指標、不同型別的指標以及何時使用它們。之後,您將公開 Golang 應用程式的指標並使用 Grafana 將它們視覺化。

指標和標籤

簡單地說,指標衡量一個特定的值,例如隨著時間的推移應用程式的響應時間。一旦使用某種檢測系統從應用程式中公開指標, Prometheus 就會將它們儲存在時間序列資料庫中,並使用查詢使它們迅速可用。

# Total number of HTTP request
http_requests_total

# Response status of HTTP request
response_status

# Duration of HTTP requests in seconds
http_response_time_seconds

如果您對特定指標有多個服務,則可以新增標籤以指定該指標來自哪個服務。例如,您可以將服務標籤新增到 http_requests_total 指標以區分每個服務的請求。另一個有用的指標是不同響應狀態的 URL

# Total number of HTTP request
http_requests_total{service="builder"}

# Response status of HTTP request
response_status{path="/"}
response_status{path="/articles"}

使用正確的標籤來增強指標將使查詢它們變得容易,尤其是當您有許多不同的服務時。

指標型別

Prometheus 提供了四種不同的指標型別,每種型別都有其優點和缺點,這使得它們適用於不同的用例。在本文的這一部分,我們將仔細研究這四個。

Counters:

Counters 是一種簡單的度量型別,只能在重新啟動時遞增或重置為零。它通常用於計算原始資料,例如對服務的請求總數或完成的任務數。因此,大多數 Counters 都使用 _total 字尾命名,例如 http_requests_total

# Total number of HTTP request
http_requests_total

# Total number of completed jobs
jobs_completed_total

這些 Counters 的絕對值通常無關緊要,並且不會為您提供有關應用程式狀態的太多資訊。真實資訊可以通過它們隨時間的演變來收集,這可以使用 rate() 函式獲得。

Gauges:

Gauges 也表示單個數值,但與 Counters 不同,該值可以上升也可以下降。因此,儀表通常用於測量值,例如溫度、溼度或當前記憶體使用情況。

Counters 不同, Gauges 的當前值是有意義的,可以直接用於圖表和測試。

Histograms:

Histograms 用於測量屬於特定預定義儲存桶的值觀察的頻率。這意味著他們將提供有關響應時間和訊號異常值等指標分佈的資訊。

預設情況下, Prometheus 提供以下儲存桶: .005, .01, .025, .05, .075, .1, .25, .5, .75, 1, 2.5, 5, 7.5, 10。 這些儲存桶不適合每次測量,因此可以輕鬆更改。

Summaries

Summaries Histograms 非常相似,因為它們都暴露了給定資料集的分佈。一個主要區別是 Histograms 估計分位數在 Prometheus 伺服器上,而摘要是在客戶端計算的。

對於某些預定義的分位數,摘要更準確,但由於客戶端計算,可能會耗費更多的資源。這就是為什麼建議在大多數用例中使用 Histograms

設定我們的 Go 專案

在我們可以使用 Prometheus 之前,我們首先需要構建一個簡單的應用程式來公開一些基本指標。為此,我們將構建一個簡單的 Golang HTTP 伺服器,在訪問 localhost:9000 時提供靜態 HTML CSS 檔案。

讓我們從建立專案所需的檔案開始。這可以使用以下命令完成:

mkdir static
touch main.go Dockerfile static/index.html

HTTP 伺服器是使用 Mux 編寫的,它將為包含您在上面建立的 HTML CSS 檔案的靜態目錄提供服務。

package main

import (
 "fmt"
 "github.com/gorilla/mux"
 "log"
 "net/http"
)


func main() {
 router := mux.NewRouter()

 // Serving static files
 router.PathPrefix("/").Handler(http.FileServer(http.Dir("./static/")))

 fmt.Println("Serving requests on port 9000")
 err := http.ListenAndServe(":9000", router)
 log.Fatal(err)
}

HTML 檔案將僅包含帶有 “Hello World!” H1 標籤。作為其內容並匯入 CSS 檔案。

<html>
<head>
    <title>Hello server</title>
    <link rel="stylesheet" href="style.css"/>
</head>
<body>
<div>
  <h1>Hello World!</h1>
</div>
</body>
</html>

嚮應用程式新增指標

現在應用程式的基本功能已經完成,我們可以開始公開 Prometheus 稍後將抓取的指標。 Golang Prometheus 官方庫會自動公開一些內建指標,只需要匯入並新增到 HTTP 伺服器即可。

package main

import (
 "fmt"
 "github.com/gorilla/mux"
 "log"
 "net/http"
    "github.com/prometheus/client_golang/prometheus/promhttp"
)


func main() {
 router := mux.NewRouter()

 // Serving static files
 router.PathPrefix("/").Handler(http.FileServer(http.Dir("./static/")))
    
    // Prometheus endpoint
 router.Path("/prometheus").Handler(promhttp.Handler())

 fmt.Println("Serving requests on port 9000")
 err := http.ListenAndServe(":9000", router)
 log.Fatal(err)
}

現在我們已經添加了 Prometheus 庫並在 /prometheus 上公開了處理程式,我們可以通過啟動應用程式並導航到 localhost:9000/prometheus 來檢視指標。輸出應與此類似:

# HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles.
# TYPE go_gc_duration_seconds summary
go_gc_duration_seconds{quantile="0"} 2.07e-05
go_gc_duration_seconds{quantile="0.25"} 7.89e-05
go_gc_duration_seconds{quantile="0.5"} 0.000137
go_gc_duration_seconds{quantile="0.75"} 0.0001781
go_gc_duration_seconds{quantile="1"} 0.0002197
go_gc_duration_seconds_sum 0.0071928
go_gc_duration_seconds_count 56
# HELP go_goroutines Number of goroutines that currently exist.
# TYPE go_goroutines gauge
go_goroutines 8
# HELP go_info Information about the Go environment.
# TYPE go_info gauge
go_info{version="go1.15"} 1
# HELP go_memstats_alloc_bytes Number of bytes allocated and still in use.
# TYPE go_memstats_alloc_bytes gauge
go_memstats_alloc_bytes 4.266136e+06
# HELP go_memstats_alloc_bytes_total Total number of bytes allocated, even if freed.
# TYPE go_memstats_alloc_bytes_total counter
go_memstats_alloc_bytes_total 1.17390144e+08
# HELP go_memstats_buck_hash_sys_bytes Number of bytes used by the profiling bucket hash table.
# TYPE go_memstats_buck_hash_sys_bytes gauge
go_memstats_buck_hash_sys_bytes 1.456289e+06
# HELP go_memstats_frees_total Total number of frees.
# TYPE go_memstats_frees_total counter
go_memstats_frees_total 435596
# HELP go_memstats_gc_cpu_fraction The fraction of this program's available CPU time used by the GC since the program started.
# TYPE go_memstats_gc_cpu_fraction gauge
go_memstats_gc_cpu_fraction 1.5705717722141224e-06
# HELP go_memstats_gc_sys_bytes Number of bytes used for garbage collection system metadata.
# TYPE go_memstats_gc_sys_bytes gauge
go_memstats_gc_sys_bytes 4.903096e+06
# HELP go_memstats_heap_alloc_bytes Number of heap bytes allocated and still in use.
# TYPE go_memstats_heap_alloc_bytes gauge
go_memstats_heap_alloc_bytes 4.266136e+06
# HELP go_memstats_heap_idle_bytes Number of heap bytes waiting to be used.
# TYPE go_memstats_heap_idle_bytes gauge
go_memstats_heap_idle_bytes 6.1046784e+07
# HELP go_memstats_heap_inuse_bytes Number of heap bytes that are in use.
# TYPE go_memstats_heap_inuse_bytes gauge
go_memstats_heap_inuse_bytes 5.210112e+06
# HELP go_memstats_heap_objects Number of allocated objects.
# TYPE go_memstats_heap_objects gauge
go_memstats_heap_objects 17572
# HELP go_memstats_heap_released_bytes Number of heap bytes released to OS.
# TYPE go_memstats_heap_released_bytes gauge
go_memstats_heap_released_bytes 6.0588032e+07
# HELP go_memstats_heap_sys_bytes Number of heap bytes obtained from system.
# TYPE go_memstats_heap_sys_bytes gauge
go_memstats_heap_sys_bytes 6.6256896e+07
# HELP go_memstats_last_gc_time_seconds Number of seconds since 1970 of last garbage collection.
# TYPE go_memstats_last_gc_time_seconds gauge
go_memstats_last_gc_time_seconds 1.61550102568985e+09
# HELP go_memstats_lookups_total Total number of pointer lookups.
# TYPE go_memstats_lookups_total counter
go_memstats_lookups_total 0
# HELP go_memstats_mallocs_total Total number of mallocs.
# TYPE go_memstats_mallocs_total counter
go_memstats_mallocs_total 453168
# HELP go_memstats_mcache_inuse_bytes Number of bytes in use by mcache structures.
# TYPE go_memstats_mcache_inuse_bytes gauge
go_memstats_mcache_inuse_bytes 27776
# HELP go_memstats_mcache_sys_bytes Number of bytes used for mcache structures obtained from system.
# TYPE go_memstats_mcache_sys_bytes gauge
go_memstats_mcache_sys_bytes 32768
# HELP go_memstats_mspan_inuse_bytes Number of bytes in use by mspan structures.
# TYPE go_memstats_mspan_inuse_bytes gauge
go_memstats_mspan_inuse_bytes 141576
# HELP go_memstats_mspan_sys_bytes Number of bytes used for mspan structures obtained from system.
# TYPE go_memstats_mspan_sys_bytes gauge
go_memstats_mspan_sys_bytes 147456
# HELP go_memstats_next_gc_bytes Number of heap bytes when next garbage collection will take place.
# TYPE go_memstats_next_gc_bytes gauge
go_memstats_next_gc_bytes 6.42088e+06
# HELP go_memstats_other_sys_bytes Number of bytes used for other system allocations.
# TYPE go_memstats_other_sys_bytes gauge
go_memstats_other_sys_bytes 1.931943e+06
# HELP go_memstats_stack_inuse_bytes Number of bytes in use by the stack allocator.
# TYPE go_memstats_stack_inuse_bytes gauge
go_memstats_stack_inuse_bytes 851968
# HELP go_memstats_stack_sys_bytes Number of bytes obtained from system for stack allocator.
# TYPE go_memstats_stack_sys_bytes gauge
go_memstats_stack_sys_bytes 851968
# HELP go_memstats_sys_bytes Number of bytes obtained from system.
# TYPE go_memstats_sys_bytes gauge
go_memstats_sys_bytes 7.5580416e+07
# HELP go_threads Number of OS threads created.
# TYPE go_threads gauge
go_threads 13
# HELP process_cpu_seconds_total Total user and system CPU time spent in seconds.
# TYPE process_cpu_seconds_total counter
process_cpu_seconds_total 1.83
# HELP process_max_fds Maximum number of open file descriptors.
# TYPE process_max_fds gauge
process_max_fds 1.048576e+06
# HELP process_open_fds Number of open file descriptors.
# TYPE process_open_fds gauge
process_open_fds 10
# HELP process_resident_memory_bytes Resident memory size in bytes.
# TYPE process_resident_memory_bytes gauge
process_resident_memory_bytes 2.8770304e+07
# HELP process_start_time_seconds Start time of the process since unix epoch in seconds.
# TYPE process_start_time_seconds gauge
process_start_time_seconds 1.61549436213e+09
# HELP process_virtual_memory_bytes Virtual memory size in bytes.
# TYPE process_virtual_memory_bytes gauge
process_virtual_memory_bytes 1.564209152e+09
# HELP process_virtual_memory_max_bytes Maximum amount of virtual memory available in bytes.
# TYPE process_virtual_memory_max_bytes gauge
process_virtual_memory_max_bytes -1
# HELP promhttp_metric_handler_requests_in_flight Current number of scrapes being served.
# TYPE promhttp_metric_handler_requests_in_flight gauge
promhttp_metric_handler_requests_in_flight 1
# HELP promhttp_metric_handler_requests_total Total number of scrapes by HTTP status code.
# TYPE promhttp_metric_handler_requests_total counter
promhttp_metric_handler_requests_total{code="200"} 447
promhttp_metric_handler_requests_total{code="500"} 0
promhttp_metric_handler_requests_total{code="503"} 0

這些指標很棒,但大多數時候它們並不是很有用。我們現在想要公開自定義指標,而不是低階指標,這些指標將公開我們應用程式的內部資訊,我們以後可以視覺化或在測試或健康檢查中使用這些資訊。

讓我們從一個相當基本的指標開始:向伺服器發出的 HTTP 請求總數以計數器表示。

package main

import (
 "fmt"
 "github.com/gorilla/mux"
 "github.com/prometheus/client_golang/prometheus"
 "github.com/prometheus/client_golang/prometheus/promauto"
 "github.com/prometheus/client_golang/prometheus/promhttp"
 "log"
 "net/http"
 "strconv"
)

var totalRequests = prometheus.NewCounterVec(
 prometheus.CounterOpts{
  Name: "http_requests_total",
  Help: "Number of get requests.",
 },
 []string{"path"},
)

func prometheusMiddleware(next http.Handler) http.Handler {
 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  rw := NewResponseWriter(w)
  next.ServeHTTP(rw, r)

  totalRequests.WithLabelValues(path).Inc()
 })
}

func init() {
 prometheus.Register(totalRequests)
}

func main() {
 router := mux.NewRouter()
 router.Use(prometheusMiddleware)

 // Prometheus endpoint
 router.Path("/prometheus").Handler(promhttp.Handler())

 // Serving static files
 router.PathPrefix("/").Handler(http.FileServer(http.Dir("./static/")))

 fmt.Println("Serving requests on port 9000")
 err := http.ListenAndServe(":9000", router)
 log.Fatal(err)
}

讓我們分解程式碼更改以更好地理解:

該指標需要使用 prometheus 包建立 .NewCounterVec() 方法用於建立新的計數器度量。要在 HTTP 處理程式中公開建立的指標,我們必須使用 register() 方法將指標註冊到 Prometheus 。最後,我們需要在程式碼中實現指標的功能。在這裡,我們建立並註冊了一個新的 HTTP 中介軟體,該中介軟體在每次伺服器接收到 HTTP 請求時執行,並使用 Inc() 方法增加指標計數器。

以下程式碼塊包含另外兩個具有不同指標型別的指標: response_status response_time

package main

import (
 "fmt"
 "github.com/gorilla/mux"
 "github.com/prometheus/client_golang/prometheus"
 "github.com/prometheus/client_golang/prometheus/promauto"
 "github.com/prometheus/client_golang/prometheus/promhttp"
 "log"
 "net/http"
 "strconv"
)

type responseWriter struct {
 http.ResponseWriter
 statusCode int
}

func NewResponseWriter(w http.ResponseWriter) *responseWriter {
 return &responseWriter{w, http.StatusOK}
}

func (rw *responseWriter) WriteHeader(code int) {
 rw.statusCode = code
 rw.ResponseWriter.WriteHeader(code)
}

var totalRequests = prometheus.NewCounterVec(
 prometheus.CounterOpts{
  Name: "http_requests_total",
  Help: "Number of get requests.",
 },
 []string{"path"},
)

var responseStatus = prometheus.NewCounterVec(
 prometheus.CounterOpts{
  Name: "response_status",
  Help: "Status of HTTP response",
 },
 []string{"status"},
)

var httpDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{
 Name: "http_response_time_seconds",
 Help: "Duration of HTTP requests.",
}, []string{"path"})

func prometheusMiddleware(next http.Handler) http.Handler {
 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  route := mux.CurrentRoute(r)
  path, _ := route.GetPathTemplate()

  timer := prometheus.NewTimer(httpDuration.WithLabelValues(path))
  rw := NewResponseWriter(w)
  next.ServeHTTP(rw, r)

  statusCode := rw.statusCode

  responseStatus.WithLabelValues(strconv.Itoa(statusCode)).Inc()
  totalRequests.WithLabelValues(path).Inc()

  timer.ObserveDuration()
 })
}

func init() {
 prometheus.Register(totalRequests)
 prometheus.Register(responseStatus)
 prometheus.Register(httpDuration)
}

func main() {
 router := mux.NewRouter()
 router.Use(prometheusMiddleware)

 // Prometheus endpoint
 router.Path("/prometheus").Handler(promhttp.Handler())

 // Serving static files
 router.PathPrefix("/").Handler(http.FileServer(http.Dir("./static/")))

 fmt.Println("Serving requests on port 9000")
 err := http.ListenAndServe(":9000", router)
 log.Fatal(err)
}

將應用程式 Docker 化

現在指標已在應用程式中實現,我們可以對應用程式進行 Docker 化,以便更輕鬆地使用 Prometheus 執行它。

FROM golang:1.15.0

# Set the Current Working Directory inside the container
WORKDIR /app

RUN export GO111MODULE=on

# Copy go mod and sum files
COPY go.mod go.sum ./

# Download all dependencies. Dependencies will be cached if the go.mod and go.sum files are not changed
RUN go mod download

COPY . .

# Build the application
RUN go build -o main .

# Expose port 9000 to the outside world
EXPOSE 9000

# Command to run the executable
CMD ["./main"]

Dockerfile 將下載依賴項,複製所有檔案並構建應用程式。完成 Dockerfile 後,我們可以將容器和 Prometheus 放入一個 Docker-Compose `檔案中。

version: '3.1'

services:
  golang:
    build:
      context: ./
      dockerfile: Dockerfile
    container_name: golang
    restart: always
    ports:
      - '9000:9000'
  prometheus:
    image: prom/prometheus:v2.24.0
    volumes:
      - ./prometheus/:/etc/prometheus/
      - prometheus_data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--web.console.libraries=/usr/share/prometheus/console_libraries'
      - '--web.console.templates=/usr/share/prometheus/consoles'
    ports:
      - 9090:9090
    restart: always

volumes:
  prometheus_data:

在啟動應用程式之前,我們現在唯一需要做的就是配置 Prometheus 端點。為此,我們將建立一個配置檔案:

mkdir prometheus
touch prometheus/prometheus.yml

在這裡,我們定義了 Prometheus 應該從中抓取資料的頁面的 URL ,對於我們的應用程式,它等於 ContainerIP:Port/prometheus

global:
  scrape_interval:     15s
  evaluation_interval: 15s

scrape_configs:
  - job_name: prometheus
    static_configs:
      - targets: ['localhost:9090']
  - job_name: golang 
    metrics_path: /prometheus
    static_configs:
      - targets:
        - golang:9000

新增配置後,我們可以使用 docker-compose 啟動應用程式:

docker-compose up -d

現在我們可以通過在瀏覽器中訪問 localhost:9090 來訪問 Prometheus

使用 Grafana 視覺化指標

現在 Prometheus 已成功收集指標,您將繼續使用 Grafana 視覺化資料。為此,您需要首先通過將 Grafana 容器新增到 docker-compose 檔案來啟動它。

version: '3.1'

services:
  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    ports:
      - "3000:3000"
    volumes:
      - grafana-storage:/var/lib/grafana
  golang:
    build:
      context: ./
      dockerfile: Dockerfile
    container_name: golang
    restart: always
    ports:
      - '9000:9000'
  prometheus:
    image: prom/prometheus:v2.24.0
    volumes:
      - ./prometheus/:/etc/prometheus/
      - prometheus_data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--web.console.libraries=/usr/share/prometheus/console_libraries'
      - '--web.console.templates=/usr/share/prometheus/consoles'
    ports:
      - 9090:9090
    restart: always

volumes:
  grafana-storage:
  prometheus_data:

新增 Grafana 容器和將儲存 Grafana 配置和資料的卷後,您可以重新啟動 docker-compose

docker-compose up -d

現在 Grafana 已啟動,您可以通過在瀏覽器中訪問 http://localhost:3000 來訪問它。它將要求您輸入使用者憑據,預設為 admin 作為使用者名稱和密碼。

登入後,您可以通過導航到配置>資料來源並單擊“新增資料來源”來建立新資料來源。之後,選擇 Prometheus ,然後填寫必要的資訊。

成功新增資料來源後,您可以繼續建立新儀表板以視覺化您的指標。

儀表板由可讓您視覺化指標的面板組成,因此請單擊“新增面板”開始。

現在您可以通過在度量欄位中指定一個度量來選擇它:例如 http_requests_total

由於您沒有經常訪問該應用程式,因此您的儀表板可能不會顯示與我一樣多的資料。獲取更多測試資料的最佳方法是使用負載測試工具。

我喜歡使用 hey 負載測試工具,這是一個用於負載生成的開源 CLI 應用程式,但您也可以使用其他工具。下載後,您可以使用以下命令生成流量。

hey -z 5m -q 5 -m GET -H "Accept: text/html" http://127.0.0.1:9000

您現在可以通過新增其他帶有指標的面板來試驗儀表板並根據自己的喜好對其進行自定義。如果您想要一個視覺化我們已實施的所有指標的示例儀表板,您可以從 Github 下載它,然後將其匯入。

https://github.com/TannerGabriel/learning-go/tree/master/advanced-programs/PrometheusHTTPServer/grafana

來源

以下是用於撰寫本文的資源列表:

  • https://blog.pvincent.io/2017/12/prometheus-blog-series-part-1-metrics-and-labels/

  • https://prometheus.io/docs/guides/go-application/

  • https://dev.to/eminetto/using-prometheus-to-collect-metrics-from-golang-applications-35gc

結論

在本文中,您在 Golang 應用程式中設定了 Prometheus ,並實現了您自己的自定義 Prometheus 指標,然後您在 Grafana 中對其進行了視覺化。

更多文件

請關注微信公眾號 雲原生CTO [1]

參考資料

[1]

參考地址: https://gabrieltanner.org/blog/collecting-prometheus-metrics-in-golang