[Bahasa] Tracer: Open Telemetry، Golang و Jagger Simple Implementation
![[Bahasa] Tracer: Open Telemetry، Golang و Jagger Simple Implementation [Bahasa] Tracer: Open Telemetry، Golang و Jagger Simple Implementation](https://i1.wp.com/media2.dev.to/dynamic/image/width=1000,height=500,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb1v5lv88bq9ih9yuz1t2.png?w=780&resize=780,470&ssl=1)
ردیاب
Tracer بخشی از Observability است که نقش مهمی در پیاده سازی Microservices Architecture ایفا می کند و یک نمای کلی “Trace” از فرآیندهای در حال اجرا در یک منطق برنامه ارائه می دهد.
به زبان ساده، در Webservice، Tracer یک نمای کلی از مدت زمان اجرای یک منطق را با انتشار یک سیگنال Trace ارائه می دهد. بعداً، این ردیاب پس از ارسال سیگنال از طریق صادر کننده به جمع کننده، به صورت یک دهانه تودرتو دیده می شود. [OpenTelemetry: Traces]
OpenTelemetry
برای اینکه بتواند سیگنالهای Traces را که بعداً میتواند توسط گردآورنده جمعآوری شود، منتقل کند، وبسرویس به OpenTelemetry به عنوان کتابخانهای نیاز دارد که به یک پروتکل استاندارد مشاهدهپذیری تبدیل شده است که معمولاً پروتکل OpenTelemetry (OTLP) نامیده میشود. [OpenTelemetry: Language – Go]
جیگر
تجسم ردیابی سیگنال واقعاً برای ارائه یک نمای کلی از فرآیندهای رخ داده در وب سرویس مورد نیاز است. Jaeger یک پلت فرم منبع باز است که با استفاده از پروتکل ارتباطی HTTP یا gRPC از OTLP پشتیبانی می کند. [Jaeger]
اجرا در Golang
پیادهسازی Tracer در زبان برنامهنویسی Golang یک مورد ساده را پیادهسازی میکند که در آن وبسرویس فقط دادههایی را با مدت زمان پاسخ متفاوت برمیگرداند. کتابخانه های مورد استفاده عبارتند از:
- چی: چارچوب HTTP
- OpenTelemetry: سیگنالینگ تله متری
OpenTelemetry را به عنوان یک ماژول تله متری راه اندازی کنید
پیاده سازی ماژول تله متری در دایرکتوری pkg/telemetry/telemetry.go
:
package telemetry
import (
"context"
"errors"
"time"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
"go.opentelemetry.io/otel/sdk/resource"
"go.opentelemetry.io/otel/sdk/trace"
)
// enumeration constant for which protocol used
const (
HTTP uint8 = iota
GRPC
)
// setup client to connect web-service with Jaeger
func SetupTraceClient(ctx context.Context, protocol uint8, endpoint string) otlptrace.Client {
switch protocol {
case GRPC:
return otlptracegrpc.NewClient(otlptracegrpc.WithEndpoint(endpoint), otlptracegrpc.WithInsecure(), otlptracegrpc.WithCompressor("gzip"))
default:
return otlptracehttp.NewClient(otlptracehttp.WithEndpoint(endpoint), otlptracehttp.WithInsecure(), otlptracehttp.WithCompression(otlptracehttp.NoCompression))
}
}
func setupTraceProvider(ctx context.Context, traceClient otlptrace.Client) (*trace.TracerProvider, error) {
// set resource
res, err := resource.New(
ctx,
resource.WithFromEnv(),
)
if err != nil {
return nil, err
}
// init trace exporter
traceExporter, err := otlptrace.New(ctx, traceClient)
if err != nil {
return nil, err
}
// init trace exporter
traceProvider := trace.NewTracerProvider(
trace.WithBatcher(
traceExporter,
trace.WithBatchTimeout(time.Duration(time.Second*3)),
),
trace.WithResource(res), // Discover and provide attributes from OTEL_RESOURCE_ATTRIBUTES and OTEL_SERVICE_NAME environment variables.
)
return traceProvider, nil
}
func SetupTelemetrySDK(ctx context.Context, traceClient otlptrace.Client) (func(context.Context) error, error) {
var err error
var shutdownFuncs []func(context.Context) error
shutdown := func(ctx context.Context) error {
var err error
for _, fn := range shutdownFuncs {
err = errors.Join(err, fn(ctx))
}
shutdownFuncs = nil
return err
}
handleErr := func(inErr error) {
err = errors.Join(inErr, shutdown(ctx))
}
traceProvider, err := setupTraceProvider(ctx, traceClient)
if err != nil {
handleErr(err)
return shutdown, err
}
shutdownFuncs = append(shutdownFuncs, traceProvider.Shutdown)
otel.SetTracerProvider(traceProvider)
return shutdown, nil
}
سپس، پیکربندی Telemetry را در تابع اصلی تنظیم کنید main.go
:
package main
import (
"context"
"fmt"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/go-chi/chi/v5"
"github.com/wahyurudiyan/medium/otel-jaeger/config"
"github.com/wahyurudiyan/medium/otel-jaeger/pkg/telemetry"
"github.com/wahyurudiyan/medium/otel-jaeger/router"
)
func SetupTelemetry(ctx context.Context, config *config.Config) (func(context.Context) error, error) {
otlpCli := telemetry.SetupTraceClient(ctx, telemetry.GRPC, config.JaegerGRPCEndpoint)
shutdownFn, err := telemetry.SetupTelemetrySDK(ctx, otlpCli)
return shutdownFn, err
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
cfg := config.Get()
shutdownFn, err := SetupTelemetry(ctx, cfg)
if err != nil {
shutdownFn(ctx)
panic(err)
}
r := chi.NewRouter()
r.Route("/api", func(r chi.Router) {
router.Router(r)
})
srv := http.Server{
Addr: "0.0.0.0:8080",
Handler: r,
}
go func() {
fmt.Println("Server running at port:", srv.Addr)
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
fmt.Printf("listen: %s\n", err)
}
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
defer shutdownFn(ctx)
fmt.Println("Server is shutting down...")
if err := srv.Shutdown(context.Background()); err != nil {
fmt.Println("Server forced to shutdown:", err)
}
fmt.Println("Server exiting")
}
از Tracer در مدیریت فایل استفاده کنید router/router.go
برای اینکه بتوانید سیگنال های Traces را ساطع کنید:
package router
import (
"encoding/json"
"net/http"
"time"
"github.com/go-chi/chi/v5"
"github.com/wahyurudiyan/medium/otel-jaeger/pkg/random"
"go.opentelemetry.io/otel"
)
var (
tracer = otel.Tracer("WebServer-Otel-Jaeger")
)
func getUserHandler(w http.ResponseWriter, r *http.Request) {
_, span := tracer.Start(r.Context(), "GetUser")
defer span.End()
user := struct {
Name string
Email string
Password string
}{
Name: "John Doe",
Email: "john@email.com",
Password: "Super5ecr3t!",
}
blob, _ := json.Marshal(&user)
sleepDuration := time.Duration(time.Millisecond * time.Duration(random.GenerateRandNum()))
time.Sleep(sleepDuration)
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write(blob)
}
func Router(router chi.Router) {
router.Get("/user", getUserHandler)
}
استقرار
پیکربندی Docker برای این ساخت وب سرویس، از مکانیسم تصویر ساخت چند مرحله ای در Dockerfile
:
FROM golang:1.23.4 AS build
WORKDIR /src
COPY . .
RUN go get -v
RUN CGO_ENABLED=0 go build -o /bin/service .
FROM alpine:latest
WORKDIR /app
COPY --from=build /bin/service /bin/service
CMD ["/bin/service"]
در مرحله بعد، تصویر ساخت از طریق فایل انجام می شود docker-compose.yaml
با پیکربندی زیر:
services:
web-service:
container_name: service
build:
context: .
dockerfile: Dockerfile
environment:
OTEL_SERVICE_NAME: service-otel-jaeger
JAEGER_GRPC_ENDPOINT: jaeger:4317
entrypoint: ["service"]
ports:
- 8080:8080
jaeger:
container_name: jaeger
image: jaegertracing/all-in-one:latest
environment:
COLLECTOR_ZIPKIN_HOST_PORT: :9411
ports:
- 16686:16686
- 4317:4317
- 4318:4318
- 9411:9411
روشن service.jaeger.ports
، درگاهی که در معرض دید قرار می گیرد پورتی است برای:
- 16686: داشبورد جیگر
- 4317: Jaeger OTLP Protobuf با پروتکل gRPC
- 4318: Jaeger OTLP Protobuf/JSON با پروتکل HTTP
- 9411: زیپکین کلکسیونر
برنامه ای را که روی آن تعریف شده است اجرا کنید docker-compose.yaml
، می توان از دستور استفاده کرد:
docker compose up --build
پس از اجرا شدن برنامه، می توانید برنامه را در نقطه پایانی ضربه بزنید http://127.0.0.1:8080/api/user
، اگر وب سرویس و برنامه متصل باشند، نام سرویس مانند تصویر ظاهر می شود.
span
ظاهر می شود تا مدت زمان لازم برای اجرای یک فرآیند را مشخص کند.
تست بارگذاری
حالا بیایید از ابزار CLI استفاده کنیم hey
[https://github.com/rakyll/hey] برای اجرای تست بارگذاری برای انجام یک تست بارگذاری ساده می توان از دستور زیر استفاده کرد:
hey -c 100 -z 10m http://127.0.0.1:8080/api/user
این دستور یک بار تست را برای 100 درخواست در ثانیه (RPS) به مدت 10 دقیقه اجرا می کند. نتایجی که در صفحه Jaeger UI ظاهر می شود مانند زیر خواهد بود.
اگر آزمایش بار کامل شده باشد، گزارشی از نتایج آزمایش بار ارائه می شود.
Summary:
Total: 600.9545 secs
Slowest: 1.2674 secs
Fastest: 0.1005 secs
Average: 0.5553 secs
Requests/sec: 179.9071
Total data: 7568120 bytes
Size/request: 70 bytes
Response time histogram:
0.101 [1] |
0.217 [21210] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
0.334 [10993] |■■■■■■■■■■■■■■■■■■■■
0.451 [10719] |■■■■■■■■■■■■■■■■■■■■
0.567 [10919] |■■■■■■■■■■■■■■■■■■■■
0.684 [10830] |■■■■■■■■■■■■■■■■■■■■
0.801 [10749] |■■■■■■■■■■■■■■■■■■■■
0.917 [21675] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
1.034 [10902] |■■■■■■■■■■■■■■■■■■■■
1.151 [113] |
1.267 [5] |
Latency distribution:
10% in 0.2009 secs
25% in 0.3027 secs
50% in 0.6010 secs
75% in 0.8028 secs
90% in 0.9604 secs
95% in 1.0028 secs
99% in 1.0069 secs
Details (average, fastest, slowest):
DNS+dialup: 0.0000 secs, 0.1005 secs, 1.2674 secs
DNS-lookup: 0.0000 secs, 0.0000 secs, 0.0000 secs
req write: 0.0000 secs, 0.0000 secs, 0.0237 secs
resp wait: 0.5552 secs, 0.1005 secs, 1.2660 secs
resp read: 0.0001 secs, 0.0000 secs, 0.0216 secs
Status code distribution:
[200] 108116 responses
پروژه Github
برای کسانی که می خواهند کد کامل را امتحان کنند یا ببینند، می توانند مخزن زیر را شبیه سازی کنند: https://github.com/wahyurudiyan/otel-jaeger.