Skip to content

Commit 00149c5

Browse files
committed
docs: add development guide for containerizing a Golang application with Docker
1 parent 9e29a37 commit 00149c5

File tree

7 files changed

+566
-356
lines changed

7 files changed

+566
-356
lines changed

content/guides/grafana-prometheus/_index.md renamed to content/guides/go-prometheus-monitoring/_index.md

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ summary: |
77
linkTitle: Monitor with Prometheus and Grafana
88
languages: [go]
99
params:
10-
time: 60 minutes
10+
time: 45 minutes
1111
---
1212

1313
The guide teaches you how to containerize a Golang application and monitor it with Prometheus and Grafana. In this guide, you'll learn how to:
@@ -16,15 +16,23 @@ The guide teaches you how to containerize a Golang application and monitor it wi
1616
>
1717
> Docker would like to thank [Pradumna Saraf](https://twitter.com/pradumna_saraf) for his contribution to this guide.
1818
19+
## Overview
20+
21+
To make sure our application is working as intended, monitoring is really important. In this guide, you'll learn how to containerize a Golang application and monitor it with Prometheus and Grafana.
22+
23+
We will create a Golang server with some endpoints to simulate a real-world application. Then we will expose metrics from the server using Prometheus. Finally, we will visualize the metrics using Grafana. We will containerize the Golang application and using the Docker Compose file, we will connect all the services- Golang, Prometheus, and Grafana.
24+
1925
## What will you learn?
2026

21-
* Containerize and run a Golang application using Docker
22-
* Set up a local environment to develop a Golang application using containers
23-
* How to use Docker Compose to run multiple services and connect them together to monitor a Golang application with Prometheus and Grafana.
27+
* Create a Golang application with Prometheus metrics.
28+
* Containerize a Golang application.
29+
* Use Docker Compose to run multiple services and connect them together to monitor a Golang application with Prometheus and Grafana.
30+
* Visualize the metrics using Grafana.
2431

2532
## Prerequisites
2633

2734
- A good understanding of Golang is assumed.
35+
- You must me familiar with Prometheus and Grafana concepts. If you are new to Prometheus and Grafana.
2836
- You must have familiarity with Docker concepts like containers, images, and Dockerfiles. If you are new to Docker, you can start with the [Docker basics](/get-started/docker-concepts/the-basics/what-is-a-container.md) guide.
2937

3038
Start by containerizing an existing Bun application.
Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
---
2+
title: Building the application
3+
linkTitle: Understand the application
4+
weight: 10 #
5+
keywords: go, golang, prometheus, grafana, containerize, monitor
6+
description: Learn how to create a Golang server to register metrics with Prometheus.
7+
aliases:
8+
- /guides/go-prometheus-monitoring/application/
9+
---
10+
11+
## Prerequisites
12+
13+
* You have a [Git client](https://git-scm.com/downloads). The examples in this section use a command-line based Git client, but you can use any client.
14+
15+
## Overview
16+
17+
To make sure our application is working an intended, monitoring is really important. In this guide, you'll learn how to containerize a Golang application and monitor it with Prometheus and Grafana. In this guide, you'll learn how to:
18+
19+
## Getting the sample application
20+
21+
Clone the sample application to use with this guide. Open a terminal, change
22+
directory to a directory that you want to work in, and run the following
23+
command to clone the repository:
24+
25+
```console
26+
$ git clone https://github.com/dockersamples/go-prometheus-monitoring.git
27+
```
28+
29+
Once you cloned you will see the following content structure inside `go-prometheus-monitoring` directory,
30+
31+
```text
32+
go-prometheus-monitoring
33+
├── CONTRIBUTING.md
34+
├── Docker
35+
│ ├── grafana.yml
36+
│ └── prometheus.yml
37+
├── Dockerfile
38+
├── LICENSE
39+
├── README.md
40+
├── compose.yaml
41+
├── go.mod
42+
├── go.sum
43+
└── main.go
44+
```
45+
46+
## Understanding the application
47+
48+
Below is complete logic of the application you will find in the `main.go`. Let's break this to understand code better.
49+
50+
```go
51+
package main
52+
53+
import (
54+
"strconv"
55+
56+
"github.com/gin-gonic/gin"
57+
"github.com/prometheus/client_golang/prometheus"
58+
"github.com/prometheus/client_golang/prometheus/promhttp"
59+
)
60+
61+
// Define metrics
62+
var (
63+
HttpRequestTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
64+
Name: "api_http_request_total",
65+
Help: "Total number of requests processed by the API",
66+
}, []string{"path", "status"})
67+
68+
HttpRequestErrorTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
69+
Name: "api_http_request_error_total",
70+
Help: "Total number of errors returned by the API",
71+
}, []string{"path", "status"})
72+
)
73+
74+
// Custom registry (without default Go metrics)
75+
var customRegistry = prometheus.NewRegistry()
76+
77+
// Register metrics with custom registry
78+
func init() {
79+
customRegistry.MustRegister(HttpRequestTotal, HttpRequestErrorTotal)
80+
}
81+
82+
func main() {
83+
router := gin.Default()
84+
85+
// Register /metrics before middleware
86+
router.GET("/metrics", PrometheusHandler())
87+
88+
router.Use(RequestMetricsMiddleware())
89+
router.GET("/health", func(c *gin.Context) {
90+
c.JSON(200, gin.H{
91+
"message": "Up and running!",
92+
})
93+
})
94+
router.GET("/v1/users", func(c *gin.Context) {
95+
c.JSON(200, gin.H{
96+
"message": "Hello from /v1/users",
97+
})
98+
})
99+
100+
router.Run(":8000")
101+
}
102+
103+
// Custom metrics handler with custom registry
104+
func PrometheusHandler() gin.HandlerFunc {
105+
h := promhttp.HandlerFor(customRegistry, promhttp.HandlerOpts{})
106+
return func(c *gin.Context) {
107+
h.ServeHTTP(c.Writer, c.Request)
108+
}
109+
}
110+
111+
// Middleware to record incoming requests metrics
112+
func RequestMetricsMiddleware() gin.HandlerFunc {
113+
return func(c *gin.Context) {
114+
path := c.Request.URL.Path
115+
c.Next()
116+
status := c.Writer.Status()
117+
if status < 400 {
118+
HttpRequestTotal.WithLabelValues(path, strconv.Itoa(status)).Inc()
119+
} else {
120+
HttpRequestErrorTotal.WithLabelValues(path, strconv.Itoa(status)).Inc()
121+
}
122+
}
123+
}
124+
```
125+
126+
In this part of the code, we have imported the required packages `gin`, `prometheus`, and `promhttp`. Then we have defined a couple of variables, `HttpRequestTotal` and `HttpRequestErrorTotal` are Prometheus counter metrics, and `customRegistry` is a custom registry that will be used to register these metrics. The name of the metric is a string that you can use to identify the metric. The help string is a string that will be shown when you query the `/metrics` endpoint to understand the metric. The reason we are using the custom registry is so avoid the default Go metrics that are registered by default by the Prometheus client. Then using the `init` function we are registering the metrics with the custom registry.
127+
128+
```go
129+
import (
130+
"strconv"
131+
132+
"github.com/gin-gonic/gin"
133+
"github.com/prometheus/client_golang/prometheus"
134+
"github.com/prometheus/client_golang/prometheus/promhttp"
135+
)
136+
137+
// Define metrics
138+
var (
139+
HttpRequestTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
140+
Name: "api_http_request_total",
141+
Help: "Total number of requests processed by the API",
142+
}, []string{"path", "status"})
143+
144+
HttpRequestErrorTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
145+
Name: "api_http_request_error_total",
146+
Help: "Total number of errors returned by the API",
147+
}, []string{"path", "status"})
148+
)
149+
150+
// Custom registry (without default Go metrics)
151+
var customRegistry = prometheus.NewRegistry()
152+
153+
// Register metrics with custom registry
154+
func init() {
155+
customRegistry.MustRegister(HttpRequestTotal, HttpRequestErrorTotal)
156+
}
157+
```
158+
159+
In the `main` function, we have created a new instance of the `gin` framework and created three routes. You can see the health endpoint that is on path `/health` that will return a json with `{"message": "Up and running!"}` and the `/v1/users` endpoint that will return a json with `{"message": "Hello from /v1/users"}`. The third route is for the `/metrics` endpoint that will return the metrics in the Prometheus format. Then we have `RequestMetricsMiddleware` middleware, it will be called for every request made to the API. It will record the incoming requests metrics like status codes and paths. Finally, we are running the gin application on port 8000.
160+
161+
```golang
162+
func main() {
163+
router := gin.Default()
164+
165+
// Register /metrics before middleware
166+
router.GET("/metrics", PrometheusHandler())
167+
168+
router.Use(RequestMetricsMiddleware())
169+
router.GET("/health", func(c *gin.Context) {
170+
c.JSON(200, gin.H{
171+
"message": "Up and running!",
172+
})
173+
})
174+
router.GET("/v1/users", func(c *gin.Context) {
175+
c.JSON(200, gin.H{
176+
"message": "Hello from /v1/users",
177+
})
178+
})
179+
180+
router.Run(":8000")
181+
}
182+
```
183+
184+
Now comes the middleware function `RequestMetricsMiddleware`. This function is called for every request made to the API. It increments the `HttpRequestTotal` counter (different counter for different paths and status codes) if the status code is less than or equal to 400. If the status code is greater than 400, it increments the `HttpRequestErrorTotal` counter (different counter for different paths and status codes). The `PrometheusHandler` function is the custom handler that will be called for the `/metrics` endpoint. It will return the metrics in the Prometheus format.
185+
186+
```golang
187+
// Custom metrics handler with custom registry
188+
func PrometheusHandler() gin.HandlerFunc {
189+
h := promhttp.HandlerFor(customRegistry, promhttp.HandlerOpts{})
190+
return func(c *gin.Context) {
191+
h.ServeHTTP(c.Writer, c.Request)
192+
}
193+
}
194+
195+
// Middleware to record incoming requests metrics
196+
func RequestMetricsMiddleware() gin.HandlerFunc {
197+
return func(c *gin.Context) {
198+
path := c.Request.URL.Path
199+
c.Next()
200+
status := c.Writer.Status()
201+
if status < 400 {
202+
HttpRequestTotal.WithLabelValues(path, strconv.Itoa(status)).Inc()
203+
} else {
204+
HttpRequestErrorTotal.WithLabelValues(path, strconv.Itoa(status)).Inc()
205+
}
206+
}
207+
}
208+
```
209+
210+
That's it, this was the complete gist of our application. Now it's time to run and test if our app is registering metrics correctly.
211+
212+
## Running the application
213+
214+
Make sure you are still inside `go-prometheus-monitoring` directory in the terminal, and run the following command. Install the dependencies by running `go mod tidy` command and then build and run the application by running `go run main.go` command. Then visit `http://localhost:8000/health` or `http://localhost:8000/v1/users`. You should see the output `{"message": "Up and running!"}` or `{"message": "Hello from /v1/users"}`. If you are able to see this then our app is successfully up and running.
215+
216+
217+
Now let's check our application's metrics by accessing the `/metrics` endpoint.
218+
Open `http://localhost:8000/metrics` in your browser. You should see similar output to the one below.
219+
220+
```text
221+
# HELP api_http_request_error_total Total number of errors returned by the API
222+
# TYPE api_http_request_error_total counter
223+
api_http_request_error_total{path="/",status="404"} 1
224+
api_http_request_error_total{path="//v1/users",status="404"} 1
225+
api_http_request_error_total{path="/favicon.ico",status="404"} 1
226+
# HELP api_http_request_total Total number of requests processed by the API
227+
# TYPE api_http_request_total counter
228+
api_http_request_total{path="/health",status="200"} 2
229+
api_http_request_total{path="/v1/users",status="200"} 1
230+
```
231+
232+
In the terminal, press `ctrl`+`c` to stop the application.
233+
234+
:::note
235+
If you don't want to run the application locally, and want to run it in a Docker container, skip to next page where we create a Dockerfile and containerize the application.
236+
:::
237+
238+
## Summary
239+
240+
In this section, we learned how to create an Golang app to register metrics with Prometheus. By implementing middleware functions, we were able to increment the counters based on the request path and status codes.
241+
242+
## Next steps
243+
244+
In the next section, you'll learn how to containerize your application.

0 commit comments

Comments
 (0)