亲手把 Envoy 跑起来再弄坏
目标
亲自启动 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 也会退出。使用不带 --fork 的 setsid 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 会批量写入文件访问日志。请求后立即 grep 时日志可能尚未写入——请等待约 10 秒。
- 重新启动时,
> envoy.log会清空文件。如果正在收集日志,就必须重新生成。
阅读顺序
按以下顺序阅读 Envoy 配置就不会混淆。
listener → filter chain → http_connection_manager → route_config → cluster
步骤
- 使用最小配置启动 →
01-boot.txt - 查看 admin →
02-admin.txt - 路由顺序 →
03-route.txt - 超时 →
04-timeout.txt - 重试 →
05-retry.txt - 异常点检测 →
06-outlier.txt - 响应标志 →
07-flags.txt - 总结 →
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 中至少写三行:路由不生效时首先检查什么、重试在什么情况下会变得危险、UT 与 URX 的区别。
正文中必须出现 순서, 재시도, 플래그。