链路追踪
本页介绍怎样接入 trace 存储,让一次 Workspace 命令在 Grafana 里显示为一条时间线,以及各服务怎样配置、怎样排查。
概述
Trace 让一次 Workspace 命令在 Grafana 里显示为一条时间线:从网关或 kibo 进入 ctrl,经执行器的每次尝试、node 的每个 RPC,到 guest 的调用,每一跳的耗时与失败点都在上面。它是可选的外部依赖,由部署方运营。不接入时各服务不产生 span,行为与没有 trace 时相同。
- ctrl、node(包括它的数据面进程)、E2B 网关与 Exposure 网关各自产生 span,用 OTLP/HTTP 批量推送到 trace 存储,不落盘。
- guest 内的代理不产生 span。它从 node 的调用里读出 trace 上下文,写进自己的日志记录。
- 每条日志记录带当时的
trace_id与span_id,从 trace 能查到日志,从日志能查到 trace,见日志字段。
trace 只用于排障,不是用量数据。存储不可达、队列满或凭证到期时,span 直接丢弃,之后不会补推,丢弃数量见 kibotos_traces_dropped_total。
一次命令怎样连成 trace
一次 Workspace 命令的全部 span 共用一个由命令 ID 算出的 trace ID。 命令可能跨多次执行尝试、多个 ctrl 进程与多台 node,这样不用在任何地方保存 trace 上下文,就能把它们连在一起。
| span | 属于 | 父 |
|---|---|---|
调用方发起命令的那次 RPC,例如网关的 CreateWorkspace 调用 | 调用方自己的 trace | 调用方的 span |
命令旅程的根 command CreateWorkspace | 命令旅程 | 无。结算时一次性发出,从受理时刻跨到结算时刻 |
执行器的一次尝试 command.attempt | 命令旅程 | 旅程根 |
| 这次尝试里 ctrl 对 node 的调用、node 对 guest 的调用 | 命令旅程 | 尝试 |
- 调用方的那次 RPC 的 span 带一个 link,指向命令旅程的根。Grafana 里在这个 span 的 References 处能直接打开旅程。
- 命令旅程的 trace ID 是命令 ID 的 SHA-256 的前 16 字节。知道命令 ID 就能直接算出 trace ID;按
command_id标签搜索也能找到。 - 命令还没有结算时,旅程根还不存在,尝试 span 照样能按 trace ID 打开。
采样
采样只在入口决定一次,同一个请求内的后续调用跟随入口的决定。
| 入口 | 采样 |
|---|---|
| ctrl 的 Operator API 与 ctrl API | 全部 |
| E2B 网关的控制 API | 全部 |
| E2B 网关的 envd 与应用端口、Exposure 网关、node 数据面 | 按比例,默认 0.01 |
| ctrl 对 node、node 对 guest 的调用 | 跟随调用方 |
| 周期对账、凭证续取这类后台调用 | 不采样 |
- 按比例采样按 trace ID 判定,比例相同的入口对同一条 trace 得出同一结论,所以网关采样的请求,node 数据面也会采样。
- 调用方请求里的
traceparent只提供 trace ID 与父 span,采样标志不读:租户不能决定 Kibotos 记录多少 trace。tracestate与baggage不读也不转发。 - 网关与 node 数据面的每个响应带
traceresponse头,值是00-<trace_id>-<span_id>-<flags>,flags为01表示这次请求被采样。拿着它就能在 Grafana 里找到这次请求。
凭证
| 进程 | 地址与凭证从哪里来 |
|---|---|
| ctrl | 配置文件 [traces] 段。ctrl 用签名私钥给自己签 token |
| node | 向 ctrl 取得地址、CA、token 与采样比例,只放在内存里。node 没有 trace 相关的配置 |
| 两个网关 | 环境变量,token 从部署方给的文件读取 |
- ctrl 签的 token 与实例指标存储的同形:ES256 签名的 JWT,
sub是 node ID 或ctrl,kibotos_role是node或ctrl,有效期 1 小时,剩余不足一半时换新。多一个声明kibotos_sink: traces,kid以trc-开头。 - 可以与实例指标共用一把私钥。追踪的前置代理同时校验
kibotos_role与kibotos_sink,实例指标的前置代理拒绝带kibotos_sink的 token,所以两种 token 互相不能代用。 - ctrl 没有
[traces]段时告诉 node 不产生 span。node 之后每 5 分钟再问一次,所以 ctrl 加上[traces]并重启后,node 最多 5 分钟后开始推送。 - ctrl 不可达期间,node 用手里的 token 推送到到期为止,之后结束的 span 按
no_credential丢弃。
参考部署:VictoriaTraces 与 vmauth
VictoriaTraces 单节点版只让 vmauth 访问,自身不设口令;vmauth 对外提供推送与查询入口,并负责认证。
| 用途 | 地址 |
|---|---|
ctrl 的 [traces].endpoint,node 从 ctrl 取得同一个地址;网关的 *_TRACES_ENDPOINT | https://traces.example.com:8427/insert/opentelemetry/v1/traces |
| Grafana 的 Jaeger 数据源 | https://traces.example.com:8427/select/jaeger |
vmauth 至少用 v1.152.0,参考部署与实例指标存储同一版本。它的配置有三类用户:
| 用户 | 认证 | 能访问 |
|---|---|---|
kibotos-node、kibotos-ctrl | ctrl 签的 JWT,kibotos_role 与 kibotos_sink 都要匹配 | /insert/opentelemetry/v1/traces |
| 每个网关一个 | 静态 bearer token | /insert/opentelemetry/v1/traces |
| 每个看板一个 | basic auth | /select/jaeger/api/... |
下面几节在宿主上或用 Helm chart 部署存储,两者都要用到下一节的私钥与 token。
签名私钥与 token
在 ctrl 宿主上生成签名私钥并导出公钥,私钥只给 kibotos-ctrl 读取。也可以直接用实例指标的那把,共用的含义见凭证:
sudo install -o kibotos-ctrl -g kibotos-ctrl -m 0600 /dev/null /etc/kibotos/ctrl/traces-signing-key.pem
sudo openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out /etc/kibotos/ctrl/traces-signing-key.pem
sudo openssl pkey -in /etc/kibotos/ctrl/traces-signing-key.pem -pubout > traces-signing-public.pem再为每个网关生成一个 bearer token,为 Grafana 生成一个口令。文件不能带换行:vmauth 把内容原样比较:
set -eu
umask 077
for name in e2b gw; do openssl rand -hex 32 | tr -d '\n' > traces-$name-token; done
openssl rand -hex 24 | tr -d '\n' > traces-grafana-passwordtraces-e2b-token 与 traces-gw-token 分别放到两个网关宿主上,作为它们的 *_TRACES_TOKEN_FILE 指向的文件,见网关的配置。
宿主
VictoriaTraces 单节点版的启动参数:
| 参数 | 示例 | 说明 |
|---|---|---|
-storageDataPath | /var/lib/victoria-traces | 数据目录,放在本地磁盘 |
-retentionPeriod | 7d | 保留期,最短 1d |
-httpListenAddr | 127.0.0.1:10428 | 只让同机的 vmauth 访问 |
vmauth 的启动参数与实例指标存储的相同,-auth.config 指向下面的文件。把 traces-signing-public.pem 的内容写进两个 JWT 用户。网关的 token 与 Grafana 的口令从 vmauth 进程的环境变量 KIBOTOS_TRACES_WRITER_E2B_TOKEN、KIBOTOS_TRACES_WRITER_GW_TOKEN 与 KIBOTOS_TRACES_READER_GRAFANA_PASSWORD 读取:
users:
- name: kibotos-node
jwt:
default_vm_access_claim: {}
public_keys:
- |
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...
-----END PUBLIC KEY-----
match_claims:
kibotos_role: node
kibotos_sink: traces
url_map:
- src_paths: ["/insert/opentelemetry/v1/traces"]
url_prefix: "http://127.0.0.1:10428/"
- name: kibotos-ctrl
jwt:
default_vm_access_claim: {}
public_keys:
- |
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...
-----END PUBLIC KEY-----
match_claims:
kibotos_role: ctrl
kibotos_sink: traces
url_map:
- src_paths: ["/insert/opentelemetry/v1/traces"]
url_prefix: "http://127.0.0.1:10428/"
- name: e2b
bearer_token: "%{KIBOTOS_TRACES_WRITER_E2B_TOKEN}"
url_map:
- src_paths: ["/insert/opentelemetry/v1/traces"]
url_prefix: "http://127.0.0.1:10428/"
- name: gw
bearer_token: "%{KIBOTOS_TRACES_WRITER_GW_TOKEN}"
url_map:
- src_paths: ["/insert/opentelemetry/v1/traces"]
url_prefix: "http://127.0.0.1:10428/"
- name: grafana
username: grafana
password: "%{KIBOTOS_TRACES_READER_GRAFANA_PASSWORD}"
url_map:
- src_paths: ["/select/jaeger/api/.+"]
url_prefix: "http://127.0.0.1:10428/"- 两个 JWT 用户都要有
default_vm_access_claim: {},原因与实例指标存储相同。两个声明都要匹配,所以用同一把私钥签的指标 token 不带kibotos_sink,会被拒绝。 - 公钥直接写在配置文件里:vmauth 只在配置文件本身变化时重新加载。
Kubernetes
deploy/helm/kibotos-traces/ 装出 VictoriaTraces(StatefulSet 与持久卷,只在集群内可达,NetworkPolicy 只放行 vmauth)、vmauth 与它的 Service。结构与实例指标存储的 chart相同。
前提:集群的 CNI 执行 NetworkPolicy。
用签名私钥与 token 生成的文件创建 Secret,然后安装。示例用命名空间 kibotos-traces:
set -eu
kubectl create namespace kibotos-traces
kubectl -n kibotos-traces create secret generic kibotos-traces-e2b --from-file=token=traces-e2b-token
kubectl -n kibotos-traces create secret generic kibotos-traces-gw --from-file=token=traces-gw-token
kubectl -n kibotos-traces create secret generic kibotos-traces-grafana --from-file=password=traces-grafana-password
cat > traces.yaml <<EOF
vmauth:
publicKeys:
- |
$(sed 's/^/ /' traces-signing-public.pem)
writers:
- name: e2b
secretName: kibotos-traces-e2b
- name: gw
secretName: kibotos-traces-gw
readers:
- name: grafana
secretName: kibotos-traces-grafana
service:
type: LoadBalancer
annotations:
service.beta.kubernetes.io/alibaba-cloud-loadbalancer-address-type: intranet
EOF
helm install kibotos-traces deploy/helm/kibotos-traces -n kibotos-traces -f traces.yaml --wait
kubectl -n kibotos-traces get service kibotos-traces-vmauth最后一条输出的 EXTERNAL-IP 就是存储的地址。
三个存储 chart 共有的键见 chart 的通用 values,本 chart 里存储的键以 victoriaTraces. 开头。本 chart 特有的键与默认值:
| 键 | 说明 |
|---|---|
vmauth.publicKeys | 公钥 PEM 的列表,必填。轮换私钥期间新旧并存 |
vmauth.writers | 网关的推送用户,每项 name 与 secretName(键 token) |
victoriaTraces.retentionPeriod | 默认 7d,最短 1d |
victoriaTraces.storage.size | 默认 20Gi |
- 设了
vmauth.tls.secretName、证书由私有 CA 签发时,CA 交给 ctrl 的ca_file与网关的*_TRACES_CA_FILE。 - vmauth 自己的
/metrics、/flags、pprof 与配置重载用 chart 生成的 Secret<release>-vmauth-admin的键key作authKey,不对网络敞开。 - 配置变化后 vmauth 重启的几秒里推送失败,这批 span 丢弃。
容量
- 每次命令约有一个根 span、每次尝试一个 span、每个 node RPC 两个 span(ctrl 的客户端与 node 的服务端),外加 node 对 guest 的调用。
- 数据面请求按比例采样,默认每 100 个请求记 1 个。调高比例前先估算数据面请求量。
- 每个进程最多排队 2048 个 span,每 5 秒或攒满 512 个推送一次。
ctrl 的配置
只有 ctrl 有 [traces] 段。没有这个段时 ctrl 不产生 span,node 也不产生。每个键的取值范围与默认值见 ctrl 配置参考的 [traces]。signing_key_file 可以与 [metrics] 用同一个文件;sample_ratio 要与网关的 *_TRACES_SAMPLE_RATIO 相同。
在 /etc/kibotos/ctrl/config.toml 末尾加上下面一段,然后重启 ctrl。node 不需要改配置,也不需要重启。完整示例见 ctrl.example.toml 末尾。
[traces]
endpoint = "https://traces.example.com:8427/insert/opentelemetry/v1/traces"
signing_key_file = "/etc/kibotos/ctrl/traces-signing-key.pem"init、enrollment create、check-upgrade与服务本身都校验[traces]并读取私钥与 CA 文件,文件读不到或格式不对时都会失败。- 启用后,ctrl 日志有一行
trace export enabled,每台 node 日志有一行node tracing enabled。
网关的配置
两个网关的变量同形,前缀分别是 KIBOTOS_E2B_ 与 KIBOTOS_GW_。示例见 e2b.example.env 与 gw.example.env 末尾。
| 变量 | 说明 |
|---|---|
*_TRACES_ENDPOINT | 完整的 OTLP/HTTP trace 地址。不设时网关不产生 span |
*_TRACES_TOKEN_FILE | 设了地址时必填。文件内容是 bearer token,去掉首尾空白后非空,不超过 8 KiB;网关账户要能读取 |
*_TRACES_CA_FILE | 可选。替换系统根证书的 PEM 文件 |
*_TRACES_SAMPLE_RATIO | 可选。envd、应用端口与 Exposure 代理的采样比例,0 到 1,默认 0.01。与 ctrl 的 sample_ratio 取同一个值,网关采样的请求在 node 上也会被采样 |
- 没有设置地址、却设置了 token、CA 或比例时,网关拒绝启动。
- token 只在启动时读一次,换 token 后重启网关。
- 启用后,网关日志有一行
trace export enabled。
Grafana
在 Grafana 里加一个 Jaeger 类型的数据源,URL 填 vmauth 地址加 /select/jaeger(集群内是 http://kibotos-traces-vmauth.<命名空间>.svc:8427/select/jaeger),Basic auth 填一个只读用户:宿主上的配置里的 grafana,或 Kubernetes 上 vmauth.readers 里的用户。
deploy/traces/grafana-command-journey.json「Kibotos 命令旅程」:
- 变量「标签过滤」用 logfmt 写法缩小范围,例如
workspace_id=ws-…或command_id=…。 - 上面两张表分别列 ctrl 与 E2B 网关最近的 trace,点 Trace ID 在下方打开整条 trace。
- 右上角的链接「同一 trace_id 的日志」打开日志看板,按当前的 Trace ID 过滤。
span 推送后要过几秒才能查到。
日志字段
处理请求或执行尝试时写出的日志记录带 trace_id 与 span_id,都是小写十六进制。没有 trace 上下文的记录不带这两个字段。
- 在 VictoriaLogs 里按
trace_id:<ID>查询,能看到同一次请求在 ctrl、node 与 guest 写下的记录。guest 的记录经 node 转写,保留这两个字段。 - 不采样的请求照常续接 trace ID,它们的记录同样带
trace_id,日志之间照样能关联,只是存储里没有这条 trace。 - 不接入 trace 存储时,日志不带这两个字段。
不会出现在 trace 里的内容
span 的属性只有下面几类,其余一律在推送前去掉:
| 属性 | 内容 |
|---|---|
rpc.system.name、rpc.method、rpc.response.status_code、error.type | Connect 调用的方法与结果码 |
http.request.method、http.route、http.response.status_code | HTTP 请求的方法、路由模式与状态码 |
workspace_id、instance_id、volume_id、transfer_id、template_id、exposure_id、command_id | 请求涉及的对象 |
kibotos.attempt、kibotos.settlement_code | 执行器的尝试序号与命令的结算码 |
- 请求路径、查询串、主机名、端口、对端地址、User-Agent、Workspace 的 labels 与 annotations、文件名、凭证、请求与响应体都不进入 span。
- span 的事件与错误文本也不推送:错误文本可能带用户的路径,结果码已经在属性里。
- 资源属性只有
service.name、service.version,node 另有kibotos.node_id。
安全
- ctrl、node 与网关要能访问 vmauth。租户不能访问它:guest 网络与公网都不应能连到它的地址。
- 能推送的人可以伪造 span,搅乱排障时看到的时间线,但不能读。能读的人看到的是对象 ID、方法名与耗时,没有用户数据。
- 签名私钥泄露后,持有者能以任何 node 的身份推送;与实例指标共用私钥时,两边一起轮换。轮换步骤与实例指标的私钥轮换相同,公钥改在本 chart 的
vmauth.publicKeys。
排查
| 现象 | 原因 | 处理 |
|---|---|---|
| Grafana 里没有任何 trace | ctrl 没有 [traces],或网关没有设置 *_TRACES_ENDPOINT | 补上后重启 ctrl 或网关 |
| 只有 ctrl 与网关的 span,没有 node 的 | node 还没有问到凭证(最多 5 分钟),或连不上 vmauth | 查看 node 日志里的 node tracing enabled 与 trace export failed |
日志有 trace export failed,code 为 401 | vmauth 的公钥与签名私钥不匹配,或网关的 token 与 Secret 里的不同 | 重新导出公钥,或核对 token 文件与 Secret 的内容,注意换行 |
日志有 trace export failed,code 为 0 | 连不上 vmauth | 检查地址与网络 |
kibotos_traces_dropped_total{reason="queue_full"} 增长 | span 产生得比推送快 | 降低数据面的采样比例,或检查存储是否变慢 |
| 只有 E2B 网关的 span,命令旅程打不开 | 命令还没有结算,或 ctrl 没有启用 trace | 按命令 ID 搜索尝试 span;确认 ctrl 日志有 trace export enabled |