欢迎光临
我们一直在努力

Higress all in one

Higress AI 网关可观测性体系:从 0 到 1 搭建 Promtail + Loki + Grafana 全链路监控

AI 网关上线后,如何监控它的运行状态?如何按用户、按模型、按路由统计 Token 消耗?如何在数据丢失时快速发现并回灌?本文分享了一套完整的生产级可观测性体系搭建过程,涵盖 Envoy 结构化日志、Promtail 两阶段解析、Loki 存储与查询、Grafana 多维看板,以及 Prometheus 指标采集的全链路设计。

一、背景与目标

1.1 为什么 AI 网关需要专门的可观测性

传统 API 网关的可观测性主要关注 QPS、延迟、错误率。但 AI 网关多了一个关键维度——Token 消耗。

Token 是 LLM 调用的计费单位,也是成本管控的核心指标。企业需要回答以下问题:

问题说明
今天一共消耗了多少 Token? 成本核算
哪个团队消耗最多? 内部计费
哪个模型用得最多? 模型选型优化
某个用户的 Token 趋势是否正常? 异常检测
首 Token 延迟是多少? 用户体验监控

这些问题,传统的 Prometheus + Grafana 方案无法直接回答——因为 Token 信息在 LLM 的响应体中,不在 HTTP 指标里。

1.2 我们的目标

┌─────────────────────────────────────────────────────────────┐
│ 可观测性体系目标 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 网关运行状态监控 │
│ – QPS、延迟、错误率、TCP 连接数 │
│ – 容器 CPU、内存使用率 │
│ │
│ 2. AI 业务指标监控 │
│ – 按模型统计 Token 消耗(Input / Output) │
│ – 按用户/团队统计 Token 消耗 │
│ – 按路由 + 用户 + 模型的细粒度统计 │
│ – 首 Token 延迟、请求处理耗时 │
│ │
│ 3. 日志采集与查询 │
│ – 全量审计日志结构化存储 │
│ – 支持按用户、模型、路由的日志查询 │
│ – 日志保留 180 天 │
│ │
│ 4. 异常检测与自愈 │
│ – 采集链路断流告警 │
│ – positions offset 异常自动修复 │
│ │
└─────────────────────────────────────────────────────────────┘

1.3 技术选型

组件角色选型理由
Envoy Access Log 日志源 Higress 基于 Envoy,原生支持结构化 JSON 日志
ai-statistics 插件 Token 采集 Higress 官方插件,从 LLM 响应中提取 Token 统计
Promtail 日志采集 Loki 官方日志采集器,支持 pipeline 解析
Loki 日志存储 轻量级日志聚合,与 Grafana 深度集成
Prometheus 指标采集 采集 Envoy 暴露的运行时指标
cAdvisor 容器指标 采集容器 CPU、内存、网络指标
Grafana 可视化 统一看板,同时展示 Prometheus 指标和 Loki 日志

二、整体架构

┌─────────────────────────────────────────────────────────────────────┐
│ Higress AI Gateway │
│ │
│ ┌──────────┐ ┌──────────────┐ ┌───────────────────────┐ │
│ │ key-auth │───▶│ ai-proxy │───▶│ ai-statistics │ │
│ │ (认证) │ │ (协议转换) │ │ (Token 统计采集) │ │
│ └──────────┘ └──────────────┘ └───────────┬───────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────┐ │
│ │ Envoy Access Log │ │
│ │ (JSON 结构化日志) │ │
│ │ 含 consumer, ai_log, │ │
│ │ route_name 等字段 │ │
│ └────────────┬─────────────┘ │
└─────────────────────────────────────────────────┼───────────────────┘


┌─────────────────────────────────────────────────────────────────────┐
│ 可观测性数据链路 │
│ │
│ ┌────────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Promtail │────▶│ Loki │◀────│ Grafana │ │Prometheus│ │
│ │ (日志采集) │ │ (日志存储)│ │ (可视化) │ │(指标采集) │ │
│ └────────────┘ └──────────┘ └──────────┘ └────┬─────┘ │
│ │ │
│ ┌────────────┐ ┌──────────┐ │ │
│ │ cAdvisor │────▶│Prometheus│◀───────────────────────────┘ │
│ │ (容器指标) │ │ │ │
│ └────────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────────────┘

两条数据链路:

链路数据源采集方式存储展示
日志链路 Envoy Access Log → Promtail → Loki → Grafana 日志文件 tail Loki 文件系统存储 LogQL 查询
指标链路 Envoy metrics + cAdvisor → Prometheus → Grafana HTTP pull Prometheus TSDB PromQL 查询

三、Envoy 结构化日志 — 数据源头

3.1 日志格式设计

Higress 基于 Envoy,通过 higress-config ConfigMap 配置 access log 格式。我们设计了一套包含所有必要字段的 JSON 格式:

# higress-config ConfigMap
mesh: |
accessLogEncoding: TEXT
accessLogFile: /dev/stdout
accessLogFormat: |
{
"consumer": "%FILTER_STATE(wasm.consumer:PLAIN)%",
"api_key": "%REQ(X-CONSUMER-NAME)%",
"ai_log": "%FILTER_STATE(wasm.ai_log:PLAIN)%",
"authority": "%REQ(X-ENVOY-ORIGINAL-HOST?:AUTHORITY)%",
"bytes_received": "%BYTES_RECEIVED%",
"bytes_sent": "%BYTES_SENT%",
"downstream_local_address": "%DOWNSTREAM_LOCAL_ADDRESS%",
"downstream_remote_address": "%DOWNSTREAM_REMOTE_ADDRESS%",
"duration": "%DURATION%",
"method": "%REQ(:METHOD)%",
"path": "%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%",
"protocol": "%PROTOCOL%",
"request_id": "%REQ(X-REQUEST-ID)%",
"response_code": "%RESPONSE_CODE%",
"response_flags": "%RESPONSE_FLAGS%",
"route_name": "%ROUTE_NAME%",
"start_time": "%START_TIME%",
"trace_id": "%REQ(X-B3-TRACEID)%",
"upstream_cluster": "%UPSTREAM_CLUSTER%",
"upstream_host": "%UPSTREAM_HOST%",
"user_agent": "%REQ(USER-AGENT)%",
"x_forwarded_for": "%REQ(X-FORWARDED-FOR)%",
"response_code_details": "%RESPONSE_CODE_DETAILS%"
}

3.2 关键字段说明

字段来源用途
consumer FILTER_STATE(wasm.consumer) ai-statistics 插件写入的消费者名称
api_key REQ(X-CONSUMER-NAME) Lua 脚本注入的用户名 Header
ai_log FILTER_STATE(wasm.ai_log) ai-statistics 插件写入的 Token 统计 JSON
route_name ROUTE_NAME Envoy 匹配到的路由名称
start_time START_TIME 请求开始时间(RFC3339 格式)
response_code RESPONSE_CODE HTTP 响应码
duration DURATION 请求处理耗时(毫秒)
response_flags RESPONSE_FLAGS Envoy 响应标志(如 UF=上游连接失败)

3.3 ai_log 字段结构

ai_log 是一个嵌套 JSON 字符串,由 ai-statistics 插件写入:

{
"model": "qwen3.8-max",
"input_token": 156,
"output_token": 892,
"total_token": 1048,
"input_token_cost": 0,
"output_token_cost": 0,
"total_token_cost": 0,
"first_token_latency": 230,
"request_process_duration": 1580
}

字段说明
model 实际使用的模型名
input_token 输入 Token 数
output_token 输出 Token 数
total_token 总 Token 数
first_token_latency 首 Token 延迟(毫秒)
request_process_duration 请求处理总耗时(毫秒)

3.4 日志输出示例

一条完整的 Envoy access log:

{
"consumer": "user-a",
"api_key": "user-a",
"ai_log": "{\\"model\\":\\"qwen3.8-max\\",\\"input_token\\":156,\\"output_token\\":892,\\"total_token\\":1048,\\"first_token_latency\\":230,\\"request_process_duration\\":1580}",
"authority": "api.example.com",
"bytes_received": 2048,
"bytes_sent": 15360,
"duration": 1620,
"method": "POST",
"path": "/v1/chat/completions",
"response_code": "200",
"response_flags": "-",
"route_name": "ai-route-aliyun.internal",
"start_time": "2026-09-01T10:30:45.123Z",
"upstream_host": "10.0.1.5:443"
}


四、ai-statistics 插件 — Token 采集

4.1 插件配置

apiVersion: extensions.higress.io/v1alpha1
kind: WasmPlugin
metadata:
name: aistatistics2.0.1
namespace: higresssystem
spec:
priority: 900
defaultConfig:
# 定义要采集的属性
attributes:
# 从请求 Header 读取消费者名称
key: consumer
value_source: request_header
value: xmseconsumer
apply_to_log: true # 写入 access log
as_separate_log_field: true # 作为独立字段(而非嵌套在 ai_log 中)

# 以下属性从 LLM 响应体中提取
key: model
value: "$response.body.choices.@first.model|$response.body.model"
apply_to_log: true
as_separate_log_field: true

key: input_token
value: "$response.body.usage.prompt_tokens"
apply_to_log: true
as_separate_log_field: true

key: output_token
value: "$response.body.usage.completion_tokens"
apply_to_log: true
as_separate_log_field: true

key: total_token
value: "$response.body.usage.total_tokens"
apply_to_log: true
as_separate_log_field: true

key: first_token_latency
value: "$first_token_latency"
apply_to_log: true
as_separate_log_field: true

key: request_process_duration
value: "$request_process_duration"
apply_to_log: true
as_separate_log_field: true

4.2 工作原理

LLM 响应返回

▼ ai-statistics 插件 (priority: 900)

│ 1. 解析响应体 JSON
│ 2. 提取 usage.prompt_tokens → input_token
│ 3. 提取 usage.completion_tokens → output_token
│ 4. 提取 usage.total_tokens → total_token
│ 5. 读取首 Token 时间戳,计算 first_token_latency
│ 6. 计算 request_process_duration
│ 7. 从 x-mse-consumer Header 读取消费者名称
│ 8. 将所有属性写入 FILTER_STATE

▼ Envoy Access Log

│ ai_log 字段 = 上述属性的 JSON 序列化
│ consumer 字段 = 消费者名称(独立字段)

4.3 关键设计决策

决策选择原因
as_separate_log_field true 每个属性作为独立字段,便于 Promtail 分别提取为 label
consumer 来源 request_header: x-mse-consumer 从 Lua 脚本注入的 Header 读取,而非从响应体提取
model 提取路径 双路径 | 分隔 兼容不同 Provider 的响应格式(choices.model vs body.model)

五、Promtail — 日志采集与解析

5.1 采集架构

Envoy access.log(容器内 /dev/stdout)

▼ 日志文件挂载
/var/log/higress/proxy/access.log*(宿主机)

▼ Promtail 容器挂载
/var/log/higress/proxy/access.log*(Promtail 容器内)

▼ Pipeline 解析
Loki(结构化日志 + labels)

5.2 完整配置

server:
http_listen_port: 9080
grpc_listen_port: 0

positions:
filename: /tmp/positions/positions.yaml

clients:
url: http://loki:3100/loki/api/v1/push

scrape_configs:
# ===== 审计日志(核心)=====
job_name: higressauditlogs
static_configs:
targets:
localhost
labels:
job: higressauditlogs
__path__: /var/log/higress/proxy/access.log*
pipeline_stages:
# 第 1 次解析:外层 Envoy JSON
json:
expressions:
start_time: start_time
ai_log: ai_log
route: route_name
consumer: consumer
api_key: api_key
# 使用 start_time 替换日志时间戳
timestamp:
source: start_time
format: RFC3339Nano
# 第 2 次解析:内层 ai_log JSON 字符串
json:
source: ai_log
expressions:
model: model
# 提取为 Loki labels
labels:
model:
route:
consumer:
api_key:

# ===== 网关运行日志 =====
job_name: higressgatewaylogs
static_configs:
targets:
localhost
labels:
job: higressgatewaylogs
__path__: /var/log/higress/gateway.log

# ===== 控制面日志 =====
job_name: higresspilotlogs
static_configs:
targets:
localhost
labels:
job: higresspilotlogs
__path__: /var/log/higress/pilot.log

5.3 Pipeline 详解

Pipeline 是 Promtail 的核心,负责将原始日志转换为结构化数据。我们的审计日志需要两阶段解析:

原始日志(单行 JSON)

▼ 第 1 阶段:解析外层 JSON

│ json:
│ start_time → "2026-09-01T10:30:45.123Z"
│ ai_log → "{\\"model\\":\\"qwen3.8-max\\",…}" (字符串)
│ route → "ai-route-aliyun.internal"
│ consumer → "user-a"
│ api_key → "user-a"

▼ 时间戳替换

│ timestamp:
│ source: start_time
│ format: RFC3339Nano

│ → 日志时间戳从"采集时间"变为"请求实际发生时间"

▼ 第 2 阶段:解析内层 ai_log JSON

│ json:
│ source: ai_log
│ model → "qwen3.8-max"

▼ Labels 提取

│ labels:
│ model: "qwen3.8-max"
│ route: "ai-route-aliyun.internal"
│ consumer: "user-a"
│ api_key: "user-a"

▼ 写入 Loki(带 labels 索引)

5.4 关键设计决策

决策说明
两阶段 JSON 解析 ai_log 是嵌套 JSON 字符串,需要先用 source 字段指定来源,再解析内部字段
时间戳替换 使用 timestamp 插件将日志时间戳替换为 start_time,确保回灌历史日志时时间正确
labels 选择 只提取 model、route、consumer、api_key 四个 labels,避免 labels 基数过高影响 Loki 性能
通配符路径 access.log* 匹配所有轮转文件(access.log、access.log.1、access.log-20260831 等)

5.5 Labels 基数控制

Loki 的 labels 会创建索引,labels 组合越多,索引越大。我们只提取了 4 个 labels:

Label基数(示例)说明
model ~10 模型数量有限,适合做 label
route ~6 路由数量有限
consumer ~10 用户数量有限
api_key ~10 与 consumer 基本一致

不提取为 label 的字段:request_id、upstream_host、response_code 等,这些字段基数高或查询频率低,用日志内容过滤(|= "xxx")更合适。


六、Loki — 日志存储与查询

6.1 存储配置

auth_enabled: false

server:
http_listen_port: 3100
grpc_listen_port: 9096

common:
path_prefix: /loki
storage:
filesystem:
chunks_directory: /loki/chunks
rules_directory: /loki/rules
replication_factor: 1
ring:
instance_addr: 127.0.0.1
kvstore:
store: inmemory

ingester:
wal:
enabled: false # 关闭 WAL,避免竞态丢数据(详见系列第二篇)

schema_config:
configs:
from: 2020-10-24
store: boltdbshipper
object_store: filesystem
schema: v11
index:
prefix: index_
period: 24h

limits_config:
retention_period: 180d # 日志保留 180 天
reject_old_samples: true
reject_old_samples_max_age: 4320h
ingestion_rate_mb: 100
ingestion_burst_size_mb: 200
per_stream_rate_limit: 5MB
per_stream_rate_limit_burst: 10MB
allow_structured_metadata: false # Loki 3.x 适配
max_line_size: 4194304 # 4MB,防止长行被丢弃

query_scheduler:
max_outstanding_requests_per_tenant: 1024

6.2 关键配置说明

配置值说明
wal.enabled false 关闭 WAL,避免 2.9 版本竞态丢数据
retention_period 180d 日志保留 6 个月
max_line_size 4MB Loki 3.x 默认 256KB,Envoy 日志含嵌套 JSON 可能超长
allow_structured_metadata false boltdb-shipper 不支持 TSDB 索引,必须关闭
query_scheduler.max_outstanding_requests_per_tenant 1024 默认 100,Grafana 并发查询容易被拒

6.3 LogQL 查询示例

查询某用户的所有 AI 请求日志

{job="higress-audit-logs", consumer="user-a"} |= "ai_log"

查询某模型的所有请求

{job="higress-audit-logs", model="qwen3.8-max"}

统计某路由的请求数

count_over_time({job="higress-audit-logs", route="ai-route-aliyun.internal"} |= "ai_log" [1h])

统计某用户的 Token 总量

sum by(api_key) (
sum_over_time(
{job="higress-audit-logs", api_key="user-a"}
|= "ai_log"
| json total_token="total_token"
| unwrap total_token [24h]
)
)


七、Prometheus — 指标采集

7.1 采集配置

global:
scrape_interval: 15s
evaluation_interval: 15s

scrape_configs:
# Prometheus 自身
job_name: 'prometheus'
static_configs:
targets: ['localhost:9090']

# Higress 网关(Envoy metrics)
job_name: 'higress-gateway'
static_configs:
targets: ['higress:15090']
metrics_path: /stats/prometheus
scrape_interval: 15s
scrape_timeout: 10s

# 容器指标
job_name: 'cadvisor'
static_configs:
targets: ['cadvisor:8080']
scrape_interval: 15s
scrape_timeout: 10s

7.2 采集的指标类型

指标来源关键指标用途
Envoy envoy_http_downstream_rq_total 下游请求总量
Envoy envoy_http_downstream_rq_xx 按响应码分类的请求数
Envoy envoy_http_downstream_cx_active 活跃连接数
Envoy envoy_cluster_upstream_rq_time 上游请求延迟
cAdvisor container_cpu_usage_seconds_total 容器 CPU 使用率
cAdvisor container_memory_usage_bytes 容器内存使用量
cAdvisor container_network_receive_bytes_total 容器网络接收字节

八、Grafana — 多维看板

8.1 看板结构

我们构建了一个综合看板 Higress AI Gateway Dashboard,包含以下面板组:

┌─────────────────────────────────────────────────────────────┐
│ General(网关总览) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │Downstream│ │Downstream│ │Downstream│ │TCP Recv │ │
│ │Request │ │Success │ │Request │ │Bytes │ │
│ │Volume │ │Rate │ │Duration │ │ │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │Upstream │ │Upstream │ │Upstream │ │TCP Sent │ │
│ │Request │ │Success │ │Request │ │Bytes │ │
│ │Volume │ │Rate │ │Duration │ │ │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
├─────────────────────────────────────────────────────────────┤
│ Workload(容器资源) │
│ ┌──────────────────────┐ ┌──────────────────────┐ │
│ │ CPU │ │ Memory │ │
│ └──────────────────────┘ └──────────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ Request(请求分析) │
│ ┌──────────┐ ┌──────────┐ │
│ │Downstream│ │Upstream │ │
│ │QPS │ │QPS │ │
│ └──────────┘ └──────────┘ │
├─────────────────────────────────────────────────────────────┤
│ Token(AI 业务指标) │
│ ┌──────────────────────────────────────────────┐ │
│ │ Token Usage Detail(明细表格) │ │
│ │ 路由 | 用户 | 模型 | 请求数 | Token 总量 │ │
│ └──────────────────────────────────────────────┘ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │Token Per │ │Input Tps │ │Output Tps│ │First Tkn │ │
│ │Second │ │(Model) │ │(Model) │ │Latency │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
│ ┌──────────────────────┐ ┌──────────────────────┐ │
│ │ Model Token Usage │ │ Consumer Token Usage │ │
│ │ (按模型饼图) │ │ (按用户饼图) │ │
│ └──────────────────────┘ └──────────────────────┘ │
│ ┌──────────────────────────────────────────────┐ │
│ │ Token Usage by Route & Consumer │ │
│ │ (路由+用户+模型 三维聚合表格) │ │
│ └──────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ WAF(安全防护) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │WAF │ │WAF Denied│ │WAF Denied│ │
│ │Processed │ │by Phase │ │by RuleID │ │
│ └──────────┘ └──────────┘ └──────────┘ │
├─────────────────────────────────────────────────────────────┤
│ XDS(配置同步) │
│ ┌──────────────────────┐ ┌──────────────────────┐ │
│ │ XDS Active Conn │ │ XDS Requests Size │ │
│ └──────────────────────┘ └──────────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ Service Top(服务排行) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │QPS Top 10│ │Failure │ │Active Cx │ │RT Top 10 │ │
│ │ │ │Top 10 │ │Top 10 │ │(Slow) │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────┘

8.2 核心面板查询

面板 1:Downstream QPS(Prometheus)

round(sum(irate(envoy_http_downstream_rq_total{higress="$gateway"}[5m])), 0.001)

面板 2:Token Per Second(Loki)

sum by(model) (
sum_over_time(
{job="higress-audit-logs", model!=""}
|= "ai_log"
| json total_token="total_token"
| unwrap total_token [$__range]
)
)

面板 3:Model Token Usage(按模型饼图)

sum by(model) (
sum_over_time(
{job="higress-audit-logs", model!=""}
|= "ai_log"
| json total_token="total_token"
| unwrap total_token [$__range]
)
)

面板 4:Consumer Token Usage(按用户饼图)

sum by(route) (
sum_over_time(
{job="higress-audit-logs", model!=""}
|= "ai_log"
| json total_token="total_token"
| unwrap total_token [$__range]
)
)

面板 5:Token Usage by Route & Consumer(三维聚合表格)

请求数查询:

sum by(route, api_key, model) (
count_over_time(
{job="higress-audit-logs", model!=""}
|= "ai_log"
| json
| ai_log!="-"
[$__range]
)
)

Token 总量查询:

sum by(route, api_key, model) (
sum_over_time(
{job="higress-audit-logs", model!=""}
|= "ai_log"
| json total_token="total_token"
| unwrap total_token
[$__range]
)
)

Grafana Transformations:

  • Group By: route, api_key, model
  • 列重命名:route → 路由, api_key → 用户, model → 模型
  • 正则提取:从路由名中提取可读名称(如 ai-route-aliyun.internal → aliyun)
面板 6:First Token Latency(首 Token 延迟)

avg by(model) (
avg_over_time(
{job="higress-audit-logs", model!=""}
|= "ai_log"
| json first_token_latency="first_token_latency"
| unwrap first_token_latency
[$__range]
)
)

8.3 Prometheus vs Loki 数据源选择

数据类型数据源原因
QPS、延迟、错误率 Prometheus Envoy 原生暴露指标,适合实时聚合
Token 消耗统计 Loki Token 信息在日志中,需要 LogQL 解析
容器 CPU/内存 Prometheus cAdvisor 暴露指标
审计日志明细 Loki 日志查询是 Loki 的强项
按用户/模型聚合 Loki 需要从日志中提取维度

九、Docker Compose 一键部署

9.1 完整编排

services:
# Higress AI Gateway
higress:
image: higressregistry.cnhangzhou.cr.aliyuncs.com/higress/allinone:latest
container_name: higressaigateway
restart: unlessstopped
ports:
"8001:8001" # 控制台
"80:80" # HTTP
"443:8443" # HTTPS
"15090:15090" # Metrics
environment:
MODE=full
CONFIG_TEMPLATE=aigateway
O11Y=on # 开启可观测性
volumes:
./data:/data
./logs:/var/log/higress
networks:
higressnet

# Prometheus – 指标采集
prometheus:
image: prom/prometheus:latest
container_name: higressprometheus
restart: unlessstopped
ports:
"9095:9090"
volumes:
./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
prometheusdata:/prometheus
command:
'–storage.tsdb.retention.time=180d'
'–web.enable-lifecycle'
networks:
higressnet

# Grafana – 可视化
grafana:
image: grafana/grafana:latest
container_name: higressgrafana
restart: unlessstopped
ports:
"3005:3000"
environment:
GF_SECURITY_ADMIN_USER=admin
GF_SECURITY_ADMIN_PASSWORD=admin
GF_SECURITY_ALLOW_EMBEDDING=true
volumes:
./grafana/provisioning:/etc/grafana/provisioning:ro
./grafana/dashboards:/var/lib/grafana/dashboards:ro
grafanadata:/var/lib/grafana
networks:
higressnet

# Loki – 日志存储
loki:
image: grafana/loki:3.7.1
container_name: higressloki
restart: unlessstopped
ports:
"3100:3100"
volumes:
./loki/lokiconfig.yaml:/etc/loki/localconfig.yaml:ro
lokidata:/loki
networks:
higressnet

# Promtail – 日志采集
promtail:
image: grafana/promtail:2.9.0
container_name: higresspromtail
restart: unlessstopped
volumes:
./loki/promtailconfig.yaml:/etc/promtail/config.yml:ro
./logs:/var/log/higress:ro
./loki/positions:/tmp/positions
networks:
higressnet

# cAdvisor – 容器指标
cadvisor:
image: gcr.io/cadvisor/cadvisor:latest
container_name: higresscadvisor
restart: unlessstopped
ports:
"8081:8080"
volumes:
/:/rootfs:ro
/var/run:/var/run:ro
/sys:/sys:ro
/var/lib/docker/:/var/lib/docker:ro
networks:
higressnet

networks:
higress-net:
driver: bridge

volumes:
prometheus-data:
grafana-data:
loki-data:

9.2 启动顺序

1. docker compose up -d higress # 先启动网关
2. docker compose up -d loki # 启动日志存储
3. docker compose up -d promtail # 启动日志采集
4. docker compose up -d prometheus # 启动指标采集
5. docker compose up -d cadvisor # 启动容器指标
6. docker compose up -d grafana # 最后启动可视化

9.3 验证部署

# 检查所有容器状态
docker compose ps

# 验证 Loki 就绪
curl -s http://localhost:3100/ready
# 输出: ready

# 验证 Prometheus 就绪
curl -s http://localhost:9095/-/ready

# 验证 Grafana 就绪
curl -s http://localhost:3005/api/health
# 输出: {"commit":"…","database":"ok","version":"…"}

# 验证日志正在采集
curl -sG 'http://localhost:3100/loki/api/v1/query' \\
–data-urlencode 'query=count_over_time({job="higress-audit-logs"}[5m])'


十、运维实践

10.1 日志轮转与保留

# 配置 logrotate(/etc/logrotate.d/higress)
/var/log/higress/proxy/access.log {
daily
rotate 30
compress
delaycompress
missingok
notifempty
copytruncate
}

配置说明
daily 每天轮转
rotate 30 保留 30 天
copytruncate 复制后截断,不中断 Promtail

10.2 Promtail 自愈脚本

positions offset 异常时自动修复(详见系列第二篇):

#!/bin/bash
# promtail-watchdog.sh
# 检测 offset > 文件大小 → 重置为 0 → 重启 Promtail

POSITIONS_FILE="/path/to/positions.yaml"
LOG_DIR="/path/to/logs/proxy"

while IFS= read -r line; do
# 提取文件路径和 offset
CONTAINER_PATH=$(echo "$line" | sed 's/^[[:space:]]*//;s/:.*//')
OFFSET=$(echo "$line" | grep -oP '"\\K[0-9]+(?=")' || echo "0")
HOST_PATH=$(echo "$CONTAINER_PATH" | sed 's|^/var/log/higress/proxy/|'"${LOG_DIR}"'/|')

[ -f "$HOST_PATH" ] || continue
FILE_SIZE=$(stat -c%s "$HOST_PATH" 2>/dev/null || echo "0")

if [ "$FILE_SIZE" -lt "$OFFSET" ] && [ "$OFFSET" -gt 0 ]; then
# offset 异常,重置为 0
sed -i "s|${CONTAINER_PATH}: \\"${OFFSET}\\"|${CONTAINER_PATH}: \\"0\\"|g" "$POSITIONS_FILE"
docker compose restart promtail
fi
done < "$POSITIONS_FILE"

10.3 常见问题排查

问题排查命令可能原因
Grafana 无数据 curl localhost:3100/ready Loki 未就绪
Token 曲线为零 curl localhost:3100/metrics | grep lines_received 日志未采集或 WAL 丢数据
QPS 有数据但 Token 为零 检查 Promtail positions offset 异常或日志轮转
日志查询为空 docker logs higress-promtail Promtail 配置错误或连接失败
看板延迟高 curl localhost:3100/metrics | grep query Loki 查询并发过高

10.4 性能调优建议

调优项建议值说明
Loki ingestion_rate_mb 100 提高写入速率限制
Loki max_line_size 4MB 防止长日志行被丢弃
Loki query_scheduler.max_outstanding_requests 1024 提高并发查询上限
Promtail positions 持久化 挂载 volume 避免容器重启后从头采集
Prometheus scrape_interval 15s 平衡实时性和资源消耗
Grafana dashboard 刷新 30s 避免频繁查询压垮 Loki

十一、总结

本文展示了一套完整的 Higress AI 网关可观测性体系:

层级组件职责
数据源 Envoy Access Log + ai-statistics 结构化日志 + Token 统计
日志采集 Promtail 两阶段 JSON 解析 + 时间戳替换 + labels 提取
日志存储 Loki 结构化日志存储 + LogQL 查询
指标采集 Prometheus + cAdvisor Envoy 指标 + 容器指标
可视化 Grafana 多维看板(QPS/Token/延迟/资源)

核心设计思路:

  • 日志即指标:Token 统计不单独暴露 metrics,而是嵌入日志,通过 LogQL 聚合。简化了架构,同时保留了日志明细查询能力。

  • 两阶段解析:Envoy 日志是嵌套 JSON(外层 Envoy 字段 + 内层 ai_log),Promtail 用两次 json stage 逐层解析。

  • 时间戳替换:用 start_time 替换采集时间,确保回灌历史日志时时间正确。

  • Labels 基数控制:只提取 4 个低基数 labels(model/route/consumer/api_key),避免索引膨胀。

  • 双数据源看板:Prometheus 负责实时指标(QPS/延迟),Loki 负责业务指标(Token),Grafana 统一展示。


  • 如果你也在搭建 AI 网关的可观测性体系,希望这篇从零到一的实践分享能给你一些参考。欢迎在评论区交流讨论!

    赞(0)
    未经允许不得转载:171主机测评 » Higress all in one
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址