OpenTelemetry开发实战

2025-11-12T14:11:02+08:00 | 33分钟阅读 | 更新于 2025-11-12T14:11:02+08:00

@

学习目标

学完本章,你应该能够:

  1. 讲清主流日志技术栈的演进(ELK/EFK → Loki),以及 Loki 为什么「轻量、存储成本低」,并按「日志流选择器 + 管道操作符」写出基本 LogQL 查询。
  2. 讲清 Kubernetes 系统指标是从哪几个 endpoint 暴露的、用什么组件采集;用 Prometheus Operator / ServiceMonitor CRD 声明式地采集业务指标。
  3. 在 Go 服务里用官方 SDK 暴露 Counter / Gauge / Histogram 三种自定义指标,并接入 Prometheus 抓取,用 PromQL 计算 QPS 和 P95 分位。
  4. 以 Python + FastAPI 为例完整集成 OpenTelemetry SDK:埋点 trace、日志带 trace_id / span_id、用 exemplar 把指标与 trace 关联。
  5. 在 Grafana 里配置 Loki / Prometheus / Tempo 三个数据源,打造「日志 ⇄ 追踪 ⇄ 指标」三合一查询面板,实现一键互通跳转。
  6. 理解零代码(自动注入)集成 OpenTelemetry 的原理、配置方式与局限,并能对比它和 SDK 集成方式的取舍。

前置知识:

  • 能打开 K3s / k8s 集群并执行 helm install、kubectl;理解 DaemonSet、Deployment、Pod、Service、命名空间这些基本概念。
  • 会用 Docker Compose / Docker 构建镜像。
  • 会写简单的 Go main 函数和 Python 脚本,能读懂 context.Context 传递。
  • 上节课已了解 OpenTelemetry 的 Trace / Metric / Log 三种信号与 Collector 的架构。

本章你会动手做的事:

  1. 用 Helm 在一个 K3s 集群里部署 Loki + Promtail + Grafana,采集全集群日志,写出 LogQL 查询并在 Grafana 里看到日志趋势。
  2. 用 kube-prometheus-stack 一键部署 Prometheus + Alertmanager + Grafana,抓取集群系统指标;再部署 Blackbox Exporter 做网站可用性监控。
  3. 用 Go 写一个带三个自定义指标(请求数、状态码计数、耗时直方图)的 HTTP 服务,构建镜像部署到集群,用 ServiceMonitor 让 Prometheus 抓取,并建一个多面板 Dashboard。
  4. 用 Python + FastAPI 写一个模拟微服务调用链的三服务应用,完整集成 OTel SDK,把 trace / log / metric 三样数据分别送到 Tempo / Loki / Prometheus。
  5. 在 Grafana 里配置派生字段、Prometheus exemplar 和 Tempo 的 trace-to-logs / trace-to-metrics,实现三合一互查。
  6. 用 opentelemetry-instrument + 环境变量把同样的服务改造成零代码注入,观察差异。
OTel 开发实战Loki 日志Prometheus 指标集成 OTel SDK三合一面板零代码集成Promtail + LogQL轻量低存储成本cAdvisor + 白盒监控ServiceMonitor + PromQLPython FastAPItrace_id 关联三端Derived Fieldsexemplar + data linkOTel-instrument环境变量注入

先建立整体直觉:一门课打通「三支柱 + 三存储 + 一张面板」

这一章我们不再停留在概念,而是把可观测性的三条数据链路真正跑起来。用一句话概括本章的完整闭环:

日志 → Loki、指标 → Prometheus、追踪 → Tempo,最后都汇到 Grafana 一张面板里,并且用 trace_id 把三条链路互相打通。

flowchart LR
    App[应用
产生 Log/metric/trace] -->|Promtail / SDK / instrument| COL[(三大后端)] COL --> G[Grafana
三合一查询面板] G -->|trace_id 关联合一| R1[日志 ⇄ 追踪] G -->|exemplar 关联| R2[指标 ⇄ 追踪]

整个章节分成五步:先把每一条链路单独打通(第 1、2 讲采集日志和指标),再把 OTel SDK 集成让三条信号能带同一条 trace_id(第 3 讲),接着在 Grafana 里把它们关联成三合一面板(第 4 讲),最后一讲讲一种省代码的零代码注入方式(第 5 讲)。

术语提醒:这一章里你会反复看到两个「pro」开头的东西——在 Loki 场景里 Promtail 是日志采集器,在指标场景里 Prometheus 是指标存储。它们都来自同一个 Grafana/云原生生态,别混。


第一讲:Loki 日志采集实战

日志技术栈:从 ELK / EFK 到 Loki

说到日志采集,业界最常见的选型有三类,其中传统派是 ELK / EFK:

缩写组件职责
EElasticsearch存储 + 索引日志(类似 Loki 的职责)
F / LFluentd / Fluent Bit / Logstash日志采集器,增量化地上传日志
KKibana查询面板(类比 Grafana)

早期大家普遍用 EFK 里的 Logstash,但 Logstash 性能、效率上偏重,所以很多人把采集器换成 Fluentd / Fluent Bit 来提性能。而今天更主流的选择是走「轻量级」路线——也就是 Loki:

  • Loki 是 Grafana 推出的日志查询工具,Loki 对应 Elasticsearch,负责存储 + 索引日志。
  • Promtail 是它的采集器。从名字就能记:Linux 里的 tail -F 命令就是不断观察文件新增的日志流,Promtail 就是干这个——把日志增量化地推到 Loki 里。
  • Grafana 提供日志检索能力,与指标面统一。

相比之下,Loki 的优势是轻量、容易和 Grafana 集成(指标和日志能放同一套面板)、存储成本低(取决于它的索引策略)、处理效率高;而 EFK 的问题是资源占用大、部署和维护复杂度高、长期日志的存储成本高。

复习提示:类比:Loki 和 Elasticsearch 的区别,就像「索引册子」和「全文誊抄」。Elasticsearch 几乎对日志内容做全文索引,Keyword 搜索快,但每个字都要索引,存储和内存蹭蹭涨;Loki 只索引标签(labels)和时间戳,日志行内容不索引——查日志时先用标签快速定位到目标日志流,再在流里做关键字过滤,所以又快又省。这正是它存储成本低的根本原因。

安装:用 Helm 快速拉起 Loki + Promtail + Grafana

官方文档提供了两种安装方式:

  1. 直接用 Helm 装 loki-stack——它包含 Loki、Promtail、Grafana 全套。缺点是内置版本通常比较旧。
  2. 分别单独安装 Loki、Promtail、Grafana,并在 Grafana 里手动配一个 Loki 数据源。本讲用这种方式,可控性更好。

以我们开通的 K3s 集群为例,大概流程是:

# 1. 加 Grafana 官方 Chart 仓库(Loki / Promtail / Grafana / Tempo 基本都在里面)
helm repo add grafana https://grafana.github.io/helm-charts
helm repo update

# 2. 配置 KUBECONFIG,指向你的 K3s 集群
export KUBECONFIG=$(pwd)/k3s.yaml
kubectl get nodes

# 3. 安装 Loki(单体模式,读取 loki-values.yaml)
helm install loki grafana/loki \
  --namespace loki-stack \
  --create-namespace \
  -f loki-values.yaml

Loki 单体的最简配置(快速上手版,注意这不是生产级):

# loki-values.yaml(示意)
loki:
  config:
    # 用 TSDB 作为索引存储,速度更快、扩展性更好(生产建议开启)
    schema_config:
      configs:
        - from: 2023-01-01
          store: tsdb
          object_store: filesystem
          schema: v12
    storage_config:
      filesystem:
        directory: /loki/index
  singleBinary:
    replicas: 1        # 单体二进制模式(target=all)

安装完成后日志会打印一条关键信息:Loki 默认启用了多租户,要求你从 X-Scope-OrgID 这个 header 里拿到租户 ID 传进去。如果不提供,Loki 是采集不到日志的——这个坑在配置 Grafana 数据源时一定要记住。

接着安装作为日志采集器的 Promtail(它以 DaemonSet 方式在每个 Node 上部署):

# promtail-values.yaml(示意)
config:
  clients:
    - url: http://loki-gateway.loki-stack.svc.cluster.local/loki/api/v1/push
      # Loki 开了多租户,必须带上租户 ID,否则采不到日志
      headers:
        X-Scope-OrgID: "1"

安装后可以用 kubectl get svc -n loki-stack 看到 loki-gateway 这个 Service——它就是 Promtail 的日志上报网关。

安装 Grafana 则很简单,不需要 values 文件,装完取出动态生成的 admin 密码,再做端口转发,本地访问 Grafana:

helm install grafana grafana/grafana --namespace loki-stack
kubectl get secret --namespace loki-stack grafana -o jsonpath="{.data.admin-password}" | base64 --decode
kubectl port-forward service/grafana -n loki-stack 3000:80

在 Grafana 里 Configuration → Data sources 添加 Loki 数据源。重点有两点:

  1. URL 要填 Promtail 上报的那个 gateway,例如 http://loki-gateway.loki-stack.svc.cluster.local:3100。因为我们是单体部署,读和写走同一个服务。
  2. 最重要的一项:在 Custom HTTP Headers(或 YAML 里的 httpHeaderName)里,把这个网关接受租户 ID 的 X-Scope-OrgID: 1 加上。

保存后测试连通,然后可以在 Grafana 里 Explore → Loki 选一个 label。有 label 说明采集正常,然后写 {app="loki"} 查最近 5 分钟,就能看到集群里任意工作负载的日志。

LogQL:日志流选择器 + 管道操作符

LogQL 很像 PromQL,可以拆成两大部分:

第一部分是「日志流选择器」——用来选你想查哪个 Pod / 业务,例如:

{app="loki"}

这些标签是 Promtail 采集时根据工作负载的 label 自动帮你打上的(app、container、service、namespace、pod、instance、job 等)。

第二部分是「管道(Pipeline)」操作符——在选定的日志流里做关键字过滤,有四种匹配方式:

管道操作符含义示例
`=`包含关键字
!=不包含关键字!= "health"
`~`正则匹配
!~正则不匹配!~ "debug"

组合起来就是一条完整的 LogQL:

{app="loki"} |= "level=error"

在 Grafana 的 Explore 里可以切到 Code 模式手写,也可以回到 Builder 模式,它会把代码反填成表单。

结构化日志才能做运算:logfmt 与 json

日志格式由业务输出决定,一般有两种:logfmt(key=value key2=value2)和 JSON。它们都包含「键值对」,好处是——你可以对某个键的值做运算,而不只是灰蒙蒙地全文匹配。

例如日志里有一个字段叫请求耗时 duration,你就可以查出「耗时超过 500ms 的所有日志」。但做运算之前,必须先用内置函数把日志格式化成结构化字段:

{app="loki"} | logfmt | duration > 5ms
  • | logfmt:用 logfmt 解释器解析日志行,自动生成里面的标签值(如 duration、status)。
  • | json:同理,用 JSON 解释器解析。
  • 解析后就能直接对 duration、status 等字段做运算:duration > 10ms、logfmt | status = 200。

如果你的业务日志特别乱又不方便改应用,Loki 还提供更复杂的正则 / 提取方式(| regexp、| pattern)来做兜底。

重写输出格式:line_format

业务日志往往又杂又吵,可以用 line_format 重写日志输出。前提是先 | logfmt 把字段解析出来,再重新组装:

{app="loki"} | logfmt | line_format "status={{.status}} duration={{.duration}}"

这样每行日志就只输出你要的 status 和 duration,干净很多。

用日志当指标:从日志里算出 P95

日志里既然打了耗时、状态码,这些其实就已经是「指标」了。Loki 提供了一组 ..._over_time 聚合函数,可以从日志里直接算出分位、均值等指标:

quantile_over_time(0.99,
  {app="loki"} | logfmt
  | unwrap duration [1m]
) by (status)

第二个参数里的 unwrap duration 表示把解析出来的 duration 字段作为采样值取出,[1m] 是一分钟窗口,最后 by (status) 按状态码分组。跑出来就是「按 status 分组的 99% 请求耗时」,在 Grafana 里能看到 200 / 500 两条趋势线。前提是业务日志里得真的有 duration、status 这些字段。

其他 LogQL 技巧

  • 删除颜色标记:业务日志常带的 ANSI 颜色编码,可以用 | strip_ansi 去掉,方便查看。
  • 标签格式化:用 | label_format 重命名 / 重组标签。
  • 删除 / 保留标签:| label_drop 删掉不要的标签、| label_keep 只保留部分标签,避免日志标签太多造成索引膨胀。

原理:Promtail 为什么能采到所有 Pod 日志

Kubernetes 里,所有 Pod 打印到标准输出的日志,都会被 Kubelet(容器运行时)写入宿主机的某个特定目录下。也就是说,只要能从宿主机上采集这个目录的日志,就能间接采到所有 Pod 的日志。

Promtail 正是基于这个原理,以 DaemonSet 方式在每个 Node 上部署一个副本,并通过 hostPath 挂载宿主机的日志目录(如 /var/log/pods 和 /var/lib/docker/containers),read-only 挂载进容器里。它就像 tail -F 一样实时读新增日志行,然后以 HTTP 方式推给 Loki。Promtail 拿到 /var/log/pods/<namespace>/<pod>/<container>/0.log 这类文件,就能按命名空间、Pod、容器解析出标签。

flowchart LR
    POD[Pod 打印日志到标准输出] --> NODE["宿主机 /var/log/pods 目录 (Kubelet 写入)"]
    NODE -->|hostPath 挂载| PT[Promtail
DaemonSet 每 Node 一个] PT -->|tail -F 增量化| LOKI[Loki
存储+索引] LOKI --> GF[Grafana 查询展示]

Loki 生产实践

  • 启用 TSDB 索引 + 对象存储:生产建议给索引启用 TSDB(更快、扩展性更好),并用 from 指定从某个时间点起的新日志才用 TSDB,老日志不用管。对象存储(腾讯云 COS / AWS S3)来存日志——S3 这类存储几乎没有容量上限,成本很低,特别适合长期、海量地存日志。
  • 多租户:Loki 原生支持多租户,大规模生产可以配置多租户(每个团队 / 业务线一个租户 ID)。需要单独配 Loki 和 Promtail,Promtail 配置里填租户 ID。在 Grafana 数据源里可以通过配置多个租户值(1|2)来切换查询,查询日志时带上租户 ID 即可筛选。

Loki 核心组件数据流(谁在干活)

理解 Loki 的架构,你需要记住这几个核心组件和两条链路(写链路 + 读链路):

  • Distributor(分发器):日志接收的第一个组件,接收 Promtail 压上来的日志并分发给 Ingester。
  • Ingester(摄取器):接收日志,很重要的能力是暂存(内存队列),攒一批再写入云存储,而不是来一条写一条。
  • Querier(查询器):从云存储读取日志数据。
  • Query Frontend(查询前端):直接跟 Grafana 对接,处理查询请求、管理调度和缓存(短时间重复查询直接走缓存)。
  • Ruler(规则引擎):执行查询与告警策略,结合日志数据设置告警。
  • Compactor(压缩器):合并压缩日志块,跟对象存储交互,减少存储占用、提高查询性能。

特别注意那个双向箭头:数据从 Distributor 进来后给 Ingester,Ingester 内存在写入云存储前是缓存。所以 Grafana 查询时,Querier 既要读 COS/S3 也要把 Ingester 里还没落盘的数据取出来,合并后交给查询前端,才是完整的日志数据。整个架构跟 Prometheus/Mimir 那套时序引擎很相似。

Loki 的三种部署方式

Loki 支持三种部署方式,区别在于「组件是否拆分 + 是否水平扩展」,镜像其实是同一个,靠启动参数 -target=all / read / write / backend 区分:

部署方式原理适用日志量说明
单体(Single Binary)所有组件跑在单进程二进制里(-target=all)每天约 20GB 内快速上手,本讲 demo 用这个;其实也能写副本数水平扩展
简单可扩展(Simple Scalable)读写分离:write(Distributor+Ingester)、read(Query Frontend+Querier)、backend(Compactor 等),各可横向扩容,配套 Gateway 做分发每天几个 TBHelm 默认安装方式,多数中型公司够用
微服务(Microservices)每个组件单独部署、各自可扩展每天超过 10TB维护难度最高,大型公司使用

哪怕只有单体模式,也把每天的日志量上限说清楚——这有助于你按日志量挑部署方式。简单可扩展模式里 Grafana 查询走的也是 Gateway,读写组件共用同一个镜像、只是启动方式不同。


第二讲:Prometheus 指标采集实战

先看 K8s 系统指标是怎么暴露的

指标采集前,先把 Kubernetes 系统级指标的来源弄清楚。从「容器运行时」一路往上:

  • 容器运行时把指标交给内置在 Kubelet 里的 cAdvisor(收集、处理、导出容器指标)。
  • cAdvisor 再在 Kubelet 的几个 metrics endpoint 上暴露指标,并且每个端点提供对应的 /metrics/summary。
  • metrics-server(附加组件)负责收集、聚合这些指标,提供 Metrics API。
  • 有了 Metrics API,kubectl top 和 HPA(Pod 水平自动扩缩容)才能拿到 Pod 内存 / CPU 用量。

系统级指标主要有三个 endpoint(用 kubectl proxy 转发到本地后可以一个个访问):

  • /metrics/cadvisor:cAdvisor 采集的容器指标(container_*)。
  • /metrics/resource 与 /metrics:K8s 资源使用指标(container_cpu_usage_seconds_total、container_memory_usage_bytes 等)。
  • /metrics/probes:健康检查指标(readiness / liveness 探针)。

Prometheus 抓这些指标相对简单——把 metricsPath 指到对应的 endpoint,定时去 scrape 就行。但配置很复杂(服务发现、relabel、认证机制等),所以有 Prometheus Operator 帮你做默认配置。

Prometheus Operator:用 CRD 声明式管理采集

原生 Prometheus 配置复杂,Prometheus Operator 是安装 Prometheus 的最佳实践:安装简单、自带大量标准 ServiceMonitor 和告警规则。它的核心思想是用 CRD 管理 Prometheus 的配置,这样就能用声明式方式(kubectl apply YAML)来配置和部署。

Operator 提供的几个关键 CRD 你需要重点掌握:

CRD作用
ServiceMonitor配置怎么采集指标(按 label 匹配 Service)— 最常用
PodMonitor类似 ServiceMonitor,但针对没有 Service 的 Pod / job / deployment
PrometheusRule配置告警规则
Prometheus / AlertmanagerPrometheus 与告警组件实例
Probe(配合 Blackbox Exporter)做对外网站的黑盒探活

社区方案:kube-prometheus-stack 一键安装

Operator 仍需自己写 CRD,门槛还是有的。社区更简单的方案是 kube-prometheus-stack——它内置了运行所需的全部组件(kube-state-metrics 替代 metrics-server、node-exporter 导出 Linux 内核级指标、Grafana),并且 Kubernetes 常用指标的抓取策略、relabel、告警规则全部预配置好,开箱即用,是入门首选。

典型安装配置大概要做这些事:让 Grafana 给 admin 配好密码而不是走 secret 动态生成;开启 dashboard 编辑能力;把 ServiceMonitor 和 PodMonitor 的 selector 配置为能发现所有命名空间下的对象;给 Prometheus 加额外的 label(多集群场景用集群名区分);配置持久化存储(如 storageClass)。

helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm upgrade --install kube-prometheus-stack \
  prometheus-community/kube-prometheus-stack -f kube-prometheus-values.yaml

部署后用 NodePort 暴露 Grafana(比如 30080),登录后可以看到 operator 自动帮你添加好了 Prometheus 和 Alertmanager 两个数据源,并且自带了海量 dashboard:

  • Resource / Workload:工作负载的 CPU / 内存 usage 与配额。
  • Kubernetes 节点:cluster exporter(node-exporter)的节点状态、IO。
  • 网络指标:集群级网络进出字节。
  • Prometheus 自身监控:PTSDB 等自身状态。

基本上你想要的系统级监控,这一键装完都有。

黑盒监控:Blackbox Exporter 监控网站可用性

日常最常接到的需求是「7×24 小时监控公司网站可用性」,用 Prometheus 就能解决——这就是黑盒监控(Blackbox),因为无需接入任何业务逻辑。

实现它需要装两个东西:

  1. Blackbox Exporter:负责真正去请求目标网站并返回指标。
  2. 一个 Probe CRD(Kube Prober):声明要监控哪些网站、用什么模块、多长时间探一次。
# blackbox-probe.yaml(示意)
apiVersion: monitoring.coreos.com/v1
kind: Probe
metadata:
  name: website-availability
  namespace: monitoring
spec:
  module: http_2xx        # 期望 2xx 返回
  prober:
    url: http://blackbox-exporter.monitoring.svc.cluster.local:9115
  targets:
    staticConfig:
      targets:
        - https://www.infoq.cn   # 你的目标网站列表
        - https://www.geekbang.org
  interval: 10s            # 每 10 秒探一次

部署后,在 Prometheus 的 Target 里会出现 blackbox job,对应你配置的两个网站。Prometheus 会访问这个黑盒探针、Grafana 端再加载内置的 Blackbox dashboard,就能看到每个网站的状态、返回码、SSL 证书过期时间、探针完成时间、DNS 检查耗时等一整套可用性指标。

flowchart LR
    PROM[Prometheus] -->|请求带 mode=target 参数| BB[Blackbox Exporter]
    BB -->|真正请求| SITE[目标网站]
    SITE -->|返回结果| BB
    BB -->|输出指标 /metrics| PROM
    PROM --> GF[Grafana Blackbox 面板]

这里值得注意一个细节:Prometheus 发起的请求,Object 的 endpoint 其实是同一个 Blackbox Exporter 地址,只是带上了 module 和 target 两个 HTTP 参数——Blackbox 再根据 target 参数真正去请求你的网站。

白盒监控:在 Go 服务里暴露自定义指标

黑盒之外是白盒监控——侵入业务代码,让应用自己输出指标。以 Go 为例,只需集成三个官方包:

import (
    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/collectors"
    "github.com/prometheus/client_golang/prometheus/promhttp"
)

然后对外提供一个 /metrics 接口,Prometheus 定期来抓取。Go 官方 SDK 输出指标时有四种类型:

类型英文特点key 方法
计数器Counter只能往上加Inc() / Add(float64)
仪表盘Gauge可升可降的当前值Set(float64)
直方图Histogram分桶统计,算分位Observe(float64)
摘要Summary客户端算分位(更精确但开销大)Observe(float64)

我们写一个最简单的 HTTP 服务,集成三个指标:请求总数(Counter)、响应状态码计数(Counter)、请求耗时直方图(Histogram):

// Demo:Prometheus 白盒监控
package main

import (
    "math/rand"
    "net/http"
    "time"

    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promauto"
    "github.com/prometheus/client_golang/prometheus/promhttp"
)

// 1. 请求总数:Counter,带 status 和 method 两个标签
var httpRequestsTotal = promauto.NewCounterVec(prometheus.CounterOpts{
    Name: "http_requests_total",
    Help: "Total number of HTTP requests",
}, []string{"method"})

// 2. 响应状态码:Counter,带 status 标签
var httpResponseStatus = promauto.NewCounterVec(prometheus.CounterOpts{
    Name: "http_response_status",
    Help: "Count of HTTP response statuses",
}, []string{"status"})

// 3. 请求耗时:Histogram,带 method 标签,默认桶
var httpRequestDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{
    Name: "http_request_duration_seconds",
    Help: "HTTP request response time",
}, []string{"method"})

// 中间件:所有请求都会经过
func prometheusMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        rw := &respWriter{ResponseWriter: w, statusCode: http.StatusOK}
        next.ServeHTTP(rw, r)
        httpRequestDuration.WithLabelValues(r.Method).Observe(time.Since(start).Seconds())
        httpResponseStatus.WithLabelValues(fmt.Sprint(rw.statusCode)).Inc()
    })
}

关键点:

  • 标准计数器写法:prometheus.NewCounterVec(...) 里传 Name / Help 两种标签;Counter 只能 Inc() 增加。
  • 中间件必须挂在 HTTP 所有请求上,这样每个请求进入/返回时都会更新指标。
  • 最后用 promauto.NewXxx 自动注册 + promhttp.Handler() 暴露 /metrics,Prometheus 就能抓到你自定义的指标(它能在 /metrics 里同时看到 Go 运行时堆栈等指标)。

把服务 go build 成镜像推到仓库,部署成 Deployment,可以加一个 sidecar 容器持续请求接口来制造流量,作为压测/模拟来源(转写里这个 sidecar 反复因为镜像问题 container not found / ImagePullBackOff,最终换官方镜像解决——这类 sidecar 镜像的可用性是常见坑)。

让 Prometheus 抓取业务指标:ServiceMonitor

业务应用能输出指标后,剩下的问题是让 Prometheus 知道去哪抓。原理和抓 K8s 指标一样——告诉 Prometheus 抓指标的接口地址。用 CRD 声明式配置即可,首选 ServiceMonitor(Service 带负载均衡,可通过 label 匹配多个 Pod);PodMonitor 适合没有 Service 的场景(直接抓 Pod / job / deployment)。

ServiceMonitor 这样写:

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: demo2
  labels:
    # 必须和 Prometheus 的 serviceMonitorSelector 匹配,否则不会被捕获
    release: kube-prometheus-stack
spec:
  selector:
    matchLabels:
      app: demo2          # 匹配你的 Service 标签
  endpoints:
    - port: http          # 注意:是 Service 里的端口名,不是端口号
      interval: 30s       # 多久抓一次
      path: /metrics

ServiceMonitor 最大的好处是「责任分离」:工程团队只负责把接口暴露出来,基础设施团队只负责写 ServiceMonitor 这个 CRD 并部署。它还会自动注入一组 relabel 标签(pod、namespace、service、container、instance、job),让你查询时直接用这些标签定位,非常方便。

复习提示:选中即捕获的前提:ServiceMonitor 的 metadata.labels 必须和 Prometheus 实例里配置的 serviceMonitorSelector(或 release 标签)匹配。不写会让你的 ServiceMonitor 静默地不被任何 Prometheus 捕获——排查目标抓取时最容易忽略这一点。

部署后在 Prometheus 的 Targets 里能看到这个 target 已被抓(Health=UP),并在 Graph 里查到 http_requests_total、http_response_status、http_request_duration_seconds_bucket 这些指标。

PromQL:从瞬时值到聚合查询

PromQL 和 LogQL 很像——查某个指标,再用标签选择器定位到具体的指标:

http_requests_total{method="GET"}

这种瞬时值频率很高、看意义不大,所以通常用它做聚合查询。下面介绍三种常用的聚合写法。

1) 按接口分组的平均 QPS(Counter 配 rate):

sum(rate(http_requests_total[1m])) by (method)

由内而外读:http_requests_total 是 Counter;[1m] 取过去 1 分钟;rate() 算变化速率;sum ... by (method) 按 method 分组求和——得到每个接口的 QPS。去掉 by (method) 得到的就是系统总 QPS。健康检查和 /metrics 因为是周期性采样、速率恒定,画出来是直线。

2) 计算请求耗时 P95 分位(Histogram 配 quantile):

histogram_quantile(0.95,
  sum by (le) (rate(http_request_duration_seconds_bucket[1m])))

_bucket 分桶本质也是 Counter,所以先 rate() 再聚合,最后用 histogram_quantile(0.95, ...) 算出第 95 百分位的请求延迟。sum by (le) 按桶上限分组。

在 Grafana 里做一个指标监控面板

用上面的 PromQL,我们可以在 Grafana 建一个多 panel 的 Dashboard,通常包含:

面板PromQL / 说明
实时 QPSsum(rate(http_requests_total[$__rate_interval])),Gauge 类型
QPS 趋势同上,Time series 类型
总请求数sum(http_response_status) by (status),按状态码分组
接口 QPS 明细sum(rate(http_requests_total[1m])) by (method)
请求延迟分位histogram_quantile(0.95, sum by (le) (rate(http_request_duration_seconds_bucket[$__rate_interval])))

Prometheus 生产实践的两个坑

坑一:Histogram 默认 bucket 导致分位计算偏差。很多人直接用官方默认 bucket,但采集后算分位会不符合实际。因为默认 bucket 覆盖范围偏 Web 通用(如 01000s),而你的业务延迟如果只在 100200ms,大部分值都落在同一桶里(如 0~250ms),算分位就产生偏差。解决办法是按你的业务自定义 bucket 边界,更贴合现状。如果不喜欢桶的粒度,也可以用 Summary,但它在 SDK 侧(客户端)算分位,开销更大、不支持跨实例聚合。

一句话决策:需要聚合就选 Histogram,需要非常精确的分位数且范围分布未知就选 Summary。

坑二:annotation 方式抓取不推荐。网上很多教程教你用 prometheus.io/scrape: "true" 注解抓指标,这种方式把「抓取配置」和「应用定义」耦合了,维护成本高,且不支持多端口、不支持认证/relabel、不支持标签选择。所以后来 Prometheus 才加入 ServiceMonitor / PodMonitor 增强抓取——生产里用 ServiceMonitor 就能解决大部分问题。


第三讲:集成 OpenTelemetry SDK

目标:让 log / metric / trace 都带上同一个 trace_id

前两讲我们分别打通了日志和指标,但它们是割裂的。这一讲用 Python + FastAPI 完整集成 OpenTelemetry SDK,让三条信号能带上同一条 trace_id,为下一讲的三合一面板做铺垫。

集成前先了解一条核心思路:真正要把三样数据关联起来,靠的就是 trace_id / span_id 这条唯一标识:

  • Trace(追踪):通过 SDK 把 span 以 gRPC/OTLP 方式发到 Tempo(Grafana Tempo,分布式追踪后端),Tempo 里能看到完整追踪和自定义 attribute。
  • Logs(日志):集成 OTel 后,在输出日志时带上 trace_id 和 span_id(从 OTel SDK 里取),这样就能用 trace_id 把日志和追踪关联起来。
  • Metrics(指标):用 Prometheus exporter 输出指标时,利用 exemplar(示例) 机制把 trace_id 注入进指标。需要打开 Prometheus 的 exemplar 存储开关,否则这段注释(exemplar)会被直接丢弃。

搭建 Python + FastAPI 服务

创建 demo3 目录,引用 OTel / FastAPI 相关库:fastapi、uvicorn、opentelemetry-api/sdk、opentelemetry-instrumentation-fastapi、opentelemetry-exporter-otlp 等。为了抽象,我们把指标和一个 setup_open_telemetry 抽到 utils.py 里。

在 utils.py 里定义一组业务指标(用 Prometheus 客户端),包括:

  • http_requests_total(Counter,按 path + app_name)
  • http_response_total(Counter,按 path + status_code + app_name)
  • http_request_duration_seconds(Histogram,按 path)
  • 错误请求计数(Counter,按 path + exception_type)
  • requests_in_progress(Gauge,正在处理的请求数)

setup_open_telemetry(app, app_name, endpoint) 里做的事:

from opentelemetry import trace
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor

def setup_open_telemetry(app, app_name, otlp_grpc_endpoint):
    # 1. resource:给所有数据打上的资源属性(service.name、自定义 app 标签)
    res = Resource.create({
        "service.name": app_name,
        "service.version": "1.0.0",
        "app": app_name,   # 之后 Grafana 三合一面板会用到这个标签
    })

    # 2. TracerProvider + BatchSpanProcessor(批量发送,效率高)
    provider = TracerProvider(resource=res)
    provider.add_span_processor(BatchSpanProcessor(
        OTLPSpanExporter(endpoint=otlp_grpc_endpoint, insecure=True)))
    trace.set_tracer_provider(provider)

    # 3. 用官方库自动埋点 FastAPI 请求
    FastAPIInstrumentor.instrument_app(app)

应用主要逻辑里的关键点:

  • 读环境变量 OTEL_EXPORTER_OTLP_ENDPOINT,默认 http://tempo:4317(gRPC)。
  • 模拟微服务链路:应用 A 的 /check 接口会请求应用 B 的 /iotask 和应用 C 的 /cputask(其实都是同一个镜像部署成 A/B/C 三个不同的 Deployment),并带上 otel.propagate 注入的 headers 传递 trace context。
  • 日志格式里显式加上 %(otel_trace_id)s / %(otel_span_id)s,这样日志里就带上了 trace_id、span_id。
# 日志格式:把 OTel 的 trace_id/span_id 打进每行日志
LOG_FORMAT = "%(asctime)s %(levelname)s %(name)s "
             "trace_id=%(otel_trace_id)s span_id=%(otel_span_id)s %(message)s"

把三个 Flask/FastAPI 应用(App A / B / C)分别构建成 opentelemetry-python-app 镜像(A 带一个会持续请求 /check 的 sidecar),再配一个 ServiceMonitor 让 Prometheus 抓取这三服务的指标,日志则正常走 Promtail 采到 Loki。

验证:三种数据都带上了 trace_id

部署完可以在 Grafana 里做三项验证:

  1. 日志:Explore 选 Loki,{app="app-a"} 查最近 5 分钟,能看到日志里正常打印出 trace_id=... span_id=...。
  2. 指标:访问 /metrics,能看到我们定义的那些指标,每个指标带 exemplar 注释(# HELP ... 之后的 trace_id 那段)。
  3. 追踪:Tempo 的 Explore 里能看到 app-a → app-b / app-c 组成的分布式追踪链路。

一句话:只要日志/metric/trace 都带同一条 trace_id,打通三合一面板就有了前提。 这部分的 Prometheus exporter 记得开启 otlp_exemplar / exemplar 存储相关开关,否则 metric 侧的 trace_id 不会进存储。日志那边要小心:日志格式里 trace_id 字段拼错、或采集后没打上标签,后面派生字段就提取不到。


第四讲:打造日志、指标和分布式追踪三合一查询面板

什么是三合一查询面板

上一讲我们把日志、指标、追踪都接进来了,但它们在 Grafana 里还是割裂的。三合一面板要做到:这些数据都能用 trace_id 关联起来,于是可以

  • 用日志去查当时的追踪;
  • 用追踪去查当时的日志;
  • 用指标去查当时的追踪(通过 exemplar)……

并且一键链接跳转到当时的 trace_id。这就是「日志 ⇄ 指标 ⇄ 追踪」三合一查询。

每个数据源要做的打通配置

1) Loki:派生字段(Derived Fields)提取 trace_id

配置 Loki 的目的是给日志创建派生字段,从日志内容里把 trace_id 正则提取出来,并把它用作 Tempo 的查询语法,去把 Tempo 数据一并拉过来。

在 Loki 数据源里 → Derived fields → 添加一个字段,name 为 trace_id,正则表达式从日志里匹配出 trace_id,data source 选 Tempo。

2) Prometheus:开启 exemplar 关联 Tempo

在 Prometheus 数据源里开启 exemplar 相关的 data link,把 trace_id 字段关联到 Tempo。这样在指标里看到 exemplar 标记时,就能一键跳到当时的分布式追踪。

3) Tempo:trace to logs / trace to metrics

Tempo 数据源里需要两项配置:

  • trace to logs:选 Loki 为日志源,配置 service name 到 Loki 的 label 映射。例如把 Tempo 里的 service.name=app-a 这个属性 relabel 成 Loki 的 app 标签的值。这样点「查看日志」时查询条件自动填充成 {app="app-a"}。
  • trace to metrics:选 Prometheus 为指标源,同样做 service name → 指标的 service 标签映射,并可配置一个查询条件(如直接给你写好的 P95 histogram_quantile 查询)。
  • 另外在 Tempo → Additional settings 里把 Node graph 开启,能看到完整的调用链关系图。

把这些配置做完后你会发现,Grafana 内部就是不断做「query × data link」的转换,把一条 trace_id 从一套数据跳到另一套数据。

四个互通实验

实验一:Log → Trace(日志查到追踪)

筛选 {app="app-a"} 的日志,找到一条异常日志,里面带着 trace_id。点击这行日志,会出现衍生字段 trace_id 的链接,点进去 → 跳转到 Tempo → 就能看到当时这条日志对应的整条调用链。

sequenceDiagram
    participant L as Loki 日志
    participant G as Grafana
    participant T as Tempo 追踪
    L->>G: 日志行含 trace_id(派生字段)
    G->>T: data link 用 trace_id 查询
    T-->>G: 展示该 trace 调用链

实验二:Metric → Trace(指标查到追踪)

在 Prometheus Explore 里查 http_request_duration_seconds_bucket{app_name="app-a"}。要点是先开启 exemplar 开关——开启后图表上会出现少量菱形标记(相关性标记,Scatter/points)。点击菱形,会弹出 Query with Tempo,再点进去就能看到当时该请求的分布式追踪调用链。

实验三:Trace → Log(追踪查到日志)

打开 Tempo,选一个过去几分钟的 trace,点击该 trace 后选择 View logs,Grafana 会自动把 service.name 的值填到 Loki 的 app 标签里,查询出该 trace_id 对应的全部日志。

实验四:Trace → Metric(追踪查到指标)

还在 Tempo 里,点击某个 trace 的 P95 链接,就能跳到 Prometheus 查看该服务当时的指标情况。

两条与 OTel 无关的链路

特别注意:三合一里有两条链路跟 OpenTelemetry 无关:

  • Metric → Log:通过日志提取指标(LogQL 的 ..._over_time 聚合)实现,跟你是否用 OTel 无关。
  • Log → Metric:通过与指标的服务标签关联来实现反向查指标,同样不依赖 OTel。

其余四条与 trace_id / exemplar 相关的链路(Log→Trace、Trace→Log、Metric→Trace、Trace→Metric)才是依赖 OTel 埋点把 trace_id 带全的。


第五讲:零代码集成 OpenTelemetry

零代码是指「trace 这块不用写代码」

零代码集成的意思是:trace 部分不需要你手写任何 OTel SDK 的代码。注意——这并不代表你的 metrics 和 logs 完全不用写代码。准确说,零代码主要省掉的是你之前在代码里做的:

  • setup_open_telemetry(...) 那一大段(TracerProvider、SpanProcessor、endpoint 等);
  • 在日志里手动设置 trace_id / span_id 的代码。

不过业务应用自己的 Prometheus 集成 / 日志那部分代码仍然是需要的。

原理:opentelemetry-instrument 自动注入

以 Python 为例,零代码集成的核心机制是:在启动时用 opentelemetry-instrument 去启动 Python。它会注入埋点能力——这是一种类似「插装」的机制。

# 原来
python main.py
# 零代码:换成(本质是检测并自动插装)
opentelemetry-instrument python main.py

需要安装的库:opentelemetry-distro、opentelemetry-exporter-otlp、opentelemetry-instrumentation-fastapi 等。

改造代码时,把 utils.py 里的 setup_open_telemetry 整个删掉、man.py 里相关的 trace / 日志配置删掉,只保留 metrics 和业务逻辑即可——因为这些不是面向追踪的。

用环境变量代替代码配置

零代码方式下,你要在 Deployment 的 manifest 里通过环境变量把 OTel 配置注入进去(这些是 OpenTelemetry 标准的 OTEL_* 环境变量):

# 零代码集成的关键:环境变量 + 复写启动命令
spec:
  containers:
    - name: app
      image: opentelemetry/python-app:latest
      env:
        # 资源属性:相当于代码里 Resource.Create 的那几个 attribute
        - name: OTEL_RESOURCE_ATTRIBUTES
          value: "app=opentelemetry-python"
        - name: OTEL_SERVICE_NAME
          value: "opentelemetry-python"
        # 指标 / 日志 / 追踪 exporter 与 endpoint
        - name: OTEL_METRICS_EXPORTER
          value: "otlp"
        - name: OTEL_LOGS_EXPORTER
          value: "otlp"
        - name: OTEL_EXPORTER_OTLP_ENDPOINT
          value: "http://tempo:4317"
        # 日志级别
        - name: OTEL_EXPORTER_OTLP_LOGS_LEVEL
          value: "debug"
      # 用自动注入的启动器复写启动命令
      command:
        - opentelemetry-instrument
        - python
        - main.py
      ports:
        - containerPort: 8080

关键环境变量:

环境变量作用
OTEL_RESOURCE_ATTRIBUTES资源属性,相当于代码里的 Resource.create(含自定义 app 标签)
OTEL_SERVICE_NAME服务名
OTEL_METRICS_EXPORTER / OTEL_LOGS_EXPORTER指定 metrics / logs exporter 为 otlp
OTEL_EXPORTER_OTLP_ENDPOINT发送到 Tempo/Collector 的 gRPC endpoint
OTEL_EXPORTER_OTLP_LOGS_LEVEL日志级别(如 debug)

同时部署一个 ServiceMonitor 让 Prometheus 抓取它的指标。

零代码的效果与局限

部署后验证:

  • 日志:在 Loki 里查 {app="opentelemetry-python"}——你并没有配置任何日志相关的 OTel 代码,但每行日志仍然自动带上了 trace_id / span_id(是自动注入进去的)。
  • 追踪:Tempo 里能看到该服务自动生成的 trace。
  • 指标:exemplar 也会被集成进去,但 trace_id 这一项可能有问题——因为零代码没有在指标里配置 exemplar,会导致「用指标查调用链」这条链路不通。

零代码集成的核心局限:它很难同时打通 log / metric / trace 三样,尤其是指标侧无法可靠带 trace_id(exemplar),所以「用指标查到追踪」这条链路经常断。

SDK 集成 vs 自动集成(零代码)

维度SDK 集成自动集成(零代码)
是否修改业务逻辑需要改源代码基本不改源码
定制性更好一般,只能通过环境变量/复写内置变量
是否引入额外启动器不需要需要引入 opentelemetry-instrument(动态库/启动器)
实现方式自己控制依赖语言本身的插装机制
集成难度较大(编码量多)较小(编码量少)
三端全打通能力好弱(尤其指标 exemplar)
复习提示:我的建议:如果你想要的是真正「All-in-One」的三合一查询效果——在 Grafana 里既用日志查指标、又用指标查日志、还能随时关联到追踪——建议用 SDK 集成方式,就像第四讲演示的那样。零代码方式适合快速把 trace 先跑起来,但指标的 trio 关联会打折扣。

总结

这一章我们从零把 OpenTelemetry 的三条可观测链路全部跑通并关联起来了:

  • Loki 日志:从 ELK/EFK 到 Loki 的选型演进;用 Helm 部署 Loki + Promtail + Grafana;LogQL = 日志流选择器 + 管道操作符;logfmt/json 结构化后才能做运算;三种部署方式对应不同日志量。
  • Prometheus 指标:K8s 系统指标来自 cAdvisor 和 Metrics API;用 kube-prometheus-stack 一键预配置;Blackbox Exporter 做网站可用性黑盒监控;Go 服务集成 Prometheus SDK 暴露 Counter/Gauge/Histogram;ServiceMonitor 声明式抓取 + PromQL 聚合与分位;Histogram bucket 与 annotation 的坑。
  • 集成 OTel SDK:Python + FastAPI 全量集成,让 trace/log/metric 都带同一条 trace_id,日志格式注入 trace_id/span_id,指标用 exemplar 关联 trace。
  • 三合一面板:Loki 派生字段、Prometheus exemplar data link、Tempo 的 trace-to-logs / trace-to-metrics + Node graph,实现 Log⇄Metric⇄Trace 互通跳转。
  • 零代码集成:用 opentelemetry-instrument + 环境变量自动注入;局限在指标 exemplar 无法可靠打通,追求 All-in-One 建议用 SDK 集成。

把这些带同一 trace_id 的日志、指标、追踪落到存储(Loki / Prometheus / Tempo)后,下一步就可以喂给 AIOps——用大模型 / Agent 做异常检测、根因定位与自动修复了。这正是整个训练营最后要达成的目标。

自测题与动手练习

自测题(合上书能答出来,才算懂):

面试官
Loki 相比 ELK/EFK 为什么「轻量、存储成本低」?它在索引上采用什么策略?
候选人
Elasticsearch 几乎对日志做全文索引,占存储和内存;Loki 只对时间戳和 labels 建索引,日志行内容不索引。查询时先用 label 快速定位日志流,再在流内做关键字过滤,所以又快又省、存储成本低,也因此特别轻量。
面试官
LogQL 由哪两部分组成?要「对日志里某个字段做运算」,前提是什么?
候选人
两部分:日志流选择器({app="loki"} 选 Pod/业务)+ 管道操作符(|=/!=/|~/!~ 做关键字或正则匹配)。要先对字段做运算,必须先用 \| logfmt 或 \| json 解析器把字段解析成结构化标签值,才能对 duration、status 这类字段比大小、分组。
面试官
为什么 Promtail 以 DaemonSet 部署在每个 Node 上就能采到所有 Pod 的日志?它靠什么读到宿主机文件?
候选人
K8s 里所有 Pod 打印到标准输出的日志都会被写进宿主机 /var/log/pods 等目录(Kubelet 完成)。Promtail 用 DaemonSet 每 Node 一个副本 + hostPath 只读挂载宿主机目录,像 tail -F 一样实时读新增日志行并推给 Loki,从而间接采到该节点所有 Pod 日志。
面试官
Prometheus Operator 里抓业务指标首选哪个 CRD?它相比 annotation 抓取方式好在哪里?
候选人
首选 ServiceMonitor(有 Service 场景);没有 Service 用 PodMonitor。好处:责任分离(应用只暴露接口,基设只写 CRD)、自动注入 pod/namespace/service/container/job 等 relabel 标签;而 annotation 方式配置与应用耦合、不支持多端口/认证/relabel/标签选择,不推荐。
面试官
PromQL 里如何算「按接口的平均 QPS」和「P95 请求延迟」?
候选人
QPS:sum(rate(http_requests_total[1m])) by (method)(Counter 用 rate 算速率再聚合)。P95:histogram_quantile(0.95, sum by (le) (rate(http_request_duration_seconds_bucket[1m])))(直方图先 rate 再按 le 聚合再取分位)。
面试官
直方图算分位为什么可能偏差?业务延迟集中在 100~200ms 时默认 bucket 会怎样?如何解决?
候选人
默认 bucket 覆盖范围偏通用,业务值若都落在同一桶(如 0~250ms),分位计算就产生偏差。解决:按业务自定义 bucket 边界;或者用 Summary(客户端算分位更精确,但开销大、不支持跨实例聚合)。需要聚合选直方图,需要精确分位且范围未知选 Summary。
面试官
要让日志 / 指标 / 追踪能相互关联,核心是给三类数据注入什么?分别如何实现(三个数据源各自怎么做)?
候选人
核心是统一 trace_id。实现:日志输出时带 trace_id/span_id(OTel SDK 取);指标通过 exemplar 把 trace_id 注入(需开启存储);追踪天然带 trace_id 存 Tempo。这样每条日志、每个指标、每条 trace 都指向同一请求。
面试官
三合一面板里,Loki / Prometheus / Tempo 三个数据源分别要做哪些配置来打通?
候选人
Loki:加衍生字段用正则提取 trace_id 并连到 Tempo;Prometheus:开启 exemplar 的 data link 连到 Tempo;Tempo:配置 trace-to-logs(service.name → Loki 的 app 标签)、trace-to-metrics(service.name → Prometheus 的 service 标签 + P95 查询),并开启 Node graph 看调用关系。
面试官
零代码集成 OpenTelemetry 的原理是什么?打开 / 关闭了哪些东西?它的核心局限是什么?
候选人
原理:启动时用 opentelemetry-instrument python main.py 自动插装,通过 OTEL_* 环境变量注入配置,无需手写 setup_open_telemetry 和日志里手动设 trace_id 的代码。局限:metrics 侧无法可靠带 exemplar trace_id,导致「用指标查到追踪」链路经常不通;要真正的 All-in-One 三合一建议用 SDK 集成。

动手练习(建议真做一遍):

  1. 在 K3s 集群里用 Helm 分别安装 Loki + Promtail + Grafana,配好 X-Scope-OrgID 租户头后,写一条 {app="..."} |= "error" | logfmt | duration > 5ms 的 LogQL,并顺手用 quantile_over_time 从日志里算出按状态的 P95 耗时。
  2. 把 Go 服务的 promauto.NewHistogramVec 换成一个自定义 bucket(贴合你 100~200ms 的业务延迟),对比 /metrics 里 _bucket 的分桶变化,理解默认 bucket 的偏差来源。
  3. 部署三服务 Python 应用(App A/B/C 模拟链式调用),确认日志/metric/trace 都带同一条 trace_id 后,再按第四讲把 Loki 衍生字段、Prometheus exemplar、Tempo trace-to-logs/metrics 配好,分别做完 Log→Trace、Metric→Trace、Trace→Log、Trace→Metric 四个互通实验。
  4. 把同一个应用改成 opentelemetry-instrument + 环境变量启动,改用 env 注入 OTEL_*,验证日志仍自动带 trace_id,再验证「用指标查 trace」这条链路是否还通。

本章小结

复习提示:
  • Loki:只索引时间戳+labels,故轻量低存储成本;LogQL = 选择器 + 管道;结构化(logfmt/json)后才能算字段;三种部署方式按日志量选(单体→简单可扩展→微服务)。
  • Prometheus:K8s 系统指标来自 cAdvisor 与 Metrics API;kube-prometheus-stack 一键预配置;黑盒用 Blackbox Exporter;白盒用 Prometheus SDK 暴露 Counter/Gauge/Histogram;生产用 ServiceMonitor 而非 annotation。
  • OTel SDK:集成核心是让 log/metric/trace 都带同一 trace_id——日志打 trace_id/span_id、指标走 exemplar、追踪天然带 trace_id。
  • 三合一面板:Loki 派生字段 + Prometheus exemplar + Tempo trace-to-logs/metrics + Node graph,四步点穿 Log⇄Metric⇄Trace;两条与 OTel 无关的链路是 metric⇄log(LogQL 提指标 / 服务标签关联)。
  • 零代码:opentelemetry-instrument + OTEL_* 环境变量自动注入,省掉 trace 埋点代码;但指标 exemplar 打不通,追求 All-in-One 用 SDK 集成更稳。
  • 下一篇我们会把这套带统一 trace_id 的可观测数据接到 LLM / Agent,做自动根因分析与自动修复——把「看到问题」升级成「解决」。
About Me

没什么想介绍的,一个很大众的码农…

喜欢代码,车,马,真的是 🐎

讨厌别人让我给自己的代码写注释 最厌烦别人的程序没有写注释

目标

学AI,加油!加油!