可观测技术
更新时间: 2025/07/07
在Gitcode上查看源码

1. 概述

1.1 文档目的

本文档旨在指导社区开发者基于 BMC 可观测框架(OpenTelemetry),为新增硬件部件或软件功能开发完整的可观测能力,包括跟踪(Traces)、指标(Metrics)和日志(Logs)的采集、上报与验证

1.2 前置条件

  • BMC 固件已升级至支持可观测框架的版本(libmc4lua ≥ 1.80.5 支持跟踪与指标数据采集接口,libmc4lua ≥ 1.90.21 支持日志上报和调试日志能力归一)
  • 已准备可观测后端(至少包含 otelcol,建议包含 Zipkin/Prometheus/Elasticsearch/Grafana)

1.3 关键术语表

术语说明
Trace一次业务请求形成的完整调用链,由多个 Span 组成
SpanTrace 中的最小工作单元,包含名称、时间、状态、属性和事件
MeterMetrics 入口对象,用于创建各类 Instrument
Instrument指标采集工具,如 Counter/UpDownCounter/Histogram
LogRecord日志记录单元,包含级别、内容、属性及可选 Trace 关联字段
Attribute键值对元数据,用于对 Trace/Metric/Log 数据打标签
opentelemetry-collector-contrib接收并转发可观测数据的中转组件(otelcol)
可视化后端Zipkin、Prometheus、Elasticsearch、Grafana 等查询与展示系统

2. 背景介绍

2.1 概念介绍

2.1.1 可观测

可观测是基于openTelemetry规范构建的可视化能力,主要围绕系统中的可观测数据(包括指标、跟踪和日志),提供采集、上报以及与多种可视化工具集成的能力,帮助用户更好地监控系统状态、预测运行趋势、分析和定位系统故障

2.1.2 跟踪(Traces)

跟踪是一组事件,这些事件是由单个逻辑操作触发的,通过应用程序的各个组件处理后最终整合在一起。跟踪可能包含跨越进程、网络和安全边界的事件,因此也被称为分布式跟踪,当有人按下按钮在网站上启动一个操作时,就可能会启动分布式跟踪

具体来说,跟踪可以被认为是跨度(Span)的有向无环图 (DAG),其中跨度之间的边定义为父子关系。例如,以下是一个由6个跨度组成的跟踪示例:

text
            [Span A]  ←←←(the root span)
               |
        +------+------+
        |             |
    [Span B]      [Span C] ←←←(Span C is a `child` of Span A)
        |             |
    [Span D]      +---+-------+
                  |           |
               [Span E]    [Span F]

使用时间轴来显示跟踪轨迹会更容易理解,如下图所示:

text
––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–> time

 [Span A···················································]
   [Span B··············································]
      [Span D··········································]
    [Span C········································]
         [Span E·······]        [Span F··]

2.1.3 指标(Metrics)

指标是对系统行为、性能或状态的量化测量,可以是任何可测量的数值,例如CPU使用率、内存占用率、请求延迟、错误率等。记录测量值主要涉及计量器(Meter)、仪表(Instrument)和测量值(Measurement):

  • 计量器(Meter):主要负责创建和管理仪表(Instrument)
  • 仪表(Instrument):具有名称、类型、描述和单位等标识,主要用于捕捉和记录测量值
  • 测量值(Measurement):单次记录的原始值,包含键值对属性标签,用于标记和区分不同场景下的测量值
text
+------------------+
| MeterProvider    |                 +-----------------+             +--------------+
|   Meter A        | Measurements... |                 | Metrics...  |              |
|     Instrument X +-----------------> In-memory state +-------------> MetricReader |
|     Instrument Y |                 |                 |             |              |
|   Meter B        |                 +-----------------+             +--------------+
|     Instrument Z |
|     ...          |                 +-----------------+             +--------------+
|     ...          | Measurements... |                 | Metrics...  |              |
|     ...          +-----------------> In-memory state +-------------> MetricReader |
|     ...          |                 |                 |             |              |
|     ...          |                 +-----------------+             +--------------+
+------------------+

2.1.4 日志(Logs)

日志用于记录应用程序的事件和状态信息,方便调试和故障排查

  • OTel Logging API:原生日志接口(Logger/LoggerProvider)
  • 上下文注入:自动添加 TraceID/SpanID 实现日志-追踪关联 核心组件和数据流向如下
text
+----------------------------------------------------------------+
|                           Application                          |
|                                                                |
|          +----------------------+       +-------------------+  |
|          |   OTel Logging API   |       |   OTel Context    |  |
|          |   (Logger/Logger-   +------->   Propagator       |  |
|          |  Provider/LogRecord) |       | (TraceID/SpanID)  |  |
|          +----------+-----------+       +---------+---------+  |
|                     |                             |            |
|                     |        Inject Context       |            |
|                     +<----------------------------+            |
|                     |                                          |
|          +----------v-----------+                              |
|          |   OTel Logs SDK      |                              |
|          |  +-----------------+ |                              |
|          |  | LogRecord       | |       +-------------------+  |
|          |  | Processor       +--------->   Resource        |  |
|          |  | (Batch/Simple)  | |       | (service.name,    |  |
|          |  +--------+--------+ |       |  host.name, etc.) |  |
|          |           |          |       +---------+---------+  |
|          |  +--------v--------+ |                 |            |
|          |  | LogRecord       | |    Add Resource |            |
|          |  | Exporter        +<------------------+            |
|          |  | (OTLP/Console)  | |                              |
|          |  +-----------------+ |                              |
|          +----------+-----------+                              |
+----------------------------------------------------------------+

2.2 数据模型

2.2.1 跟踪

跟踪的数据模型

text
┌───────────────────────────────┐
│         TracerProvider        │
├───────────────────────────────┤
│ + getTracer(name, version)    │
└──────────────┬────────────────┘
               │ creates

┌───────────────────────────────┐
│            Tracer             │
├───────────────────────────────┤
│ + startSpan(name, options)    │
└──────────────┬────────────────┘
               │ creates

┌───────────────────────────────┐ has  ┌──────────────┐
│             Span              │◆──▶ │ SpanContext  │
├───────────────────────────────┤      ├──────────────┤
│ - name: string                │      │ - traceId    │
│ - startTime: timestamp        │      │ - spanId     │
│ - endTime: timestamp          │      │ - traceFlags │
│ - parentSpanId: String        │      │ - isRemote   │
├───────────────────────────────┤      └──────────────┘
│ + setAttribute(k,v)           │ contains ┌─────────────┐
│ + addEvent(name, attributes)  │───────▶ │  Attribute  │
│ + recordException(exception)  │          ├─────────────┤
│ + end()                       │          │ - key       │
└──────────────┬────────────────┘          │ - value     │
               │ contains                  └─────────────┘
               ▼                                 ▲
        ┌─────────────┐       contains           │
        │    Event    │──────────────────────────┘
        ├─────────────┤
        │ - name      │
        │ - timestamp │
        └─────────────┘

跨度的组成

  • 操作名称
  • 开始和结束时间戳
  • 属性:键值对列表
  • 事件:零个或多个事件的集合,每个事件本身都是一个元组(时间戳、名称、属性)。名称必须是字符串
  • 父级跨度标识符
  • 链接:指向零个或多个因果相关的跨度的链接(通过这些相关的跨度上下文)
  • 上下文:引用跨度所需的跨度上下文(Span Context)信息

跨度的名称

跨度的名称简洁地标识了跨度所代表的工作,例如RPC方法名称、函数名称或更大计算中的子任务或阶段的名称

跨度的类型

  • SERVER 表示跨度涵盖了服务端处理的远程请求,而客户端等待响应
  • CLIENT 表示跨度描述了一个远程服务的请求,客户端等待响应。当 CLIENT 跨度的上下文被传播时,CLIENT 跨度通常会成为远程 SERVER 跨度的父级
  • PRODUCER 表示跨度描述了本地或远程操作的启动或调度。这个启动跨度通常在相关的 CONSUMER 跨度结束之前结束,甚至可能在 CONSUMER 跨度开始之前结束。在具有批处理的消息传递场景中,跟踪单个消息需要为每条消息创建一个新的 PRODUCER 跨度
  • CONSUMER 表示跨度代表由生产者启动的操作的处理,生产者不等待结果
  • INTERNAL 默认值。表示跨度代表应用程序内的内部操作,而不是具有远程父级或子级的操作

2.2.2 指标

指标的数据模型

text
  Metric
+------------+
|name        |
|description |
|unit        |     +------------------------------------+
|data        |---> |Gauge, Counter, Histogram, ...      |
+------------+     +------------------------------------+

  Data [One of Gauge, Counter, Histogram, ...]
+-----------+
|...        |  // Metadata about the Data.
|points     |--+
+-----------+  |
               |      +---------------------------+
               |      |DataPoint 1                |
               v      |+------+------+   +------+ |
            +-----+   ||label |label |...|label | |
            |  1  |-->||value1|value2|...|valueN| |
            +-----+   |+------+------+   +------+ |
            |  .  |   |+-----+                    |
            |  .  |   ||value|                    |
            |  .  |   |+-----+                    |
            |  .  |   +---------------------------+
            |  .  |                   .
            |  .  |                   .
            |  .  |                   .
            |  .  |   +---------------------------+
            |  .  |   |DataPoint M                |
            +-----+   |+------+------+   +------+ |
            |  M  |-->||label |label |...|label | |
            +-----+   ||value1|value2|...|valueN| |
                      |+------+------+   +------+ |
                      |+-----+                    |
                      ||value|                    |
                      |+-----+                    |
                      +---------------------------+

指标的组成

  • 指标由元数据和数据组成
  • 元数据部分包含多个属性:
    • 指标名称
    • 属性(维度,也被称为标签)
    • 值类型(整数、浮点等)
    • 计量单位
  • 数据是计数器、仪表、直方图等类型之一
    • 数据点包含时间戳、属性和值

指标的名称

  • 不能为空字符串
  • 不区分大小写的ASCII字符串
  • 第一个字符必须是字母。后续字符必须是字母数字字符、“_”、“.”、“-”和“/”
  • 最大长度为 255 个字符

指标的名称约定

  • limit - 一种测量某种已知总量的恒定值的工具应称为entity.limit。例如,system.memory.limit 表示系统内存的总量
  • usage - 一种测量已知总量(有限的)中已使用量的工具应称为 entity.usage。例如,system.memory.usage 带有属性 state = used | cached | free |…表示每种状态下的内存量。在适当的情况下,所有属性值的总使用量应等于总量
  • utilization - 一种测量使用量相对于其限制的比例工具应称为 entity.utilization。例如,system.memory.utilization 表示正在使用的内存比例
  • time - 一种测量时间流逝的工具应称为 entity.time。例如,system.cpu.time 带有属性 state = idle | user | system |…时间测量不一定是墙钟时间,可能小于或大于测量之间的实际墙钟时间
  • io - 一种测量双向数据流的工具应称为 entity.io,并具有方向属性。例如,system.network.io

2.2.3 日志

日志的数据模型

text
Logger
+------------------+
| name             |
| version          |     +------------------------------------+
| ...              |---> |LogRecord 1                         |
+------------------+     |+----------------+----------------+|
                         || Timestamp      |  timestamp     ||
                         |+----------------+----------------+|
                         || SeverityNumber |  severity_num  ||
                         |+----------------+----------------+|
                         || SeverityText   |  severity_text ||
                         |+----------------+----------------+|
                         || Body           |  body          ||
                         |+----------------+----------------+|
                         || Attributes     |  +-----------+ ||
                         ||                |  |key: value | ||
                         ||                |  |...        | ||
                         ||                |  +-----------+ ||
                         |+----------------+----------------+|
                         || TraceId        |  (optional)    ||
                         || SpanId         |  (optional)    ||
                         |+----------------+----------------+|
                         +------------------------------------+
                                        .
                                        .
                                        .
                         +------------------------------------+
                         |LogRecord N                         |
                         | ...                                |
                         +------------------------------------+

日志的组成

  • 定义了一个与供应商无关的日志数据模型(Log Record),包含核心字段如时间戳、严重级别、消息体、Trace ID、Span ID、资源属性(服务名、实例ID等)、属性(键值对)

2.2.4 属性

属性在跟踪、指标和日志中都有使用。属性是一个键值对,必须具有以下要求:

  • 属性键必须是一个非空的字符串。键的大小写敏感,大小写不同的键被视为不同的键
  • 属性值可以是:
    • 一个原始类型:字符串、布尔值、双精度浮点数(IEEE 754-1985)或带符号的 64 位整数
    • 一个原始类型值的数组。数组必须是同质的,即它不能包含不同类型的值
  • 属性的最大个数为128

属性的名称

以otel.开头的属性名称属于OpenTelemetry规范保留定义

属性的名称应遵循以下规则:

  • 名称应为小写
  • 使用命名空间,并使用点字符分隔命名空间。例如,service.version 表示服务版本,其中 service 是命名空间,version 是该命名空间中的一个属性。命名空间可以嵌套。例如,telemetry.sdk 是顶级 telemetry 命名空间中的一个命名空间,而 telemetry.sdk.name 是 telemetry.sdk 命名空间中的一个属性。在合适的情况下使用命名空间(和点分隔符)
  • 对于名称中每个多词的点分隔组件,使用下划线分隔单词(即使用 snake_case)。例如,http.response.status_code 表示 http 命名空间中的状态码。仅在点(命名空间)的使用没有意义或改变名称的语义意义时使用下划线。例如,使用 rate_limiting 而不是 rate.limiting
  • 属性、事件、指标和其他名称应具有描述性和明确性。在引入描述对象某个属性的名称时,包含属性名称。例如,使用 file.owner.name 而不是 file.owner,以及 system.network.packet.dropped 而不是 system.network.dropped。避免引入在不同约定或工具中使用时含义不同的名称和命名空间。例如,使用 security_rule 而不是 rule
  • 在不影响清晰度的情况下使用更短的名称。在多词组件中删除不必要的命名空间组件或单词。例如,vcs.change.id 与 vcs.repository.change.id 一样精确地描述拉取请求 ID,使用 vcs.change.id 而不是 vcs.repository.change.id

2.3 可观测架构

2.3.1 可观测数据流图

  1. 应用组件通过应用框架基于openTelemetry SDK封装的跟踪、指标、日志接口采集应用可观测数据
  2. 应用框架通过可观测组件openTelemetry SDK采样器、处理器和导出器处理可观测数据,并上报到可观测组件fluent-bit服务端
  3. 可观测组件fluent-bit服务端根据可观测配置,转发和上报可观测数据到openUBMC外收集器及可视化平台

3. 应用接口

3.1 跟踪

本模块用于Trace相关能力接口的lua层隔离,以实现Lua层、封装层、开源引入层,Lua层与封装层、开源引入层隔离,Lua层与封装层之间通过弱引用隔离,在无法require到封装库时,Lua层提供空对象空接口保护业务不受影响

注意:当前业务通过调用libmc4lua提供的lua封装接口使用trace能力,libmc4lua弱依赖opentelemetry库,因此本地构建无法根据依赖拉取组件,目前本地自验只能通过手动依赖trace库来使libmc4lua依赖到,否则只能调用到libmc4lua打桩的空接口

3.1.1 tracer创建

tracer是Trace能力构建中最初一环,业务需要持有tracer用于创建span,利用span的记录存储上报能力构建跨服务跟踪链路,目前组件初始化过程中自动以组件名作为tracer name创建tracer并存储,组件后续调用get_tracer()将直接返回tracer,组件也可以通过传入参数name新建tracer

lua
-- 方法原型
local tracer = trace.get_tracer(name, version, url)

-- 使用示例
local trace = require 'telemetry.trace'
local name = ...
local trace_custom = trace.get_tracer(name, '1.0.0')
local trace_default = tracer.get_tracer()

req:

  • name: 必选参数,string类型,tracer名称,一般使用库名称. e.g. "hwdiscovery"
  • version: 可选参数,string类型,库版本. e.g. "1.0.0"
  • url: 可选参数,string类型,指向语义约定文档,用于属性命名规范. e.g. "https://xxx/"

rsp:

  • tracer: userdata类型,由业务持有并调用start_span进行span创建,然后进行后续操作
  • msg: string类型,错误信息,由业务选择是否接收,会返回失败的具体信息,若成功则不返回

3.1.2 span创建

span是跟踪链路中的最小功能单元,生命周期一般为单个函数或代码块,业务在需要跟踪的位置创建span,并通过span提供的能力进行数据跟踪,在生命周期结束后进行上报,后台根据上报span间的关联将不同服务上报的span整合为一条完整的跟踪链路

lua
-- 方法原型
local child_span = tracer:start_span(name, attribute, options)
local child_span_opt = trace.start_span(name, attribute, options)

-- 使用示例
function get_object()
    local root_span = tracer:start_span("get_object", {"level": "notice", "opcode": 1})
    local parent_span_context = root_span:get_context()
    local child_span = tracer:start_span("get_object", {"level": "notice", "opcode": 1}, {parent = parent_span_context})

    -- 支持不通过tracer直接创建span,trace会自动获取当前组件名与组件版本创建tracer
    local child_span_opt = trace.start_span("get_object", {"level": "notice", "opcode": 1}, {parent = parent_span_context})
end

req:

  • name: 必选参数,string类型,span的名称,无需唯一性,span创建时会分配span_id作为唯一性标识,业务不感知. e.g. "get_object"
  • attribute: 必选参数,字典类型,键必须为string,值只支持stringboolintdouble类型,span的属性,由用户根据需要使用,后续跟随span数据导出,可用于链路标记、数据筛选等. e.g. {"level": "notice", "opcode": 1}
  • options: 可选参数,字典类型,代表span创建时的可选配置项。可选项有:
    • parent: table类型,只能使用由span:get_context()导出的spancontext数据,传入时创建为child span,继承上级的trace_id, 不传入时创建为root span,作为整条跟踪链路的起始
    • force_sample: bool类型,作为组件自定义采样标记,在业务需要强制跟踪采样时传入true

rsp:

  • span: userdata类型,由业务持有并调用相关方法完成跟踪链路的构建,Lua层未引用到封装层或创建失败会返回一个noop span,进行的任何操作都不会生效,数据也不会上报
  • msg: string类型,错误信息,由业务选择是否接收,会返回失败的具体信息,若成功则不返回

3.1.3span操作

set_attribute 设置span属性

span属性后续跟随span数据导出,可用于链路标记,数据筛选等

lua
-- 方法原型
span:set_attribute(key, value)

-- 使用示例
function get_object()
    local span = trace.start_span(name, attribute)

    span:set_attribute("level", "notice")

    local err_msg = span:set_attribute(key, value)
    local err_msg then
        log:error("%s", err_msg)
    end
end

req:

  • key: 必选参数,string类型,属性名称,若已存在该属性名,则该操作会修改原属性. e.g. "level"
  • value: 必选参数,支持stringboolintdouble类型,对应属性名的属性值

rsp:

  • msg: string类型,错误信息,由业务选择是否接收,会返回失败的具体信息,若成功则不返回

span之间建立非父子关系的关联,一般为异步场景使用

lua
-- 方法原型
span:add_link(span_context, attribute)

-- 使用示例
function get_object()
    local span = trace.start_span(name, attribute)

    span:add_link(span_context, {"level": "notice", "opcode": 1})

    local err_msg = span:add_link(span_context, attribute)
    local err_msg then
        log:error("%s", err_msg)
    end
end

req:

  • span_context: 必选参数,字典类型,只能使用由span:get_context()导出的spancontext数据
  • attribute: 可选参数,字典类型,键必须为string,值只支持stringboolintdouble类型,span的属性,添加links时的补充属性,由用户根据需要使用,后续跟随span数据作为links字段的额外字段导出. e.g. {"level": "notice", "opcode": 1}

rsp:

  • msg: string类型,错误信息,由业务选择是否接收,会返回失败的具体信息,若成功则不返回

add_event 添加span事件

在span中记录事件,类似日志记录操作,是用于可视化的主要部分

lua
-- 方法原型
span:add_event(name, attribute)

-- 使用示例
function get_object()
    local span = trace.start_span(name, attribute)

    span:add_event(string.format("get object failed: %s", ret), {"level": "notice", "opcode": 1})

    local err_msg = span:add_event(name, attribute)
    local err_msg then
        log:error("%s", err_msg)
    end
end

req:

  • name: 必选参数,string类型,事件描述信息,即日志记录内容. e.g. "get object failed"
  • attribute: 可选参数,字典类型,键必须为string,值只支持stringboolintdouble类型,添加event时的补充属性,添加event时的补充属性,由用户根据需要使用,后续跟随span数据作为event字段的额外字段导出. e.g. {"level": "notice", "opcode": 1}

rsp:

  • msg: string类型,错误信息,由业务选择是否接收,会返回失败的具体信息,若成功则不返回

set_status 设置当前span状态

设置当前span状态,默认状态为unset,支持unsetokerror三种状态设置,span状态可用于尾部采样、可视化筛选等

lua
-- 方法原型
span:set_status(status, description)

-- 使用示例
function get_object()
    local span = trace.start_span(name, attribute)

    span:set_status("ok", "get object successed")

    local err_msg = span:set_status(status, description)
    local err_msg then
        log:error("%s", err_msg)
    end
end

req:

  • status: 必选参数,string类型,span状态,仅可设置description. e.g. "ok"
  • description: 必选参数,string类型,状态补充描述. e.g. "get object successed"

rsp:

  • msg: string类型,错误信息,由业务选择是否接收,会返回失败的具体信息,若成功则不返回

get_context 获取当前span的上下文信息

获取当前span的上下文信息,用于后续span进行linkchild span创建

lua
-- 方法原型
local context = span:get_context()

-- 使用示例
function get_object()
    local span = trace.start_span(name, attribute)

    local context = span:get_context()

    local context, err_msg = span:get_context()
    local err_msg then
        log:error("%s", err_msg)
    end
end

rsp:

  • context: span的上下文信息,字典类型,组成字段为trace_id, span_id, trace_state, is_remoting, trace_flags, 业务不感知具体信息,仅用来传递,禁止修改context内容,否则会导致下级span链接失败
  • msg: string类型,错误信息,由业务选择是否接收,会返回失败的具体信息,若成功则不返回

is_recording 检查当前span是否可操作

采样策略制定、发生错误等都会导致创建span失败而返回no operation span,当业务需要记录的内容需要通过耗时或资源占用等方式获取,可以通过该方法进行判断是否需要收集记录信息

lua
-- 方法原型
local is_recording = span:is_recording()

-- 使用示例
function get_object()
    local span = trace.start_span(name, attribute)

    if span:is_recording() then
        span:add_event()
        span:set_status()
    end
end

rsp:

  • is_recording: bool类型,反映当前span是否可操作,发生错误时则返回false

finish 结束当前span

结束当前span,自动触发存储或上报等操作

lua
-- 方法原型
span:finish()

-- 使用示例
function get_object()
    local span = trace.start_span(name, attribute)

    span:add_event()
    span:set_status()

    span:finish()
end

pcall 执行方法并捕获执行状态并记录到span中进行上报

类似lua的pcall方法,保护调用之外,同时捕获函数内部的执行结果,刷新span状态为"ok"或"error"并上报, 并且会自动结束此次跟踪

lua
-- 方法原型
span:pcall(cb, ...)

-- 使用示例
function get_object()
    local span = trace.start_span(name, attribute)
    local function record(time, msg)
        if type(msg) ~= "string" then
            error("msg must be string")
        end
        print(time, msg)
    end

    local ok, ret = span:pcall(record, time, msg)
end

req:

  • function: 必选参数,pcall进行保护调用的函数. e.g. function();
  • param: 可选不定参数,保护调用函数的参数

rsp:

  • status: bool类型,表示函数是否成功执行(true表示成功,false表示失败)。
  • result: 函数的返回值(如果成功)或错误信息(如果失败)

3.2 指标

本模块用于metrics相关能力接口的Lua层隔离,以实现Lua层、封装层、开源引入层隔离,Lua层与封装层之间通过弱引用隔离,在无法require到封装库时,Lua层提供空对象空接口保护业务不受影响

核心概念简述

  • MeterProvider:整个 Metrics API 的入口点,负责创建和管理所有 Meter 实例
  • Meter:由 MeterProvider 创建,负责生成具体的指标工具Instrument。根据业务需要,通过CreateXxxInstrument()创建不同类型
  • Instrument:由 Meter 创建,直接负责采集和上报具体的测量数据

层级结构示例:

plaintext
+-- MeterProvider()
    |
    +-- Meter(name='test1', version='1.0.0', schema url='https://test//test')
    |   |
    |   +-- Instrument<Counter>(name='counter_test', description='test counter', unit='kb')
    |   |
    |   +-- instrument...
    |
    +-- Meter(name='test2', version='1.1.0', schema url='https://test//test')
        |
        +-- Instrument<ObservableCounter>(name='observable_counter_test', description='test observable counter', unit='kb')
        |
        +-- Instruments...

3.2.1 meter创建

metermetrics能力构建中最初的一环,业务需要持有meter根据业务创建不同类型的instrument来记录数据。组件通过调用get_meter()来创建meter

lua
-- 方法原型
local meter = metrics.get_meter(name, version, schema_url)

-- 使用示例
local metrics = require 'telemetry.metrics'
local meter_custom = metrics.get_meter(name)

req:

  • name: 必选参数, string类型, meter名称
  • version: 可选参数, 版本号
  • schema_url: 可选参数, 关联的语义约定的URL

3.2.2 instrument创建

instrument是负责采集和上报具体的测量数据的载体,业务根据需要选择创建不同类型的instrument

NOTE

仪器命名呈现在最终可视化系统中,直接体现了数据上报的易读性,需要着重审视命名合理性

lua
-- 方法原型
meter:create_counter(name, description, unit)
meter:create_updowncounter(name, description, unit)
meter:create_observable_counter(name, description, unit)
meter:create_observable_updowncounter(name, description, unit)
meter:create_observable_gauge(name, description, unit)

-- 使用示例
local counter = meter:create_counter("counter_data", "count some data for test", "kb")
  • create_counter:创建一个单调递增的计数器counter
  • create_updowncounter:创建一个可递增递减的计数器counter
  • create_observable_counter:创建一个异步的单调递增的计数器observable_counter
  • create_observable_updowncounter:创建一个异步的可递增递减的计数器observable_updowncounter
  • create_observable_gauge:创建一个异步的瞬时仪表observable_gauge

req:

  • name: 必选参数, string类型, instrument名称
  • description: 可选参数, instrument的相关描述信息
  • unit: 可选参数, 采集数据的单位,e.g. "kb"

3.2.3 instrument方法调用

同步仪器

同步仪器包括counterupdowncounterhistogram,通过显式调用直接记录指标

lua
-- 创建 Meter
local meter = metrics.get_meter()

-- 创建 Counter
local counter = meter:create_counter("my_counter")

-- 增加Counter计数器的值
counter:add(10)
lua
-- 创建 Meter
local meter = metrics.get_meter()

-- 创建 UpDownCounter
local updowncounter = meter:create_updowncounter("my_updowncounter")

-- 增加或减少UpDownCounter的值
updowncounter:add(5)    -- 增加
updowncounter:add(-3)   -- 减少

-- 使用 Attribute 给指标添加维度示例
flash_io_counter:add(data_size, {file_type="log", mc_name="xx"})
flash_io_counter:add(data_size, {file_type="persistence", mc_name="xx"})
lua
-- 创建 Meter
local meter = metrics.get_meter()

-- 创建 Histogram
local histogram = meter:create_histogram("my_histogram")

-- 记录Histogram的值
histogram:record(5)

-- 使用 Attribute 给指标添加维度示例
record:record(data, {mc_name="xx"})

在实际业务的度量采样中,添加标签可以从多个维度去分析同一个指标,可视化系统支持通过标签筛选和聚合指标。例如:

  • otelcol_flash_io_total{file_type = "log"}可查看指标在写入类型为日志的flash写入量数值变化
  • sum(otelcol_flash_io_total{file_type = "persistence"})可查看指标在写入类型为持久化的flash写入总量的数值变化

异步仪器

异步仪器包括observable_counterobservable_updowncounterobservable_gauge,也称为观察仪器,则是通过注册回调函数来采集数据。这些回调函数会在收集指标时被调用,这些数据需要定期轮询获取

lua
-- 创建 Meter
local meter = metrics.get_meter()

-- 创建ObservableCounter
local observable_counter = meter:create_observable_counter("my_observable_counter")

-- 注册回调函数
observable_counter:add_callback(function()
    return 30
end)

3.3 日志

本模块用于logs相关能力接口的Lua层隔离,以实现Lua层、封装层、开源引入层隔离,Lua层与封装层之间通过弱引用隔离,在无法require到封装库时,Lua层提供空对象空接口保护业务不受影响

日志接口与调试日志接口已归一,内部通过键值对列表和参数列表方式区分上报日志和调试日志

3.3.1 日志记录

接口功能
log:debug(body, attrs)上报debug级别日志
log:info(body, attrs)上报info级别日志
log:notice(body, attrs)上报notice级别日志
log:warn(body, attrs)上报warn级别日志
log:error(body, attrs)上报error级别日志
lua
-- 记录debug日志
log:debug('my debug log')

-- 记录debug日志,和相关属性信息
log:debug('my debug log', {system_id=1, mc_name="xx"})

-- 其他级别日志使用方法同上

4. 可观测后端环境搭建指导

可观测后端环境一共需要五个,包括:

  • opentelemetry-collector-contrib(用于接收bmc发送的可观测数据,再将可观测数据分别发送给zipkin、prometheus和elasticsearch后端接收,实现类似中转站的能力)
  • zipkin(用于接收可观测traces数据)
  • prometheus(用于接收可观测metics数据)
  • elasticsearch(用于接收可观测logs数据)
  • grafana(统一的可视化后端,可直接通过grafana对接zipkin、prometheus和elasticsearch,用于查看上报的可观测数据)

个人使用可以通过下载对应软件的windows包进行搭建,linux或其他操作系统仅软件包形式有差异,配置方法均相同

4.1 opentelemetry-collector-contrib 搭建

opentelemetry-collector-contrib(简称otelcol),可从otelcol下载地址进行下载。BMC与otelcol之间需要支持TLS加密,从而防止可观测数据泄漏,因此在otelcol环境搭建之前需要先准备证书

4.1.1 证书制作方法

安装XCA

XCA是一款开源的x509证书生成工具,可用来生成BMC与otelcol之间认证的TLS证书,可从XCA安装包下载地址进行下载

说明:如果遇到XCA 2.9.0下载过慢,可选择XCA 2.8.0或更低版本进行下载

新建数据库

点击左上角文件->新建数据库->设置数据库密码

制作CA证书

(1)创建证书

(2)来源

一定要点击应用模板扩展信息!

(3)主题

(4)扩展

证书的有效期要早于服务端以及客户端的系统时间!建议有效期的起始时间设置早一点

创建完成后应有一CA证书

制作SSL证书

(1)创建证书

(2)来源

(3)主题

(4)扩展

证书的有效期要早于服务端以及客户端的系统时间!建议有效期的起始时间设置早一点

创建完成后应有一CA证书签发的ssl证书

导出证书

(1)导出CA证书 选中CA证书->导出

(2)导出SSL证书 选中SSL证书->导出

(3)导出SSL私钥

最终得到的证书和密钥说明

1)可观测CA证书,用于导入openUBMC,或者用于开启双向认证后配置otelcol的client_ca_file参数

shell
observability_CA.crt

2)可观测openTelemetry Collector Contrib证书,用于配置otelcol的cert_file参数

shell
observability.otelcol.crt

3)可观测openTelemetry Collector Contrib私钥,用于配置otelcol的key_file参数

shell
observability.otelcol.key.pem

4.1.2 配置opentelemetry-collector-contrib

完成证书制作之后,就可以进行部署otelcol了,下载的安装包中没有配置文件(otelcol-config.yaml),需要手动创建otelcol-config.yaml这个配置文件,然后配置如下:

yml
receivers:
  otlp:
    protocols:
      http:
        endpoint: "0.0.0.0:4318"  # OTLP over HTTP,4318端口为bmc与otelcol通信端口,可自行更改为其他端口使用
        max_request_body_size: 67108864  # 64MB
        tls:
          # client_ca_file: observability_CA.crt    # 用于双向认证的CA证书,bmc未开启双向认证不要设置该属性
          cert_file: observability.otelcol.crt            # otelcol证书
          key_file: observability.otelcol.key.pem   # otelcol密钥

processors:
  # 批处理器 - 控制数据分批
  batch:
    send_batch_size: 1024
    send_batch_max_size: 2048
    timeout: 1s

  # 内存限制器
  memory_limiter:
    limit_mib: 512
    spike_limit_mib: 128
    check_interval: 5s

exporters:
  debug:
    verbosity: detailed
  prometheus:
    endpoint: "127.0.0.1:49091" # prometheus对外端口
    namespace: "otelcol"
  elasticsearch:
    endpoint: "https://127.0.0.1:9200"  # elasticsearch默认端口,不要修改
    user: "elastic"
    password: "xxxxxx"  # elasticsearch初始启动过程可获取默认密码,或通过自行重置获取密码
    tls:
      ca_file: /home/elasticsearch/elasticsearch-9.1.0/config/certs/http_ca.crt
  zipkin:
    endpoint: "http://127.0.0.1:9411/api/v2/spans" # 9411为zipkin默认端口,无需修改

service:
  pipelines:
    metrics:
      receivers: [otlp]
      exporters: [debug, prometheus]
    logs:
      receivers: [otlp]
      exporters: [debug, elasticsearch]
    traces:
      receivers: [otlp]
      exporters: [debug, zipkin]

BMC默认开启单向认证,因此otelcol必须要配置cert_file属性和key_file属性;如果开启双向认证,那么需要配置client_ca_file属性

启动otelcol命令,--config参数后填otelcol-config.yaml文件路径:

bash
otelcol-contrib --config otelcol-config.yaml

特别提醒: 如果启动otelcol时提示启动失败,原因为某个端口号被占用,需要将占用了该端口号的程序关闭才能使otelcol启动成功

4.2 zipkin后端搭建

Zipkin用于接收opentelemetry-collector-contrib发送的Trace类型数据,并进行可视化呈现,可通过zipkin下载地址进行下载

由于zipkin安装依赖环境上的java版本,因此部署环境java版本较低的话需要进行升级,通过openjdk下载地址下载后安装

Zipkin默认不需要额外的配置文件,只要环境上java版本满足zipkin要求,可直接执行下列命令启动:

shell
java -jar /zipkin_path/zipkin-server-3.5.1-exec.jar

如果下载了对应版本的openJDK,则执行时指定JDK路径即可

shell
/jdk_path/jdk-17.0.1/Home/bin/java -jar /zipkin_path/zipkin-server-3.5.1-exec.jar

4.3 prometheus后端搭建

prometheus用于接收opentelemetry-collector-contrib发送的Metric类型数据,并进行可视化呈现,可通过prometheus下载地址进行下载,prometheus启动依赖配置文件(prometheus.yml),配置示例如下:

  • scrape_interval为Prometheus抓取上报数据的周期,可按需调整
yml
# my global config
global:
  scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
  evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
  # scrape_timeout is set to the global default (10s).

# Alertmanager configuration
alerting:
  alertmanagers:
    - static_configs:
        - targets:
          # - alertmanager:9093

# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
rule_files:
  # - "first_rules.yml"
  # - "second_rules.yml"

# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs:
  # The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
  - job_name: "prometheus"

    # metrics_path defaults to '/metrics'
    # scheme defaults to 'http'.

    static_configs:
      - targets: ["127.0.0.1:49091"]  # 监听49091端口,用于接收otelcol发送的Metric数据,需要与otelcol中配置的prometheus端口一致
       # The label name is added as a label `label_name=<label_value>` to any timeseries scraped from this config.
        labels:
          app: "prometheus"

启动prometheus,通过49092端口对外提供web服务

shell
./prometheus --web.enable-otlp-receiver  --web.listen-address=0.0.0.0:49092 --config.file=prometheus.yml

4.4 elasticsearch后端搭建

elasticsearch用于接收opentelemetry-collector-contrib发送的Log类型数据,可通过elasticsearch下载地址进行下载,elasticsearch需要以非管理员方式启动,因此部署时需要创建非管理员用户

step1:添加普通用户

shell
useradd elasticsearch

step2:解压elasticsearch压缩包(压缩包名称按实际填写)到指定路径(例如/home/elasticsearch)

shell
tar xvf elasticsearch-9.1.0-linux-aarch64.tar.gz -C /home/elasticsearch

step3:为elasticsearch所在目录设置权限

shell
chown -R elasticsearch:elasticsearch /home/elasticsearch/

step4:启动elasticsearch(压缩包名称按实际填写)

shell
/home/elasticsearch/elasticsearch-9.1.0/bin/elasticsearch &

step5:elasticsearch首次启动将在控制台打印默认用户名密码,需要记录下来并用于opentelemetry-collector中elasticsearch密码配置

说明:如果未能记录下初始启动过程中的密码,也可以使用bin/elasticsearch-reset-password -u elastic进行密码重置

初始启动过程默认密码信息打印如下,Password for the elastic user下一行即为默认密码:

shell
 Elasticsearch security features have been automatically configured!
 Authentication is enabled and cluster connections are encrypted.

ℹ️  Password for the elastic user (reset with `bin/elasticsearch-reset-password -u elastic`):
  xxxxxx

 ...

说明:elasticsearch默认不提供可视化界面,需要通过grafana可视化后端进行查看数据

4.5 grafana后端搭建

grafana后端用于对接zipkin、prometheus和elasticsearch后端,可通过grafana统一查看所有可观测数据,无需分别登陆zipkin或prometheus的可视化界面

1)grafana安装包可从grafana下载地址进行下载

2)设置语言为中文(可选) 进入conf目录下,编辑defaults.ini文件如下字段:

bash
default_language = zh-Hans

3)启动grafana 运行bin目录下grafana-server即可

bash
./bin/grafana-server &

4)登录grafana WEB UI 默认端口号为3000,默认用户名和密码均为admin

bash
http://{ip}:3000

说明:此处的ip为grafana 部署环境的ip,由于上述配置采用全零监听,针对Linux/Windows系统对外IP即可访问(可通过ifconfig或ipconfig进行查询),如果本地环回地址未被占用,也可使用127.0.0.1进行访问

登录到grafana WEB UI之后,还需要配置数据源才能实现对接

1)点击连接 -> 数据源

2)点击右上角 添加数据源

3)选择对应类型的数据源,分别配置 prometheuselasticsearchzipkin数据源

4)在Connection位置配置数据源URL,例如 http://{ip}:{port}/

4.6 docker一键式部署

由于可观测功能涉及的可视化后端数量众多,且部署过程可能存在一些问题在本指南中没有说明,因此openUBMC提供了一份集成所有可视化后端的docker文件,集成在manifest中的dockerfile文件中,开发者确保可视化后端的下载地址均能访问即可,在完成manifest初始化之后就能将docker镜像生成出来

需要注意的是,运行镜像命令须如下执行,需要额外添加用于可视化后端通信的端口绑定及文件夹挂载,否则会出现端口无法访问、bmc与可视化后端通信失败等问题,openubmc/xx.yy为生成的镜像名称

shell
docker run \
    -p <BMC Studio服务端>:10000 \
    -p <qemu ssh对外服务端>:<qemu ssh服务端> \
    -p <zipkin对外服务端>:9411/tcp \
    -p <otelcol对外服务端>:4318/tcp \
    -p <prometheus对外服务端>:49092/tcp \
    -p <grafana对外服务端>:3000/tcp \
    ...
    -d --mount type=bind,source=/root/workspace,target=/home/workspace \
    --mount type=bind,source=<证书存放路>,target=/root/workspace/observability/collector-contrib/cert \
    --restart=always --name openubmc openubmc/xx.yy

下述四个端口为可视化后端正常运行必须使用的端口,务必进行配置

  • 9411端口为zipkin默认端口,zipkin对外服务端口建议设置为9411或者其他端口
  • 4318端口为otelcol默认端口,otelcol对外服务端口建议设置为4318或者其它端口
  • 49092端口为prometheus默认端口,prometheus对外服务端口建议设置为49092或者其他端口
  • 3000端口为grafana默认端口,grafana对外服务端口建议设置为3000或者其他端口

证书存放路径为可视化后端需要使用的证书和密钥,请将准备好的证书中的SSL证书与SSL密钥存放路径进行替换,dockerfile中/root/workspace/observability/collector-contrib/cert为镜像中otelcol读取证书文件的路径,不要进行更改

4.6.1 执行部署脚本

进入容器后执行以下步骤:

  • 1、执行su root切换为root用户
  • 2、执行cd /root/workspace/observability进入可视化后端集成的目录
  • 3、执行bash observability.sh start启动可视化后端
  • 4、一般来说执行完成第三步之后,直接执行第五步即可,但如果没有看到回显Password for the [elastic] user successfully reset.的话,说明Elasticsearch密码重置失败,需要再次执行expect /root/workspace/observability/reset_elastic_password.exp重置Elasticsearch密码为预置密码
  • 5、执行完成上述步骤后访问grafana对外服务端口(如127.0.0.1:3000)访问grafana终端,首次登陆grafana需要使用用户名为admin、密码为admin进行登录
  • 6、此外还可以直接访问zipkin对外服务端口(如127.0.0.1:9411)访问zipkin终端来查询跟踪数据;直接访问prometheus对外服务端口(如127.0.0.1:49092)访问prometheus终端来查询度量数据

grafana使用方法可阅读可观测特性使用指南进行了解

5. 常见问题解答

5.1 DT时如何导出可观测数据

跟踪数据导出:DT场景下span结束时会通过文件流导出器将span数据导出到os.get_env('PROJECT_DIR') .. '/test/.test_temp_data/test_trace.txt,用户在DT中需要收集span数据时调用trace.flush()手动刷新缓冲区,后续可通过对比span数据进行DT自测

指标数据导出:DT场景下会通过文件流导出器将metrics数据导出到os.get_env('PROJECT_DIR') .. '/test/.test_temp_data/metric.txt

text
注:导出数据为txt格式,非json格式