ai

Distributed Tracing Monitoring และ Alerting

Distributed Tracing Monitoring และ Alerting

ทำไม Monitoring และ Alerting ถึงสำคัญสำหรับ Distributed Tracing

Distributed Tracing Monitoring และ Alerting

ระบบ microservices ที่มี distributed tracing เก็บข้อมูลมหาศาล แต่ถ้าไม่มี monitoring และ alerting ที่ดี ข้อมูลเหล่านั้นก็ไม่มีประโยชน์ ต้องสร้างระบบที่แปลง trace data ให้เป็น actionable alerts ได้อัตโนมัติ เช่น แจ้งเตือนเมื่อ P99 latency ของ service ใด service หนึ่งพุ่งสูงกว่าปกติ หรือ error rate เกินค่าที่ยอมรับได้

สถาปัตยกรรมของระบบ Trace Monitoring

ข้อมูล trace จาก application ส่งผ่าน OpenTelemetry Collector ซึ่งสร้าง span metrics (RED metrics) แล้วส่งไปยัง Prometheus สำหรับ alerting ส่วน raw trace ส่งไป Jaeger สำหรับ drill-down

# docker-compose.yml - Full monitoring stack

version: '3.8'

services:

  otel-collector:

    image: otel/opentelemetry-collector-contrib:0.96.0

    command: ["--config=/etc/otel-config.yaml"]

    volumes:

      - ./otel-config.yaml:/etc/otel-config.yaml

    ports:

      - "4317:4317"

      - "4318:4318"

      - "8889:8889"



  jaeger:

    image: jaegertracing/all-in-one:1.54

    ports:

      - "16686:16686"

    environment:

      - COLLECTOR_OTLP_ENABLED=true



  prometheus:

    image: prom/prometheus:v2.50.0

    ports:

      - "9090:9090"

    volumes:

      - ./prometheus.yml:/etc/prometheus/prometheus.yml

      - ./alert-rules.yml:/etc/prometheus/alert-rules.yml



  alertmanager:

    image: prom/alertmanager:v0.27.0

    ports:

      - "9093:9093"

    volumes:

      - ./alertmanager.yml:/etc/alertmanager/alertmanager.yml



  grafana:

    image: grafana/grafana:10.3.0

    ports:

      - "3000:3000"

    environment:

      - GF_SECURITY_ADMIN_PASSWORD=admin

    volumes:

      - grafana_data:/var/lib/grafana



volumes:

  grafana_data:

ตั้งค่า OpenTelemetry Collector สร้าง Span Metrics

spanmetrics connector แปลง trace data เป็น Prometheus metrics อัตโนมัติ ได้ทั้ง request count, duration histogram และ error count

เนื้อหาเกี่ยวข้อง — ดูเพิ่มเติมเรื่อง Skaffold Dev Code Review Best Practice

# otel-config.yaml

receivers:

  otlp:

    protocols:

      grpc:

        endpoint: 0.0.0.0:4317

      http:

        endpoint: 0.0.0.0:4318



connectors:

  spanmetrics:

    histogram:

      explicit:

        buckets: [5ms, 10ms, 25ms, 50ms, 100ms, 250ms, 500ms, 1s, 2.5s, 5s, 10s]

    dimensions:

      - name: http.method

      - name: http.status_code

      - name: http.route

    exemplars:

      enabled: true

    metrics_flush_interval: 15s



processors:

  batch:

    timeout: 5s

    send_batch_size: 1024

  memory_limiter:

    check_interval: 1s

    limit_mib: 2048



exporters:

  otlp/jaeger:

    endpoint: jaeger:4317

    tls:

      insecure: true

  prometheus:

    endpoint: 0.0.0.0:8889

    namespace: traces



service:

  pipelines:

    traces:

      receivers: [otlp]

      processors: [memory_limiter, batch]

      exporters: [spanmetrics, otlp/jaeger]

    metrics/spanmetrics:

      receivers: [spanmetrics]

      exporters: [prometheus]

Prometheus Alert Rules สำหรับ Trace Metrics

สร้าง alert rules ครอบคลุม 3 สถานการณ์หลัก: latency สูง, error rate สูง และ throughput ตก

# alert-rules.yml

groups:

  - name: trace_latency_alerts

    rules:

      - alert: HighP99Latency

        expr: |

          histogram_quantile(0.99,

            sum(rate(traces_spanmetrics_duration_seconds_bucket{span_kind="SPAN_KIND_SERVER"}[5m])) by (le, service_name)

          ) > 2.0

        for: 3m

        labels:

          severity: critical

          team: platform

        annotations:

          summary: "{{ $labels.service_name }} P99 latency สูงกว่า 2 วินาที"

          description: "P99 latency ปัจจุบัน {{ $value | humanizeDuration }}"

          runbook: "https://wiki.internal/runbooks/high-latency"



      - alert: HighP50Latency

        expr: |

          histogram_quantile(0.5,

            sum(rate(traces_spanmetrics_duration_seconds_bucket{span_kind="SPAN_KIND_SERVER"}[5m])) by (le, service_name)

          ) > 0.5

        for: 5m

        labels:

          severity: warning

        annotations:

          summary: "{{ $labels.service_name }} median latency สูงกว่า 500ms"



  - name: trace_error_alerts

    rules:

      - alert: HighErrorRate

        expr: |

          sum(rate(traces_spanmetrics_calls_total{status_code="STATUS_CODE_ERROR"}[5m])) by (service_name)

          /

          sum(rate(traces_spanmetrics_calls_total[5m])) by (service_name)

          > 0.05

        for: 2m

        labels:

          severity: critical

        annotations:

          summary: "{{ $labels.service_name }} error rate {{ $value | humanizePercentage }}"



      - alert: ErrorRateSpike

        expr: |

          sum(rate(traces_spanmetrics_calls_total{status_code="STATUS_CODE_ERROR"}[5m])) by (service_name)

          /

          sum(rate(traces_spanmetrics_calls_total{status_code="STATUS_CODE_ERROR"}[1h])) by (service_name)

          > 3

        for: 1m

        labels:

          severity: warning

        annotations:

          summary: "{{ $labels.service_name }} error rate เพิ่มขึ้น 3 เท่าจากชั่วโมงที่ผ่านมา"



  - name: trace_throughput_alerts

    rules:

      - alert: LowThroughput

        expr: |

          sum(rate(traces_spanmetrics_calls_total{span_kind="SPAN_KIND_SERVER"}[5m])) by (service_name)

          < 1

        for: 5m

        labels:

          severity: warning

        annotations:

          summary: "{{ $labels.service_name }} throughput ต่ำกว่า 1 req/s อาจมีปัญหา"



      - alert: ThroughputDrop

        expr: |

          sum(rate(traces_spanmetrics_calls_total{span_kind="SPAN_KIND_SERVER"}[5m])) by (service_name)

          /

          sum(rate(traces_spanmetrics_calls_total{span_kind="SPAN_KIND_SERVER"}[1h])) by (service_name)

          < 0.3

        for: 3m

        labels:

          severity: critical

        annotations:

          summary: "{{ $labels.service_name }} throughput ลดลงมากกว่า 70%"

ตั้งค่า Alertmanager ส่งแจ้งเตือนไป Slack และ LINE

Distributed Tracing Monitoring และ Alerting
# alertmanager.yml

global:

  resolve_timeout: 5m

  slack_api_url: 'https://hooks.slack.com/services/T00000/B00000/XXXXXXX'



route:

  receiver: 'slack-default'

  group_by: ['alertname', 'service_name']

  group_wait: 30s

  group_interval: 5m

  repeat_interval: 4h

  routes:

    - match:

        severity: critical

      receiver: 'slack-critical'

      group_wait: 10s

      repeat_interval: 1h

    - match:

        severity: warning

      receiver: 'slack-warning'



receivers:

  - name: 'slack-default'

    slack_configs:

      - channel: '#alerts-platform'

        title: '{{ .GroupLabels.alertname }}'

        text: |

          *Service:* {{ .GroupLabels.service_name }}

          *Severity:* {{ .CommonLabels.severity }}

          {{ range .Alerts }}

          - {{ .Annotations.summary }}

          {{ end }}



  - name: 'slack-critical'

    slack_configs:

      - channel: '#alerts-critical'

        title: 'CRITICAL: {{ .GroupLabels.alertname }}'

        color: '#ff0000'

        text: |

          {{ range .Alerts }}

          *{{ .Annotations.summary }}*

          {{ .Annotations.description }}

          Runbook: {{ .Annotations.runbook }}

          {{ end }}

    webhook_configs:

      - url: 'https://notify-api.line.me/api/notify'

        http_config:

          authorization:

            type: Bearer

            credentials: 'LINE_NOTIFY_TOKEN'



  - name: 'slack-warning'

    slack_configs:

      - channel: '#alerts-warning'

        color: '#ffa500'

Prometheus Config สำหรับ Scrape Metrics

# prometheus.yml

global:

  scrape_interval: 15s

  evaluation_interval: 15s



alerting:

  alertmanagers:

    - static_configs:

        - targets: ['alertmanager:9093']



rule_files:

  - /etc/prometheus/alert-rules.yml



scrape_configs:

  - job_name: 'otel-collector'

    static_configs:

      - targets: ['otel-collector:8889']

    scrape_interval: 10s



  - job_name: 'prometheus'

    static_configs:

      - targets: ['localhost:9090']

Instrument Application ด้วย OpenTelemetry

# ติดตั้ง OpenTelemetry สำหรับ Go application

go get go.opentelemetry.io/otel

go get go.opentelemetry.io/otel/sdk

go get go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc

go get go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp
// main.go - Go service พร้อม OpenTelemetry

package main



import (

    "context"

    "log"

    "net/http"

    "time"



    "go.opentelemetry.io/otel"

    "go.opentelemetry.io/otel/attribute"

    "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"

    "go.opentelemetry.io/otel/sdk/resource"

    sdktrace "go.opentelemetry.io/otel/sdk/trace"

    semconv "go.opentelemetry.io/otel/semconv/v1.24.0"

    "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"

)



func initTracer() (*sdktrace.TracerProvider, error) {

    ctx := context.Background()



    exporter, err := otlptracegrpc.New(ctx,

        otlptracegrpc.WithEndpoint("otel-collector:4317"),

        otlptracegrpc.WithInsecure(),

    )

    if err != nil {

        return nil, err

    }



    tp := sdktrace.NewTracerProvider(

        sdktrace.WithBatcher(exporter,

            sdktrace.WithBatchTimeout(5*time.Second),

            sdktrace.WithMaxExportBatchSize(512),

        ),

        sdktrace.WithResource(resource.NewWithAttributes(

            semconv.SchemaURL,

            semconv.ServiceName("order-service"),

            semconv.ServiceVersion("1.0.0"),

            attribute.String("environment", "production"),

        )),

        sdktrace.WithSampler(sdktrace.ParentBased(

            sdktrace.TraceIDRatioBased(0.1),

        )),

    )



    otel.SetTracerProvider(tp)

    return tp, nil

}



func main() {

    tp, err := initTracer()

    if err != nil {

        log.Fatal(err)

    }

    defer tp.Shutdown(context.Background())



    tracer := otel.Tracer("order-service")



    handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

        ctx, span := tracer.Start(r.Context(), "process-order")

        defer span.End()



        span.SetAttributes(

            attribute.String("order.id", r.URL.Query().Get("id")),

            attribute.String("http.route", "/api/orders"),

        )



        // simulate processing

        time.Sleep(50 * time.Millisecond)

        w.Write([]byte(`{"status":"ok"}`))

        _ = ctx

    })



    wrappedHandler := otelhttp.NewHandler(handler, "order-api")

    log.Println("Starting server on :8080")

    log.Fatal(http.ListenAndServe(":8080", wrappedHandler))

}

Grafana Dashboard สำหรับ Trace Monitoring

# grafana-datasources.yml

apiVersion: 1

datasources:

  - name: Prometheus

    type: prometheus

    url: http://prometheus:9090

    isDefault: true

  - name: Jaeger

    type: jaeger

    url: http://jaeger:16686
# PromQL queries สำหรับ dashboard panels



# Panel 1: Request Rate per Service

sum(rate(traces_spanmetrics_calls_total{span_kind="SPAN_KIND_SERVER"}[5m])) by (service_name)



# Panel 2: P50/P95/P99 Latency

histogram_quantile(0.50, sum(rate(traces_spanmetrics_duration_seconds_bucket{span_kind="SPAN_KIND_SERVER"}[5m])) by (le, service_name))

histogram_quantile(0.95, sum(rate(traces_spanmetrics_duration_seconds_bucket{span_kind="SPAN_KIND_SERVER"}[5m])) by (le, service_name))

histogram_quantile(0.99, sum(rate(traces_spanmetrics_duration_seconds_bucket{span_kind="SPAN_KIND_SERVER"}[5m])) by (le, service_name))



# Panel 3: Error Rate %

100 * sum(rate(traces_spanmetrics_calls_total{status_code="STATUS_CODE_ERROR"}[5m])) by (service_name)

/ sum(rate(traces_spanmetrics_calls_total[5m])) by (service_name)



# Panel 4: Top 10 Slowest Operations

topk(10,

  histogram_quantile(0.95,

    sum(rate(traces_spanmetrics_duration_seconds_bucket[5m])) by (le, span_name, service_name)

  )

)



# Panel 5: Active Alerts

ALERTS{alertstate="firing"}

FAQ - คำถามที่พบบ่อย

Q: Span Metrics กับ Application Metrics ต่างกันอย่างไร?

แนะนำเพิ่มเติม — ระบบเทรดของ iCafeForex

A: Span Metrics สร้างจาก trace data อัตโนมัติ ครอบคลุมทุก service ที่ส่ง trace มาโดยไม่ต้องเขียน code เพิ่ม Application Metrics คือ custom metrics ที่นักพัฒนาเขียนเอง เช่น จำนวน orders, revenue ควรใช้ทั้งสองอย่างเสริมกัน

เนื้อหาเกี่ยวข้อง — อ่านต่อ: ครสซ — คู่มือฉบับสมบูรณ์ 2026

Q: ตั้ง alert threshold เท่าไหร่ดี?

A: เริ่มจากดู baseline ของระบบก่อน 1-2 สัปดาห์ แล้วตั้ง threshold ที่ 2-3 เท่าของค่า P99 ปกติ สำหรับ error rate เริ่มที่ 5% ปรับตามความเหมาะสมของแต่ละ service อย่าตั้งเข้มเกินไปจนเกิด alert fatigue

แนะนำเพิ่มเติม — ดูสัญญาณเทรดที่ XM Signal

เนื้อหาเกี่ยวข้อง — ทำความเข้าใจ Prometheus Federation RBAC ABAC Policy — คู่มือฉบับสมบูรณ์ 2026

Q: ใช้ Grafana Alerting แทน Alertmanager ได้ไหม?

A: ได้ Grafana 10+ มี alerting engine ที่ดีมาก ข้อดีคือตั้ง alert ได้จาก UI โดยไม่ต้องเขียน YAML ข้อเสียคือ Alertmanager มี features ที่ mature กว่า เช่น silencing, inhibition rules และ template ที่ยืดหยุ่นกว่า ถ้าใช้ Prometheus stack อยู่แล้วแนะนำ Alertmanager

Q: ควรเก็บ trace data นานแค่ไหน?

เนื้อหาเกี่ยวข้อง — ดูเพิ่มเติมเรื่อง ฟิวเจอร์บอร์ด 3 พับ

A: Raw traces เก็บ 7-14 วันพอ เพราะใช้สำหรับ debugging เฉพาะ incident ที่เพิ่งเกิด ส่วน span metrics ที่ Prometheus เก็บควรเก็บ 30-90 วัน เพื่อดู trend และทำ capacity planning ถ้าต้องการเก็บ metrics นานกว่านั้นใช้ Thanos หรือ Mimir

XM Legend · เทรดเดอร์ & ผู้สอน Forex 13 ปี

ผู้ก่อตั้ง SiamCafe ตั้งแต่ปี 1997 · เทรดเดอร์สาย Forex มากกว่า 13 ปี ได้รับการยกย่องเป็น XM Legend · แบ่งปันความรู้ Forex, ไอที, AI และการเทรด จากประสบการณ์จริงในตลาดจริง