LabHub
学习 学习路径 课程

Envoy 内部结构

亲手把 Envoy 跑起来再弄坏

在 LabHub 中继续学习

目标

亲自启动 Envoy,逐项创建并破坏路由、超时、重试和异常点检测。Istio 的数据平面正是 Envoy,因此这里学到的内容可以直接用于服务网格故障处理。

环境

envoy --version
curl -s localhost:9901/stats     # admin (띄운 뒤)

初始配置位于 /opt/lab/envoy/minimal.yaml。请复制后修改。

创建上游

python3 -m http.server 8081 &          # 정상

需要失败或缓慢的上游时,请使用 /opt/lab/envoy/upstream.py

python3 /opt/lab/envoy/upstream.py 8082 fail &   # 항상 503
python3 /opt/lab/envoy/upstream.py 8083 slow &   # 3초 걸림

启动与重新启动

setsid --fork nohup envoy -c e.yaml --log-level warn > envoy.log 2>&1 </dev/null
# 고친 뒤에는
pkill -f 'envoy -c'
setsid --fork nohup envoy -c e.yaml --log-level warn > envoy.log 2>&1 </dev/null

务必添加 setsid --fork。仅用 & 启动时,shell 切换后 Envoy 也会退出。使用不带 --forksetsid nohup … & 时,它能在交互式 shell 中存活,但在评分或步骤准备这类 shell 短暂连接后立即退出的路径中,会随该 shell 一起终止。--fork 会再派生一次并挂到 PID 1,因此无论在哪种情况下都能存活。评分程序检查仍在运行的 Envoy admin;进程已退出时,从第 1 步起就会失败。

配置错误时,Envoy 不会启动,而会直接退出。原因写在 envoy.log 第一行。

同一时间只能运行一个 Envoy

即使端口不同,第二个实例也会如下退出。

unable to bind domain socket with base_id=0, errno=98 (see --base-id option)

这不是端口问题,而是共享内存域套接字发生冲突。比较两份配置时,请使用 pkill -f 'envoy -c' 关闭后重新启动,或使用 --base-id 1

看不到访问日志时

常见原因有两个。

阅读顺序

按以下顺序阅读 Envoy 配置就不会混淆。

listener → filter chain → http_connection_manager → route_config → cluster

步骤

  1. 使用最小配置启动 → 01-boot.txt
  2. 查看 admin → 02-admin.txt
  3. 路由顺序 → 03-route.txt
  4. 超时 → 04-timeout.txt
  5. 重试 → 05-retry.txt
  6. 异常点检测 → 06-outlier.txt
  7. 响应标志 → 07-flags.txt
  8. 总结 → 08-notes.md

使用最小配置启动

启动一个仅包含一个监听器和一个集群的 Envoy,确认请求能够到达上游,并把结果记录到 01-boot.txt

复制 /opt/lab/envoy/minimal.yaml 后修改。启动时务必添加 setsid --fork——仅用 & 启动时,shell 切换后进程会一起退出,导致评分失败。只有使用 --fork 再派生一次并挂到 PID 1,才能在 shell 结束后继续存活。

setsid --fork nohup envoy -c e.yaml --log-level warn > envoy.log 2>&1 </dev/null

确认:curl -s localhost:10000/。按 listener → filter chain → route → cluster 的顺序阅读 Envoy 配置,就不会混淆。

通过 admin 查看内部状态

从 admin 接口(9901)提取集群列表config_dump 节名称,保存到 02-admin.txt

curl -s localhost:9901/clusters, curl -s localhost:9901/config_dump | jq -r '.configs[]."@type"'。生产环境中,要确认“Envoy 是否真的收到了我的配置”,唯一的方法就是 config_dump——文件中写的内容与 Envoy 当前持有的内容可能不同。

先写的路由优先

/api/ 两条路由发送到不同集群,并展示调换顺序后,同一请求的结果会改变。在 03-route.txt 中用 before= / after= 两行记录同一请求的响应。

Envoy 会**从上到下检查路由,并在第一个匹配处停止。**如果把 prefix: "/" 放在上面,下面的路由就全部失效。分别按两种顺序发送同一个 /api/x 请求,并按以下形式记录,同时保留所用配置:

before=api:8084 /api/x
after=ok:8081 /api/x

实际工作中,“添加了路由却不生效”的大多数情况都是这个原因。

中断缓慢上游

为耗时 3 秒的上游设置1 秒路由超时,使其返回 504,并把结果与访问日志中的响应标志一起记录到 04-timeout.txt

在路由中设置 timeout: 1s。访问日志格式包含 %RESPONSE_FLAGS% 时会记录 UT(Upstream Timeout)。请确认耗时是否正好为 1000ms——这是 Envoy 主动中断的证据。

**日志没有立即出现时,请等待约 10 秒。**Envoy 会批量写入文件访问日志,请求后立即 grep 时可能还没有内容。

实际发出了多少次重试

为始终返回 503 的上游设置 num_retries: 3,并通过统计确认重试次数,记录到 05-retry.txt

retry_policy: {retry_on: "5xx", num_retries: 3}。使用 curl -s localhost:9901/stats | grep upstream_rq_retry 确认。访问日志中会记录 URX(重试耗尽)。重试并非没有成本——上游已经濒临故障时,重试会使负载增加到 4 倍。

剔除故障端点

在包含两个端点的集群中,让其中一个返回 503,并通过统计展示异常点检测会剔除该端点,记录到 06-outlier.txt

outlier_detection: {consecutive_5xx: 2, interval: 1s, base_ejection_time: 30s}。发出约 20 次请求后,执行 curl -s localhost:9901/stats | grep -E 'ejections_active|membership_healthy'。正确时约 20 次中会有 18 次成功——前两次识别出故障端点后,就不再向它发送请求。

读取响应标志

汇总此前制造的各类失败访问日志,保存到 07-flags.txt。必须至少有两种不同的标志

UT 表示上游超时,URX 表示重试耗尽,UF 表示连接失败,NR 表示没有匹配的路由,UH 表示没有健康的上游。

收集时注意两点:日志会批量写入,因此需要等待约 10 秒才能看到;Envoy 重新启动时会清空日志文件。每制造一种失败就立即追加保存会更安全。

能够读取这些标志,是 Envoy 和 Istio 故障处理工作中最持久有用的技能——看到 5xx 后,不要先翻应用日志,应先查看标志。

总结三项内容

08-notes.md 中至少写三行:路由不生效时首先检查什么、重试在什么情况下会变得危险、UTURX 的区别。

正文中必须出现 순서, 재시도, 플래그