مقدمه
در دنیای زیرساختهای مدرن، Prometheus به استاندارد پیشفرض برای جمعآوری متریکها و هشداردهی تبدیل شده است. با این حال، بسیاری از سازمانها هنوز به سیستمهای «قدیمی» تکیه دارند—اپلیکیشنهای مونولیتیک، مینفریمها یا نرمافزارهای اختصاصی—که از پشتیبانی بومی فرمت نمایش Prometheus محروم هستند. این سیستمها اغلب از طریق تلههای SNMP، لاگهای اختصاصی یا پاسخهای ساده HTTP صحبت میکنند که با لایههای مشاهدهپذیری مدرن ناسازگارند.
تلاش برای اسکرپ (Scrape) مستقیم این سیستمها اغلب به دلیل محدودیتهای امنیتی یا پروتکلها غیرممکن است. راه حل در ساخت Exporterهای سفارشی Prometheus نهفته است. یک Exporter یک سرویس سبکوزن است که بین زیرساخت قدیمی و Prometheus قرار میگیرد و فرمتهای داده قدیمی را به متریکهای استاندارد تبدیل میکند که Prometheus آنها را درک میکند. این مقاله شما را در معماری، پیادهسازی و بهترین شیوهها برای ایجاد این پلها راهنمایی خواهد کرد.
درک معماری Exporter
قبل از ورود به کدنویسی، درک الگوی معماری حیاتی است. یک Exporter سفارشی به عنوان یک آداپتور عمل میکند. این ابزار معمولاً سه عملکرد اصلی را انجام میدهد:
1. **کشف/اتصال:** آن با استفاده از یک پروتکل موجود (مانند JDBC برای پایگاههای داده، SSH برای سرورها یا APIهای HTTP) به سیستم قدیمی متصل میشود.
2. **ترجمه:** دادههای خام را استخراج کرده و آنها را به انواع متریک Prometheus (Counter، Gauge، Histogram یا Summary) نگاشت میکند.
3. **نمایش:** یک نقطه پایانی `/metrics` را با فرمت متنی Prometheus در دسترس قرار میدهد که آماده اسکرپ شدن است.
Exporter خود باید نسبت به دادههای جمعآوری شده Stateless (بدون حالت) باشد، اما در اتصال به سیستم قدیمی Stateful (دارای حالت) باشد. بهترین شیوه این است که Exporter را از اپلیکیشن قدیمی جدا نگه دارید تا از اضافه بار عملکردی بر روی سیستم تولید حیاتی جلوگیری شود.
انتخاب زبان مناسب
اگرچه میتوانید Exporterها را با هر زبانی بسازید، اما Go (Golang) استاندارد صنعتی برای Exporterهای Prometheus است. کتابخانه رسمی `prometheus/client_golang` پریمیتیوهای (Primitives) قوی و سطح پایین را فراهم میکند که عملکرد بالا و مصرف منابع حداقلی را تضمین میکنند. پایتون نیز گزینهای قابل قبول برای اسکریپتهای سادهتر است، اما Go مدیریت همزمانی بهتری ارائه میدهد و کامپایل باینری استاتیک دارد که استقرار را سادهتر میکند.
مثال عملی: ساخت یک Exporter با Go
فرض کنید یک سرویس داخلی قدیمی داریم که یک عدد واحد را از طریق یک درخواست HTTP GET ساده در آدرس `http://legacy-system:8080/status` در دسترس قرار میدهد. ما میخواهیم این داده را به عنوان یک متریک Gauge در Prometheus در دسترس قرار دهیم.
ابتدا مطمئن شوید که وابستگیهای لازم را دارید:
```bash
go get github.com/prometheus/client_golang/prometheus
go get github.com/prometheus/client_golang/prometheus/promhttp
```
در اینجا یک پیادهسازی کامل و حداقلی از یک Exporter سفارشی آورده شده است:
package main
import (
"io/ioutil"
"log"
"net/http"
"strings"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
// legacyStatusGauge متریکی است که میخواهیم در دسترس قرار دهیم
var legacyStatusGauge = prometheus.NewGauge(
prometheus.GaugeOpts{
Name: "legacy_system_status_code",
Help: "کد وضعیت فعلی بازگردانده شده توسط سیستم قدیمی",
},
)
// Collector رابط prometheus.Collector را پیادهسازی میکند
type LegacyCollector struct{}
// Describe مجموعهای از تمام توصیفگرهای ممکن متریکها را ارسال میکند
func (c *LegacyCollector) Describe(ch chan<- *prometheus.Desc) {
ch <- legacyStatusGauge.Desc()
}
// Collect زمانی که رجیستری Prometheus متریکها را جمعآوری میکند فراخوانی میشود
func (c *LegacyCollector) Collect(ch chan<- prometheus.Metric) {
// دریافت داده از سیستم قدیمی
resp, err := http.Get("http://legacy-system:8080/status")
if err != nil {
log.Printf("Error fetching status: %v", err)
// اگر سیستم در دسترس نیست، برگردید؛ اجازه دهید Prometheus خطاهای اتصال را مدیریت کند
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Printf("Error reading body: %v", err)
return
}
// تجزیه پاسخ سیستم قدیمی (فرض بر این است که یک رشته عددی است)
statusValue := 0.0
if strings.TrimSpace(string(body)) != "" {
// در یک سناریوی واقعی، از منطق تجزیه مناسب استفاده کنید
statusValue = 1.0
}
// تنظیم مقدار Gauge
legacyStatusGauge.Set(statusValue)
// ارسال متریک به Prometheus
ch <- legacyStatusGauge
}
func main() {
// ثبت Collector
prometheus.MustRegister(&LegacyCollector{})
// در دسترس قرار دادن نقطه پایانی /metrics
http.Handle("/metrics", promhttp.Handler())
log.Println("Starting exporter on :9100")
log.Fatal(http.ListenAndServe(":9100", nil))
}
بهترین شیوهها برای محیط تولید
هنگام انتقال از نمونه اولیه به محیط تولید، بهینهسازیهای زیر را در نظر بگیرید:
* **مدیریت خطا:** هرگز اجازه ندهید یک خطای جمعآوری باعث Panic (خروج اضطراری) در Exporter شود. اگر سیستم قدیمی در دسترس نباشد، Exporter باید به کار خود ادامه دهد و مقادیر صفر یا مقادیر شناخته شده قبلی (در صورت مناسب بودن) را در دسترس قرار دهد تا Prometheus بتواند بر اساس عدم تازگی دادهها (Staleness) هشدار تولید کند.
* **احراز هویت:** اگر سیستم قدیمی شما به احراز هویت نیاز دارد، مطمئن شوید که اعتبارنامهها به صورت امن از طریق متغیرهای محیطی یا ابزارهای مدیریت اسرار مدیریت میشوند و هرگز به صورت کدنویسی شده (Hardcoded) نباشند.
* **نامگذاری متریکها:** از قراردادهای نامگذاری Prometheus پیروی کنید. از آندرلاین استفاده کنید، نه CamelCase، و واحد را در متن Help بگنجانید. از برچسبهای «اطلاعرسانی» که Cardinality (تعداد مقادیر یکتا) بالایی دارند (مانند شناسههای یکتای کاربر) خودداری کنید، زیرا این امر میتواند پایگاه داده سریهای زمانی Prometheus را دچار مشکل کند.
* **بررسی سلامت (Health Checks):** یک نقطه پایانی `/health` را در کنار `/metrics` پیادهسازی کنید تا به ارگانایزرها (Orchestrators) کانتینری (مانند Kubernetes) اجازه دهد بررسی کنند که Exporter زنده است و میتواند به بکاند متصل شود.
نتیجهگیری
ساخت Exporterهای سفارشی Prometheus یک مهارت ضروری برای مهندسان DevOps است که محیطهای هیبریدی یا مبتنی بر سیستمهای قدیمی را مدیریت میکنند. با جداسازی منطق پایش از اپلیکیشن قدیمی، شما مشاهدهپذیری را بدون به خطر انداختن پایداری کسب میکنید. اگرچه راهاندازی اولیه نیاز به تلاش دارد، اما مزایای بلندمدت هشداردهی یکپارچه، تجسم دادهها و همبستگی دادهها در سراسر کل زیرساخت شما بینظیر است. کوچک شروع کنید، تعاریف متریک خود را تکرار و بهبود دهید و مطمئن شوید که Exporterهای شما به سبک و استحکام سیستمهایی که پایش میکنند، هستند.