使用 Prometheus 监控 Golang 应用程序
关 注 微 信 公 众 号 《 云 原 生 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
下载它,然后将其导入。
http://github.com/TannerGabriel/learning-go/tree/master/advanced-programs/PrometheusHTTPServer/grafana
来源
以下是用于撰写本文的资源列表:
-
http://blog.pvincent.io/2017/12/prometheus-blog-series-part-1-metrics-and-labels/
-
http://prometheus.io/docs/guides/go-application/
-
http://dev.to/eminetto/using-prometheus-to-collect-metrics-from-golang-applications-35gc
结论
在本文中,您在 Golang
应用程序中设置了 Prometheus
,并实现了您自己的自定义 Prometheus
指标,然后您在 Grafana
中对其进行了可视化。
更多文档
请关注微信公众号 云原生CTO [1]
参考资料
参考地址: http://gabrieltanner.org/blog/collecting-prometheus-metrics-in-golang
- 一组用于 Kubernetes 的现代 Grafana 仪表板
- Go 中的构建器模式
- 让我们使用 Go 实现基本的服务发现
- 云原生下一步的发展方向是什么?
- 用更云原生的方式做诊断|大规模 K8s 集群诊断利器深度解析
- 多个维度分析k8s多集群管理工具,到底哪个才真正适合你
- 使用 Kube-capacity CLI 查看 Kubernetes 资源请求、限制和利用率
- 使用 Go 在 Kubernetes 中构建自己的准入控制器
- 云原生数仓如何破解大规模集群的关联查询性能问题?
- 云原生趋势下的迁移与灾备思考
- 2022 年不容错过的六大云原生趋势!
- 使用 Prometheus 监控 Golang 应用程序
- 云原生时代下的机遇与挑战 DevOps如何破局
- 如何在云原生格局中理解Kubernetes合规性和安全框架
- 设计云原生应用程序的15条基本原则
- 使用 Operator SDK 为 Pod 标签编写Controller
- Kubernetes Visitor 模式
- 为什么云原生是第二次云革命
- 构建云原生安全的六个重要能力
- 扩展云原生策略的步骤有哪些?