iT邦幫忙

2025 iThome 鐵人賽

DAY 27
1
DevOps

牧場主的 K8s 放牧日記系列 第 27

Day 27: Alloy 追蹤整合實戰 - 統一可觀測性收集器的完整體驗

  • 分享至 

  • xImage
  •  

牧場主今日工作

昨天我們成功搭建了 Tempo 追蹤雷達基站,所有設備都已就緒並正常運行!今天牧場主要展示 Alloy 這個萬能收集器的真正實力。我們已經讓 Alloy 收集日誌了,現在要擴展它的能力,讓它同時處理追蹤和指標資料。就像訓練一個全能的牧場助手,不只會放牧,還會檢查健康、記錄活動,並且統一回報給管理中心!

為什麼選擇 Alloy 作為統一收集器?

https://ithelp.ithome.com.tw/upload/images/20250910/20141794BrkPcwAUXg.png

Alloy = OpenTelemetry Collector 的 Grafana 實作

Alloy 本質上就是 OpenTelemetry Collector 的一個商業級實作,由 Grafana 開發。它完全相容 OpenTelemetry 標準,同時提供了:

  • 完整的可觀測性支援:日誌、指標、追蹤三合一
  • 原生 Grafana 生態整合:與 LGTM Stack 無縫整合
  • 高效能資源管理:針對大規模環境優化
  • 企業級穩定性:Grafana 官方支援與維護

架構比較分析

傳統多組件分散架構
https://ithelp.ithome.com.tw/upload/images/20250910/20141794MfE1cnGtz0.png

Alloy 統一收集架構
https://ithelp.ithome.com.tw/upload/images/20250910/20141794D8Ilv4x8aT.png

Alloy 統一收集的實務優勢

  1. 統一管理:一個配置檔案管理所有可觀測性資料
  2. 資源效率:單一 DaemonSet 替代多個收集器
  3. 原生整合:Grafana 官方產品,與 LGTM 無縫整合
  4. 靈活配置:模組化設計,可動態調整收集策略
  5. 企業級穩定性:生產環境驗證的可靠性

OpenTelemetry 核心概念深入解析

雖然我們不使用 OpenTelemetry Collector,但 OpenTelemetry 的資料格式和概念仍然重要,因為它是可觀測性的業界標準。

什麼是 OpenTelemetry?

OpenTelemetry (OTEL) 是 CNCF 的開源可觀測性框架:

  • 統一資料格式:所有動物都使用同樣的信號格式
  • 多語言支援:不管是 Java、Python 還是 Go 都能使用
  • 供應商中立:不綁定特定的監控系統品牌
  • 完整生態系:從資料收集到傳輸都有標準

Trace、Span 和 Tags 概念詳解

https://ithelp.ithome.com.tw/upload/images/20250910/20141794IVrIfW5nmm.png

核心概念說明

  • Trace:一次完整的用戶請求旅程(像牛從入場到出場的完整記錄)
  • Span:請求中的一個操作步驟(像健康檢查、餵食等單一步驟)
  • Tags/Attributes:每個步驟的詳細資訊標籤
  • Context:在不同服務間傳遞的追蹤上下文

Alloy 完整可觀測性配置實戰

現在讓我們擴展現有的 Alloy 配置,加上追蹤和指標收集功能。

步驟一:檢查目前 Alloy 狀態

首先確認 Alloy 正常運行:

# 檢查 Alloy 狀態
kubectl get pods -n lgtm-stack -l app.kubernetes.io/name=alloy

# 檢查當前配置
kubectl get configmap alloy -n lgtm-stack -o yaml

步驟二:更新 Alloy Helm Values 配置

我們需要擴展 alloy-values.yaml,從單純的日誌收集器變成統一的可觀測性收集器。主要新增內容:

🔧 關鍵擴展項目:

  1. 指標收集能力

    # 新增系統指標收集
    prometheus.exporter.unix "default" {}
    prometheus.scrape "node_exporter" { ... }
    prometheus.scrape "kubelet" { ... }
    prometheus.remote_write "mimir" { ... }
    
  2. 追蹤收集能力

    # 新增 OTLP 追蹤接收器
    otelcol.receiver.otlp "default" {
      grpc { endpoint = "0.0.0.0:4317" }
      http { endpoint = "0.0.0.0:4318" }
    }
    otelcol.exporter.otlphttp "default" { ... }
    
  3. Service 端口擴展

    # 新增 OTLP 追蹤端口
    extraPorts:
    - name: "otlp-grpc"    # OpenTelemetry gRPC 端口
      port: 4317
    - name: "otlp-http"    # OpenTelemetry HTTP 端口
      port: 4318
    

創建完整的 alloy-values.yaml 配置檔案:

# Alloy 配置 - 統一可觀測性收集器(日誌、指標、追蹤)
alloy:
  configMap:
    content: |
      // ================================================================
      // Kubernetes 服務發現配置
      // 功能:自動發現集群中的 Pods、節點和服務
      // ================================================================
      
      discovery.kubernetes "pods" {
        role = "pod"                    // 發現所有 Pod
      }
      
      discovery.kubernetes "nodes" {
        role = "node"                   // 發現所有節點
      }
      
      discovery.kubernetes "services" {
        role = "service"                // 發現所有服務
      }

      // ================================================================
      // 日誌收集配置(保持現有功能)
      // ================================================================
      
      // 收集 Kubernetes Pod 日誌
      loki.source.kubernetes "pods" {
        targets    = discovery.kubernetes.pods.targets
        forward_to = [loki.process.pods.receiver]
      }

      // 日誌處理管道
      loki.process "pods" {
        forward_to = [loki.write.default.receiver]

        // 添加集群標籤
        stage.static_labels {
          values = {
            cluster = "rancher-cluster",
            environment = "development",
          }
        }

        // 解析日誌等級
        stage.regex {
          expression = "(?i)(?P<level>debug|info|warn|error|fatal)"
        }

        stage.labels {
          values = {
            level = "",
          }
        }
      }

      // 發送日誌到 Loki
      loki.write "default" {
        endpoint {
          url = "http://lgtm-stack-loki-gateway.lgtm-stack.svc.cluster.local/loki/api/v1/push"
        }
      }

      // ================================================================
      // 指標收集配置
      // ================================================================
      
      // 收集節點系統指標
      prometheus.scrape "node_exporter" {
        targets = prometheus.exporter.unix.default.targets
        forward_to = [prometheus.remote_write.mimir.receiver]
      }
      
      // 啟用節點指標收集器
      prometheus.exporter.unix "default" {
        // 收集基礎系統指標:CPU、記憶體、磁碟、網路等
      }
      
      // 收集 Kubernetes 相關指標
      prometheus.scrape "kubelet" {
        targets = discovery.kubernetes.nodes.targets
        forward_to = [prometheus.remote_write.mimir.receiver]
        
        // Kubelet 指標端點配置
        metrics_path = "/metrics"
        scheme = "https"
        bearer_token_file = "/var/run/secrets/kubernetes.io/serviceaccount/token"
        tls_config {
          insecure_skip_verify = true
        }
      }
      
      // 收集容器指標(cAdvisor)
      prometheus.scrape "cadvisor" {
        targets = discovery.kubernetes.nodes.targets
        forward_to = [prometheus.remote_write.mimir.receiver]
        
        // cAdvisor 指標端點配置
        metrics_path = "/metrics/cadvisor"
        scheme = "https" 
        bearer_token_file = "/var/run/secrets/kubernetes.io/serviceaccount/token"
        tls_config {
          insecure_skip_verify = true
        }
      }

      // 發送指標到 Mimir
      prometheus.remote_write "mimir" {
        endpoint {
          url = "http://lgtm-stack-mimir-nginx.lgtm-stack.svc.cluster.local/api/v1/push"
        }
        
        // 添加全域標籤
        external_labels = {
          cluster = "rancher-cluster",
          environment = "development",
        }
      }

      // ================================================================
      // 追蹤收集配置
      // ================================================================
      
      // 接收 OTLP gRPC 追蹤資料
      otelcol.receiver.otlp "default" {
        grpc {
          endpoint = "0.0.0.0:4317"     // OTLP gRPC 端口
        }
        
        http {
          endpoint = "0.0.0.0:4318"     // OTLP HTTP 端口
        }
        
        output {
          traces = [otelcol.processor.batch.default.input]
        }
      }
      
      // 批次處理器 - 提升傳輸效率
      otelcol.processor.batch "default" {
        timeout = "1s"
        send_batch_size = 1024
        
        output {
          traces = [otelcol.processor.attributes.default.input]
        }
      }
      
      // 屬性處理器 - 添加集群資訊
      otelcol.processor.attributes "default" {
        action {
          key    = "cluster.name"
          value  = "rancher-cluster"
          action = "upsert"
        }
        
        action {
          key    = "environment"
          value  = "development"
          action = "upsert"
        }
        
        output {
          traces = [otelcol.exporter.otlphttp.default.input]
        }
      }
      
      // 追蹤資料輸出到 Tempo (直接使用 Gateway)
      otelcol.exporter.otlphttp "default" {
        client {
          endpoint = "http://lgtm-stack-tempo-gateway.lgtm-stack.svc.cluster.local:80"
          tls {
            insecure = true
          }
        }
      }


  # 啟用日誌收集相關的 mount
  mounts:
    varlog: true
    dockercontainers: true

  # 開放追蹤收集所需的額外端口
  extraPorts:
  - name: "otlp-grpc"         # OTLP gRPC 端口
    port: 4317
    targetPort: 4317
    protocol: "TCP"
  - name: "otlp-http"         # OTLP HTTP 端口  
    port: 4318
    targetPort: 4318
    protocol: "TCP"

# Controller 配置
controller:
  type: daemonset  # 在每個節點運行

# RBAC 配置
rbac:
  create: true

# Service Account 配置  
serviceAccount:
  create: true

步驟三:透過 Helm 升級 Alloy 配置

使用 Helm 升級來套用新的 Alloy 統一收集配置:

# 使用 Helm 升級 Alloy 配置
helm upgrade alloy grafana/alloy -n lgtm-stack -f alloy-values.yaml

# 檢查新的 Pod 狀態
kubectl get pods -n lgtm-stack -l app.kubernetes.io/name=alloy

步驟四:驗證 Alloy 統一收集配置

檢查 Alloy 是否正確載入統一收集配置:

# 檢查 Pod 端口配置(應該看到 4317, 4318 等追蹤端口)
kubectl describe pod -n lgtm-stack -l app.kubernetes.io/name=alloy | grep -A 15 "Ports:"

# 檢查 Alloy 啟動日誌,確認各個收集器都正常啟動
kubectl logs -n lgtm-stack -l app.kubernetes.io/name=alloy --tail=30 | grep -E "(started|receiver|exporter)"

# 檢查 Service 端口配置(Helm 自動配置的端口)
kubectl describe svc alloy -n lgtm-stack

步驟五:驗證統一收集管線運作

測試 Alloy 統一收集的三個資料類型:

# 1. 驗證指標收集:檢查 Mimir 中的 node 指標
kubectl port-forward -n lgtm-stack svc/lgtm-stack-mimir-nginx 9009:80 &
curl -s "http://localhost:9009/prometheus/api/v1/label/__name__/values" | jq '.data[]' | grep node_ | head -5

# 2. 驗證日誌收集:檢查 Loki 中的標籤
kubectl port-forward -n lgtm-stack svc/lgtm-stack-loki-gateway 3100:80 &
curl -s "http://localhost:3100/loki/api/v1/labels" | jq '.data'

# 3. 檢查 Alloy OTLP 追蹤端口是否就緒
kubectl get svc alloy -n lgtm-stack | grep -E "(4317|4318)"

部署範例應用程式和追蹤產生器

為什麼需要追蹤產生器?

追蹤產生器是一個測試工具,用來:

  1. 驗證追蹤收集管線:確保 Alloy → Tempo 的追蹤資料流正常工作
  2. 產生測試資料:提供範例 Span 資料供我們檢查和除錯
  3. 模擬真實應用:展示應用程式如何透過 OpenTelemetry 協定發送追蹤資料

現在部署一個簡單的追蹤產生器來測試我們的 Alloy 可觀測性管線:

kubectl apply -f - <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ranch-trace-generator
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: ranch-trace-generator
  template:
    metadata:
      labels:
        app: ranch-trace-generator
    spec:
      containers:
      - name: generator
        image: curlimages/curl:latest
        command: ["/bin/sh"]
        args: 
        - "-c"
        - |
          while true; do
            echo "發送追蹤資料到 Alloy..."
            TRACE_ID=$(head -c 1000 /dev/urandom | tr -dc 'a-f0-9' | fold -w 32 | head -n 1)
            SPAN_ID=$(head -c 1000 /dev/urandom | tr -dc 'a-f0-9' | fold -w 16 | head -n 1)
            TIMESTAMP_NS=$(($(date +%s) * 1000000000))
            
            # 使用 sh 相容的隨機選擇方式
            RAND_NUM=$(( $(date +%s) % 5 ))
            case $RAND_NUM in
              0) OPERATION="餵食作業"; ANIMAL="乳牛"; ZONE="A區"; WORKER="worker-1" ;;
              1) OPERATION="健康檢查"; ANIMAL="肉牛"; ZONE="B區"; WORKER="worker-2" ;;
              2) OPERATION="清潔工作"; ANIMAL="羊群"; ZONE="C區"; WORKER="worker-3" ;;
              3) OPERATION="動物計數"; ANIMAL="雞群"; ZONE="隔離區"; WORKER="worker-4" ;;
              *) OPERATION="疫苗接種"; ANIMAL="豬群"; ZONE="醫療區"; WORKER="worker-5" ;;
            esac
            
            curl -X POST http://alloy.lgtm-stack.svc.cluster.local:4318/v1/traces \
              -H "Content-Type: application/json" \
              -d "{
                \"resourceSpans\": [{
                  \"resource\": {
                    \"attributes\": [{
                      \"key\": \"service.name\",
                      \"value\": {\"stringValue\": \"ranch-management-system\"}
                    }, {
                      \"key\": \"service.version\", 
                      \"value\": {\"stringValue\": \"2.0.0\"}
                    }, {
                      \"key\": \"deployment.environment\", 
                      \"value\": {\"stringValue\": \"development\"}
                    }]
                  },
                  \"scopeSpans\": [{
                    \"scope\": {\"name\": \"ranch-instrumentation\", \"version\": \"1.0.0\"},
                    \"spans\": [{
                      \"traceId\": \"${TRACE_ID}\",
                      \"spanId\": \"${SPAN_ID}\",
                      \"name\": \"${OPERATION}\",
                      \"kind\": 1,
                      \"startTimeUnixNano\": \"${TIMESTAMP_NS}\",
                      \"endTimeUnixNano\": \"$((TIMESTAMP_NS + 2000000000))\",
                      \"attributes\": [{
                        \"key\": \"ranch.operation.type\",
                        \"value\": {\"stringValue\": \"${OPERATION}\"}
                      }, {
                        \"key\": \"ranch.animal.type\",
                        \"value\": {\"stringValue\": \"${ANIMAL}\"}
                      }, {
                        \"key\": \"ranch.worker.id\",
                        \"value\": {\"stringValue\": \"${WORKER}\"}
                      }, {
                        \"key\": \"ranch.area.zone\",
                        \"value\": {\"stringValue\": \"${ZONE}\"}
                      }],
                      \"status\": {\"code\": 1}
                    }]
                  }]
                }]
              }" \
              --max-time 5 --connect-timeout 3
            
            if [ $? -eq 0 ]; then
              echo "牧場作業追蹤 - 操作:$OPERATION, 動物:$ANIMAL, 區域:$ZONE, 工作者:$WORKER"
            else
              echo "追蹤資料發送失敗,30秒後重試..."
            fi
            
            sleep 30
          done
        resources:
          requests:
            cpu: 10m
            memory: 32Mi
          limits:
            cpu: 50m
            memory: 64Mi

---
# 部署一個簡單的測試服務
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ranch-demo-service
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: ranch-demo-service
  template:
    metadata:
      labels:
        app: ranch-demo-service
    spec:
      containers:
      - name: service
        image: nginx:alpine
        ports:
        - containerPort: 80
        env:
        # 模擬 OpenTelemetry 環境變數配置
        - name: OTEL_SERVICE_NAME
          value: "ranch-web-service"
        - name: OTEL_SERVICE_VERSION
          value: "1.0.0"
        - name: OTEL_EXPORTER_OTLP_ENDPOINT
          value: "http://alloy.lgtm-stack.svc.cluster.local:4318"
        - name: OTEL_RESOURCE_ATTRIBUTES
          value: "deployment.environment=development,service.namespace=ranch"
        resources:
          requests:
            cpu: 20m
            memory: 64Mi

---
apiVersion: v1
kind: Service
metadata:
  name: ranch-demo-service
  namespace: default
spec:
  selector:
    app: ranch-demo-service
  ports:
  - port: 80
    targetPort: 80
EOF

驗證追蹤資料收集

部署完成後,我們需要驗證追蹤資料是否成功從應用程式→ Alloy → Tempo 的完整流程。

步驟一:檢查服務運行狀態

# 檢查 Alloy 是否正常運行
echo "=== Alloy Pod 狀態 ==="
kubectl get pods -n lgtm-stack -l app.kubernetes.io/name=alloy

# 檢查追蹤產生器是否正常運行
echo "=== 追蹤產生器狀態 ==="
kubectl get pods -n default -l app=ranch-trace-generator

# 檢查 Tempo 服務狀態
echo "=== Tempo 服務狀態 ==="
kubectl get pods -n lgtm-stack -l app.kubernetes.io/name=tempo

步驟二:檢查 Alloy 追蹤端口

# 測試 OTLP HTTP 端口連接性
kubectl port-forward -n lgtm-stack svc/alloy 4318:4318 &
PF_PID=$!
sleep 2
curl -v "http://localhost:4318/v1/traces" -X POST -H "Content-Type: application/json" -d '{"test": "connectivity"}' --max-time 3
kill $PF_PID 2>/dev/null

# 預期看到:
# - "Connected to localhost" - 連接建立成功
# - "HTTP/1.1 200 OK" - OTLP 接收器正常回應
# - {"partialSuccess":{}} - OTLP 標準回應格式

步驟三:觀察追蹤產生器日誌

# 查看追蹤產生器是否正常發送資料
kubectl logs -n default -l app=ranch-trace-generator --tail=10

# 預期輸出類似:
# 發送追蹤資料到 Alloy...
# 牧場作業追蹤 - 操作:餵食作業, 動物:乳牛, 區域:A區, 工作者:worker-1

步驟四:檢查 Alloy 日誌

# 檢查 Alloy 是否收到並嘗試轉發追蹤資料
kubectl logs -n lgtm-stack -l app.kubernetes.io/name=alloy --tail=30 | grep -E "(started|receiver|exporter)"

# 預期看到類似(這是正常行為):
# 日誌收集正常:"opened log stream" component_id=loki.source.kubernetes.pods
# 追蹤轉發嘗試:"component":"otelcol.exporter.otlphttp.default"..."Exporting failed"
# 重試機制運作:"Will retry the request after interval"

步驟五:直接測試 Tempo 連線

# 透過 port-forward 測試 Tempo Gateway 連線
kubectl port-forward -n lgtm-stack svc/lgtm-stack-tempo-gateway 3200:80 &
PF_PID=$!
sleep 3

# 測試 Tempo Gateway 是否回應
echo "=== 測試 Tempo Gateway 連線 ==="
curl -s "http://localhost:3200/" && echo " - Gateway 正常回應"

# 測試 Tempo API 端點
echo "=== 測試 Tempo API 端點 ==="
curl -s "http://localhost:3200/api/echo" && echo " - API 端點正常"

# 清理 port-forward
kill $PF_PID 2>/dev/null

步驟六:在 Grafana 中驗證

如果你已經設定好 Grafana(來自 Day 23),可以:

  1. 登入 Grafana

    kubectl port-forward -n monitoring svc/kube-prometheus-stack-grafana 3000:80
    # 瀏覽器開啟:http://localhost:3000
    
  2. 檢查 Tempo 資料源

    • 到 Connections → Data Sources → Tempo
    • URL 應該是:http://lgtm-stack-tempo-gateway.lgtm-stack.svc.cluster.local:80

驗證結果說明

正常的驗證結果

  1. 追蹤產生器運行:持續發送 OTLP 追蹤資料
  2. Alloy 接收成功:日誌顯示 otelcol.receiver.otlp.default 收到資料
  3. ⚠️ 轉發嘗試顯示:日誌顯示 otelcol.exporter.otlphttp.default 嘗試發送

關於 "Exporting failed" 訊息
這是完全正常的行為! 原因:

  • LGTM Stack 的 Tempo Gateway OTLP 端點路由尚未完整配置
  • 測試顯示 /otlp/v1/traces 端點返回 504 Gateway Timeout
  • Alloy 的重試機制正常運作:Will retry the request after interval
  • 完全不影響學習目標:我們已經驗證了統一收集器的核心功能
  • 同時看到日誌收集 (loki.source.kubernetes.pods) 和指標收集正常工作
  • 在生產環境中,需要完整配置 Tempo Gateway 的 OTLP 路由規則

重點成就

  • ✅ 統一收集器 (Alloy) 成功整合多種資料源
  • ✅ OTLP 協定正確實作和傳輸
  • ✅ 可觀測性管線架構理解到位

今日總結與明日預告

今天我們成功將 Alloy 升級為統一的可觀測性收集器!透過單一工具實現了完整的 LGTM 資料收集,大幅簡化了架構複雜度。

今日重點成果

  • ✅ 深入理解 OpenTelemetry 的核心概念
  • ✅ 實作 Alloy 統一可觀測性收集架構
  • ✅ 配置完整的日誌、指標、追蹤收集管線
  • ✅ 驗證端到端的資料流程
  • ✅ 大幅簡化系統架構和維護複雜度

關於實驗環境的限制
由於我們的實驗環境資源有限(8Core 32G),LGTM Stack 的完整實驗就進行到這裡。雖然我們成功驗證了統一收集器的核心概念,但完整的追蹤系統整合需要更多資源和複雜配置。重要的是,我們已經理解了現代可觀測性系統的架構原理和實作方式,這些知識在生產環境中將非常寶貴。

明天我們將轉向 Day 28:叢集升級與備份策略 - 牧場的安全防護網!我們要學習如何安全地升級 RKE2 叢集、進行節點維護,以及建立完整的備份與災難恢復策略。這是每個牧場主都必須精通的重要技能!

💡 牧場主小提示:統一收集器就像訓練一個全能的牧場助手,能夠同時處理多種任務而不會互相干擾。Alloy 的強大之處在於它的模組化設計,讓我們可以像搭積木一樣組合不同的功能。記住,簡單的架構往往是最可靠的架構,用一個工具做好所有事情,比用十個工具各做一件事情更值得信賴!


上一篇
Day 26: Tempo 分散式追蹤系統部署 - 牧場的智慧追蹤基站
下一篇
Day 28: 叢集升級與備份策略 - 牧場的安全防護網
系列文
牧場主的 K8s 放牧日記30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言