基於 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:
- 從 http/gRPC 導出數據到 ClickHouse
- 它還應該知道 ClickHouse 連接的憑證
- 暴露其中之一
因此,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
但有兩點我們想提及:
首先,我們必須透過網頁 UI 連接 ClickHouse 數據庫,在 Settings
> Database Connections
> + Database
。
其次,為了驗證我們的客戶端代碼是否有效,我們最好使用 SQL 查詢來獲取一些數據。預設情況下,將會有一個名為 otel_logs
的表格,用於所有 Open Telemetry 日誌,由上面的示例代碼 defaultOtelLogger.emit()
上傳。
然後,您應該能夠根據您的需求構建一些圖表或儀表板(Dashboard)。
總結
這篇博客文章絕對是代碼密集型的,但我確實相信代碼可以自解釋,尤其是有了我的註釋。 如果您發現某些部分令人困惑或不清楚,請在下方評論。我將盡力改進。
感謝您閱讀我的博客的時間!