先选服务边界,再算错误率

frontend → orders 调用链(orders 先调用 inventory,成功后再调用 external-stub)跨越多跳。Istio 的 HTTP 请求指标可以分别由客户端代理和服务端代理报告,reporter="source" 与 reporter="destination" 并不是两个互不重叠的用户请求集合。若把两侧 istio_requests_total 直接相加,或者把库存服务的请求数当成 frontend 对用户的请求数,分母从一开始就错了。

要回答“frontend 对用户的成功率”还是“inventory 入站调用的成功率”,先选择一个明确的边界,再固定 destination_workload、命名空间、reporter、窗口和所包含的 HTTP 状态。示例固定在 service-mesh-lab 命名空间;若采集端汇聚多个集群,还须按实际暴露的集群标签限定同一目标集群,并在三个选择器中保持一致。以下 PromQL 仅是待在冻结版本核对标签与指标类型后的查询示意,不是本站抓取到的值;它衡量的是指定服务入站 HTTP 尝试中代理观测到的 5xx 比率,不自动等于完整用户 SLO:

来源代理和目标代理可能分别报告同一次请求,计算 SLO 必须先确定一个观察边界

1
2
3
sum(rate(istio_requests_total{reporter="destination",destination_workload="inventory-v1",destination_workload_namespace="service-mesh-lab",response_code=~"5.."}[5m]))
/
sum(rate(istio_requests_total{reporter="destination",destination_workload="inventory-v1",destination_workload_namespace="service-mesh-lab"}[5m]))

这个式子要先防零分母,再约定是否把代理本地拒绝、网关失败与不经目标入站代理的请求计入 SLO。版本切换后 v1/v2 都要纳入相同边界,否则部署 v2 会让计算“凭空变好”。用一个时间点的 counter 差值算请求率也不够;Prometheus 的 rate 需要观察窗口和足够样本,采集间隔、重启与缺失抓取都须记录。

p99 不能从各实例的 p99 再平均

如果冻结版以经典直方图暴露 istio_request_duration_milliseconds_bucket,服务总体 p99 应先按相同观测边界汇总各实例的桶,再在累计分布上求分位数:

1
2
3
4
5
histogram_quantile(0.99,
sum by (le) (rate(istio_request_duration_milliseconds_bucket{
reporter="destination",destination_workload="inventory-v1",destination_workload_namespace="service-mesh-lab"
}[5m]))
)

单位是毫秒;桶插值得到的是估计值,并非第 N 个请求的精确耗时。若使用 native histogram 或部署版本改变了桶/名称,必须改用对应查询,不能照抄此式。客户端真实等待时间还包括目标服务外的排队、网络、前置跳数和可能的重试;库存服务的 p99 不能直接命名为“用户 p99”。

固定输入做正负对照

先发送带唯一 ID 的固定数量正常 quote,核对客户端完成数、各代理 reporter 维度计数、库存应用日志及采集到的样本窗口。负例需在隔离测试桩中新增可控的库存侧 5xx,再配置一次真实代理重试:把逻辑请求数、客户端最终响应、各上游尝试数分别汇总,说明哪种计数变大了;现有库存 GET /quote 尚无可控 5xx 接口,也不能拿第 01 篇的应用重发冒充代理重试。延迟负例使用已有 -quote-delay,同时核对客户端 deadline 与库存实际处理时间。以同样的时间窗口和查询范围比较,不把模拟的输入数当成采集系统已成功保存的样本数。

验收项 实际计数/窗口/版本
正常请求及两端 reporter 对照 NOT_RUN:无 Kubernetes/Istio/Prometheus
5xx 分母与重试尝试数 NOT_RUN
跨实例桶聚合及延迟对照 NOT_RUN

最后才谈标签基数:response_code、工作负载、来源/目标等维度可用于聚合,但不能把每次请求的 X-Request-ID 作为指标标签,否则每个请求新增时间序列;逐请求检索交给日志或追踪。若未冻结抓取端和保留策略,不能宣称上述查询已经得到可用 SLO。

在同一采集窗口核对分母

以下命令在 examples/service-mesh 目录执行,使用 04 章 Kubernetes 1.33.4 / Istio 1.27.0 sidecar 基线,应用容器名 app。先清理前章授权、故障与出口策略;下列预期值尚不是集群实测结果。

1
2
3
4
5
export SM_CONTEXT=kind-service-mesh-sidecar
k() { kubectl --context="$SM_CONTEXT" -n service-mesh-lab "$@"; }
k apply -f mainline/13/probe.yaml
k rollout status deploy/sm-probe --timeout=120s
OUT="evidence/mainline-23/$(date -u +%Y%m%dT%H%M%SZ)"; mkdir -p "$OUT"

mainline/23/telemetry.yaml 显式选择 prometheus provider。Prometheus 使用 Istio 1.27.0 仓库的 samples/addons/prometheus.yaml;先把文件下载到证据目录、记录哈希再安装,避免把 latest 采集配置混进冻结实验。

1
2
3
4
5
6
curl -fsSL https://raw.githubusercontent.com/istio/istio/1.27.0/samples/addons/prometheus.yaml -o "$OUT/prometheus.yaml"
shasum -a 256 "$OUT/prometheus.yaml" > "$OUT/prometheus.sha256"
kubectl --context="$SM_CONTEXT" apply -f "$OUT/prometheus.yaml"
k apply -f mainline/23/telemetry.yaml
k exec deploy/sm-probe -c curl -- sh -c 'for i in $(seq 1 100); do curl -fsS -H "x-request-id: ch23-$i" "http://inventory:8080/quote?sku=paper" > /dev/null || exit 1; done' > "$OUT/load.stdout" 2> "$OUT/load.stderr"
kubectl --context="$SM_CONTEXT" -n istio-system port-forward svc/prometheus 19090:9090

另一个终端保留 OUT 路径,在至少两个抓取样本之后查询。mainline/23/errors.promql 选择 destination reporter、整个 inventory Service,包含 v1/v2;分母为零时不产生健康百分比。p99.promql 先合并 le 桶,再计算分位数。

1
2
3
4
5
curl -fsSG http://127.0.0.1:19090/api/v1/query --data-urlencode "query=$(cat mainline/23/errors.promql)" > "$OUT/errors.json"
curl -fsSG http://127.0.0.1:19090/api/v1/query --data-urlencode "query=$(cat mainline/23/p99.promql)" > "$OUT/p99.json"
curl -fsSG http://127.0.0.1:19090/api/v1/query --data-urlencode 'query=sum by (reporter,response_code)(increase(istio_requests_total{destination_service_name="inventory",destination_service_namespace="service-mesh-lab"}[5m]))' > "$OUT/reporters.json"
k logs deploy/inventory-v1 -c app > "$OUT/inventory-v1.log"
k logs deploy/inventory-v2 -c app > "$OUT/inventory-v2.log"

断言首先检查 JSON status 为 success,结果非空、值不是 NaN,并记录查询时间;空结果不是 0% 错误率。100 是输入逻辑请求数,increase 的窗口边缘外推可能得到非整数,也可能包含其他请求,不能直接断言样本恰等于 100。为精确计数对照,应使用无其他流量的短实验窗口,保留抓取时间与 counter 原值。

错误组复用 15 章 fault-lab /quote?mode=fail,查询中的 destination_service_name 改为 fault-lab;预期 destination 的 5xx 分子增加。延迟组请求 mode=slow,先后比较相同桶与窗口。恢复 mode=ok 并再发送正常组,要求新短窗口错误比例下降,而不是要求包含故障历史的 5 分钟窗口立刻归零。最后删除 telemetry.yaml;仅当 Prometheus 是本章专门创建时删除保存的 addon 清单。没有真实 Prometheus 查询结果时,不能把 PromQL 语法检查算成 SLO 已验收。

固定源码入口:Istio 1.27.0 pilot/pkg/networking/telemetry/telemetry.go。该链接用于核对配置生成或执行机制;完整资源和命令保存在 examples/service-mesh/mainline/23/。静态检查通过不等于集群实验通过。

练习与参考解答

  1. 客户端发 100 次,目的端指标有 103 次,能直接说丢了 3 次请求吗?参考解答:不能。先检查重试是否导致多次上游尝试、指标窗口和所选维度;用户完成数与代理尝试数不是同一分母。
  2. 两个库存实例 p99 分别为 10ms 与 100ms,平均值 55ms 是否为整体 p99?参考解答:不是。需要同一边界、同一桶配置、同一窗口下的桶数据聚合,再计算整体分位数。

从测量值到目标与错误预算

SLI 是按上述边界得到的测量值,SLO 则还需要目标与评估窗口。假设约定“frontend 在滚动 30 天内,合格用户请求的成功率至少为 99.9%”,并预先定义合格请求和成功响应,那么基于请求数的错误预算就是该窗口合格请求数的 0.1%。若合格请求为 100 万次,预算为 1000 次失败;已有 700 次失败时剩余 300 次。这是算术示例,不是本系列实测;按请求数定义的预算也不能直接换成停机分钟数。前面的 5 分钟错误率可用于短窗口观察,单凭它无法判定 30 天目标是否达成。库存某一跳的 p99 同样不能替代 frontend 的端到端延迟 SLI。目标、窗口与指标的定义见 Google SRE 的服务级别目标说明。

官方资料与系列导航

系列导航:22 访问日志怎样区分代理失败和业务失败 → 23 网格指标怎样用于 SLO(本文)→ 24 自动追踪为什么仍会断链。