第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章 性能调优,从可编程回到"快"——并发模型、连接池、超时重试、内核参数与正确的压测方法。