← 返回信息流
AI 资讯Hacker News·3 小时前

Zeroserve:基于eBPF脚本化配置的零配置Web服务器

原标题:Zeroserve: A zero-config web server you can script with eBPF

速览

Zeroserve利用eBPF技术实现无需配置的Web服务器,支持通过脚本灵活定制。

AI 深度解读

Zeroserve:基于 eBPF 的零配置 Web 服务器深度解读

背景

在 Web 服务器领域,nginxCaddy 长期以来占据着主导地位。它们通常采用声明式配置语言(如 location 块、rewrite 规则、map 指令等)来定义行为。然而,当声明式配置达到极限时,用户往往需要引入外部的脚本运行时(如 Lua 或 Caddy 的插件系统)来扩展功能。这种架构导致行为被分割在两个层面:一部分是静默增长控制流的指令,另一部分则是运行在请求生命周期中某个难以追踪位置的脚本。这种分离增加了理解和维护的复杂度。

Zeroserve 的出现旨在解决这一痛点。它不仅仅是一个 Web 服务器,更是一个试图将“配置”与“逻辑”合二为一的实验性项目。其核心理念是“程序即配置”(Program-as-configuration),通过利用用户态 eBPF(Extended Berkeley Packet Filter)技术,允许开发者编写单一的、沙盒化的程序来处理每一个请求,从而消除传统声明式配置与脚本逻辑之间的割裂。

核心内容

1. 极简架构与零配置理念

Zeroserve 是一个小型、快速且无需配置的 HTTPS 服务器。用户只需提供一个包含网站内容的 tar 归档文件,服务器即可通过 HTTP/2 和 TLS 1.3 提供服务,并支持热重载(Hot Reload)和极小的内存占用。

其核心创新在于“程序即配置”。Zeroserve 没有传统的配置文件。开发者编写的 eBPF 程序本身就是配置。这个单一的、普通的、沙盒化的程序可以看到每一个请求,并决定其命运:路由、修改头信息、身份验证、速率限制或反向代理。这种设计让开发者能够从头到尾阅读一个程序来理解整个请求路径,而非在分散的配置指令和脚本之间跳转。

2. 基于 Tar 归档的静态文件服务

Zeroserve 将整个网站打包为一个单一的 tar 文件。

  • 索引机制:服务器在加载时建立 路径 -> 字节范围 的映射表。
  • 无解压部署:文件通过向 tar 归档本身发起字节范围读取(byte-range reads)来服务,无需解压到磁盘。
  • 原子性部署:网站完全存在于该文件中,避免了因 location 规则错误暴露文档根目录的风险。部署新版本只需替换 tar 文件并发送 SIGHUP 信号,即可原子性地交换站点、脚本和 TLS 材料,且不会断开现有连接。

打包与运行示例:

zeroserve --pack ./public > site.tar
zeroserve --addr 0.0.0.0:8080 site.tar
# 热重载
killall -SIGHUP zeroserve

3. 用户态 eBPF 脚本引擎

这是 Zeroserve 最引人注目的特性。放置在 .zeroserve/scripts/ 目录下的 .c 文件会在打包时通过 clangllc 编译为 eBPF 对象。

  • 用户态执行:eBPF 代码完全在用户态运行。Zeroserve 将其加载到其内部的 async-ebpf 运行时中。这意味着内核的 BPF 子系统和 CAP_BPF 权限无需介入,服务器进程保持普通且无特权。
  • JIT 编译async-ebpf 将 eBPF 字节码即时编译(JIT)为原生机器代码(基于 uBPF),使得配置代码以原生 x86-64 性能运行。
  • 沙盒安全:通过“指针笼”(Pointer Cage)技术模拟内核验证器的功能,防止程序访问非法内存。所有内存访问都被屏蔽到程序自己的内存区域中,确保意外访问被限制在脚本内存内。
  • 可抢占式执行:脚本直接运行在 Zeroserve 的单线程事件循环上。为防止单个慢速脚本阻塞其他连接,运行时支持完全抢占。定时器可以中断 JIT 编译后的原生代码执行,将控制权交回事件循环。

4. 脚本编程模型与功能

脚本按文件名排序链式运行,共享每个请求的元数据映射表。如果脚本调用 zs_respondzs_reverse_proxy,链式调用将短路(提前终止)。

脚本示例( enrich 请求):

#include <zeroserve.h>

ZS_ENTRY
zs_u64 entry(void) {
    char peer[64];
    if (zs_req_peer(peer, sizeof(peer)) <= 0) zs_strcpy(peer, "unknown");
    
    // 发布值供 HTML 模板使用
    zs_meta_set(ZS_STR("visitor"), ZS_STR(peer));
    
    // 为所有响应附加头信息
    zs_meta_set(ZS_STR("zs.response.header.x-served-by"), ZS_STR("zeroserve-ebpf"));
    return 0;
}

上述脚本设置的元数据有两个用途:

  1. zs.response.header.* 前缀的键值对将成为所有响应(包括静态文件、zs_respond 和代理响应)的头信息。
  2. 其他键值对用于微小的模板引擎:HTML 文件中的 <zs-meta>visitor</zs-meta> 占位符将在输出时被替换,从而实现无需模板引擎的动态静态页面。

支持的 Helper 函数:

  • 请求检查与修改:读取方法、路径、查询参数、头信息和对等地址;重写 URI 或在响应发出前设置/移除头信息。
  • 加密与编码:支持 SHA-256、HMAC-SHA-256、Base64、Hex 和 getrandom
  • JSON 处理:解析请求体、构建和修改文档树,并通过 zs_json_respond 回复。
  • 速率限制:基于令牌桶算法,密钥可以是 IP 或 API Key,状态在热重载后保留。
  • AWS SigV4:支持签名 Authorization 头信息和预签名 URL,用于访问 S3 等服务。
  • OIDC 登录:完整的依赖方流程(Authorization Code + PKCE),登录会话存储在密封的 XChaCha20-Poly1305 Cookie 中,使无状态服务器也能实现“Google 登录”门控。

动态端点示例:

ZS_ENTRY
zs_u64 entry(void) {
    char path[64];
    zs_req_path(path, sizeof(path));
    if (zs_strcmp(path, "/health") != 0) return 0;
    
    zs_meta_set(ZS_STR("zs.response.header.content-type"), ZS_STR("application/json"));
    zs_respond(200, ZS_STR("{\"status\":\"ok\"}\n"));
    return 0;
}

5. 性能与 I/O 架构

  • I/O 模型:所有网络和磁盘 I/O 均通过 io_uring(经由 monoio 运行时)提交。每个实例是一个单线程事件循环。虽然单线程看似局限,但在“增加进程数”作为扩展单元的场景下,这种设计非常高效,允许同一台机器上共存多个实例。
  • 性能基准:在 8 核 Ryzen 7 3700X 上,将 zeroservenginx 1.26Caddy 2.11 限制在单核运行。使用 wrk 进行 HTTP/1.1 over TLS 1.3 的压力测试。结果显示,在单核处理小静态文件(174 字节)时,zeroservenginx 快约 17%,且尾部延迟更紧凑。

6. 完整的 TLS 支持

尽管主打“零配置”,Zeroserve 内置了比其定位更完整的 TLS 功能:

  • 仅支持 TLS 1.3,
查看原文 →su3.io