•17 min read

OpenTelemetry LGTM Stack: Hướng dẫn triển khai Loki, Grafana, Tempo & Mimir trong môi trường Production

OpenTelemetry LGTM Stack: Hướng dẫn triển khai Loki, Grafana, Tempo & Mimir trong môi trường Production

Khả năng quan sát ở quy mô lớn đòi hỏi một phương pháp tiếp cận thống nhất đối với nhật ký, số liệu và dấu vết. Bộ công cụ Grafana LGTM—Loki, Grafana, Tempo và Mimir—cung cấp một giải pháp mạnh mẽ, dựa trên đám mây. Hướng dẫn này trình bày chi tiết việc triển khai sản xuất, tập trung vào tích hợp OpenTelemetry (OTel), tương quan tín hiệu chéo và tối ưu hóa chi phí.

Audio Briefing
0:00 / 0:00

Tổng quan kiến trúc

Bộ công cụ LGTM, được bổ sung bởi OpenTelemetry Collectors, tạo thành một nền tảng quan sát gắn kết.

Các thành phần chính:

  • OpenTelemetry Collector: Agent phổ quát để thu thập, xử lý và xuất dữ liệu đo từ xa. Nó hoạt động như hệ thần kinh trung ương, chuẩn hóa dữ liệu trước khi chuyển tiếp đến các thành phần LGTM tương ứng.
  • Mimir (Số liệu): Lưu trữ dài hạn, có khả năng mở rộng theo chiều ngang cho các số liệu Prometheus. Nó cung cấp tính sẵn sàng cao, đa người thuê và truy vấn hiệu quả.
  • Loki (Nhật ký): Hệ thống tổng hợp nhật ký được thiết kế để tiết kiệm chi phí và khả năng mở rộng. Nó lập chỉ mục siêu dữ liệu (nhãn) thay vì toàn bộ nội dung nhật ký, giúp truy vấn hiệu quả.
  • Tempo (Dấu vết): Backend theo dõi phân tán khối lượng lớn, chi phí thấp. Nó lưu trữ các dấu vết và cho phép truy xuất hiệu quả dựa trên ID dấu vết.
  • Grafana: Lớp trực quan hóa, cung cấp bảng điều khiển, cảnh báo và khám phá thống nhất trên tất cả các tín hiệu đo từ xa.
  • Lưu trữ đối tượng (S3/GCS): Lưu trữ dài hạn chính cho Mimir, Loki và Tempo, tận dụng các kho lưu trữ đối tượng dựa trên đám mây để đảm bảo độ bền và hiệu quả chi phí.
Advertisement

Các đường ống OpenTelemetry Collector

OTel Collector rất quan trọng để chuẩn hóa dữ liệu đo từ xa. Chúng tôi sẽ cấu hình nó để nhận OTLP, xử lý và xuất sang Mimir, Loki và Tempo.

Cấu hình Collector (otel-collector-config.yaml)

receivers:
  otlp:
    protocols:
      grpc:
      http:

processors:
  batch:
    send_batch_size: 1024
    timeout: 10s
  resource/add_service_name: # Ensure service.name is present for correlation
    attributes:
      - key: service.name
        value: unknown_service
        action: upsert
  attributes/add_k8s_labels: # Example: Add Kubernetes labels as resource attributes
    actions:
      - key: k8s.pod.name
        from_attribute: k8s.pod.name
        action: upsert
      - key: k8s.namespace.name
        from_attribute: k8s.namespace.name
        action: upsert
  transform/logs: # Transform OTel logs to Loki-compatible format
    log_statements:
      - context: log
        statements:
          - set(attributes["loki.tenant.id"], "default") # Multi-tenancy example
          - set(attributes["loki.resource.labels"], Concat([resource.attributes["service.name"], resource.attributes["k8s.namespace.name"]], ","))
          # Ensure trace_id and span_id are available as attributes for Loki
          - set(attributes["trace_id"], SpanIDToHex(trace_id))
          - set(attributes["span_id"], SpanIDToHex(span_id))
  transform/metrics: # Example: Add tenant ID to metrics
    metric_statements:
      - context: metric
        statements:
          - set(attributes["tenant_id"], "default")
  resource/add_tenant_id: # Add tenant ID to resource attributes for traces
    attributes:
      - key: tenant_id
        value: default
        action: upsert

exporters:
  otlp/mimir:
    endpoint: mimir-gateway.observability.svc.cluster.local:9090 # Mimir OTLP endpoint
    tls:
      insecure: true # Use proper TLS in production
  otlp/loki:
    endpoint: loki-gateway.observability.svc.cluster.local:3100 # Loki OTLP endpoint
    tls:
      insecure: true
  otlp/tempo:
    endpoint: tempo-gateway.observability.svc.cluster.local:4317 # Tempo OTLP endpoint
    tls:
      insecure: true
  logging: # For debugging
    verbosity: detailed

service:
  pipelines:
    metrics:
      receivers: [otlp]
      processors: [resource/add_service_name, attributes/add_k8s_labels, transform/metrics, batch]
      exporters: [otlp/mimir, logging]
    logs:
      receivers: [otlp]
      processors: [resource/add_service_name, attributes/add_k8s_labels, transform/logs, batch]
      exporters: [otlp/loki, logging]
    traces:
      receivers: [otlp]
      processors: [resource/add_service_name, resource/add_tenant_id, batch]
      exporters: [otlp/tempo, logging]

Giải thích:

  • receivers.otlp: Cấu hình collector để chấp nhận dữ liệu OTLP qua gRPC và HTTP.
  • processors.batch: Tập hợp dữ liệu đo từ xa để xuất hiệu quả.
  • processors.resource/add_service_name: Đảm bảo service.name luôn hiện diện, rất quan trọng cho tương quan.
  • processors.attributes/add_k8s_labels: Minh họa việc làm phong phú dữ liệu đo từ xa bằng siêu dữ liệu Kubernetes, hữu ích cho việc lọc và nhóm.
  • processors.transform/logs: Điều này rất quan trọng đối với Loki. Nó thêm loki.tenant.id và loki.resource.labels (mà Loki sử dụng làm nhãn được lập chỉ mục) và trích xuất trace_id và span_id vào các thuộc tính nhật ký, cho phép tương quan dấu vết-sang-nhật ký.
  • exporters.otlp/mimir, otlp/loki, otlp/tempo: Xuất OTLP trực tiếp đến các thành phần LGTM tương ứng. Đảm bảo các điểm cuối này có thể phân giải được trong cụm của bạn.
  • service.pipelines: Xác định cách dữ liệu chảy qua các bộ nhận, bộ xử lý và bộ xuất cho từng loại tín hiệu (số liệu, nhật ký, dấu vết).

Tương quan tín hiệu chéo: Dấu vết-sang-Nhật ký & Nhật ký-sang-Dấu vết

Khả năng quan sát thống nhất phụ thuộc vào khả năng điều hướng giữa các tín hiệu. Grafana tạo điều kiện này với các cấu hình liên kết dữ liệu.

1. Truy sâu từ Dấu vết đến Nhật ký

Khi xem một dấu vết trong Tempo, bạn muốn chuyển đến các nhật ký liên quan đến một span cụ thể. Điều này yêu cầu trace_id và span_id phải có mặt dưới dạng thuộc tính trong nhật ký của bạn. Bộ xử lý transform/logs trong OTel Collector xử lý điều này.

Cấu hình liên kết dữ liệu Grafana (Nguồn dữ liệu Tempo):

{
  "name": "Logs for Span",
  "url": "/explore?orgId=1&left=[\"now-1h\",\"now\",\"Loki\",{\"expr\":\"{service_name=\\\"$${service.name}\\\", trace_id=\\\"$${traceID}\\\", span_id=\\\"$${spanID}\\\"}\",\"refId\":\"A\",\"queryType\":\"range\"},{\"ui\":true}]",
  "targetBlank": true
}

Giải thích:

  • $${service.name}: Suy ra từ span dấu vết.
  • $${traceID}: ID dấu vết của span hiện tại.
  • $${spanID}: ID span của span hiện tại.
  • expr sử dụng LogQL của Loki để lọc nhật ký theo service_name, trace_id và span_id. Các nhãn này phải có mặt trong nhật ký Loki của bạn (như được cấu hình bởi OTel Collector).

2. Truy sâu từ Nhật ký đến Dấu vết

Từ một dòng nhật ký trong Loki, bạn muốn chuyển đến dấu vết tương ứng trong Tempo. Điều này yêu cầu trace_id phải có mặt dưới dạng thuộc tính trong nhật ký.

Cấu hình liên kết dữ liệu Grafana (Nguồn dữ liệu Loki):

{
  "name": "Trace for Log",
  "url": "/explore?orgId=1&left=[\"now-1h\",\"now\",\"Tempo\",{\"query\":\"$${__line.trace_id}\",\"queryType\":\"search\",\"refId\":\"A\"},{\"ui\":true}]",
  "targetBlank": true
}

Giải thích:

  • $${__line.trace_id}: Biến Grafana đặc biệt này trích xuất thuộc tính trace_id từ dòng nhật ký hiện tại. Điều này giả định OTel Collector của bạn đã thêm trace_id làm thuộc tính vào nhật ký.

Bảng điều khiển SLI/SLO tín hiệu cao

Tận dụng Mimir, chúng ta có thể xây dựng các bảng điều khiển SLI/SLO mạnh mẽ. Ví dụ này tập trung vào SLI độ trễ yêu cầu.

1. Định nghĩa SLI/SLO

  • SLI: Tỷ lệ phần trăm yêu cầu được phục vụ với độ trễ p99 dưới 500ms.
  • SLO: 99,9% yêu cầu phải đáp ứng SLI trong cửa sổ 7 ngày.

2. Quy tắc ghi Mimir cho SLI

Xác định các quy tắc ghi trong Mimir để tổng hợp trước dữ liệu SLI. Điều này làm giảm tải truy vấn trên các số liệu thô và cải thiện hiệu suất bảng điều khiển.

# rules.yaml for Mimir
groups:
  - name: service-sli
    rules:
      - record: service_latency_sli_total
        expr: |
          sum by (service_name, http_route) (
            rate(http_server_request_duration_bucket{le="0.5"}[5m])
          )
      - record: service_latency_slo_error_budget
        expr: |
          1 - (
            sum by (service_name, http_route) (
              rate(http_server_request_duration_bucket{le="0.5"}[5m])
            )
            /
            sum by (service_name, http_route) (
              rate(http_server_request_duration_count[5m])
            )
          )

Giải thích:

  • http_server_request_duration_bucket: Giả định ứng dụng của bạn xuất biểu đồ thời lượng yêu cầu máy chủ HTTP OpenTelemetry.
  • service_latency_sli_total: Đếm các yêu cầu trong mục tiêu độ trễ.
  • service_latency_slo_error_budget: Tính toán ngân sách lỗi đã tiêu thụ.

3. Các bảng điều khiển Grafana

  • Trạng thái SLI hiện tại:
    (
      sum by (service_name, http_route) (service_latency_sli_total)
      /
      sum by (service_name, http_route) (rate(http_server_request_duration_count[5m]))
    ) * 100
    
  • Tỷ lệ tiêu hao ngân sách lỗi (cửa sổ 7 ngày):
    sum by (service_name, http_route) (
      increase(service_latency_slo_error_budget[7d])
    )
    
  • Ngân sách lỗi còn lại:
    100 - (
      sum by (service_name, http_route) (
        increase(service_latency_slo_error_budget[7d])
      )
    )
    
Advertisement

Tối ưu hóa chi phí lưu trữ đối tượng (S3/GCS)

Mimir, Loki và Tempo phụ thuộc nhiều vào lưu trữ đối tượng. Tối ưu hóa điều này là rất quan trọng để kiểm soát chi phí.

1. Chính sách lưu giữ dữ liệu

Cấu hình thời gian lưu giữ dựa trên mức độ quan trọng của dữ liệu và tuân thủ.

  • Mimir: Ngắn hạn (ví dụ: 30 ngày) cho các số liệu độ phân giải cao, dài hơn cho các số liệu tổng hợp. Bộ ingester và compactor của Mimir quản lý điều này.
  • Loki: Thường dài hơn (ví dụ: 90-180 ngày) vì nhật ký thường cần thiết cho pháp y. table_manager và compactor của Loki xử lý việc lưu giữ.
  • Tempo: Thường ngắn hơn (ví dụ: 7-30 ngày) do khối lượng lớn, trừ khi tuân thủ cụ thể yêu cầu dài hơn. compactor của Tempo quản lý việc lưu giữ.

Ví dụ cấu hình lưu giữ Loki (loki.yaml):

table_manager:
  retention_period: 90d # 90 days for index and chunks
compactor:
  retention_enabled: true
  retention_period: 90d

2. Các tầng lưu trữ & Chính sách vòng đời

Tận dụng các chính sách vòng đời của nhà cung cấp đám mây để chuyển dữ liệu cũ hơn sang các tầng lưu trữ rẻ hơn (ví dụ: S3 Standard-IA, Glacier, GCS Nearline, Coldline).

Ví dụ chính sách vòng đời S3 (Terraform aws_s3_bucket_lifecycle_configuration):

resource "aws_s3_bucket_lifecycle_configuration" "loki_bucket_lifecycle" {
  bucket = aws_s3_bucket.loki_chunks.id

  rule {
    id     = "loki-retention"
    status = "Enabled"

    transition {
      days          = 30
      storage_class = "STANDARD_IA" # Infrequent Access
    }

    transition {
      days          = 60
      storage_class = "GLACIER" # Archival
    }

    expiration {
      days = 90 # Final deletion
    }
  }
}

3. Nén dữ liệu

Tất cả các thành phần LGTM đều sử dụng nén (Snappy, LZ4, Zstd) cho dữ liệu được lưu trữ trong bộ lưu trữ đối tượng. Đảm bảo cấu hình của bạn đang tận dụng các codec hiệu quả. Điều này thường là mặc định, nhưng đáng để xác minh.

4. Chiến lược phân mảnh & lập chỉ mục

  • Loki: Tối ưu hóa max_chunk_age và chunk_target_size để cân bằng hiệu suất ghi và hiệu quả truy vấn. max_chunk_age nhỏ hơn có nghĩa là xả thường xuyên hơn, có thể nhiều đối tượng nhỏ hơn, nhưng cập nhật chỉ mục nhanh hơn.
  • Tempo: Cân nhắc các chiến lược phân mảnh ID dấu vết nếu bạn có tốc độ nhập rất cao để phân phối tải trên các bộ ingester.

Các vấn đề sản xuất & Khắc phục sự cố

1. Áp lực ngược OTel Collector / OOMKilled

Triệu chứng: Các pod OTel Collector bị OOMKilled hoặc hiển thị mức sử dụng CPU/bộ nhớ cao, làm mất dữ liệu đo từ xa. Nguyên nhân: Tốc độ nhập vượt quá khả năng xử lý/xuất, hoặc cài đặt bộ xử lý batch quá mạnh đối với bộ nhớ khả dụng. Khắc phục:

  • Tăng tài nguyên: Cấp phát thêm CPU/bộ nhớ cho các pod OTel Collector.
  • Điều chỉnh bộ xử lý batch: Tăng send_batch_size và timeout để cho phép các lô lớn hơn, nhưng theo dõi bộ nhớ.
  • Thêm bộ xử lý memory_limiter: Cấu hình giới hạn mềm và cứng để ngăn OOM và loại bỏ dữ liệu một cách duyên dáng dưới áp lực cực lớn.
    processors:
      memory_limiter:
        limit_mib: 256
        spike_limit_mib: 64
        check_interval: 1s
    
  • Mở rộng quy mô: Triển khai thêm các phiên bản OTel Collector phía sau bộ cân bằng tải.

2. Giảm hiệu suất truy vấn Loki

Triệu chứng: Các truy vấn LogQL chậm, đặc biệt là những truy vấn liên quan đến regex hoặc phạm vi thời gian lớn. Nguyên nhân:

  • Quá nhiều kết hợp nhãn duy nhất (tính đa dạng cao).
  • Các truy vấn quét quá nhiều luồng nhật ký.
  • Các biểu thức LogQL không hiệu quả.
  • Không đủ tài nguyên Loki query-frontend/querier. Khắc phục:
  • Xem xét nhãn: Giảm thiểu số lượng nhãn duy nhất. Tránh thêm các thuộc tính có tính đa dạng cao (ví dụ: ID yêu cầu, đường dẫn URL đầy đủ) làm nhãn Loki. Thay vào đó, hãy sử dụng chúng làm thuộc tính nội dung nhật ký.
  • Tối ưu hóa LogQL: Bắt đầu truy vấn bằng các nhãn có tính chọn lọc cao. Sử dụng line_format để trích xuất dữ liệu từ nội dung nhật ký thay vì lập chỉ mục.
  • Tăng tài nguyên Loki: Mở rộng quy mô các thành phần query-frontend và querier.
  • Tối ưu hóa chỉ mục: Đảm bảo max_chunk_age và chunk_target_size được cân bằng.

3. Lỗi nhập dấu vết Tempo

Triệu chứng: Dấu vết bị thiếu hoặc không đầy đủ trong Tempo. Nguyên nhân:

  • OTel Collector không chuyển tiếp dấu vết chính xác.
  • Các bộ ingester của Tempo bị quá tải hoặc không khỏe mạnh.
  • Công cụ dịch vụ không chính xác (ví dụ: thiếu truyền ngữ cảnh dấu vết). Khắc phục:
  • Kiểm tra nhật ký OTel Collector: Tìm lỗi xuất sang Tempo.
  • Giám sát các bộ ingester của Tempo: Kiểm tra tình trạng và mức sử dụng tài nguyên của chúng. Mở rộng quy mô nếu cần.
  • Xác minh công cụ: Đảm bảo tất cả các dịch vụ đang truyền ngữ cảnh dấu vết chính xác (ví dụ: tiêu đề W3C Trace Context). Sử dụng curl với tiêu đề traceparent để kiểm tra.
  • Tăng max_block_bytes của bộ ingester Tempo: Nếu dấu vết rất lớn, đây có thể là một nút thắt cổ chai.

4. Các vấn đề về tính đa dạng cao của Mimir

Triệu chứng: Các bộ ingester/compactor của Mimir đang gặp khó khăn, chi phí lưu trữ cao, truy vấn số liệu chậm. Nguyên nhân: Quá nhiều kết hợp nhãn duy nhất trên các số liệu. Khắc phục:

  • Ghi nhãn lại số liệu: Sử dụng bộ xử lý metricstransform của OTel Collector hoặc relabel_configs của Prometheus để loại bỏ hoặc đổi tên các nhãn có tính đa dạng cao trước khi nhập.
    processors:
      metricstransform:
        transforms:
          - include: "http_request_duration_seconds_bucket"
            action: "delete_label"
            label: "request_id" # Example: delete high-cardinality label
    
  • Xem xét công cụ: Giáo dục các nhà phát triển về việc tránh các nhãn có tính đa dạng cao.
  • Giới hạn Mimir: Cấu hình max_series_per_user và max_label_names_per_series của Mimir để ngăn chặn lạm dụng.

Các câu hỏi thường gặp

Q1: Làm cách nào để xử lý đa người thuê với bộ công cụ LGTM?

A1: Mimir, Loki và Tempo được thiết kế cho đa người thuê.

  • Mimir: Sử dụng tiêu đề HTTP X-Scope-OrgID (hoặc thuộc tính OTLP tenant_id) để phân tách người thuê. Mỗi người thuê có dữ liệu riêng biệt.
  • Loki: Tương tự như Mimir, sử dụng X-Scope-OrgID hoặc thuộc tính nhật ký OTLP loki.tenant.id.
  • Tempo: Sử dụng X-Scope-OrgID hoặc thuộc tính tài nguyên OTLP tenant_id.
  • Grafana: Cấu hình các nguồn dữ liệu riêng biệt cho mỗi người thuê, hoặc sử dụng các biến mẫu cho X-Scope-OrgID trong bảng điều khiển.

Q2: Cách khuyến nghị để triển khai bộ công cụ LGTM trong Kubernetes là gì?

A2: Helm charts là tiêu chuẩn. Grafana Labs cung cấp các biểu đồ chính thức cho Loki, Mimir và Tempo. Đối với OpenTelemetry Collector, sử dụng biểu đồ Helm opentelemetry-collector. Đảm bảo bạn cấu hình lưu trữ bền vững cho các thành phần có trạng thái (ví dụ: Mimir ingesters, Loki ingesters) và sử dụng backend lưu trữ đối tượng mạnh mẽ.

Q3: Làm cách nào để tự giám sát bộ công cụ LGTM?

A3: Các thành phần LGTM hiển thị các số liệu Prometheus. Triển khai một phiên bản Prometheus chuyên dụng (hoặc sử dụng chính Mimir) để thu thập các số liệu này. Tạo các bảng điều khiển Grafana để giám sát tình trạng, mức sử dụng tài nguyên, tốc độ nhập, độ trễ truy vấn và tỷ lệ lỗi của chúng. Các số liệu chính bao gồm loki_ingester_received_entries_total, mimir_ingester_received_samples_total, tempo_ingester_traces_received_total và các số liệu _grpc_server_handled_total khác nhau.

Q4: Tôi có thể sử dụng các số liệu Prometheus hiện có với Mimir không?

A4: Có. Mimir là một kho lưu trữ dài hạn cho Prometheus. Bạn có thể cấu hình các máy chủ Prometheus hiện có của mình để ghi từ xa vào Mimir, hoặc sử dụng bộ nhận Prometheus của OpenTelemetry Collector và bộ xuất OTLP sang Mimir. Cách thứ hai thường được ưu tiên hơn cho một phương pháp OTel thống nhất.

Q5: Những đánh đổi giữa lập chỉ mục dựa trên nhãn của Loki và lập chỉ mục nhật ký toàn văn truyền thống là gì?

A5:

Tính năngLoki (Dựa trên nhãn)Truyền thống (Toàn văn)
Chi phíLưu trữ thấp hơn, ít tính toán hơn cho lập chỉ mụcLưu trữ cao hơn, nhiều tính toán hơn cho lập chỉ mục
Tốc độ truy vấnNhanh cho các truy vấn được lọc theo nhãn, chậm hơn cho văn bảnNhanh cho tìm kiếm toàn văn, chậm hơn cho các bộ lọc phức tạp
Khả năng mở rộngCó khả năng mở rộng cao theo chiều ngangCó khả năng mở rộng, nhưng quản lý chỉ mục có thể phức tạp
Tính linh hoạtYêu cầu các nhãn được xác định trước để tìm kiếm hiệu quảCó thể tìm kiếm trên bất kỳ nội dung nhật ký nào
Trường hợp sử dụngGỡ lỗi hoạt động, các mẫu đã biếtPhân tích bảo mật, khám phá ngẫu hứng

Loki vượt trội khi bạn biết mình đang tìm kiếm gì (ví dụ: nhật ký từ service=X trong namespace=Y). Đối với các tìm kiếm toàn văn tùy ý trên các nhật ký không có cấu trúc, rộng lớn, các hệ thống truyền thống có thể hoạt động tốt hơn nhưng với chi phí cao hơn đáng kể.

Share this article:

Stay Updated

Get the latest posts delivered straight to your inbox.

Free Developer Utilities

Free In-Browser Developer Tools

Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.

Explore Tools
Advertisement