نوشتن Prometheus Exporter با گولنگ - آموزش کامل
2025/11/24Prometheus استاندارد مانیتورینگ در دنیای Cloud-Native است. یکی از کاربردهای رایج Go برای DevOps Engineers، نوشتن Exporter های سفارشی برای expose کردن متریکهای اپلیکیشن یا زیرساخت است.
در این آموزش یاد میگیرید چطور یک Prometheus Exporter بنویسید.
Prometheus چگونه کار میکند؟
┌─────────────┐ scrape ┌──────────────┐
│ Prometheus │ ──────────────> │ Exporter │
│ Server │ /metrics │ (Go App) │
└─────────────┘ └──────────────┘
│
│ query
▼
┌─────────────┐
│ Grafana │
└─────────────┘- Exporter متریکها را در endpoint
/metricsexpose میکند - Prometheus هر چند ثانیه این endpoint را scrape میکند
- Grafana از Prometheus query میگیرد و dashboard نمایش میدهد
انواع متریک در Prometheus
| نوع | کاربرد | مثال |
|---|---|---|
| Counter | مقادیر افزایشی | تعداد درخواستها |
| Gauge | مقادیر متغیر | دمای CPU |
| Histogram | توزیع مقادیر | زمان پاسخ |
| Summary | خلاصه آماری | percentile ها |
راهاندازی پروژه
mkdir myexporter && cd myexporter
go mod init myexporter
# نصب کتابخانه Prometheus
go get github.com/prometheus/client_golang/prometheus
go get github.com/prometheus/client_golang/prometheus/promhttpExporter ساده: Hello World
package main
import (
"log"
"net/http"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
// تعریف یک Counter
var requestsTotal = prometheus.NewCounter(
prometheus.CounterOpts{
Name: "myapp_requests_total",
Help: "Total number of requests",
},
)
func init() {
// ثبت متریک
prometheus.MustRegister(requestsTotal)
}
func handler(w http.ResponseWriter, r *http.Request) {
// افزایش counter با هر درخواست
requestsTotal.Inc()
w.Write([]byte("Hello, World!"))
}
func main() {
http.HandleFunc("/", handler)
http.Handle("/metrics", promhttp.Handler())
log.Println("Server starting on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}# اجرا
go run main.go
# در ترمینال دیگر
curl localhost:8080/
curl localhost:8080/metrics | grep myappخروجی /metrics:
# HELP myapp_requests_total Total number of requests
# TYPE myapp_requests_total counter
myapp_requests_total 5Exporter کامل: متریکهای HTTP API
package main
import (
"log"
"math/rand"
"net/http"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var (
// Counter با labels
httpRequestsTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{
Namespace: "myapp",
Name: "http_requests_total",
Help: "Total HTTP requests",
},
[]string{"method", "endpoint", "status"},
)
// Histogram برای زمان پاسخ
httpRequestDuration = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Namespace: "myapp",
Name: "http_request_duration_seconds",
Help: "HTTP request duration in seconds",
Buckets: []float64{.001, .005, .01, .025, .05, .1, .25, .5, 1},
},
[]string{"method", "endpoint"},
)
// Gauge برای درخواستهای فعال
httpRequestsInFlight = prometheus.NewGauge(
prometheus.GaugeOpts{
Namespace: "myapp",
Name: "http_requests_in_flight",
Help: "Number of HTTP requests currently in flight",
},
)
)
func init() {
prometheus.MustRegister(httpRequestsTotal)
prometheus.MustRegister(httpRequestDuration)
prometheus.MustRegister(httpRequestsInFlight)
}
// Middleware برای جمعآوری متریکها
func metricsMiddleware(next http.HandlerFunc, endpoint string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// افزایش درخواستهای فعال
httpRequestsInFlight.Inc()
defer httpRequestsInFlight.Dec()
// Wrapper برای گرفتن status code
wrapper := &responseWrapper{ResponseWriter: w, statusCode: 200}
// اجرای handler اصلی
next.ServeHTTP(wrapper, r)
// ثبت متریکها
duration := time.Since(start).Seconds()
statusStr := http.StatusText(wrapper.statusCode)
httpRequestsTotal.WithLabelValues(
r.Method, endpoint, statusStr,
).Inc()
httpRequestDuration.WithLabelValues(
r.Method, endpoint,
).Observe(duration)
}
}
type responseWrapper struct {
http.ResponseWriter
statusCode int
}
func (w *responseWrapper) WriteHeader(code int) {
w.statusCode = code
w.ResponseWriter.WriteHeader(code)
}
// Handlers
func healthHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
}
func apiHandler(w http.ResponseWriter, r *http.Request) {
// شبیهسازی latency تصادفی
delay := time.Duration(rand.Intn(100)) * time.Millisecond
time.Sleep(delay)
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`{"status": "success"}`))
}
func slowHandler(w http.ResponseWriter, r *http.Request) {
// شبیهسازی درخواست کند
time.Sleep(500 * time.Millisecond)
w.Write([]byte("Slow response"))
}
func main() {
// Register handlers با middleware
http.HandleFunc("/health",
metricsMiddleware(healthHandler, "/health"))
http.HandleFunc("/api",
metricsMiddleware(apiHandler, "/api"))
http.HandleFunc("/slow",
metricsMiddleware(slowHandler, "/slow"))
// Metrics endpoint
http.Handle("/metrics", promhttp.Handler())
log.Println("Server starting on :8080")
log.Println("Metrics available at :8080/metrics")
log.Fatal(http.ListenAndServe(":8080", nil))
}Exporter برای سیستم: متریکهای سرور
package main
import (
"log"
"net/http"
"os"
"runtime"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var (
// اطلاعات اپلیکیشن
appInfo = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Namespace: "myapp",
Name: "info",
Help: "Application information",
},
[]string{"version", "go_version", "hostname"},
)
// Uptime
appUptime = prometheus.NewGauge(
prometheus.GaugeOpts{
Namespace: "myapp",
Name: "uptime_seconds",
Help: "Application uptime in seconds",
},
)
// Goroutines
goRoutines = prometheus.NewGauge(
prometheus.GaugeOpts{
Namespace: "myapp",
Name: "goroutines",
Help: "Number of goroutines",
},
)
// Memory
memoryAlloc = prometheus.NewGauge(
prometheus.GaugeOpts{
Namespace: "myapp",
Name: "memory_alloc_bytes",
Help: "Allocated memory in bytes",
},
)
memorySys = prometheus.NewGauge(
prometheus.GaugeOpts{
Namespace: "myapp",
Name: "memory_sys_bytes",
Help: "System memory in bytes",
},
)
// GC
gcPauseDuration = prometheus.NewSummary(
prometheus.SummaryOpts{
Namespace: "myapp",
Name: "gc_pause_seconds",
Help: "GC pause duration",
Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
},
)
)
var startTime = time.Now()
func init() {
prometheus.MustRegister(appInfo)
prometheus.MustRegister(appUptime)
prometheus.MustRegister(goRoutines)
prometheus.MustRegister(memoryAlloc)
prometheus.MustRegister(memorySys)
prometheus.MustRegister(gcPauseDuration)
}
func collectMetrics() {
hostname, _ := os.Hostname()
appInfo.WithLabelValues("1.0.0", runtime.Version(), hostname).Set(1)
for {
// Uptime
appUptime.Set(time.Since(startTime).Seconds())
// Goroutines
goRoutines.Set(float64(runtime.NumGoroutine()))
// Memory stats
var m runtime.MemStats
runtime.ReadMemStats(&m)
memoryAlloc.Set(float64(m.Alloc))
memorySys.Set(float64(m.Sys))
time.Sleep(5 * time.Second)
}
}
func main() {
// شروع collector در background
go collectMetrics()
http.Handle("/metrics", promhttp.Handler())
log.Println("Exporter running on :9090")
log.Fatal(http.ListenAndServe(":9090", nil))
}Exporter سفارشی: مانیتورینگ سرویس خارجی
یک exporter برای مانیتورینگ یک API خارجی:
package main
import (
"encoding/json"
"log"
"net/http"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
// Collector interface را پیادهسازی میکند
type APICollector struct {
apiURL string
// متریکها
up *prometheus.Desc
responseTime *prometheus.Desc
usersTotal *prometheus.Desc
ordersTotal *prometheus.Desc
}
// API Response structure
type APIStatus struct {
Status string `json:"status"`
UsersCount int `json:"users_count"`
OrdersCount int `json:"orders_count"`
}
func NewAPICollector(url string) *APICollector {
return &APICollector{
apiURL: url,
up: prometheus.NewDesc(
"external_api_up",
"Is the external API up",
nil, nil,
),
responseTime: prometheus.NewDesc(
"external_api_response_time_seconds",
"Response time of the external API",
nil, nil,
),
usersTotal: prometheus.NewDesc(
"external_api_users_total",
"Total users from external API",
nil, nil,
),
ordersTotal: prometheus.NewDesc(
"external_api_orders_total",
"Total orders from external API",
nil, nil,
),
}
}
// Describe متریکها را توصیف میکند
func (c *APICollector) Describe(ch chan<- *prometheus.Desc) {
ch <- c.up
ch <- c.responseTime
ch <- c.usersTotal
ch <- c.ordersTotal
}
// Collect متریکها را جمعآوری میکند
func (c *APICollector) Collect(ch chan<- prometheus.Metric) {
start := time.Now()
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Get(c.apiURL)
duration := time.Since(start).Seconds()
if err != nil {
ch <- prometheus.MustNewConstMetric(c.up, prometheus.GaugeValue, 0)
ch <- prometheus.MustNewConstMetric(c.responseTime, prometheus.GaugeValue, duration)
return
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
ch <- prometheus.MustNewConstMetric(c.up, prometheus.GaugeValue, 0)
ch <- prometheus.MustNewConstMetric(c.responseTime, prometheus.GaugeValue, duration)
return
}
ch <- prometheus.MustNewConstMetric(c.up, prometheus.GaugeValue, 1)
ch <- prometheus.MustNewConstMetric(c.responseTime, prometheus.GaugeValue, duration)
// Parse response
var status APIStatus
if err := json.NewDecoder(resp.Body).Decode(&status); err == nil {
ch <- prometheus.MustNewConstMetric(c.usersTotal, prometheus.GaugeValue, float64(status.UsersCount))
ch <- prometheus.MustNewConstMetric(c.ordersTotal, prometheus.GaugeValue, float64(status.OrdersCount))
}
}
func main() {
// ثبت collector سفارشی
collector := NewAPICollector("https://api.example.com/status")
prometheus.MustRegister(collector)
http.Handle("/metrics", promhttp.Handler())
log.Println("Exporter running on :9091")
log.Fatal(http.ListenAndServe(":9091", nil))
}تنظیمات Prometheus
prometheus.yml:
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'myapp'
static_configs:
- targets: ['localhost:8080']
- job_name: 'myapp-system'
static_configs:
- targets: ['localhost:9090']
- job_name: 'external-api'
scrape_interval: 30s
static_configs:
- targets: ['localhost:9091']Docker Compose برای تست
version: '3.8'
services:
myexporter:
build: .
ports:
- "8080:8080"
prometheus:
image: prom/prometheus:latest
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=adminQueryهای Prometheus مفید
# نرخ درخواست در ثانیه
rate(myapp_http_requests_total[5m])
# نرخ درخواست به تفکیک endpoint
sum(rate(myapp_http_requests_total[5m])) by (endpoint)
# میانگین زمان پاسخ
histogram_quantile(0.95, rate(myapp_http_request_duration_seconds_bucket[5m]))
# درخواستهای با خطا
rate(myapp_http_requests_total{status!="OK"}[5m])
# Uptime
myapp_uptime_seconds
# Memory usage
myapp_memory_alloc_bytes / 1024 / 1024 # MBبهترین شیوهها
| شیوه | توضیح |
|---|---|
| Namespace استفاده کنید | myapp_ prefix |
| Labelهای معنادار | method, endpoint, status |
| Help text کامل | توضیح واضح متریک |
| Unit در نام | _seconds, _bytes |
| از Histogram استفاده کنید | به جای Summary برای aggregation |
| Labelهای با cardinality بالا نه | مثلاً user_id |
ساختار پروژه کامل
myexporter/
├── main.go
├── collector/
│ ├── http.go
│ ├── system.go
│ └── custom.go
├── Dockerfile
├── docker-compose.yml
├── prometheus.yml
└── go.modDockerfile
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o exporter .
FROM alpine:3.18
COPY --from=builder /app/exporter /exporter
EXPOSE 8080
CMD ["/exporter"]جمعبندی
با Prometheus client library میتوانید:
- متریکهای سفارشی برای اپلیکیشنهای خود بسازید
- سرویسهای خارجی را مانیتور کنید
- متریکهای سیستمی جمعآوری کنید
- با Grafana داشبورد بسازید
این یکی از کاربردهای عملی Go برای DevOps است که مستقیماً در کار روزانه استفاده میشود.