第30章 可编程代理:Lua / Wasm / eBPF / xDS,代理的"软件定义"
学习目标
- 理解代理从"静态配置"到"可编程逻辑"的演进,及四个可编程层次
- 分清 Lua / Wasm / eBPF / xDS 各自在哪运行、用什么语言、什么场景
- 理解 Proxy-Wasm 如何让你用任意语言写跨代理的插件
- 知道何时选 Lua、何时选 Wasm、何时下沉到 eBPF
前置知识
原理
为什么代理要"可编程"
声明式配置(proxy_pass、ACL、路由规则)能覆盖通用需求,但业务的定制逻辑——按某个自定义 Header 鉴权、调用外部服务决定路由、改写特定字段、埋自定义指标——是无法穷举的。于是代理提供扩展点,让你注入代码。这就是"软件定义代理":数据面行为由可热更新的程序决定,而非写死的配置。
四个可编程层次
抽象高 ┌─────────────────────────────────────────────┐ 灵活/重
│ ① 声明式 DSL ACL / 路由规则 / RBAC │ 配置即代码
│ ② 嵌入脚本 Lua OpenResty / HAProxy / Kong │ 运行时逻辑
│ ③ Wasm 插件 Proxy-Wasm(多语言、沙箱) │ 安全可扩展
│ ④ eBPF 内核层(Cilium,数据路径/L4) │ 极致性能
抽象低 └─────────────────────────────────────────────┘ 性能/底层
② Lua:嵌入式脚本(最成熟)
在代理进程内嵌一个 Lua 解释器,在请求处理各阶段跑你的脚本:
- OpenResty(第13章):
access_by_lua/content_by_lua/balancer_by_lua,Kong、APISIX 的插件机制之基 - Envoy Lua filter、HAProxy Lua
优点:成熟、热更新、生态广;缺点:单一语言(Lua)、需小心别写阻塞调用堵事件循环。
③ Wasm:多语言、沙箱化的插件
Proxy-Wasm 是新一代扩展标准:你用 Rust/Go/C++/AssemblyScript 写 filter,编译成 WebAssembly,由代理在沙箱里加载执行:
你的逻辑(Rust/Go/...)──编译──▶ filter.wasm ──加载──▶ Envoy/APISIX 沙箱执行
相比 Lua 的优势:
- 多语言:用你擅长的语言写,不限于 Lua
- 安全隔离:Wasm 沙箱,插件崩溃/越界不影响代理本体
- 可移植:Proxy-Wasm ABI 是标准,同一个 wasm 能跑在 Envoy、APISIX 等多种代理上
- 动态加载:不重启换插件
Istio 用 WasmPlugin CRD 给 mesh 数据面动态下发 wasm 扩展(第29章)。代价:比原生稍慢、ABI 仍在演进。
④ eBPF:内核层可编程(极致性能)
Lua/Wasm 在用户态代理进程里跑;eBPF 把可编程能力下沉到内核(第11章、第12章)。Cilium 用它做 L3/L4 策略、负载均衡、可观测、sockmap 加速——数据不进用户态,性能最高,但只适合 L4 级逻辑(看不懂 L7 应用语义那么细)。
① xDS:配置层面的"可编程"
第15章 的 xDS 也是一种"可编程"——控制面用代码动态生成配置、热推数据面。它不改"每个请求怎么处理"的逻辑,但能动态改"路由/集群/证书",是声明式的动态化。
四层对比:选哪个
| 层 | 运行在 | 语言 | 性能 | 安全隔离 | 热更新 | 适合 |
|---|---|---|---|---|---|---|
| DSL/xDS | 配置层 | YAML/规则 | — | — | ✅ | 通用路由/策略 |
| Lua | 用户态代理内 | Lua | 中高 | 弱 | ✅ | 鉴权/改写/限流(成熟) |
| Wasm | 用户态沙箱 | Rust/Go/C++ | 中高 | 强 | ✅ | 复杂/多语言/安全扩展 |
| eBPF | 内核 | C/eBPF | 最高 | 内核校验 | ✅ | L4 策略/加速/可观测 |
选择心法:简单逻辑用 Lua;复杂/多语言/要隔离用 Wasm;L4 级、要极致性能用 eBPF;只是动态改配置用 xDS。
️ 实现 / 命令
实验一:Envoy Lua filter 改写头
http_filters:
- name: envoy.filters.http.lua
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
inline_code: |
function envoy_on_request(handle)
handle:headers():add("x-via-lua", "1") -- 注入请求头
end
function envoy_on_response(handle)
handle:headers():replace("server", "redacted") -- 抹掉响应的 server 头
end
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
实验二:OpenResty Lua 动态限流(呼应第13章)
location /api/ {
access_by_lua_block {
local limit = require "resty.limit.req"
local lim = limit.new("my_limit_store", 100, 50) -- 100r/s,突发50
local delay, err = lim:incoming(ngx.var.binary_remote_addr, true)
if not delay then
if err == "rejected" then return ngx.exit(429) end -- 限流返回 429
end
}
proxy_pass http://backend;
}
实验三:Istio WasmPlugin(多语言扩展)
# 给 mesh 数据面动态加载一个 wasm 扩展(用 Rust/Go 写并编译)
apiVersion: extensions.istio.io/v1alpha1
kind: WasmPlugin
metadata: { name: my-auth }
spec:
selector: { matchLabels: { app: myapp } }
url: oci://registry.example.com/my-auth-wasm:1.0 # wasm 镜像
phase: AUTHN
实验四:对比三层"加一个头"的代价
Lua :进程内解释执行,几微秒级,热更新
Wasm :沙箱执行,略高于 Lua,但安全隔离 + 多语言
eBPF :内核执行,最快,但只能做 L4 级(加不了 L7 业务头)
排错
| 现象 | 根因 | 解决 |
|---|---|---|
| Lua 拖慢整个代理 | 脚本里有阻塞调用 | 用非阻塞 API(resty.*),别在热路径做重活 |
| Wasm 插件不生效 | ABI 版本/编译目标不符 | 对齐 Proxy-Wasm SDK 与代理版本 |
| Wasm 加载失败 | 镜像拉取/签名 | 检查 OCI 仓库可达、imagePullPolicy |
| eBPF 程序加载失败 | 内核版本太老 | 升级内核(Cilium 有最低版本要求) |
| 扩展崩溃影响代理 | 用了无隔离的扩展 | 优先 Wasm 沙箱,而非裸 C 模块 |
本章小结
- "软件定义代理"四层:DSL/xDS(配置)→ Lua(脚本)→ Wasm(沙箱多语言)→ eBPF(内核)。
- Lua 成熟(OpenResty/Kong/APISIX);Wasm(Proxy-Wasm)多语言+沙箱+可移植,Istio 用 WasmPlugin 下发。
- eBPF 下沉内核,性能最高但限 L4;xDS 是配置层的动态化。
- 选型:简单 Lua、复杂/隔离 Wasm、L4 极致 eBPF、动态配置 xDS。
下一章 第31章 性能调优,从可编程回到"快"——并发模型、连接池、超时重试、内核参数与正确的压测方法。