阿里云 Arms Python 接入&使用分享

427 阅读7分钟

背景

做的项目服务上线后,因其为 Python 技术栈,Arms 的实时监控对其支持一直不是很完善,跟不上 Java 那样成熟的监控体系(截止 20241017),但近期因开始考虑一些业务告警的实施,需要对 Arms Python 监控的支持进行研究。

产品介绍

在 Arms 产品体系内,对 Python 的支持为链路追踪,由可观测链路 OpenTelemetry 版产品提供。

什么是 OpenTelemetry

OpenTelemetry 是一个开源的可观测性框架和工具包, 专注于监控数据的采集和上报,而关于存储和呈现则由遵守 OpenTracing 标准的服务来承接即可, 因此众多云厂商的监控均会支持此套标准。

当然市场上也有类似的开源产品, 例如 Skywalking, Jaeger 等。

目前大部分都是选择云厂商部署,所以选择上都优先使用厂商推荐的方案。

如果是内部运维自建平台,倾向使用整套解决方案,例如 Skywalking 等, 运维&使用成本更低。

特性OpenTelemetryApache SkyWalkingJaeger
OpenTracing 兼容性完全兼容,且是新标准支持 OpenTracing 及 OpenTelemetryOpenTracing 参考实现,完全兼容
客户端支持语言支持多种语言(Java、Go、Python 等)支持 Java、Go、Python、Node.js 等支持 Java、Go、Python、Node.js 等
存储与后端工具集成(如 Jaeger、ElasticSearch)支持 ElasticSearch、MySQL、H2支持 ElasticSearch、Cassandra、Kafka 等
传输协议支持HTTP、gRPC、OTLPHTTP、gRPC、KafkaHTTP、gRPC、Thrift
UI 丰富程度依赖集成 Jaeger 或 Grafana 等 UI 工具内置丰富的 UI,包括拓扑图、调用链分析提供基本的 UI,功能相对较简单
实现方式 - 代码侵入性侵入性低,支持自动集成低侵入性,采用无侵入式代理中等侵入性,需要手动在代码中加入追踪点
扩展性高扩展性,支持自定义采集器、处理器等高扩展性,支持插件机制及多种中间件较好扩展性,支持自定义采样策略、存储等
Trace 查询依赖 Jaeger 或 Zipkin 查询支持强大的查询功能,支持多维度条件过滤提供强大的查询功能,支持多种查询维度
告警支持需与外部工具集成(如 Prometheus)内置告警机制,支持与外部系统集成不提供,需与 Prometheus 等工具集成
性能损失低到中,取决于采样率及配置低到中,随着监控范围增加,性能开销上升低到中,主要取决于采样率和存储开销

基本概念

Trace : 一个调用链代表一个事务或者流程在(分布式)系统中的执行过程, 在OpenTracing标准中,trace是多个span组成的一个有向无环图(DAG),每一个span代表trace中被命名并计时的连续性的执行片段。

Span : 一个span代表系统中具有开始时间和执行时长的逻辑运行单元。span之间通过嵌套或者顺序排列建立逻辑因果关系。

Metric:统计数据,例如应用、接口、数据库的请求数据、响应时间、异常数等。

image.png

出自阿里云-什么是调用链

使用集成

阿里云接入

接入支持 OpenTelemetry Skywalking Jaeger, 但此部分选择了阿里云推荐的 通过OpenTelemetry上报Python应用数据 自动埋点接入方案。

应用改造

应用自身需要安装 OpenTelemetry 相关依赖,以便支持监控采集应用数据

OpenTelemetry 支持采集的框架可以参考这里 github.com/open-teleme…

# 安装依赖
pip install opentelemetry-distro opentelemetry-exporter-otlp -i https://mirrors.aliyun.com/pypi/
# 解析项目源码, 并安装项目相关埋点扩展库支持
RUN PIP_INDEX_URL=https://mirrors.aliyun.com/pypi/ opentelemetry-bootstrap -a install

服务接入

应用启动时, 需要注入 OTEL_EXPORTER_XXX 等环境变量,并以 opentelemetry-instrument启动。

这里主要配置的是数据上传的协议以及端点,默认的协议为 grpc, 但本文档采用的是 http 协议传输

# 项目名
export OTEL_SERVICE_NAME=<your_app_name>
# 上报协议
export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
# traces 上传端点
export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=<your_http_trcaes_endpoint>
# metrics 上传端点
export OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=<your_http_metrics_endpoint>

# 启动命令,启用自动埋点
opentelemetry-instrument <your_run_command>

数据流转

启用自动埋点后,OpenTelemetry 会自动采集所有内部链路数据,并通过设置的上报端点进行上报。

数据存储由 sls 日志提供服务,看板则由 Arms 可观测链路支持

image.png

出自阿里云-#数据是如何上报的?

能力覆盖

基础能力

基于 OpenTelemetry 提供分布式链路追踪能力, 能实现应用运行过程中的切面剖析。

业务监控

基于 OpenTelemetry, Arms 提供了以下指标的监控告警:

  • 应用提供服务统计 :基于接口名称提供 次数、错误次数、错误率、响应时间 的监控
  • 异常监控 :基于接口名称提供 发生次数 的监控告警
  • 数据库指标 : 基于数据库名称提供 慢调用、调用次数、错误次数、响应时间 的监控
  • 应用依赖服务统计 : 基于接口调用类型提供 响应时间、调用次数、错误率 的监控

目前 数据库指标 & 应用依赖服务统计 均存在无法过滤问题,阿里云反馈 11 月初修复。

扩展能力

链路日志打通

目前应用的日志中一般会打印 TraceId 来查询问题。而因 OpenTelemetry 主打就是分布式链路追踪,其内部存在一套完整的 TraceId 生成和传递。

作为应用接入, 我们可以打通可观测链路和日志,以提高问题排查的效率。当然官方提供了 如何将TraceId和SpanId写入日志 的接入文档。

但这里面存在两个实际的小问题:

  • 全程黑盒,未提供应用获取 TraceId 的方案。
  • 如本地环境不接入 OpenTelemetry 时, TraceId 就无效。

因此可以将 本地 TraceId 和 OpenTelemetry TraceId 做代码层面的结合,以解决以上的小问题。

在以下代码片段中 TraceContextVar 是项目中搭建的 TraceId 上下文,我们从 OpenTelemetry 获取 Trace, 并开启一个自定义 Trace 来获取当前的 traceId

注意:

  1. 上报的 traceId 可能不是代码中获取的原始 traceId,这里做了一个进制转化,同时补齐 32 位与最终可观测链路中的 TraceId 对齐。
  2. TraceContextVar 中针对外部注入的 trace_id 做了兼容处理, 如为空, 则内部生成一个随机 uuid 来承接
  3. TraceId 一般是请求入口,未来也可以扩展接收外部传入的 TraceId,完成上下游打通。
import typing

from logger_kit.context.variables import TraceContextVar
from logger_kit.customer_logger import logger
from opentelemetry import trace

from fastapi import Request, Response

async def http_middleware(
    request: Request,
    call_next: typing.Callable[[Request], typing.Awaitable[Response]],
) -> Response:
    """请求处理中间件"""

    tracer = trace.get_tracer(__name__)
    # 创建新的span
    with tracer.start_as_current_span("http") as http_span:
        # 获取 arms opentelemetry 服务提供的 trace_id
        _trace_id = ""
        if http_span.get_span_context().trace_id != trace.INVALID_TRACE_ID:
            # 默认是 10 进制, 需要转为 16 进制 与 arms 对齐
            _trace_id = hex(int(http_span.get_span_context().trace_id))[2:]
            if _trace_id:
                # 另外要补全 32 位(8 位十六进制数),用 '0' 补足
                _trace_id = _trace_id.zfill(8)
        request.state.trace_id = TraceContextVar.set(_trace_id)
        logger.info("%s URL --> %s", request.method, request.url)
        resp = await call_next(request)
        resp.headers["x_biz_trace_id"] = request.state.trace_id
        # reset trace_id
        return resp

自定义 Span 上报

基于 Trace Span 的能力, 我们可以自定义 Span 以及上报一些指标数据,用于分析和判断。

from opentelemetry import trace

def call_method():
    tracer = trace.get_tracer(__name__)
    # 创建新的span
    with tracer.start_as_current_span("stream_message") as call_method_span:
        call_method_span.set_attribute("app.threads.len", 0)
        # 处理自己的业务
        ···

能力禁用

自动埋点场景下, 如果某些业务存在大量无效上报, 则可以通过禁用模块或者排除 URL 来实现

# 排除某些 URL
export OTEL_PYTHON_EXCLUDED_URLS="client/.*/info,healthcheck"
# 禁用模块
OTEL_PYTHON_DISABLED_INSTRUMENTATIONS=redis,kafka,grpc_client

能力缺失

当然除了链路追踪, 我们一般也会关注性能指标部分,但目前 OpenTelemetry 还无法满足,常见的就有以下几个部分:

  • Python 线程池监控
    • 业务 thread pool 线程池
    • web 服务器接收请求线程池
  • 定时任务的监控
    • apschedule 定时调度

监控覆盖

当然除了 OpenTelemetry 提供的业务监控,云厂商还有一些其他监控产品可以帮助我们覆盖每个角落,以提高服务的可治理性。

附录