OpenTelemetry 中文教程

1. 项目概述

OpenTelemetry 是一个开源的可观测性框架,由 Cloud Native Computing Foundation (CNCF) 孵化和维护。它提供了一套统一的标准和工具,用于生成、收集和导出遥测数据(追踪、指标和日志),帮助开发者更好地理解和监控软件系统的行为。

主要功能

  • 统一的遥测数据采集:支持追踪、指标和日志
  • 厂商中立:提供标准化的遥测数据格式和API
  • 多语言支持:支持多种编程语言
  • 灵活的导出:支持导出到多种后端系统
  • 自动和手动 instrumentation:支持自动和手动添加遥测代码
  • 上下文传播:在分布式系统中自动传播追踪上下文
  • 与现有系统集成:与 Prometheus、Jaeger、Zipkin 等集成

技术栈特点

  • 基于开放标准:由 CNCF 维护的开放标准
  • 模块化设计:核心组件和语言特定实现分离
  • 可扩展架构:支持插件和扩展
  • 性能优先:设计注重低开销
  • 云原生友好:与 Kubernetes 等云原生技术深度集成

适用环境

  • 微服务架构
  • 分布式系统
  • 云原生环境
  • 容器化应用
  • 传统单体应用

2. 核心概念

2.1 遥测数据类型

OpenTelemetry 支持三种主要的遥测数据类型:

  • **追踪 (Traces)**:记录请求在系统中的完整调用路径,包含多个跨度 (Spans)
  • **指标 (Metrics)**:记录可聚合的数值数据,如计数器、仪表盘、直方图等
  • **日志 (Logs)**:记录离散的事件和消息

2.2 核心组件

  • API:提供编程语言特定的接口,用于生成和处理遥测数据
  • SDK:提供API的具体实现,包含配置、采样、处理和导出功能
  • Instrumentation:自动或手动添加遥测代码的组件
  • Collector:集中处理和导出遥测数据的独立服务

2.3 数据模型

  • Trace:由多个相关的 Spans 组成,表示一个完整的请求处理过程
  • Span:表示一个操作或工作单元,包含开始时间、结束时间、属性、事件等
  • Metric:表示一个可测量的值,包含名称、标签、值等
  • Log:表示一个离散的事件记录,包含时间戳、 severity、消息等
  • Resource:表示遥测数据的来源,如服务名称、主机名、环境等

3. 安装与配置

3.1 安装 OpenTelemetry SDK

3.1.1 Node.js 应用

npm install @opentelemetry/sdk-node @opentelemetry/auto-instrumentations-node @opentelemetry/exporter-jaeger @opentelemetry/exporter-prometheus

3.1.2 Java 应用

<!-- pom.xml -->
<dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-sdk</artifactId>
    <version>1.30.0</version>
</dependency>
<dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-exporter-otlp</artifactId>
    <version>1.30.0</version>
</dependency>
<dependency>
    <groupId>io.opentelemetry.instrumentation</groupId>
    <artifactId>opentelemetry-instrumentation-runtime-telemetry</artifactId>
    <version>1.30.0</version>
</dependency>

3.1.3 Python 应用

pip install opentelemetry-sdk opentelemetry-exporter-otlp opentelemetry-instrumentation-requests

3.2 配置 OpenTelemetry Collector

3.2.1 安装 Collector

使用 Docker 运行 Collector:

docker run -d \n  --name otel-collector \n  -p 4317:4317 \n  -p 4318:4318 \n  -p 8888:8888 \n  -v ./collector-config.yaml:/etc/otelcol/config.yaml \n  otel/opentelemetry-collector-contrib:0.86.0

3.2.2 Collector 配置

# collector-config.yaml
receivers:
  otlp:
    protocols:
      grpc:
      http:

exporters:
  otlp:
    endpoint: jaeger:4317
    tls:
      insecure: true
  prometheus:
    endpoint: 0.0.0.0:8889
  logging:
    loglevel: debug

processors:
  batch:
  memory_limiter:
    limit_mib: 4000
    spike_limit_mib: 800
    check_interval: 5s

extensions:
  health_check:
  pprof:
  zpages:

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [otlp, logging]
    metrics:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [prometheus, logging]
    logs:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [logging]
  extensions: [health_check, pprof, zpages]

3.3 环境变量配置

OpenTelemetry 支持通过环境变量进行配置:

# 服务名称
OTEL_SERVICE_NAME=my-service

# 导出器配置
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317

# 采样配置
OTEL_TRACES_SAMPLER=parentbased_traceidratio
OTEL_TRACES_SAMPLER_ARG=0.1

# 资源属性
OTEL_RESOURCE_ATTRIBUTES=deployment.environment=production,service.version=1.0.0

4. 基本使用

4.1 自动 Instrumentation

4.1.1 Node.js 示例

// tracing.js
const { NodeSDK } = require('@opentelemetry/sdk-node');
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-grpc');
const { OTLPMetricExporter } = require('@opentelemetry/exporter-metric-otlp-grpc');
const { PeriodicExportingMetricReader } = require('@opentelemetry/sdk-metrics');

// 创建追踪导出器
const traceExporter = new OTLPTraceExporter({
  url: 'http://localhost:4317'
});

// 创建指标导出器
const metricExporter = new OTLPMetricExporter({
  url: 'http://localhost:4317'
});

// 创建指标读取器
const metricReader = new PeriodicExportingMetricReader({
  exporter: metricExporter,
  exportIntervalMillis: 10000
});

// 创建 SDK 实例
const sdk = new NodeSDK({
  traceExporter,
  metricReader,
  instrumentations: [getNodeAutoInstrumentations()],
  serviceName: 'my-service'
});

// 启动 SDK
sdk.start();

// 处理进程结束
process.on('SIGTERM', () => {
  sdk.shutdown()
    .then(() => console.log('OpenTelemetry SDK shutdown'))
    .catch((error) => console.error('Error shutting down SDK', error))
    .finally(() => process.exit(0));
});

module.exports = sdk;
// app.js
// 在应用代码之前导入追踪配置
require('./tracing');

const express = require('express');
const app = express();

app.get('/', (req, res) => {
  res.send('Hello World!');
});

app.get('/api/users', async (req, res) => {
  // 模拟数据库查询
  await new Promise(resolve => setTimeout(resolve, 100));
  res.json([{ id: 1, name: 'User 1' }, { id: 2, name: 'User 2' }]);
});

app.listen(3000, () => {
  console.log('Server listening on port 3000');
});

4.1.2 Java 示例

使用 Java Agent 进行自动 instrumentation:

java -javaagent:opentelemetry-javaagent.jar \n  -Dotel.service.name=my-service \n  -Dotel.exporter.otlp.endpoint=http://localhost:4317 \n  -jar my-application.jar

4.2 手动 Instrumentation

4.2.1 Node.js 示例

const { trace } = require('@opentelemetry/api');

// 获取当前追踪器
const tracer = trace.getTracer('my-service');

// 创建新的跨度
function processOrder(orderId) {
  // 创建根跨度
  const span = tracer.startSpan('process-order');
  
  try {
    // 设置跨度属性
    span.setAttribute('order.id', orderId);
    
    // 执行操作
    validateOrder(orderId, span);
    prepareShipment(orderId, span);
    
    // 设置跨度状态为成功
    span.setStatus({ code: 0 });
  } catch (error) {
    // 设置跨度状态为错误
    span.setStatus({ code: 2, message: error.message });
    span.recordException(error);
    throw error;
  } finally {
    // 结束跨度
    span.end();
  }
}

// 创建子跨度
function validateOrder(orderId, parentSpan) {
  const span = tracer.startSpan('validate-order', {
    parent: parentSpan
  });
  
  try {
    // 执行验证操作
    console.log(`Validating order ${orderId}`);
    span.setAttribute('order.status', 'validating');
    
    // 模拟验证过程
    return new Promise(resolve => {
      setTimeout(() => {
        span.setAttribute('order.status', 'valid');
        resolve(true);
      }, 50);
    });
  } finally {
    span.end();
  }
}

function prepareShipment(orderId, parentSpan) {
  const span = tracer.startSpan('prepare-shipment', {
    parent: parentSpan
  });
  
  try {
    // 执行发货准备操作
    console.log(`Preparing shipment for order ${orderId}`);
    span.setAttribute('shipment.status', 'preparing');
    
    // 模拟准备过程
    return new Promise(resolve => {
      setTimeout(() => {
        span.setAttribute('shipment.status', 'prepared');
        resolve(true);
      }, 100);
    });
  } finally {
    span.end();
  }
}

// 调用函数
processOrder('12345');

4.3 指标示例

const { metrics } = require('@opentelemetry/api');

// 获取指标记录器
const meter = metrics.getMeter('my-service');

// 创建计数器
const httpRequestsCounter = meter.createCounter('http_requests_total', {
description: 'Total number of HTTP requests'
});

// 创建仪表盘
const activeUsersGauge = meter.createObservableGauge('active_users', {
description: 'Number of active users'
});

// 创建直方图
const responseTimeHistogram = meter.createHistogram('http_response_time_ms', {
description: 'HTTP response time in milliseconds',
  boundaries: [10, 50, 100, 200, 500, 1000]
});

// 记录指标
function handleRequest(req, res) {
  const startTime = Date.now();
  
  // 增加请求计数器
  httpRequestsCounter.add(1, {
    method: req.method,
    path: req.path,
    status_code: res.statusCode
  });
  
  // 模拟处理时间
  setTimeout(() => {
    const responseTime = Date.now() - startTime;
    
    // 记录响应时间
    responseTimeHistogram.record(responseTime, {
      method: req.method,
      path: req.path,
      status_code: res.statusCode
    });
    
    res.end('Hello World!');
  }, Math.random() * 100);
}

// 注册仪表盘回调
activeUsersGauge.addCallback((observableResult) => {
  // 模拟获取活跃用户数
  const activeUsers = Math.floor(Math.random() * 1000);
  observableResult.observe(activeUsers);
});

4.4 日志示例

const { logs } = require('@opentelemetry/api');

// 获取日志记录器
const logger = logs.getLogger('my-service');

// 记录日志
function processPayment(paymentId, amount) {
  // 记录信息日志
  logger.info('Processing payment', {
    payment_id: paymentId,
    amount: amount,
    status: 'processing'
  });
  
  try {
    // 模拟支付处理
    if (amount > 1000) {
      throw new Error('Amount exceeds limit');
    }
    
    // 记录成功日志
    logger.info('Payment processed successfully', {
      payment_id: paymentId,
      amount: amount,
      status: 'success'
    });
    
    return { success: true, paymentId };
  } catch (error) {
    // 记录错误日志
    logger.error('Payment processing failed', {
      payment_id: paymentId,
      amount: amount,
      status: 'failed',
      error: error.message
    });
    
    return { success: false, paymentId, error: error.message };
  }
}

// 调用函数
processPayment('pay_123', 500);
processPayment('pay_456', 1500);

5. 高级特性

5.1 上下文传播

OpenTelemetry 自动处理分布式系统中的上下文传播:

  • HTTP 传播:通过 HTTP 头传播追踪上下文
  • gRPC 传播:通过 gRPC 元数据传播追踪上下文
  • 消息队列传播:通过消息属性传播追踪上下文

5.1.1 自定义传播

const { propagation } = require('@opentelemetry/api');
const { W3CTraceContextPropagator } = require('@opentelemetry/core');

// 设置全局传播器
propagation.setGlobalPropagator(new W3CTraceContextPropagator());

// 手动注入上下文
function injectContext(headers) {
  const context = propagation.createContext();
  propagation.inject(context, headers, {
    set: (carrier, key, value) => {
      carrier[key] = value;
    }
  });
  return headers;
}

// 手动提取上下文
function extractContext(headers) {
  return propagation.extract(undefined, headers, {
    get: (carrier, key) => carrier[key]
  });
}

// 使用示例
const headers = {};
injectContext(headers);
console.log('Headers with context:', headers);

const extractedContext = extractContext(headers);
console.log('Extracted context:', extractedContext);

5.2 采样策略

OpenTelemetry 支持多种采样策略:

  • AlwaysOnSampler:总是采样
  • AlwaysOffSampler:从不采样
  • TraceIdRatioBasedSampler:基于追踪 ID 比率采样
  • ParentBasedSampler:基于父跨度的采样决策
  • CustomSampler:自定义采样策略

5.2.1 配置采样策略

const { NodeSDK } = require('@opentelemetry/sdk-node');
const { TraceIdRatioBasedSampler } = require('@opentelemetry/sdk-trace-node');

const sdk = new NodeSDK({
  // 配置 10% 采样率
  sampler: new TraceIdRatioBasedSampler(0.1),
  // 其他配置...
});

5.3 资源检测

OpenTelemetry 自动检测和添加资源属性:

  • 环境变量:从 OTEL_RESOURCE_ATTRIBUTES 环境变量读取
  • 系统信息:检测主机名、操作系统等
  • 容器信息:检测容器 ID、镜像等
  • 云提供商信息:检测云提供商、区域等

5.3.1 自定义资源

const { NodeSDK } = require('@opentelemetry/sdk-node');
const { Resource } = require('@opentelemetry/resources');
const { SemanticResourceAttributes } = require('@opentelemetry/semantic-conventions');

const resource = new Resource({
  [SemanticResourceAttributes.SERVICE_NAME]: 'my-service',
  [SemanticResourceAttributes.SERVICE_VERSION]: '1.0.0',
  [SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]: 'production',
  'custom.attribute': 'custom-value'
});

const sdk = new NodeSDK({
  resource,
  // 其他配置...
});

5.4 与现有系统集成

OpenTelemetry 可以与多种现有系统集成:

5.4.1 与 Prometheus 集成

# collector-config.yaml
exporters:
  prometheus:
    endpoint: 0.0.0.0:8889

service:
  pipelines:
    metrics:
      receivers: [otlp]
      processors: [batch]
      exporters: [prometheus]

5.4.2 与 Jaeger 集成

# collector-config.yaml
exporters:
  otlp:
    endpoint: jaeger:4317
    tls:
      insecure: true

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [otlp]

5.4.3 与 Zipkin 集成

# collector-config.yaml
exporters:
  zipkin:
    endpoint: http://zipkin:9411/api/v2/spans

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [zipkin]

6. 最佳实践

6.1 Instrumentation 策略

  • 优先使用自动 Instrumentation:减少手动代码,提高覆盖率
  • 补充手动 Instrumentation:对关键业务逻辑添加手动追踪
  • 标准化命名:使用一致的跨度和指标命名约定
  • 添加有意义的属性:为跨度和指标添加有意义的标签
  • 保持适当的粒度:不要创建过多或过少的跨度

6.2 性能优化

  • 合理采样:根据系统规模和性能需求设置适当的采样率
  • 批量导出:配置适当的批处理大小和间隔
  • 异步处理:使用异步方式处理和导出遥测数据
  • 资源限制:为 Collector 设置适当的资源限制
  • 监控遥测开销:监控遥测本身的开销

6.3 数据管理

  • 设置合理的保留期:根据业务需求和存储成本设置
  • 数据聚合:在导出前适当聚合数据
  • 敏感数据处理:避免在遥测数据中存储敏感信息
  • 数据压缩:启用数据压缩以减少网络传输
  • 备份重要数据:对重要的遥测数据进行备份

6.4 告警和监控

  • 基于遥测数据设置告警:如错误率、响应时间等
  • 监控遥测系统本身:确保遥测系统正常运行
  • 分级告警:根据严重程度设置不同级别的告警
  • 与现有监控系统集成:如 Grafana、Prometheus Alertmanager

6.5 安全最佳实践

  • 加密传输:使用 TLS 加密遥测数据传输
  • 访问控制:限制对遥测数据的访问
  • 认证和授权:为遥测系统添加认证和授权
  • 审计日志:记录对遥测系统的访问和修改
  • 合规性:确保遥测数据处理符合相关法规

7. 实际应用场景

7.1 微服务架构监控

场景:一个由多个微服务组成的电商系统

实施步骤

  1. 为每个微服务添加 OpenTelemetry Instrumentation
  2. 部署 OpenTelemetry Collector 集群
  3. 配置导出到 Jaeger 和 Prometheus
  4. 使用 Grafana 创建统一仪表盘
  5. 设置基于遥测数据的告警

查询与分析

  • 追踪分析:查看请求在微服务间的完整调用链
  • 性能分析:识别性能瓶颈和慢服务
  • 错误分析:快速定位错误源头
  • 依赖分析:分析服务间的依赖关系

7.2 云原生应用监控

场景:部署在 Kubernetes 上的应用

实施步骤

  1. 使用 OpenTelemetry Operator 部署 Collector
  2. 为应用添加自动 Instrumentation
  3. 配置与 Kubernetes 集成
  4. 导出到云提供商的可观测性服务

优势

  • 自动发现:自动发现和监控新部署的服务
  • 统一视图:在 Kubernetes 上下文中查看遥测数据
  • 弹性扩展:根据负载自动扩展 Collector
  • 云集成:与云提供商的可观测性服务无缝集成

7.3 传统应用现代化

场景:将传统单体应用迁移到微服务架构

实施步骤

  1. 为单体应用添加 OpenTelemetry Instrumentation
  2. 监控迁移过程中的性能变化
  3. 为新的微服务添加 Instrumentation
  4. 比较迁移前后的性能和可靠性

优势

  • 基线建立:建立迁移前的性能基线
  • 渐进式迁移:监控每个迁移步骤的影响
  • 问题早发现:在迁移过程中及早发现问题
  • 验证成功:验证迁移后的性能改进

8. 总结

OpenTelemetry 是一个功能强大、灵活可扩展的可观测性框架,它为现代软件系统提供了统一的遥测数据采集、处理和导出解决方案。通过本文的介绍,您应该已经了解了 OpenTelemetry 的核心概念、安装配置、基本使用和高级特性。

关键优势

  • 开放标准:由 CNCF 维护的开放标准,避免厂商锁定
  • 统一解决方案:同时支持追踪、指标和日志
  • 多语言支持:支持多种编程语言
  • 灵活导出:支持导出到多种后端系统
  • 低开销设计:注重性能和低开销
  • 云原生友好:与 Kubernetes 等云原生技术深度集成

应用场景

  • 微服务架构监控和故障排查
  • 分布式系统性能分析
  • 云原生应用可观测性
  • 传统应用现代化
  • DevOps 持续监控

OpenTelemetry 正在成为可观测性领域的事实标准,它不仅提供了强大的工具和库,还建立了一套开放的标准和最佳实践。通过采用 OpenTelemetry,您可以构建更加可靠、可维护的软件系统,同时获得对系统行为的深入洞察。

« 上一篇 Zipkin 中文教程 下一篇 » Node Exporter 中文教程