WildCat's Blog

基於 Open Telemetry 的自助遙測解決方案

This blog post is translated from its original English version.

為什麼我想自己託管遙測堆棧(Telemetry Stack)

到了 2024 年,市場上實際上沒有可負擔得起、完全可自定義且注重隱私的遙測解決方案。 本文介紹了一種遙測/數據分析的另一種方式,即利用 Open Telemetry 構建解決方案,開發成本幾乎為零。

這個堆棧可能看起來非常簡單和脆弱。但它比付費的 SaaS 解決方案擁有更大的潛力。如果您發現此解決方案有用,請考慮在 GitHub 上贊助我,https://github.com/sponsors/imWildCat。 多年來,我發現要將高級軟件技能變現更加困難。您的贊助對我創建更多有用內容意義重大。謝謝!

简单的背景介绍

在我的應用程式 Twilar,這是一個離線優先的稍後閱讀應用程式,我們重視隱私。我們計劃停用第三方遙測解決方案,如 Google Firebase。 此外,我們還想測量應用程式的高頻性能數據,這樣我們就能構建世界一流的體驗。 順便說一下,如果您想支持我但不在 GitHub 上,您可以考慮購買這款應用程式:)再次感謝!

為什麼選擇 Open Telemetry

Open Telemetry 已經是一個廣泛使用的遙測標準,具有生產就緒的 SDK 和後端(收集器、出口器等)。許多大型科技公司在未明確提及的情況下使用此解決方案。

它基本上提供了滿足您幾乎所有遙測“收集”和“存儲”需求的端到端解決方案。然而,對於數據的可視化,您需要另一個項目,如 Apache Superset,MetaBase。

順便說一下,對於數據存儲,您可以使用任何您喜歡的 SQL/非 SQL 數據庫(https://opentelemetry.io/docs/specs/semconv/attributes-registry/db/)或其他解決方案。

在這篇文章中,我們將使用 ClickHouse。

開始使用(服務器端)

├── collector
│   ├── Dockerfile
│   └── otel-collector-config.yaml
├── docker-compose.yml
└── superset
    ├── Dockerfile
    ├── superset-init.sh
    └── superset_config.py

Docker Compose

“Talk is cheap, show me your code”

在這篇文章中,我們使用 Docker Compose 來展示應用程式的樣子。

version: "3"
services:
  otel-collector: # 服務端點接收 http/grpc 數據
    image: otel/opentelemetry-collector-contrib:0.92.0
    restart: always
    volumes:
      - ./collector/otel-collector-config.yaml:/etc/otel-collector-config.yaml
    command: ["--config=/etc/otel-collector-config.yaml", "${OTELCOL_ARGS}"]
    environment:
      - CLICKHOUSE_PASSWORD=${CLICKHOUSE_PASSWORD}
    depends_on:
      - clickhouse
    # ports:
    #   - "4317:4317" # gRPC
    #   - "4318:4318" # HTTP

  clickhouse:
    image: clickhouse/clickhouse-server
    # ports:
    #   - "8123:8123" # HTTP 接口
    environment:
      - CLICKHOUSE_DB=app_otel
      - CLICKHOUSE_USER=app_otel
      - CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT=1
      - CLICKHOUSE_PASSWORD=${CLICKHOUSE_PASSWORD}
    volumes:
      # 原始數據
      - ./data/clickhouse/data:/var/lib/clickhouse
      # 日誌
      - ./data/clickhouse/log:/var/log/clickhouse-server
    expose:
      - "8123"
      - "9000"
    ulimits:
      nofile:
        soft: 262144
        hard: 262144
    logging: &default_logging
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

  superset:
    build: # 我們需要 clickhouse 驅動程序,所以我們必須為此映像運行 pip install
      context: ./superset
      dockerfile: Dockerfile
    depends_on:
      - clickhouse
    # ports:
    #   - "8088:8088" # Superset 網頁應用
    environment:
      - SUPERSET_HOME=/data
      - SUPERSET_LOAD_EXAMPLES=true
      - ADMIN_USERNAME=${SUPERSET_ADMIN_USERNAME}
      - ADMIN_EMAIL=${SUPERSET_ADMIN_EMAIL}
      - ADMIN_PASSWORD=${SUPERSET_ADMIN_PASSWORD}
      - SUPERSET_SECRET_KEY=${SUPERSET_SECRET_KEY}
    volumes:
      - ./data/superset_home:/data
    logging: *default_logging

  # TODO: 如果您不需要 ClickHouse 或其他數據庫驅動程序,請避免自定義構建
  # superset:
  #   image: apache/superset:latest-dev
  #   depends_on:
  #     - clickhouse
  #   # ports:
  #   #   - "8088:8088" # Superset 網頁應用
  #   environment:
  #     - SUPERSET_LOAD_EXAMPLES=yes
  #     - SUPERSET_SECRET_KEY=${SUPERSET_SECRET_KEY}
  # volumes:
  #   - ./superset_home:/app/superset_home
  # logging: *default_logging

  redis:
    image: redis:alpine
    restart: always
    volumes:
      - ./data/redis:/data

  cloudflared-collector:
    image: cloudflare/cloudflared
    command: tunnel --no-autoupdate run --url http://otel-collector:4318 --no-tls-verify
    depends_on:
      - otel-collector
    environment:
      - TUNNEL_TOKEN=${COLLECTOR_TUNNEL_TOKEN}
      - NO_AUTOUPDATE=true
      - TUNNEL_PROXY_ADDRESS=otel-collector
      - TUNNEL_PROXY_PORT=4318
    logging: *default_logging

  cloudflared-superset:
    image: cloudflare/cloudflared
    command: tunnel --no-autoupdate run --url http://superset:8088 --no-tls-verify
    depends_on:
      - superset
    environment:
      - TUNNEL_TOKEN=${SUPERSET_TUNNEL_TOKEN}
      - NO_AUTOUPDATE=true
      - TUNNEL_PROXY_ADDRESS=superset
      - TUNNEL_PROXY_PORT=8088
    logging: *default_logging

其中 .env 文件如下:

# CloudFlare 隧道令牌,因此您無需在主機機器上暴露端口並構建另一個反向代理(即 nginx)
# 但請隨意構建您自己的堆棧來公開 Superset 服務。
# 欲瞭解更多資訊,請參考 <https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/>
COLLECTOR_TUNNEL_TOKEN=
SUPERSET_TUNNEL_TOKEN=

CLICKHOUSE_PASSWORD=

SUPERSET_ADMIN_USERNAME=
SUPERSET_ADMIN_EMAIL=
SUPERSET_ADMIN_PASSWORD=

SUPERSET_SECRET_KEY=

OTELCOL_ARGS=

收集器(Collector)配置

我們需要告訴 Open Telemetry Collector:

因此,otel-collector-config.yaml 將是:

# 完整文檔:<https://opentelemetry.io/docs/collector/configuration/>
receivers:
  otlp:
    protocols:
      http: {}
      # grpc: {}

exporters:
  clickhouse:
    endpoint: tcp://clickhouse:9000?dial_timeout=10s&compress=lz4
    database: app_otel
    traces_table_name: app_otel
    username: app_otel
    password: ${env:CLICKHOUSE_PASSWORD}

processors:
  batch:
    timeout: 5s
    send_batch_size: 100000

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [clickhouse]
    logs:
      receivers: [otlp]
      processors: [batch]
      exporters: [clickhouse]
    metrics:
      receivers: [otlp]
      processors: [batch]
      exporters: [clickhouse]

請注意,這幾乎是您所需要的一切。在這種情況下,由於我們需要 ClickHouse 驅動程序的自定義 docker 映像,這裡有額外的步驟:

自定義 Superset 映像

FROM apache/superset:latest-dev

USER root

# https://superset.apache.org/docs/databases/installing-database-drivers/

RUN pip install clickhouse-connect

ENV ADMIN_USERNAME $ADMIN_USERNAME
ENV ADMIN_EMAIL $ADMIN_EMAIL
ENV ADMIN_PASSWORD $ADMIN_PASSWORD

COPY ./superset-init.sh /superset-init.sh

COPY superset_config.py /app/
ENV SUPERSET_CONFIG_PATH /app/superset_config.py

USER superset
ENTRYPOINT [ "/superset-init.sh" ]

Superset 配置文件

# 整個 superset 設置靈感來自:<https://medium.com/towards-data-engineering/quick-setup-configure-superset-with-docker-a5cca3992b28>
# 配置:https://superset.apache.org/docs/installation/configuring-superset/
import os


FEATURE_FLAGS = {
    "ENABLE_TEMPLATE_PROCESSING": True,
}

ENABLE_PROXY_FIX = True

# 也是:<https://github.com/chdb-io/chdb-superset/blob/main/include/superset_config.py>

CACHE_CONFIG = {
    "CACHE_TYPE": "RedisCache",
    "CACHE_DEFAULT_TIMEOUT": 300,
    "CACHE_KEY_PREFIX": "superset_",
    "CACHE_REDIS_HOST": "redis",
    "CACHE_REDIS_PORT": 6379,
    "CACHE_REDIS_DB": 1,
    "CACHE_REDIS_URL": "redis://redis:6379/1",
}

FILTER_STATE_CACHE_CONFIG = {
    'CACHE_TYPE': 'RedisCache',
    'CACHE_DEFAULT_TIMEOUT': 86400,
    'CACHE_KEY_PREFIX': 'superset_filter_cache',
    'CACHE_REDIS_URL': 'redis://redis:6379/1'
}

Superset 入口點(Entrypoint)

#!/bin/bash

# 初始化文檔:https://superset.apache.org/docs/installation/installing-superset-from-scratch/

# 升級 Superset metastore
superset db upgrade

# 創建 Admin 用戶,您可以從 env 或任何其他可能的地方讀取這些值
superset fab create-admin --username "$ADMIN_USERNAME" --firstname Superset --lastname Admin --email "$ADMIN_EMAIL" --password "$ADMIN_PASSWORD"

echo "創建了 Admin 用戶:$ADMIN_USERNAME"

# 設置角色和權限
superset superset init

# 啟動伺服器
/bin/sh -c /usr/bin/run-server.sh

開始使用(客戶端)

部署了這個 Docker Compose 應用程式後,下一步是測量您的代碼(服務器或客戶端),並導出分析數據。 在這篇文章中,我只分享 TypeScript 在網頁瀏覽器中的簡單代碼。有許多可用的 SDK,它們的代碼質量非常高: https://opentelemetry.io/docs/languages/

TypeScript 範例代碼

import { WebTracerProvider } from "@opentelemetry/sdk-trace-web";
import { SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import { registerInstrumentations } from "@opentelemetry/instrumentation";
// import { DocumentLoadInstrumentation } from '@opentelemetry/instrumentation-document-load'; // 測量文檔加載時間
import { logs } from "@opentelemetry/api-logs";
import {
  LoggerProvider,
  SimpleLogRecordProcessor,
  ConsoleLogRecordExporter,
  BatchLogRecordProcessor,
} from "@opentelemetry/sdk-logs";
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
import { Resource } from "@opentelemetry/resources";
import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions";
import { LogAttributeValue, SeverityNumber } from '@opentelemetry/api-logs';


const otelHost = import.meta.env.PROD
  ? "production.web.site"
  : "development.web.site";

const traceCollectorUrl = `https://${otelHost}/v1/traces`;
const logCollectorUrl = `https://${otelHost}/v1/logs`;

const resource = new Resource({
  [SemanticResourceAttributes.SERVICE_NAME]: "my-demo-app",
  [SemanticResourceAttributes.SERVICE_NAMESPACE]: "foo",
});

const webTracerProvider = new WebTracerProvider({
  resource,
});
const traceExporter = new OTLPTraceExporter({
  url: traceCollectorUrl,
});

webTracerProvider.addSpanProcessor(new SimpleSpanProcessor(traceExporter));

webTracerProvider.register();

// 自動收集頁面加載和 XHR 跟踪
registerInstrumentations({
  instrumentations: [
    // new DocumentLoadInstrumentation({}),
    // new XMLHttpRequestInstrumentation(),
  ],
});

// 要啟動日誌記錄器,您首先需要初始化日誌記錄器提供者。
const loggerProvider = new LoggerProvider({
  resource,
});
// 添加處理器以導出日誌記錄
const logExporter = new OTLPLogExporter({ url: logCollectorUrl, headers: {} });
// 注意:必須設置標頭。參考:<https://github.com/open-telemetry/opentelemetry-js/issues/3062#issuecomment-1189189494>
if (import.meta.env.DEV) {
  loggerProvider.addLogRecordProcessor(
    new SimpleLogRecordProcessor(new ConsoleLogRecordExporter())
  );
}

loggerProvider.addLogRecordProcessor(new BatchLogRecordProcessor(logExporter));

// 您也可以使用全局單例
logs.setGlobalLoggerProvider(loggerProvider);
export const defaultOtelLogger = logs.getLogger("default");

export function logTelemetry(eventName: string, params?: Record<string, LogAttributeValue>) {
  defaultOtelLogger.emit({
    SeverityNumber.INFO,
    body: eventName,
    attributes: params,
  });
}

使用 Apache Superset 分析數據

我相信 Superset 的官方文檔會比這篇文章好得多:https://superset.apache.org/docs/intro

但有兩點我們想提及:

adding_db

首先,我們必須透過網頁 UI 連接 ClickHouse 數據庫,在 Settings > Database Connections > + Database

其次,為了驗證我們的客戶端代碼是否有效,我們最好使用 SQL 查詢來獲取一些數據。預設情況下,將會有一個名為 otel_logs 的表格,用於所有 Open Telemetry 日誌,由上面的示例代碼 defaultOtelLogger.emit() 上傳。

sql_query

然後,您應該能夠根據您的需求構建一些圖表或儀表板(Dashboard)。

sample_dashboard

總結

這篇博客文章絕對是代碼密集型的,但我確實相信代碼可以自解釋,尤其是有了我的註釋。 如果您發現某些部分令人困惑或不清楚,請在下方評論。我將盡力改進。

感謝您閱讀我的博客的時間!

#Client-Tech #Server-Tech