Nginx 反向代理配置错误导致磁盘空间耗尽故障复盘
Nginx 反向代理配置错误导致磁盘空间耗尽故障复盘一、事件概述1. 事件背景2026 年 3 月 31 日晚，作者在 Hetzner 小型服务器上部署了数字产品下载服务。服务器配置为 4GB RAM 和 40GB 磁盘空间，提供包括一个 2.2GB 大文件在内的多个文件下载功能。系统架构采用 Haskell 程序处理静态文件服务（包含授权验证），前端使用 nginx 反向代理。2. 影响范围A. 影响用户数数百名付费用户B. 影响时长20:40 - 23:00，持续约 2 小时C. 影响功能所有文件下载功能完全中断（前 2 小时）大文件（2.2GB）下载失败（后续）3. 严重程度P1 级故障（核心业务受损，付费用户无法访问已购买产品）二、事件时间线1. 故障发生（20:40）A. 现象描述产品发布后几分钟内，数百名用户同时访问服务器，系统立即出现磁盘空间不足错误。B. 监控告警日志中出现重复错误信息：user error (Unexpected reply to: MAIL "<...> at kanjideck.com", Expected reply code: 250, Got this instead: 452 "4.3.1 Insufficient system storage")Grafana 显示磁盘使用率 100%（40GB/40GB），df -h 显示 /dev/sda 100% 使用。2. 应急响应（20:40-21:00）A. 初步排查使用 du -sh 分析磁盘占用，发现：/var/lib 下的 Plausible Analytics（ClickHouse 数据库）：8.5GB/nix/store（NixOS 系统配置和可执行文件）：15GB剩余约 20GB 空间来源不明，未进一步分析。B. 处理措施尝试清理 Nix store：nix-collect-garbage -d因磁盘空间不足而失败。清理日志释放空间：journalctl --vacuum-time=1s尝试清理 ClickHouse 日志表：clickhouse-client -q "TRUNCATE TABLE system.query_log"因磁盘空间不足失败。C. 效果评估临时释放少量空间，但系统仍持续消耗磁盘空间，根因未定位。3. 扩容方案（21:00-23:00）A. 实施措施Hetzner 无更大规格实例可用，采用方案 B：购买独立 Volume 挂载 /nix/store。按照 NixOS Wiki 指南迁移 store：fileSystems."/nix" = { device = "/dev/disk/by-label/nix"; fsType = "ext4"; neededForBoot = true; options = [ "noatime" ]; };B. 效果评估服务器重启后，磁盘使用率降至 50%，服务恢复。但大文件下载问题未解决。4. 根本解决（次日）A. 问题定位用户反馈大文件下载中途失败。通过调整 proxy_max_temp_file_size 从默认的 1024m 提升到 5000m，大文件可下载。使用 lsof +L1 发现 14.5GB 已删除但仍被 nginx 占用的临时文件：lsof +L1 | grep nginx # 显示大量 /tmp/nginx_proxy/ 目录下的 deleted 文件B. 根本原因nginx 默认启用响应缓冲，将后端服务器返回的大文件内容缓存到临时文件。proxy_max_temp_file_size 默认值 1024m 限制单个临时文件大小，但多用户并发时产生大量临时文件。C. 解决方案在 nginx 配置中禁用缓冲：extraConfig = '' proxy_buffering off; proxy_max_temp_file_size 0; '';D. 验证结果磁盘使用率降至 20%，不再出现波动。sequenceDiagram participant U as 用户 participant N as Nginx participant T as 临时文件 participant H as Haskell 应用 participant D as 磁盘 U->>N: 下载 2.2GB 文件 N->>H: 代理请求 H-->>N: 返回文件流 N->>T: 写入临时文件（缓冲） T->>D: 占用 1GB 空间 Note over D: 并发 20 用户 = 20GB D-->>N: 磁盘满 N-->>U: 下载失败三、问题分析1. 直接原因nginx 的 proxy_max_temp_file_size 配置不当。默认值 1024m 允许单个请求缓冲最多 1GB 到临时文件，多用户并发下载时快速耗尽磁盘空间。2. 根本原因（5 Whys 分析）A. 为什么出现这个问题？nginx 默认启用响应缓冲机制，当后端响应较大时，会将部分内容写入临时文件。B. 为什么没有及时发现？部署前未进行负载测试，未能模拟多用户并发下载大文件场景。C. 为什么配置不当？对 nginx proxy_buffering 和 proxy_max_temp_file_size 参数理解不足，仅粗略阅读文档。D. 为什么应急处理效率低？压力下未能冷静分析问题，急于清理空间而非定位根因。3. 深层反思文档阅读不够仔细，误以为提高 proxy_max_temp_file_size 可解决问题缺少生产环境部署前的压力测试和容量规划缺少应急处理经验，未建立故障排查清单四、解决方案1. 临时方案A. 实施措施清理 journalctl 日志释放紧急空间将 /nix/store 迁移到独立 VolumeB. 效果评估快速恢复服务，但未根治问题，磁盘使用率仍波动至 65%。2. 永久方案A. 配置修改禁用 nginx 响应缓冲：proxy_buffering off; proxy_max_temp_file_size 0;B. 效果评估磁盘使用率稳定在 20%，不再出现临时文件占用问题。3. 预防措施部署前进行负载测试，模拟真实用户并发场景仔细阅读 nginx 文档，理解每个配置参数的含义建立磁盘空间监控告警，设置阈值（如 80%）准备应急排查清单（lsof +L1、du -sh、df -h）五、经验总结1. 做得好的地方快速响应用户，主动说明问题正在处理在资源受限情况下，找到扩容方案（独立 Volume）最终定位并解决根本原因2. 需要改进的地方应急处理时缺乏系统性思考，急于行动而非分析对关键配置参数理解不深入缺少部署前的测试验证环节3. 技术知识点nginx proxy_buffering：控制是否缓冲后端服务器响应proxy_max_temp_file_size：设置临时文件最大大小，0 表示完全禁用lsof +L1：查找已删除但仍被进程占用的文件4. 最佳实践建议大文件下载场景应禁用 nginx 缓冲，使用流式传输生产部署前必须进行负载测试建立故障排查流程文档，减少应急时的决策压力监控不仅是可用性，还应包含资源使用趋势参考资料Running out of Disk Space in ProductionNginx ngx_http_proxy_module DocumentationNixOS Storage Optimization - Moving the store
eBPF/XDP 构建二层直接服务器返回负载均衡器教程
eBPF/XDP 构建二层直接服务器返回负载均衡器教程一、概述1. 简介A. 是什么本教程讲解如何使用 eBPF/XDP 技术从零开始构建一个二层 Direct Server Return（DSR）负载均衡器。DSR 是一种高级负载均衡模式，允许后端服务器直接响应客户端请求，无需通过负载均衡器回传响应流量。B. 为什么学解决 NAT 模式负载均衡器的性能瓶颈问题掌握 eBPF/XDP 在网络编程中的实际应用理解 DSR 架构在云原生环境中的价值C. 学完能做什么使用 eBPF/XDP 实现二层 DSR 负载均衡理解虚拟 IP（VIP）的工作原理和配置方法掌握 MAC 地址重写技术在数据平面加速中的应用2. 前置知识A. 必备技能Linux 网络基础（IP、MAC、ARP 协议）基本的 C/Go 编程能力了解 eBPF/XDP 基本概念B. 推荐知识完成前序教程：NAT 模式 XDP 负载均衡器了解网络协议栈的分层模型二、背景知识1. NAT 模式的局限性在之前的教程中，我们构建了基于 NAT 的 XDP 负载均衡器。虽然该方案可以正常工作，但存在以下问题：A. 资源消耗负载均衡器需要处理双向流量（请求和响应），这会导致：更高的 CPU 和内存占用可能成为系统瓶颈B. 流量不对称入站流量通常远小于出站流量：搜索查询或 AI 提示只有几字节搜索结果或 AI 响应可能有数千字节C. 客户端信息丢失由于 NAT 负载均衡器重写了数据包头，后端服务器：无法感知真实客户端 IP难以维护用户会话无法按源 IP 记录请求日志2. Direct Server Return 概念DSR 通过让后端服务器直接响应客户端来解决上述问题。有多种 DSR 实现方式：Layer 2 DSR（本教程重点）IP-in-IP 封装GRE 隧道基于 IP 或 TCP 头部字段的变体三、实验环境1. 网络拓扑实验环境包含 5 个节点，分布在两个网络中：节点网络IP 地址lb192.168.178.0/24192.168.178.10/24backend-01192.168.178.0/24192.168.178.11/24backend-02192.168.178.0/24192.168.178.12/24client10.0.0.0/1610.0.0.20/16gateway跨网络192.168.178.2/24 + 10.0.0.2/162. DSR 原理与传统 NAT 负载均衡不同，DSR 保留原始客户端 IP，并允许后端直接响应客户端，在回程路径上绕过负载均衡器。核心问题：后端如何看到客户端 IP（数据包经过了负载均衡器）？后端知道响应哪个客户端吗？如何确保客户端从同一 IP 收到响应？简而言之，二层 DSR 通过在负载均衡器上仅重写数据包的 MAC 地址（保持 IP 头不变），并依靠负载均衡器和后端服务器之间的虚拟 IP 来实现。四、核心概念1. 虚拟 IP（VIP）在任何网络设置中，当客户端向特定端点发送请求时，期望响应来自相同的 IP 地址。如果响应来自不同的 IP，客户端网络栈会认为出错并丢弃数据包。在 NAT 负载均衡中，这会造成问题，因为客户端请求总是通过负载均衡器，后者将其转发给具有不同 IP 的后端节点。A. VIP 解决方案为了解决这个问题，负载均衡器和后端节点需要共享相同的 IP，确保客户端始终认为响应来自其最初联系的地址。这个共享 IP 就是虚拟 IP（VIP）。B. VIP 特性与物理 IP 不同，VIP 不绑定到特定接口或节点。相反，它是由多个节点使用的共享地址，用于处理同一服务的流量。2. VIP 配置A. 负载均衡器配置在负载均衡器的主接口（eth0）上配置 VIP：sudo ip addr add 192.168.178.15/32 dev eth0B. 后端服务器配置在两个后端服务器上，在环回接口（lo）上配置 VIP：sudo ip addr add 192.168.178.15/32 dev loC. ARP 隔离两个节点具有相同 IP 会引起混乱。因此，我们将 VIP 分配给后端节点的 lo 接口。我们不希望后端通过 ARP 广告虚拟 IP。网络中没有人应该知道后端上存在 VIP，否则客户端可能绕过负载均衡器直接连接。为防止后端节点响应 lo 接口上 VIP 地址的 ARP 请求（即广告），在每个后端节点上运行以下命令：# 仅当目标 IP 分配给接收请求的接口时才响应 ARP 请求 # （防止在 eth0 上广告 VIP） sudo sysctl -w net.ipv4.conf.eth0.arp_ignore=1 # 发送 ARP 请求时，仅使用分配给出站接口的地址 # （防止节点在 ARP 中泄露 VIP 作为源 IP） sudo sysctl -w net.ipv4.conf.eth0.arp_announce=2D. 工作原理负载均衡器实际上不需要知道后端节点上存在 VIP。它只需要后端的 MAC 地址在二层转发数据包。当后端接收数据包时，它会解封装数据包并将虚拟 IP 识别为自己的 IP（在 lo 接口上配置），即使该 IP 永远不会在网络上广告。这也解释了为什么这个概念称为二层 DSR——负载均衡器仅使用后端的 MAC 地址到达后端节点，如果后端位于不同的（二层）网络，这将无法工作。3. DSR 流程sequenceDiagram participant C as 客户端 participant G as 网关 participant LB as 负载均衡器 participant B as 后端服务器 C->>G: 请求到 VIP G->>LB: 路由到负载均衡器 LB->>LB: 选择后端（哈希） LB->>B: 重写 MAC 地址（保持 IP） Note over B: VIP 配置在 lo 接口 B->>G: 直接响应客户端 G->>C: 转发响应当客户端连接到 VIP 时：网关将流量路由到负载均衡器负载均衡器通过重写（源和）目标 MAC 地址将数据包转发到后端后端在 lo 接口上配置了 VIP，它将数据包识别为自己的并处理4. 前置准备在下一步之前，需要在负载均衡器上启用 IP 转发并填充 ARP 表，以便 bpf_fib_lookup 可以解析数据包目标：# 启用 IP 转发 sudo sysctl -w net.ipv4.ip_forward=1 # 填充 ARP 表 sudo ping -c1 192.168.178.11 # backend-01 sudo ping -c1 192.168.178.12 # backend-02五、实现细节1. MAC 地址重写现在我们理解了如何避免客户端困惑（总是收到来自相同 IP 的响应）。但还有一个问题：负载均衡器如何知道将流量发送到哪个后端——特别是当它们共享（相同）VIP 时？在二层 DSR 负载均衡中，我们实际上只需要在负载均衡器中更新 MAC 地址，以便数据包在二层正确传递。A. 后端选择使用简单哈希选择后端：// 选择后端 struct four_tuple_t four_tuple; four_tuple.src_ip = ip->saddr; four_tuple.dst_ip = ip->daddr; four_tuple.src_port = tcp->source; four_tuple.dst_port = tcp->dest; four_tuple.protocol = IPPROTO_TCP; __u32 key = xdp_hash_tuple(&four_tuple) % NUM_BACKENDS; struct endpoint *backend = bpf_map_lookup_elem(&backends, &key); if (!backend) { return XDP_ABORTED; }B. FIB 查询执行 FIB 查询获取转发信息：// 执行 FIB 查询 struct bpf_fib_lookup fib = {}; int rc = fib_lookup_v4_full(ctx, &fib, ip->daddr, backend->ip, bpf_ntohs(ip->tot_len)); if (rc != BPF_FIB_LKUP_RET_SUCCESS) { log_fib_error(rc); return XDP_ABORTED; }C. MAC 地址重写仅更新 MAC 地址：// 我们只需要更新 MAC 地址 // 后端需要在 lo 上有虚拟 IP（与负载均衡器相同） // 源 IP 保留为客户端，因此后端将直接响应它 __builtin_memcpy(eth->h_source, fib.smac, ETH_ALEN); __builtin_memcpy(eth->h_dest, fib.dmac, ETH_ALEN);D. 关键点这里很酷的事情是无需在负载均衡器中维护任何连接跟踪状态（与 NAT 负载均衡器不同）。我们只需基于通过 bpf_fib_lookup 在 fib_lookup_v4_full 中检索的 MAC 地址将数据包重定向到选定的后端节点，后端根据 IP 头直接响应客户端。负载均衡器甚至不触碰 IP 头，这意味着后端仍然接收原始客户端源 IP 并可以直接回复它，绕过负载均衡器。2. 源 MAC 地址说明源 MAC 地址也需要更新，以确保后端将响应正确发送回网关（而不是直接发送给负载均衡器）。六、运行负载均衡器1. 启动后端服务在两个后端服务器上启动 HTTP 服务器，显式绑定到 VIP 上监听请求：python3 -m http.server 8000 --bind 192.168.178.152. 编译并运行负载均衡器在 lab 目录下的 lb 标签页中：go generate go build sudo ./lb -i eth0 --backends 192.168.178.11,192.168.178.12参数说明：-i eth0：指定网络接口--backends：后端服务器 IP 列表（逗号分隔）3. 测试负载均衡从客户端标签页查询 VIP：curl http://192.168.178.15:8000确认请求确实是从客户端接收的（IP 在行首）：10.0.0.20 - - [01/Jan/2026 16:03:45] "GET / HTTP/1.1" 200 -4. 验证数据流向A. 使用 tcpdump在两个后端上运行 tcpdump 查看 MAC 地址：sudo tcpdump -i eth0 -n -t -e -q tcp port 8000预期输出（简化）：LB_MAC > BACKEND_MAC, IPv4, length 74: CLIENT_IP/PORT > BACKEND_IP/PORT: tcp 0 # 负载均衡器 -> 后端（同时保留客户端源 IP） BACKEND_MAC > GATEWAY_MAC, IPv4, length 74: BACKEND_IP/PORT > CLIENT_IP/PORT: tcp 0 # 后端 -> 网关（其中客户端 IP 是目标）B. 使用 bpftool检查负载均衡器日志：sudo bpftool prog trace七、架构图graph TB subgraph 客户端网络 C[客户端 10.0.0.20] GW[网关 10.0.0.2/192.168.178.2] end subgraph 服务器网络 VIP[虚拟 IP 192.168.178.15] LB[负载均衡器 192.168.178.10] BE1[后端-01 192.168.178.11 VIP on lo] BE2[后端-02 192.168.178.12 VIP on lo] end C -->|请求到 VIP| GW GW -->|路由| LB LB -->|重写 MAC| BE1 LB -->|重写 MAC| BE2 BE1 -->|直接响应| GW BE2 -->|直接响应| GW GW --> C style VIP fill:#f9f,stroke:#333,stroke-width:2px八、局限性二层 DSR 仅在负载均衡器和后端位于同一子网（即具有直接二层连接）时才能真正工作。如果负载均衡器尝试将流量重定向到不同二层网络中的后端：它会将目标 MAC 地址设置为网关接口（尝试退出当前二层网络）由于只有负载均衡器上的 VIP 在整个网络上广告，网关会将数据包发回负载均衡器因为这是网关知道的该 VIP 的唯一路由这会导致环路A. 约束要求后端与负载均衡器共享同一网络负载均衡器和后端节点托管在同一子网会增加完全故障的风险B. 解决方案这是 IPIP DSR 负载均衡发挥作用的地方——将在后续教程中介绍。九、总结1. 核心要点DSR 允许后端直接响应客户端，绕过负载均衡器二层 DSR 通过重写 MAC 地址实现，保持 IP 头不变VIP 配置在负载均衡器的实接口和后端的 lo 接口上需要 ARP 隔离防止后端广告 VIP2. 优势负载均衡器资源消耗降低后端可以看到真实客户端 IP更适合出站流量大的场景3. 局限要求负载均衡器和后端在同一二层网络跨网络部署需要 IPIP 或 GRE 等隧道技术参考资料Building an eBPF/XDP L2 Direct Server Return Load Balancer from Scratch
使用 NGINX 作为 AI 代理实现指南一、概述1. 背景A. AI 时代的新挑战人工智能（AI）技术的快速发展创造了一个复杂的生态环境。组织开始将多个大型语言模型（LLM）提供商结合起来，并管理具有不同 API 规范的多个模型端点，以构建 AI 驱动的应用程序。这种多模型并存的环境催生了 AI Gateway 和 AI Proxy 的兴起。B. AI Proxy 的定义AI Proxy 是 AI Gateway 的简化实现，是位于应用程序与 AI 模型之间的专用基础设施组件，用于编排和保护 AI 请求的流向。2. 核心功能A. 流量控制流量控制是 AI Proxy 的核心功能，它实现了身份验证与授权机制以及速率限制，有助于防止模型滥用，并确保资源的公平分配。B. 模型转换模型转换通过提供对不同 AI 模型的统一 API 入口来实现跨 AI 模型和提供商的无缝集成，从而将复杂性从客户端应用抽象掉。C. 宕机切换宕机切换系统提供高可用性，确保请求的可靠性，能够优雅地处理模型不可用或速率受限的情况。D. 流量可观测性提供全面的监控和日志记录，以维持 AI 流程中的运营可见性。对请求与响应负载进行流量审计，对于合规性验证、故障排查与模型性能分析至关重要。二、技术方案1. 系统架构使用 NGINX 结合 NJS（NGINX JavaScript）脚本实现 AI Proxy，可以提供完整的 AI 流量治理能力。graph TB Client[客户端应用] -->|OpenAI API 格式| NGINX[NGINX AI Proxy] NGINX -->|NJS 转换| Anthropic[Anthropic API] NGINX -->|直接透传| OpenAI[OpenAI API] subgraph NGINX Router[NJS 路由脚本] Transform[模型转换] RBAC[访问控制] Failover[宕机切换] Logger[Token 日志] Router --> Transform Router --> RBAC Router --> Failover Router --> Logger end style NGINX fill:#e1f5fe style Client fill:#f3e5f5 style OpenAI fill:#c8e6c9 style Anthropic fill:#fff9c42. 工作原理A. 请求流程客户端发送统一格式的 API 请求到 NGINXNJS 脚本验证用户身份和访问权限根据请求的模型类型进行路由决策如果目标模型是 Anthropic，将请求转换为 Anthropic 格式如果目标模型是 OpenAI，直接透传请求发起子请求到后端模型提供商接收响应并进行必要格式转换记录 Token 使用情况返回统一格式的响应给客户端sequenceDiagram participant C as 客户端 participant N as NGINX participant JS as NJS 脚本 participant A as Anthropic API participant O as OpenAI API C->>N: POST /v1/chat/completions N->>JS: 路由处理 JS->>JS: 验证用户权限 JS->>JS: 检查模型类型 alt Anthropic 模型 JS->>JS: 转换请求格式 JS->>A: 子请求 A-->>JS: 响应 JS->>JS: 转换响应格式 else OpenAI 模型 JS->>O: 直接透传 O-->>JS: 响应 end JS->>JS: 记录 Token 使用 JS-->>C: 返回响应三、核心实现1. 模型路由与转换A. 请求格式转换OpenAI Chat Completions API 和 Anthropic Messages API 使用不同的请求格式。需要实现转换函数：function transformAnthropicRequest(requestBody) { let maxTokens = requestBody.max_completion_tokens || requestBody.max_tokens || 512; const anthropicRequest = { model: requestBody.model, max_tokens: maxTokens, stream: requestBody.stream || false, temperature: requestBody.temperature || 1.0, top_p: requestBody.top_p }; if (anthropicRequest.temperature > 1.0) { anthropicRequest.temperature = requestBody.temperature / 2.0; } if (requestBody.stop) { anthropicRequest.stop_sequences = Array.isArray(requestBody.stop) ? requestBody.stop : [requestBody.stop]; } const systemMessages = []; const messages = []; for (let i = 0; i < requestBody.messages.length; i++) { const msg = requestBody.messages[i]; if (msg.role === "system") { systemMessages.push({text: msg.content, type: "text"}); } else { messages.push({role: msg.role, content: msg.content}); } } if (systemMessages.length > 0) { anthropicRequest.system = systemMessages; } anthropicRequest.messages = messages; return anthropicRequest; }B. 响应格式转换function transformAnthropicResponse(anthropicResponse) { const response = JSON.parse(anthropicResponse); if (response.error) { return { error: { type: response.error.type, message: response.error.message, code: response.error.code } }; } const openaiResponse = { id: response.id, object: "chat.completion", model: response.model, choices: [], usage: { prompt_tokens: response.usage.input_tokens, completion_tokens: response.usage.output_tokens, total_tokens: response.usage.input_tokens + response.usage.output_tokens } }; for (let i = 0; i < response.content.length; i++) { const content = response.content[i]; openaiResponse.choices.push({ index: i, finish_reason: response.stop_reason, message: { role: response.role, content: content.text } }); } return openaiResponse; }2. NGINX 配置A. 基础配置js_import /etc/njs/aiproxy.js; resolver 8.8.8.8; upstream openai { zone openai 64k; server api.openai.com:443 resolve; } upstream anthropic { zone anthropic 64k; server api.anthropic.com:443 resolve; } server { listen 4242; default_type application/json; location /v1/chat/completions { set $aiproxy_user $http_x_user; js_content aiproxy.route; } location /openai { internal; rewrite ^ /v1/chat/completions break; proxy_pass_request_headers off; proxy_set_header Host "api.openai.com"; proxy_set_header Content-Type "application/json"; proxy_set_header Authorization 'Bearer ${OPENAI_API_KEY}'; proxy_method POST; proxy_pass https://openai; proxy_ssl_verify on; proxy_ssl_server_name on; } location /anthropic { internal; rewrite ^ /v1/messages break; proxy_pass_request_headers off; proxy_set_header Host "api.anthropic.com"; proxy_set_header Content-Type "application/json"; proxy_set_header anthropic-version "2023-06-01"; proxy_set_header x-api-key 'Bearer ${ANTHROPIC_API_KEY}'; proxy_method POST; proxy_pass https://anthropic; proxy_ssl_verify on; proxy_ssl_server_name on; } }四、高级功能1. 访问控制A. RBAC 配置通过 JSON 文件定义用户和模型访问权限：{ "users": { "user-a": { "models": [ {"name": "gpt-5"}, {"name": "claude-sonnet-4-20250514"} ] }, "user-b": { "models": [ {"name": "gpt-5"} ] } }, "models": { "gpt-5": { "provider": "openai", "location": "/openai" }, "claude-sonnet-4-20250514": { "provider": "anthropic", "location": "/anthropic" } } }B. 权限验证流程从请求头 X-User 中提取用户标识查询用户是否存在于配置中验证用户是否有权访问请求的模型如果验证失败，返回相应的 HTTP 错误代码graph TD A[接收请求] --> B{用户是否存在} B -->|否| C[返回 403] B -->|是| D{模型是否可访问} D -->|否| E[返回 404] D -->|是| F[继续处理请求] style C fill:#ffcdd2 style E fill:#ffcdd2 style F fill:#c8e6c92. 宕机切换A. 配置扩展在用户模型配置中添加 failover 字段：{ "users": { "user-a": { "models": [ { "name": "gpt-5", "failover": "claude-sonnet-4-20250514" } ] } } }B. 切换逻辑尝试调用主模型如果主模型返回非 200 状态码检查是否配置了 failover 模型使用 failover 模型重新发起请求返回 failover 模型的响应graph TD A[请求主模型] --> B{状态码是否为 200} B -->|是| C[返回响应] B -->|否| D{是否有 Failover 配置} D -->|否| E[返回错误] D -->|是| F[请求 Failover 模型] F --> G{状态码是否为 200} G -->|是| H[返回 Failover 响应] G -->|否| E style C fill:#c8e6c9 style H fill:#fff9c4 style E fill:#ffcdd23. Token 使用日志A. 提取 Token 信息从响应中提取 Token 使用情况并设置到 NGINX 变量：if (serviceReply.status === 200) { try { const parsedResponse = JSON.parse(responseBody); if (parsedResponse.usage) { r.variables.ai_proxy_response_prompt_tokens = parsedResponse.usage.prompt_tokens || ""; r.variables.ai_proxy_response_completion_tokens = parsedResponse.usage.completion_tokens || ""; r.variables.ai_proxy_response_total_tokens = parsedResponse.usage.total_tokens || ""; } } catch (e) { r.log(`Warning: Failed to parse response body for token extraction: ${e.toString()}`); } }B. 日志格式配置在 NGINX 主配置中定义自定义日志格式：log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for" ' 'prompt_tokens=$ai_proxy_response_prompt_tokens ' 'completion_tokens=$ai_proxy_response_completion_tokens ' 'total_tokens=$ai_proxy_response_total_tokens'; access_log /var/log/nginx/access.log main;五、部署方案1. Docker 容器部署A. 前置条件OpenAI API KeyAnthropic API KeyDocker 环境B. 部署步骤拉取 NGINX Docker 镜像docker pull nginx:1.29.1导出 API Key 到环境变量export OPENAI_API_KEY=<API_KEY> export ANTHROPIC_API_KEY=<API_KEY>创建 Docker Volume 用于密钥存储docker volume create nginx-keys启动容器docker run -it --rm -p 4242:4242 \ -v $(pwd)/config:/etc/nginx \ -v $(pwd)/njs:/etc/njs \ -v $(pwd)/templates:/etc/nginx-ai-proxy/templates \ -v nginx-keys:/etc/nginx-ai-proxy/keys \ -e NGINX_ENVSUBST_TEMPLATE_DIR=/etc/nginx-ai-proxy/templates \ -e NGINX_ENVSUBST_OUTPUT_DIR=/etc/nginx-ai-proxy/keys \ -e OPENAI_API_KEY \ -e ANTHROPIC_API_KEY \ --name nginx-ai-proxy \ nginx:1.29.12. Kubernetes 集群部署可以使用类似的架构在 Kubernetes 集群中部署，使用 ConfigMap 管理 NJS 脚本和配置文件，使用 Secret 管理 API 密钥。六、测试验证1. 基本功能测试A. User A 访问 OpenAI 模型curl -s -X POST http://localhost:4242/v1/chat/completions \ -H 'Content-Type: application/json' \ -H 'X-User: user-a' \ -d '{"model":"gpt-5","messages":[{"role":"user","content":"Hello"}]}'B. User A 访问 Anthropic 模型curl -s -X POST http://localhost:4242/v1/chat/completions \ -H 'Content-Type: application/json' \ -H 'X-User: user-a' \ -d '{"model":"claude-sonnet-4-20250514","messages":[{"role":"user","content":"Hello"}]}'C. User B 访问 OpenAI 模型curl -s -X POST http://localhost:4242/v1/chat/completions \ -H 'Content-Type: application/json' \ -H 'X-User: user-b' \ -d '{"model":"gpt-5","messages":[{"role":"user","content":"Hello"}]}'D. User B 访问 Anthropic 模型（应该失败）curl -s -X POST http://localhost:4242/v1/chat/completions \ -H 'Content-Type: application/json' \ -H 'X-User: user-b' \ -d '{"model":"claude-sonnet-4-20250514","messages":[{"role":"user","content":"Hello"}]}'2. 宕机切换测试使用无效的 OpenAI API Key 启动容器，验证 User A 的请求是否自动切换到 Anthropic：docker run -it --rm -p 4242:4242 \ -v $(pwd)/config:/etc/nginx \ -v $(pwd)/njs:/etc/njs \ -v $(pwd)/templates:/etc/nginx-ai-proxy/templates \ -v nginx-keys:/etc/nginx-ai-proxy/keys \ -e NGINX_ENVSUBST_TEMPLATE_DIR=/etc/nginx-ai-proxy/templates \ -e NGINX_ENVSUBST_OUTPUT_DIR=/etc/nginx-ai-proxy/keys \ -e OPENAI_API_KEY=bad \ -e ANTHROPIC_API_KEY \ --name nginx-ai-proxy \ nginx:1.29.1七、局限性与改进方向1. 当前方案的局限性A. 缺乏专用 AI 安全防护NGINX 作为 AI Proxy 的主要限制是缺乏专用的 AI 安全防护措施。防范针对 LLM 的特定威胁（如 prompt 注入或数据外泄攻击）通常需要专业的 AI 安全解决方案，无法仅通过 NJS 来实现最佳效果。B. 功能扩展性虽然 NJS 提供了强大的扩展能力，但对于复杂的 AI 流量治理场景，可能需要更专业的 AI Gateway 产品。2. 改进方向A. 增强安全功能集成专业的 AI 安全解决方案实现 prompt 注入检测添加敏感数据过滤B. 扩展可观测性集成 Prometheus 指标添加分布式追踪实现实时监控仪表板C. 优化性能实现连接池管理添加响应缓存优化请求批处理八、总结使用 NGINX 结合 NJS 脚本可以实现功能完善的 AI Proxy，提供模型路由与转换、访问控制、宕机切换和 Token 使用日志等核心功能。这种方案的优势在于利用 NGINX 的高性能和稳定性，为构建可控、可观测的 AI 应用入口提供了实践参考。对于需要更高级 AI 安全防护和流量治理能力的场景，可以考虑结合专业的 AI Gateway 产品，形成更完整的 AI 流量治理解决方案。参考资料使用 NGINX 作为 AI Proxy - 微信公众号
Octopus LLM API 聚合服务技术分析一、概述1. 项目简介Octopus 是一个面向个人的 LLM API 聚合与负载均衡服务，旨在为用户提供统一接入多个大语言模型提供商的能力。项目采用 Go 语言后端结合 Next.js 前端的架构设计，提供简洁优雅的 Web 管理界面。2. 核心价值A. 统一接入通过单一服务接入 OpenAI、Anthropic、Gemini 等多个 LLM 提供商，简化多模型调用流程。B. 成本优化支持多 API Key 轮询使用，智能选择最低延迟的端点，帮助用户优化使用成本和响应速度。C. 协议兼容实现 OpenAI Chat、OpenAI Responses、Anthropic API 等多种协议的无缝转换，降低客户端集成复杂度。二、系统架构1. 技术栈graph TB Client[客户端] --> API[Octopus API] API --> LB[负载均衡层] LB --> Channel1[OpenAI 通道] LB --> Channel2[Anthropic 通道] LB --> Channel3[Gemini 通道] Channel1 --> Provider1[提供商1] Channel1 --> Provider2[提供商2] Channel2 --> Provider3[提供商3] Channel3 --> Provider4[提供商4] API --> DB[(数据库)] API --> Cache[内存缓存] Cache --> DB Web[Web管理面板] --> API2. 组件说明A. 后端服务（Go）提供统一的 API 接口实现负载均衡和协议转换处理通道管理和请求路由统计数据收集与存储B. 前端管理界面（Next.js）通道配置与管理模型分组与负载均衡策略设置价格管理与统计数据展示系统配置与监控C. 数据存储支持 SQLite、MySQL、PostgreSQL存储通道配置、模型信息、统计数据自动创建表结构三、核心功能1. 通道管理通道是连接 LLM 提供商的基本配置单元。A. 多通道聚合支持同时配置多个 LLM 提供商的通道，每个通道包含：基础 URL多个 API Key多个端点地址B. 智能端点选择每个通道支持配置多个端点，系统自动选择延迟最短的端点进行请求。C. 协议类型支持协议类型自动追加路径应用场景OpenAI Chat/chat/completions对话补全OpenAI Responses/responses结构化响应Anthropic/messagesClaude 模型Gemini/models/:model:generateContentGemini 模型★ Insight ─────────────────────────────────────自动路径拼接设计：用户只需提供基础 URL，系统根据通道类型自动追加 API 路径，这种设计降低了配置复杂度，减少了用户错误。多端点智能选择：通过实时监测各端点延迟，自动选择最优路径，这种动态优化机制能有效提升用户体验。─────────────────────────────────────────────────2. 分组管理与负载均衡分组将多个通道聚合为一个统一的外部模型名称。A. 负载均衡模式graph LR Request[请求] --> Router{负载均衡器} Router -->|轮询| CH1[通道1] Router -->|随机| CH2[通道2] Router -->|故障转移| CH3[通道3] Router -->|加权| CH4[通道4] CH1 --> Provider1[提供商1] CH2 --> Provider2[提供商2] CH3 --> Provider3[提供商3] CH4 --> Provider4[提供商4]B. 模式详解模式描述适用场景轮询按顺序依次使用各通道通道能力相近，需要均匀分配请求随机随机选择可用通道通道能力相近，不需要严格顺序故障转移优先使用高优先级通道，失败时切换有主备通道之分，需要高可用性加权按配置权重分配请求通道能力不同，需要按比例分配★ Insight ─────────────────────────────────────故障转移机制：高优先级通道优先使用，失败时自动降级到低优先级通道，这种设计确保了服务的高可用性，避免了单点故障。加权负载均衡：通过权重配置实现按能力分配请求，这种方式既充分利用了高能力通道，又避免了过载风险。─────────────────────────────────────────────────3. 协议转换系统支持在不同 API 协议之间进行无缝转换。A. 转换流程sequenceDiagram participant C as 客户端 participant O as Octopus participant P as LLM 提供商 C->>O: OpenAI 格式请求 O->>O: 协议转换 O->>P: Anthropic 格式请求 P-->>O: Anthropic 格式响应 O->>O: 协议转换 O-->>C: OpenAI 格式响应B. 转换优势客户端无需修改代码即可切换模型提供商统一使用 OpenAI SDK 调用所有模型降低多模型集成复杂度4. 价格管理A. 数据来源从 models.dev 自动同步模型价格创建通道时自动添加新模型的价格信息支持用户手动设置自定义价格B. 价格优先级优先级来源说明高价格管理页面用户手动设置的价格低models.dev自动同步的默认价格★ Insight ─────────────────────────────────────价格同步机制：从 models.dev 自动获取价格数据，减少了手动维护工作，同时允许用户覆盖默认价格，兼顾了自动化和灵活性。统计数据批量写入：统计数据先存储在内存中，定期批量写入数据库，这种设计避免了频繁 I/O 操作对性能的影响。─────────────────────────────────────────────────5. 统计分析A. 统计指标请求数量Token 消耗量成本追踪通道性能数据B. 数据持久化策略统计数据先存储在内存中按配置的时间间隔批量写入数据库程序正常退出时确保数据持久化四、部署与配置1. 快速部署A. Docker 部署docker run -d --name octopus \ -v /path/to/data:/app/data \ -p 8080:8080 \ bestrui/octopusB. Docker Compose 部署wget https://raw.githubusercontent.com/bestruirui/octopus/refs/heads/dev/docker-compose.yml docker compose up -d2. 配置文件配置文件默认位于 data/config.json，首次启动时自动生成。A. 完整配置示例{ "server": { "host": "0.0.0.0", "port": 8080 }, "database": { "type": "sqlite", "path": "data/data.db" }, "log": { "level": "info" } }B. 数据库配置数据库类型配置值连接字符串格式SQLitesqlitedata/data.dbMySQLmysqluser:password@tcp(host:port)/dbnamePostgreSQLpostgrespostgresql://user:password@host:port/dbname?sslmode=disable3. 环境变量所有配置选项均可通过环境变量覆盖，格式为 OCTOPUS_ + 配置路径。环境变量配置选项说明OCTOPUS_SERVER_PORTserver.port服务端口OCTOPUS_SERVER_HOSTserver.host监听地址OCTOPUS_DATABASE_TYPEdatabase.type数据库类型OCTOPUS_DATABASE_PATHdatabase.path数据库连接字符串OCTOPUS_LOG_LEVELlog.level日志级别五、客户端集成1. OpenAI SDKfrom openai import OpenAI client = OpenAI( base_url="http://127.0.0.1:8080/v1", api_key="sk-octopus-your-api-key", ) completion = client.chat.completions.create( model="octopus-gpt-4o", messages=[ {"role": "user", "content": "你好"}, ], ) print(completion.choices[0].message.content)2. Claude Code编辑 ~/.claude/settings.json：{ "env": { "ANTHROPIC_BASE_URL": "http://127.0.0.1:8080", "ANTHROPIC_AUTH_TOKEN": "sk-octopus-your-api-key", "ANTHROPIC_MODEL": "octopus-sonnet-4-5", "ANTHROPIC_SMALL_FAST_MODEL": "octopus-haiku-4-5" } }3. Codex编辑 ~/.codex/config.toml：model = "octopus-codex" model_provider = "octopus" [model_providers.octopus] name = "octopus" base_url = "http://127.0.0.1:8080/v1"编辑 ~/.codex/auth.json：{ "OPENAI_API_KEY": "sk-octopus-your-api-key" }★ Insight ─────────────────────────────────────统一接口设计：通过 Octopus 作为中间层，所有客户端都可以使用统一的 API 格式调用不同提供商的模型，这种设计大大简化了多模型集成的复杂度。兼容性优先：完全兼容 OpenAI SDK 和其他主流工具的接入方式，用户无需修改现有代码即可接入 Octopus。─────────────────────────────────────────────────六、安全注意事项1. 默认凭据默认用户名：admin默认密码：admin首次登录后必须立即修改密码2. 关闭注意事项使用 Ctrl+C 或 SIGTERM 信号正常关闭确保统计数据正确写入数据库避免使用 kill -9 等强制终止方式七、技术亮点1. 前端资源嵌入前端构建产物嵌入到 Go 二进制文件中，部署时无需额外的前端服务器，简化了部署流程。2. 多数据库支持支持 SQLite、MySQL、PostgreSQL，用户可以根据需求选择合适的数据库，从单机部署到集群部署均可覆盖。3. 智能统计数据管理内存缓存 + 批量写入的策略，在保证数据完整性的同时，最大化了系统性能。4. 优雅的协议转换支持多种主流 LLM API 协议的互转，降低了用户切换模型提供商的成本。八、适用场景1. 个人开发者需要同时使用多个 LLM 提供商希望简化多模型调用流程需要统一的成本管理和统计分析2. 小型团队需要共享多个 API Key需要负载均衡提高可用性需要统一的模型调用接口3. 应用集成为应用提供统一的 LLM 接入层降低多模型集成复杂度便于模型切换和成本控制参考资料Octopus GitHub 仓库
Tangled 使用 Pandoc 构建静态文档网站技术分析一、概述1. 背景Tangled 团队最近重新组织了文档，并将其发布到 https://docs.tangled.org，仅使用 Pandoc 工具实现。对于小型项目，使用 Pandoc 自建静态网站完全足够。2. 核心诉求A. 代码库集成文档必须存在于 monorepo 中B. 无 JavaScript 依赖纯文本页面不需要 JavaScript 即可浏览C. 可搜索性自带的搜索引擎效果较差，倾向于使用 Ctrl+F 或真实搜索引擎D. 低复杂度构建、测试、部署应当简单E. 易于样式化能够灵活定制外观二、生态系统评估1. 评估对象作者对多个文档引擎进行了评估：A. Mintlify特点：商业文档平台问题：官网显示正在进行 AI 转型，存在方向不明确的风险B. Docusaurus特点：生成精美的文档网站问题：将页面作为完整 React SPA 提供的价值存疑C. MkDocs优点：禁用 JS 后工作良好问题：需要通过 mkdocs.yml 维护目录结构，较为繁琐D. MdBook问题：需要 SUMMARY.md 文件控制目录2. 评估结论MkDocs 和 MdBook 仍在考虑范围内，以备将来需要更多功能时使用。三、Pandoc 方案1. Pandoc 介绍Pandoc 是一个高度可定制的标记转换工具，提供了 chunkedhtml 输出格式，非常适合生成文档网站。A. 默认特性基于文档布局自动生成目录（TOC）每个章节转换为独立页面2. 定制过程A. 合并文档将所有独立的 Markdown 文件合并为一个大的 DOCS.md 文件B. 模板修改修改默认模板，使 TOC 出现在每个页面上，形成侧边栏样式C. 样式统一插入 Tailwind prose 类，确保 Markdown 内容在 tangled.org 和 docs.tangled.org 之间渲染一致3. 生成命令pandoc docs/DOCS.md \ -o out/ \ -t chunkedhtml \ --variable toc \ --toc-depth=2 \ --css=docs/stylesheet.css \ --chunk-template="%i.html" \ --highlight-style=docs/highlight.theme \ --template=docs/template.html★ Insight ─────────────────────────────────────Pandoc 的 chunkedhtml 格式自动将单一大文档拆分为多个 HTML 页面，同时保持导航结构完整通过模板定制实现侧边栏 TOC，避免了复杂 JavaScript 代码的依赖─────────────────────────────────────────────────四、避免使用 JavaScript1. 移动端侧边栏侧边栏式目录需要在移动设备上折叠。大多数评估的文档引擎需要 JavaScript 来折叠和展开侧边栏，只有 MkDocs 使用 checkbox 的 :checked 伪类技巧来避免 JavaScript。2. 实现方案对比A. details/summary 方案优点：可实现折叠功能缺点：点击外部无法折叠，是明显的 hack 方案说明：Ctrl+F 或页面内查找仍然有效B. Popover API 方案（最终采用）优点：非常适合侧边栏组件实现：使用 HTML 原生 popover API触发按钮： Table of Contents 固定定位容器： Quick Start ... ... ... C. 方案特点TOC 可独立滚动点击侧边栏外部任何位置即可折叠页面内查找无法显示 popover 中的内容仅在小视口上可折叠，大视口上始终显示★ Insight ─────────────────────────────────────Popover API 是现代 HTML 原生功能，无需 JavaScript 即可实现复杂的交互效果通过 CSS 媒体查询实现响应式设计，小屏幕自动切换为折叠式侧边栏─────────────────────────────────────────────────五、搜索功能1. 搜索策略网站目前没有原生搜索功能。参考 htmx.org 的搜索栏，搜索栏直接重定向到 Google： ... 2. 单页版本考虑到 Ctrl+F 通常比 Docusaurus 提供的搜索引擎更好用，相同文档也导出为单页格式：pandoc docs/DOCS.md \ -o out/ \ --variable toc \ --toc-depth=2 \ --css=docs/stylesheet.css \ --highlight-style=docs/highlight.theme \ --template=docs/template.htmlA. 单页版本优势所有内容在一个页面上浏览器搜索功能可轻松遍历整个网站B. 未来考虑如果文档规模超出此方案支持范围，会考虑其他选项。六、构建与部署1. 技术栈Nix：包管理和构建工具Colmena：NixOS 部署工具2. Nix Derivation使用 runCommandLocal 辅助函数编写文档站点的 Nix derivation：runCommandLocal "docs" {} '' ... ${pandoc}/bin/pandoc ${src}/docs/DOCS.md ... ... ''3. Nginx 配置NixOS 机器配置为通过 nginx 提供站点：services.nginx = { enable = true; virtualHosts = { "docs.tangled.org" = { root = "${tangled-pkgs.docs}"; locations."/" = { tryFiles = "$uri $uri/ =404"; index = "index.html"; }; }; }; };4. 部署流程使用 Colmena 部署：nix run nixpkgs#colmena -- apply5. 更新流程更新站点时，首先运行：nix flake update tangled此命令会更新 tangled flake 输入，从而更新 tangled-pkgs.docs，然后通过 Colmena 应用更改。七、方案架构graph TB subgraph 源文件 A1[Markdown 文件] A2[样式表 CSS] A3[模板 HTML] A4[高亮主题] end subgraph Pandoc 构建 B1[合并文档] B2[chunkedhtml 输出] B3[TOC 生成] end subgraph 输出目录 C1[index.html] C2[页面 1.html] C3[页面 2.html] C4[单页版本.html] end subgraph NixOS 部署 D1[Nix Derivation] D2[Nginx 服务器] D3[Colmena 部署] end A1 --> B1 A2 --> B2 A3 --> B2 A4 --> B2 B1 --> B2 B2 --> B3 B2 --> C1 B2 --> C2 B2 --> C3 B2 --> C4 C1 --> D1 C2 --> D1 C3 --> D1 C4 --> D1 D1 --> D2 D2 --> D3八、工作流程图graph LR A[编写 Markdown] --> B[合并到 DOCS.md] B --> C[Pandoc 构建] C --> D[chunkedhtml 输出] C --> E[单页版本] D --> F[Nix 打包] E --> F F --> G[Colmena 部署] G --> H[Nginx 托管] H --> I[用户访问]★ Insight ─────────────────────────────────────Nix + Colmena 组合实现了声明式部署，确保构建环境可重现Pandoc 的多种输出格式能力允许同时生成分页和单页版本，满足不同使用场景─────────────────────────────────────────────────九、技术对比1. 方案对比表特性PandocDocusaurusMkDocsMdBook无 JS 依赖是否是是构建复杂度低高中中样式定制灵活有限中等中等目录维护自动自动mkdocs.ymlSUMMARY.md输出格式多种HTMLHTMLHTML学习曲线低高中中2. 核心差异A. JavaScript 依赖Pandoc/MkDocs/MdBook：支持无 JS 浏览Docusaurus：需要 React 运行时B. 目录结构Pandoc：基于 Markdown 标题自动生成其他：需要配置文件维护十、总结与展望1. 方案优势A. 简单性构建命令简洁部署流程清晰无需复杂依赖B. 灵活性模板可定制多种输出格式样式完全可控C. 性能静态 HTML无 JavaScript 开销浏览器缓存友好2. 当前局限A. 渲染差异Pandoc 的 Markdown 渲染与 Goldmark（Tangled 主站使用）仍存在一些差异B. 功能限制暂无原生搜索依赖外部搜索引擎3. 未来计划如果文档规模持续增长，可能会考虑：开发自己的 SSG（代号 TigerStyle）重新评估 MkDocs/MdBook参考资料we rolled our own documentation site - Tangled Blogdocs.tangled.orgPandoc - Universal markup converter
从零构建 eBPF/XDP 二层直连返回负载均衡器技术教程
从零构建 eBPF/XDP 二层直连返回负载均衡器技术教程一、概述1. 简介A. 是什么本教程介绍如何使用 eBPF 和 XDP（eXpress Data Path）从零开始构建一个二层（Layer 2）Direct Server Return（DSR）负载均衡器。DSR 是一种高性能负载均衡技术，允许后端服务器直接响应客户端请求，绕过负载均衡器的回程路径。B. 为什么学理解现代高性能负载均衡的核心原理掌握 eBPF/XDP 在网络编程中的应用学习 DSR 技术如何解决传统 NAT 负载均衡的性能瓶颈深入理解二层网络转发机制C. 学完能做什么使用 eBPF/XDP 编写二层负载均衡器配置虚拟 IP（VIP）实现 DSR 架构理解 MAC 地址重写与 IP 转发的区别部署高性能、低延迟的负载均衡解决方案2. 前置知识A. 必备技能Linux 网络基础（IP、MAC、ARP 协议）基本 C 语言编程能力网络命令行工具使用（ip、tcpdump、curl）B. 推荐知识eBPF 和 XDP 基础概念NAT 负载均衡原理二层网络与三层网络的区别二、背景知识1. NAT 负载均衡的局限性在之前的教程中，我们构建了基于 NAT 的 XDP 负载均衡器。虽然这种方案可以正常工作，但存在以下问题：A. 资源消耗负载均衡器需要处理双向流量（请求和响应）入站流量通常远小于出站流量例如：搜索查询或 AI 提示只有几字节，但响应可能有几 KB 甚至更多B. 性能瓶颈负载均衡器成为网络瓶颈高流量场景下资源消耗显著C. 客户端信息丢失后端服务器无法看到真实客户端 IPNAT 负载均衡器重写了数据包头后端无法基于源 IP 进行会话管理或日志记录2. DSR 解决方案Direct Server Return（DSR）概念被引入以克服上述限制。DSR 有多种实现方式：Layer 2 DSR（本教程重点）IP-in-IP 封装GRE（Generic Routing Encapsulation）封装基于 IP 或 TCP 头部字段的变体三、网络拓扑1. 实验环境本教程使用一个包含五个节点的网络拓扑，分布在两个不同的网络中：节点列表： - lb: 192.168.178.10/24（负载均衡器） - backend-01: 192.168.178.11/24（后端服务器 1） - backend-02: 192.168.178.12/24（后端服务器 2） - client: 10.0.0.20/16（客户端） - gateway: 192.168.178.2/24 和 10.0.0.2/16（网关）2. 架构图graph TB C[客户端 10.0.0.20/16] -->|请求| GW[网关 192.168.178.2 10.0.0.2] GW -->|转发| LB[负载均衡器 192.168.178.10 VIP: 192.168.178.15] LB -->|MAC重写| B1[后端服务器1 192.168.178.11 VIP on lo] LB -->|MAC重写| B2[后端服务器2 192.168.178.12 VIP on lo] B1 -->|直接响应| C B2 -.->|直接响应| C四、核心概念1. DSR L2 负载均衡原理与 NAT 负载均衡不同，DSR 保留原始客户端 IP，并允许后端服务器直接响应客户端，绕过负载均衡器的回程路径。为了理解这是如何实现的，我们需要回答三个核心问题：后端如何看到客户端 IP？数据包通过负载均衡器时，IP 头部保持不变只有 MAC 地址被重写后端知道响应哪个客户端吗？后端看到原始客户端 IP，可以正常响应如何确保客户端接收来自同一 IP 的响应？通过虚拟 IP（VIP）实现，负载均衡器和后端共享同一 IP2. 虚拟 IP（VIP）A. 什么是 VIP在任何网络设置中，当客户端向特定端点发送请求时，期望响应来自相同的 IP 地址。如果响应来自不同的 IP，客户端的网络栈会认为出错并丢弃数据包。虚拟 IP（Virtual IP，VIP）不是绑定到特定接口或节点的物理 IP，而是由多个节点共享的地址，用于处理同一服务的流量。B. VIP 配置在负载均衡器（lb 节点）上配置 VIP：sudo ip addr add 192.168.178.15/32 dev eth0在后端服务器（backend-01 和 backend-02）上配置 VIP：sudo ip addr add 192.168.178.15/32 dev loC. 为什么后端使用 lo 接口两个节点配置相同的 IP 不会混淆客户端或网关吗？这正是我们将 VIP 分配到后端节点的 lo 接口的原因。我们不希望后端广播虚拟 IP（通过 ARP）。换句话说，网络中没人应该知道 VIP 存在于后端节点上，否则客户端可能绕过负载均衡器直接连接。D. ARP 配置为防止后端节点响应 lo 接口上 VIP 地址的 ARP 请求（即广播），在每个后端节点上运行以下命令：# 仅当目标 IP 分配给接收请求的接口时才响应 ARP 请求 # （防止在 eth0 上广播 VIP） sudo sysctl -w net.ipv4.conf.eth0.arp_ignore=1 # 发送 ARP 请求时，仅使用分配给出接口的地址 # （防止节点在 ARP 中泄露 VIP 作为源 IP） sudo sysctl -w net.ipv4.conf.eth0.arp_announce=23. 二层转发原理负载均衡器不需要知道后端节点上存在 VIP。它只需要后端的 MAC 地址在二层转发数据包。当后端接收到数据包时，它解封装并识别虚拟 IP 为自己的 IP（配置在 lo 接口上），即使该 IP 从未在网络上广播。这也正是这个概念被称为二层 DSR 的原因——负载均衡器仅使用后端的 MAC 地址到达后端节点，如果后端位于不同的（二层）网络中，这将无法工作。sequenceDiagram participant C as 客户端 participant G as 网关 participant L as 负载均衡器 participant B as 后端服务器 C->>G: 请求 VIP (192.168.178.15) G->>L: 转发到负载均衡器 Note over L: 1. 哈希选择后端 2. 重写 MAC 地址 3. IP 头保持不变 L->>B: 转发（MAC 重写，源 IP 保留） Note over B: 识别 VIP 为自己的地址 B->>C: 直接响应（绕过负载均衡器）五、实现步骤1. 准备工作A. 启用 IP 转发在负载均衡器（lb 节点）上启用 IP 转发并填充 ARP 表：sudo sysctl -w net.ipv4.ip_forward=1 # ping 后端节点以填充 ARP 表（bpf_fib_lookup 需要此步骤） sudo ping -c1 192.168.178.11 # backend-01 节点/真实 IP sudo ping -c1 192.168.178.12 # backend-02 节点/真实 IPB. 启动 HTTP 服务器在两个后端服务器（backend-01 和 backend-02）上启动 HTTP 服务器，显式绑定到 VIP：python3 -m http.server 8000 --bind 192.168.178.152. XDP 负载均衡器代码A. 核心 MAC 地址重写逻辑在二层 DSR 负载均衡中，我们只需要在负载均衡器中更新 MAC 地址，以便数据包在二层正确传递：// 使用简单哈希选择后端 struct four_tuple_t four_tuple; four_tuple.src_ip = ip->saddr; four_tuple.dst_ip = ip->daddr; four_tuple.src_port = tcp->source; four_tuple.dst_port = tcp->dest; four_tuple.protocol = IPPROTO_TCP; __u32 key = xdp_hash_tuple(&four_tuple) % NUM_BACKENDS; struct endpoint *backend = bpf_map_lookup_elem(&backends, &key); if (!backend) { return XDP_ABORTED; } // 执行 FIB 查找 struct bpf_fib_lookup fib = {}; int rc = fib_lookup_v4_full(ctx, &fib, ip->daddr, backend->ip, bpf_ntohs(ip->tot_len)); if (rc != BPF_FIB_LKUP_RET_SUCCESS) { log_fib_error(rc); return XDP_ABORTED; } // 我们只需要更新 MAC 地址 // 后端需要在 lo 接口上有虚拟 IP（与负载均衡器相同） // 源 IP 保留为客户端 IP，因此后端将直接响应客户端 __builtin_memcpy(eth->h_source, fib.smac, ETH_ALEN); __builtin_memcpy(eth->h_dest, fib.dmac, ETH_ALEN);B. 关键要点无需连接跟踪负载均衡器不需要维护任何连接跟踪状态（与 NAT 负载均衡不同）简单地基于 MAC 地址重定向数据包保留 IP 头部负载均衡器甚至不接触 IP 头部后端仍接收原始客户端源 IP后端可以直接回复客户端，绕过负载均衡器FIB 查找通过 bpf_fib_lookup 获取 MAC 地址使用 fib_lookup_v4_full 执行完整查找3. 编译和运行A. 编译负载均衡器cd lab go generate go buildB. 运行负载均衡器sudo ./lb -i eth0 --backends 192.168.178.11,192.168.178.12参数说明：-i eth0: 指定网络接口--backends: 后端服务器真实 IP 列表（逗号分隔）C. 测试负载均衡从客户端节点查询 VIP：curl http://192.168.178.15:8000D. 验证流量在后端服务器上查看 HTTP 服务器日志，确认请求确实来自客户端 IP：10.0.0.20 - - [01/Jan/2026 16:03:45] "GET / HTTP/1.1" 200 -E. 抓包验证 MAC 地址在两个后端服务器上运行 tcpdump 查看 MAC 地址：sudo tcpdump -i eth0 -n -t -e -q tcp port 8000预期输出（简化版）：LB_MAC > BACKEND_MAC, IPv4, length 74: CLIENT_IP/PORT > BACKEND_IP/PORT: tcp 0 # 负载均衡器 -> 后端（保留客户端源 IP） BACKEND_MAC > GATEWAY_MAC, IPv4, length 74: BACKEND_IP/PORT > CLIENT_IP/PORT: tcp 0 # 后端 -> 网关（客户端 IP 为目标地址）F. 查看负载均衡器日志sudo bpftool prog trace六、技术限制1. 二层网络要求DSR L2 负载均衡仅在负载均衡器和后端服务器位于同一子网（即具有直接二层连接）时才能正常工作。如果负载均衡器尝试将流量重定向到不同二层网络中的后端：负载均衡器会将目标 MAC 地址设置为网关接口（试图退出当前二层网络）由于只有负载均衡器上的 VIP 在网络上广播网关会将数据包发回负载均衡器——因为这是它知道的该 VIP 的唯一路由导致环路graph TB LB[负载均衡器] -->|目标MAC=网关| GW[网关] GW -->|VIP路由回LB| LB LB -.环路.-> GW style LB fill:#f9f,stroke:#333,stroke-width:2px style GW fill:#ff9,stroke:#333,stroke-width:2px2. 架构约束要求后端服务器与负载均衡器共享同一网络增加整体故障风险——网络故障导致全部服务不可用3. 解决方案这正是 IPIP DSR 负载均衡发挥作用的地方，将在后续教程中介绍。七、总结1. 关键要点A. DSR 优势后端服务器直接响应客户端，绕过负载均衡器保留原始客户端 IP，便于会话管理和日志记录减轻负载均衡器负担，提高整体性能B. 二层 DSR 实现原理仅重写 MAC 地址，IP 头部保持不变通过虚拟 IP（VIP）实现 IP 一致性后端在 lo 接口配置 VIP，不参与 ARP 广播C. 技术限制负载均衡器与后端必须在同一二层网络跨网络场景需要使用 IPIP 或 GRE 等封装技术2. 下一步学习IPIP DSR 负载均衡（跨网络场景）GRE DSR 负载均衡高级哈希算法与一致性哈希健康检查与故障转移参考资料Building an eBPF/XDP L2 Direct Server Return Load Balancer from Scratch
Bonding 双活模式：Balance-tlb 与 Balance-alb 技术分析
Bonding 双活模式：Balance-tlb 与 Balance-alb 技术分析一、概述1. 背景在上一篇文章中讨论了 active-backup 模式，能够在一张网卡出现故障时快速切换，配置简单。但这种方式存在明显缺点——虽然有两张网卡，但同一时间只能使用一张，只有当主网卡挂了才会使用备网卡。2. 核心问题在高可用设计中，双活优于主备，主备意味着浪费。用户希望实现两张网卡都在工作，当其中一张网卡挂了时，退回到单网卡模式。3. 解决方案Linux Bonding 驱动提供了两种双活模式：Balance-tlb（Adaptive transmit load balancing，模式 5）：发送负载均衡Balance-alb（Adaptive load balancing，模式 6）：收发双向负载均衡二、Balance-tlb 模式1. 工作原理A. 发送方向假设两张网卡都要发送数据，需要解决 MAC flapping 问题——如果同一个 MAC 地址一会从端口 1 发出，一会从端口 2 发出，交换机通过收到的包学习 MAC 地址会感到困惑。因此，两个 interface 必须使用不同的 MAC 地址。对于发送流量，可以使用两个 interface 来发送，交换机看起来就像是两个不同的主机。B. 接收方向对于接收流量，只能用一个 interface 来接收。实现方式是：在回应 ARP 请求时，总是使用其中一个 interface 的 MAC 地址来回复。graph LR subgraph 服务器 B0[bond0 IP: 192.168.1.10 主MAC: 1a:50:9c:11:58:a3] E0[eth0 MAC: 1a:50:9c:11:58:a3 主接口: 收+发] E1[eth1 MAC: 9e:fa:f1:b5:0c:28 备接口: 仅发] B0 --> E0 B0 --> E1 end C1[客户端1] -->|目标MAC: 1a:50| SW[交换机] C2[客户端2] -->|目标MAC: 1a:50| SW SW --> E0 SW --> E1 E0 -->|源MAC: 1a:50| SW E1 -->|源MAC: 9e:fa| SW SW --> C1 SW --> C22. 流量特征对于发送流量：使用两个 interface 发送数据通过 hash 机制保证同一个 TCP 流总是走同一个 interface，避免 reorder 问题实际上会根据两张卡的负载动态调整使用哪张卡对于接收流量：由于其他客户端只知道 IP 对应一个网卡的 MAC 地址，入流量总是走到其中一个 interface 上从交换机视角看，有一个主机的 MAC 地址只会发出去包，但从来没有其他人发给这个 MAC3. 适用场景虽然只是发送流量可以使用两张卡，但对于面向客户的服务器来说：进入服务器的流量较少（请求一般比较小）从服务器出去的流量较多（响应一般很大）对于短视频服务器、流媒体、HTTP 服务器等，这种方案可以有效解决性能问题。三、Balance-tlb 故障切换流程1. 角色定义主接口：接收流量的 interface备接口：不接受流量、只发送的 interface2. 故障场景A. 备接口故障接收端不受影响发送端切换到只用主接口发送B. 主接口故障发送端切换到只使用备接口发送接收端流量也要切换到备接口3. 快速切换机制接收端流量的切换需要让发送流量的地方来切换。直观思路是切换 ARP，但 ARP 太慢，在 ARP 更新到发送者之前，流量会因发送到挂掉的 MAC 地址而被丢弃。更好的解决办法是让交换机来切换：发送者的流量发送给交换机，交换机再发送给服务器客户端的目标 MAC 地址不能变，但交换机转发的端口改变这与 active-backup 中讨论的 Gratuitous ARP 类似4. MAC 地址交换机制Balance-tlb 模式需要硬件支持改写 MAC 地址。切换过程需要保持主 MAC 地址总是可用，以不影响 ARP 缓存，实现快速切换：sequenceDiagram participant C as 客户端 participant SW as 交换机 participant E0 as eth0 (主: 1a:50) participant E1 as eth1 (备: 9e:fa) Note over C,E1: 正常状态 C->>SW: 发送至 MAC: 1a:50 SW->>E0: 从端口1转发 E0->>SW: 发送 (源MAC: 1a:50) E1->>SW: 发送 (源MAC: 9e:fa) Note over C,E1: 主接口故障 E0--xE0: 故障 E1->>E1: MAC交换 eth1: 1a:50 E1->>SW: GARP (1a:50在端口2) SW->>SW: 更新MAC表 1a:50 → 端口2 Note over C,E1: 切换完成 C->>SW: 发送至 MAC: 1a:50 SW->>E1: 从端口2转发 E1->>SW: 发送 (源MAC: 1a:50)切换步骤：交换主和备的 MAC 地址，主 MAC 地址迁移到备上，继续存活通过备 interface（现在的主）发送一个 GARP，让交换机更新 MAC address table4. 配置示例ip link add bond0 type bond mode balance-tlb ip link set eth0 down ip link set eth1 down ip link set eth0 master bond0 ip link set eth1 master bond0 ip link set bond0 up ip addr add 192.168.1.10/24 dev bond05. 验证配置查看网卡信息，bond0 interface 的 MAC 地址和其中一个 slave eth0 一样：root@ubuntu-5:~$ ip link show 2: bond0: <BROADCAST,MULTICAST,MASTER,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT link/ether 1a:50:9c:11:58:a3 brd ff:ff:ff:ff:ff:ff 35: eth0: <BROADCAST,MULTICAST,SLAVE,UP,LOWER_UP> mtu 1500 qdisc fq_codel master bond0 state UNKNOWN link/ether 1a:50:9c:11:58:a3 brd ff:ff:ff:ff:ff:ff 36: eth1: <BROADCAST,MULTICAST,SLAVE,UP,LOWER_UP> mtu 1500 qdisc fq_codel master bond0 state UNKNOWN link/ether 9e:fa:f1:b5:0c:28 brd ff:ff:ff:ff:ff:ff其他客户端查询 192.168.1.10 对应的 MAC 地址，获取到的是主 MAC 地址：root@ubuntu-4:~$ arp -a | grep 192.168.1.10 ? (192.168.1.10) at 1a:50:9c:11:58:a3 [ether] on eth06. 故障切换验证在 ping 的同时手动 down 掉 eth0，可以看到 1a:50:9c:11:58:a3 这个地址迁移到了 eth1 上：root@ubuntu-5:~$ ip link set eth0 down root@ubuntu-5:~$ ip link show 35: eth0: <BROADCAST,MULTICAST,SLAVE> mtu 1500 qdisc fq_codel master bond0 state DOWN link/ether 9e:fa:f1:b5:0c:28 brd ff:ff:ff:ff:ff:ff 36: eth1: <BROADCAST,MULTICAST,SLAVE,UP,LOWER_UP> mtu 1500 qdisc fq_codel master bond0 state UNKNOWN link/ether 1a:50:9c:11:58:a3 brd ff:ff:ff:ff:ff:ff与此同时，ping 也没有失败。四、Balance-alb 模式1. 工作原理Balance-tlb 只在发送方向使用双卡，能否在接收方向也用上两张卡呢？这涉及到发送者的行为，因为作为流量接收方，有两个 MAC 地址，对于让流量发送到哪一个 interface 来说无能为力。发送者通过 ARP 决定发给哪一个 MAC。解决方案是 ARP negotiation：有两个 interface，分别有两个 MAC 地址希望这两个 interface 各收到 50% 的流量在响应 ARP 时，有时告诉客户端这个 IP 对应 MAC AA，有时说是 MAC BBgraph LR subgraph 服务器 B0[bond0 IP: 192.168.1.10] E0[eth0 MAC: 1a:50:9c:11:58:a3] E1[eth1 MAC: 9e:fa:f1:b5:0c:28] B0 --> E0 B0 --> E1 end C1[客户端1] -->|ARP Query| B0 B0 -->|ARP Reply MAC: 1a:50| C1 C1 -->|Data to 1a:50| SW[交换机] SW --> E0 C2[客户端2] -->|ARP Query| B0 B0 -->|ARP Reply MAC: 9e:fa| C2 C2 -->|Data to 9e:fa| SW SW --> E12. 技术难点A. 客户端一致性对于相同的客户端，要给出相同的 MAC 地址答案。不能让客户端一会发送到 MAC AA，一会又发送到 MAC BB，这样会造成 TCP 乱序影响性能。解决方法：追踪回复给不同客户端的 MAC 地址，后续对于此客户端都回复一样的 MAC 地址。B. ARP 学习问题主机不仅通过发送 ARP 请求获取 IP-MAC 对应关系，还会从收到的 ARP 请求中学习：当主机 Z 收到 ARP 请求时，不管有没有这个地址，都会从请求中学习到 IP 对应的 MAC 地址并缓存在自己的 ARP 缓存中1a:50:9c:11:58:a3 > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 58: Request who-has 192.168.1.14 tell 192.168.1.10这样做的目的是减少 ARP 查询，但这对 balance-alb 来说很糟糕：好不容易骗一半客户端认为 MAC 是 AA，一半认为是 BB，一旦发送 ARP 请求，所有人都知道了同一个 MAC 地址。C. ARP 广播问题ARP 请求是发送一个广播包给交换机，交换机帮助广播给所有客户端。对于一个 ARP 请求，发出去的只有一个包，无法对不同客户端区别对待。3. GARP 解决方案ARP 请求刷新所有人缓存无法避免，但可以在刷新之后，重新修正部分客户端的 ARP 缓存：sequenceDiagram participant S as 服务器 participant SW as 交换机 participant C1 as 客户端1 (缓存: 1a:50) participant C2 as 客户端2 (缓存: 9e:fa) Note over S,C2: 正常状态 C1->>S: 发送到 1a:50 C2->>S: 发送到 9e:fa Note over S,C2: 服务器发送ARP请求 S->>SW: ARP Request (源MAC: 1a:50) SW->>C1: 广播ARP SW->>C2: 广播ARP C1->>C1: 学习: 192.168.1.10 = 1a:50 C2->>C2: 学习: 192.168.1.10 = 1a:50 缓存被覆盖! Note over S,C2: 服务器发送GARP修复 S->>C1: GARP (Unicast) 192.168.1.10 = 1a:50 S->>C2: GARP (Unicast) 192.168.1.10 = 9e:fa C2->>C2: 更新缓存: 192.168.1.10 = 9e:fa解决步骤：在发送 ARP 请求收到回复之后，对所有人发送一个 Gratuitous ARP使用不同的 MAC 地址告知这些客户端，重新对客户端分流Gratuitous ARP 不仅可以是问题，也可以是 reply。当其他主机收到这个 ARP reply 时，会更新自己的 ARP 缓存。4. 配置示例配置命令与 balance-tlb 几乎一样，只有 mode 改成 balance-alb：ip link add bond0 type bond mode balance-alb ip link set eth0 down ip link set eth1 down ip link set eth0 master bond0 ip link set eth1 master bond0 ip link set bond0 up ip addr add 192.168.1.10/24 dev bond05. 验证 ARP 分流配置后，在不同的机器上查看 ARP 记录，可以看到相同的 IP 有不同的 MAC 地址：# ubuntu-3 root@ubuntu-3:~$ arp -a ? (192.168.1.10) at 9e:fa:f1:b5:0c:28 [ether] on eth0 # ubuntu-4 root@ubuntu-4:~$ arp -a ? (192.168.1.10) at 1a:50:9c:11:58:a3 [ether] on eth0在服务器上发送 ping 包，并在客户端抓包（只抓 in 方向的包）：# ubuntu-3 抓包结果 09:40:08.639278 ARP, Request who-has 192.168.1.14 tell 192.168.1.10, length 44 09:40:10.746098 ARP, Reply 192.168.1.10 is-at 9e:fa:f1:b5:0c:28, length 28 09:40:10.746125 ARP, Reply 192.168.1.10 is-at 9e:fa:f1:b5:0c:28, length 28 # ubuntu-4 抓包结果 09:40:08.639326 ARP, Request who-has 192.168.1.14 tell 192.168.1.10, length 44 09:40:10.746289 ARP, Reply 192.168.1.10 is-at 1a:50:9c:11:58:a3, length 28 09:40:10.746300 ARP, Reply 192.168.1.10 is-at 1a:50:9c:11:58:a3, length 28所有人都收到了 ARP 广播请求，随后又收到了一个 ARP reply，但 reply 中 IP 对应的 MAC 地址是不同的。6. 故障切换流程Balance-alb 的 failover 过程和 balance-tlb 基本一样，但现在无论哪一个网卡挂了，都必须要更新所有人的 ARP 缓存。Failover 流程：如果挂的是主，交换两张卡的 MAC 地址；如果是备，不需要发送 GARP 给所有人（Unicast），告诉所有人自己的 IP 对应的 MAC（当然是没挂的那一个）将 link set down 并且在其他机器上抓包，会看到有多个 GARP 包发过来：root@ubuntu-4:~$ tcpdump -i eth0 -n arp -Q in 10:04:16.689841 ARP, Reply 192.168.1.10 is-at 1a:50:9c:11:58:a3, length 28 10:04:16.690286 ARP, Reply 192.168.1.10 is-at 1a:50:9c:11:58:a3, length 28 10:04:16.690720 ARP, Reply 192.168.1.10 is-at 1a:50:9c:11:58:a3, length 28五、三种 Bonding 模式对比1. 模式总结模式实现复杂度切换速度网卡利用率硬件要求active-backup简单快速单卡无特殊要求balance-tlb简单快速发送双卡需支持 MAC 地址修改balance-alb简单较慢收发双卡需支持 MAC 地址修改2. 各自特点A. Active-backup（模式 1）实现简单，快速切换只能用单卡，资源浪费B. Balance-tlb（模式 5）实现简单，快速切换发送能用双卡，接收单卡需要硬件支持 MAC 地址修改适合出流量大的场景（如流媒体、HTTP 服务）C. Balance-alb（模式 6）实现简单，切换较慢收发双卡，充分利用带宽涉及 GARP 刷新所有客户端缓存需要硬件支持 MAC 地址修改3. 扩展性这三种方案都是用两张卡讨论的，实际上都可以加更多的卡，工作原理完全一样。4. 局限性这些方案都有缺点，都不是数据中心在用的方案，但离"完美"的方案越来越近了。数据中心的实际方案会在后续文章中讨论。参考资料Kernel Bonding Documentation数据中心网络高可用技术之从服务器到交换机：balance-tlb 和 balance-alb
Frontail Web日志实时流式工具技术分析一、概述1. 简介A. 是什么Frontail 是一个基于 Node.js 开发的日志实时流式传输工具，将传统的 tail -F 命令功能带入了 Web 浏览器。它通过 WebSocket 技术实现日志文件的实时推送，让开发者可以在浏览器界面中实时查看服务器日志，无需通过终端访问。B. 为什么学解决远程日志查看问题，无需 SSH 登录即可监控日志提供友好的 Web UI 界面，支持搜索和高亮显示支持多文件同时监控，适合微服务架构可以集成到现有 Web 基础设施中，与 Nginx 等反向代理配合使用C. 学完能做什么快速搭建日志监控 Web 服务配置基于 Basic Auth 的日志访问控制集成 HTTPS 加密传输自定义日志高亮规则通过 Nginx 反向代理嵌入现有系统2. 前置知识A. 必备技能基本的 Linux 命令行操作了解 tail -F 命令的基本用法Node.js 环境配置B. 推荐知识WebSocket 通信原理Nginx 反向代理配置HTTP 基本认证机制二、环境准备1. 系统要求Node.js 12.x 或更高版本npm 或 yarn 包管理器Linux/Unix/macOS/Windows 操作系统2. 安装步骤全局安装：npm install frontail -g本地安装：npm install frontail --save-dev3. 验证安装frontail --version三、核心概念1. 基本术语WebSocket：全双工通信协议，用于服务器主动推送日志tail -F：持续跟踪文件增长，即使文件被轮转也能继续监控Basic Auth：HTTP 基本认证，用于保护日志访问Daemon：后台进程模式，将 frontail 作为守护进程运行2. 工作原理graph LR A[日志文件] -->|tail -F| B[Frontail 服务] B -->|WebSocket| C[浏览器] C -->|搜索/过滤| B D[配置文件] -->|高亮规则| B E[Nginx] -->|反向代理| B三、核心架构1. 系统组成graph TB subgraph 前端层 A[Web UI] B[WebSocket 客户端] C[搜索过滤] end subgraph 服务层 D[Node.js 服务] E[文件监控模块] F[WebSocket 服务] G[认证模块] end subgraph 数据层 H[日志文件] I[配置文件] end A --> B B --> F F --> E E --> H G --> I四、快速上手1. Hello World 示例# 监控单个日志文件 frontail /var/log/nginx/access.log # 访问 http://localhost:90012. 核心功能演示# 监控多个文件 frontail /var/log/app/*.log # 指定端口和主机 frontail -h 0.0.0.0 -p 8080 /var/log/syslog # 启用高亮 frontail --ui-highlight /var/log/app.log3. 代码讲解Frontail 内部使用 Node.js 的 fs.watch() API 监控文件变化，通过 WebSocket 将新增的日志行实时推送到浏览器。前端使用纯 JavaScript 渲染日志流，支持自动滚动和搜索功能。五、功能详解1. 命令行参数A. 基础参数参数说明默认值-h, --host监听地址0.0.0.0-p, --port监听端口9001-n, --number启动时显示的行数10-l, --lines浏览器中存储的行数2000B. 安全参数参数说明-U, --userBasic Auth 用户名-P, --passwordBasic Auth 密码-k, --keyHTTPS 私钥文件-c, --certificateHTTPS 证书文件C. UI 参数参数说明-t, --theme主题（default、dark）--ui-hide-topbar隐藏顶部栏--ui-no-indent取消缩进--ui-highlight启用高亮--ui-highlight-preset自定义高亮规则文件D. 运行模式参数说明-d, --daemonize后台运行--pid-pathPID 文件路径--log-path日志文件路径--url-pathURL 路径前缀2. 高亮配置默认高亮规则（preset/default.json）：{ "words": { "err": "color: red;", "warn": "color: orange;" }, "lines": { "err": "font-weight: bold;", "exception": "background-color: pink;" } }自定义规则示例：{ "words": { "ERROR": "color: red; font-weight: bold;", "WARN": "color: orange;", "INFO": "color: green;", "DEBUG": "color: gray;" }, "lines": { "Exception": "background-color: #ffcccc;", "Failed": "background-color: #ffebcc;" } }3. Nginx 集成A. 反向代理配置events { worker_connections 1024; } http { server { listen 8080; server_name localhost; location /frontail { proxy_pass http://127.0.0.1:9001/frontail; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } } }B. WebSocket 支持关键配置项：proxy_http_version 1.1：启用 HTTP/1.1Upgrade 头：支持协议升级Connection "upgrade"：允许连接升级六、实战案例1. 场景描述为微服务架构搭建统一的日志监控平台2. 实现步骤A. 启动 Frontail 服务# 后台运行，使用自定义高亮规则 frontail -d \ -p 9001 \ -U admin \ -P secretpassword \ --ui-highlight-preset /etc/frontail/custom.json \ --pid-path /var/run/frontail.pid \ --log-path /var/log/frontail.log \ /var/log/service1/app.log \ /var/log/service2/app.log \ /var/log/service3/app.logB. 配置系统服务（systemd）[Unit] Description=Frontail Log Viewer After=network.target [Service] Type=forking User=logs Group=logs ExecStart=/usr/bin/frontail -d \ -p 9001 \ -U admin \ -P secretpassword \ --ui-highlight-preset /etc/frontail/custom.json \ --pid-path /var/run/frontail.pid \ --log-path /var/log/frontail.log \ /var/log/service/*/app.log Restart=always RestartSec=10 [Install] WantedBy=multi-user.targetC. Nginx HTTPS 配置server { listen 443 ssl http2; server_name logs.example.com; ssl_certificate /etc/letsencrypt/live/logs.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/logs.example.com/privkey.pem; location / { proxy_pass http://127.0.0.1:9001; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; } }七、最佳实践1. 安全建议使用 Basic Auth 保护日志访问启用 HTTPS 加密传输限制监听地址为 127.0.0.1，通过 Nginx 代理定期清理历史日志文件2. 性能优化限制浏览器存储行数（--lines 参数）对于大型日志文件，使用 logrotate 进行轮转多文件监控时注意文件描述符限制3. 运维建议使用 systemd 管理服务生命周期配置日志轮转避免磁盘占满监控 Frontail 进程状态设置合理的 PID 和日志路径八、与其他工具对比工具语言特点适用场景FrontailNode.jsWeb UI、轻量级中小规模日志监控Go FrontailGo高性能、单二进制高并发场景LogstashJava功能强大、生态完善大规模日志处理lnavC/C++终端 UI、功能丰富本地日志分析九、常见问题1. 安装问题权限不足# 使用 sudo 安装 sudo npm install frontail -g版本兼容Node.js 版本过低会导致安装失败，建议使用 Node.js 14.x 或更高版本。2. 运行问题端口被占用# 查看端口占用 lsof -i :9001 # 更换端口 frontail -p 9002 /var/log/app.log文件权限# 确保运行用户有读取权限 chmod +r /var/log/app.log3. WebSocket 断连检查 Nginx 配置中的 Upgrade 和 Connection 头设置是否正确，确保 WebSocket 协议升级不被阻止。十、参考资料参考资料Frontail GitHub Repositorycloudash.dev - Log Monitoring Solution
Octopus LLM API 聚合服务技术分析一、新闻概述1. 标题Octopus：为个人打造的 LLM API 聚合与负载均衡服务2. 发布时间2025 年 1 月 16 日3. 来源GitHub 开源项目二、核心内容1. 事件摘要A. 主要内容Octopus 是一个面向个人用户的 LLM API 聚合服务项目，旨在通过统一入口管理多个 LLM 提供商的 API，实现智能负载均衡和成本优化。B. 核心亮点多渠道聚合：统一管理多个 LLM 提供商通道智能负载均衡：支持轮询、随机、故障转移、加权等多种模式协议转换：无缝转换 OpenAI Chat、OpenAI Responses、Anthropic API 格式自动同步：模型列表和价格信息自动更新优雅 UI：简洁美观的 Web 管理面板2. 关键信息A. 技术栈后端：Go 1.24.4前端：Next.js + pnpm数据库：SQLite、MySQL、PostgreSQLB. 部署方式Docker 容器化部署二进制文件直接运行源码编译C. 默认配置服务端口：8080默认账号：admin/admin数据存储：/app/data3. 背景介绍A. 项目起源随着 LLM 服务提供商日益增多，个人用户面临管理多个 API Key、分散调用、成本统计困难等问题。Octopus 应运而生，提供统一的 API 聚合解决方案。B. 相关上下文项目借鉴了 axonhub 的 LLM API 适配模块设计，并整合了 models.dev 的 AI 模型数据库用于价格信息同步。三、详细报道1. 主要内容A. 功能特性渠道管理：支持单通道多 API Key 配置每个通道支持多个端点，智能选择延迟最短的端点自动根据通道类型追加 API 路径分组管理：将多个通道聚合为统一的对外模型名称调用时设置 model 参数为分组名称即可访问所有通道支持多种负载均衡模式负载均衡模式：轮询模式：按顺序依次分配请求到各通道随机模式：随机选择可用通道故障转移：优先使用高优先级通道，失败时切换到低优先级通道加权模式：根据配置的通道权重分配请求B. 技术改进协议转换：OpenAI Chat 格式OpenAI Responses 格式Anthropic Messages 格式Gemini Generate Content 格式自动同步：定期从 models.dev 同步模型定价数据创建通道时自动同步可用模型列表自动创建未知模型的定价信息C. 统计分析综合请求统计Token 消耗统计成本跟踪定期批量写入数据库（性能优化策略）2. 技术细节A. 系统架构graph TB Client[客户端应用] --> Octopus[Octopus API 网关] Octopus --> LoadBalancer[负载均衡器] LoadBalancer --> Channel1[通道 1] LoadBalancer --> Channel2[通道 2] LoadBalancer --> Channel3[通道 3] Channel1 --> Provider1[提供商 1] Channel2 --> Provider2[提供商 2] Channel3 --> Provider3[提供商 3] Octopus --> Database[(数据库)] Octopus --> PriceSync[价格同步] PriceSync --> ModelsDev[models.dev]B. 数据流程sequenceDiagram participant C as 客户端 participant O as Octopus participant LB as 负载均衡 participant P as LLM 提供商 participant D as 数据库 C->>O: API 请求（model=分组名） O->>LB: 负载均衡选择 LB->>O: 返回选定通道 O->>P: 转发请求（协议转换） P-->>O: 返回响应 O-->>C: 返回结果 O->>D: 异步记录统计C. 配置架构graph LR Config[配置文件] --> Server[服务器配置] Config --> Database[数据库配置] Config --> Log[日志配置] Database --> SQLite[SQLite] Database --> MySQL[MySQL] Database --> PostgreSQL[PostgreSQL]3. 数据与事实A. 支持的 LLM 提供商OpenAI（Chat Completions、Responses）Anthropic（Claude）Google（Gemini）其他兼容 OpenAI 格式的提供商B. 数据库支持数据库类型配置示例SQLitedata/data.dbMySQLuser:password@tcp(host:port)/dbnamePostgreSQLpostgresql://user:password@host:port/dbnameC. 性能优化统计数据先存储在内存按配置间隔批量写入数据库减少频繁 I/O 操作对性能的影响四、影响分析1. 行业影响A. 技术趋势LLM 服务聚合成为刚需多模型混合调用成为趋势成本优化工具需求增长B. 竞争格局与其他 API 网关相比，Octopus 更专注于个人用户场景开源免费，降低了个人用户的接入门槛轻量级设计，适合个人部署2. 用户影响A. 现有用户简化了多 API Key 管理统一调用接口，降低集成复杂度实时成本统计，便于预算控制B. 潜在用户个人开发者：快速接入多个 LLM 服务小型团队：共享 API 配额，降低成本研究人员：灵活切换不同模型进行实验C. 迁移成本部署简单，支持 Docker 一键启动兼容 OpenAI SDK，无需修改现有代码配置文件自动生成，开箱即用3. 技术趋势A. 技术方向API 聚合服务标准化智能负载均衡算法优化多协议适配成为标配B. 生态影响促进 LLM 服务的互联互通降低模型切换成本推动价格透明化五、各方反应1. 项目特点开源免费，代码托管在 GitHub活跃的开发维护清晰的文档和使用指南2. 业内评价A. 技术亮点架构简洁，易于理解和扩展前后端分离，便于独立部署多数据库支持，适应不同场景B. 应用场景个人开发环境搭建小型团队 API 管理LLM 应用后端服务3. 用户反馈A. 正面评价部署简单，快速上手UI 界面美观易用功能完整，满足个人需求B. 改进建议增加更多 LLM 提供商支持优化负载均衡算法增强监控告警功能六、相关链接1. 项目地址GitHub 仓库：https://github.com/bestruirui/octopus2. 相关项目axonhub：LLM API 适配模块参考来源models.dev：AI 模型数据库3. 技术文档完整配置说明API 接口文档部署指南参考资料Octopus GitHub Repository
gpt-load 企业级多渠道大模型 API 管理平台技术分析
gpt-load 企业级多渠道大模型 API 管理平台技术分析一、平台概述1. 简介gpt-load 是一款基于 Go 语言开发的企业级多渠道大模型 API 管理平台，专为需要集成多种 AI 服务的企业和开发者设计。它作为透明代理服务，完整保留了各 AI 服务商的原生 API 格式，支持智能密钥管理、负载均衡、故障切换和水平扩展，内置 Web 管理界面，提供高效、稳定的多 AI 服务集成管理方案。2. 核心定位企业级 AI 服务聚合中间件，解决多 AI 服务商接入、密钥管理、负载均衡等痛点问题。3. 设计目标简化 AI 服务集成复杂度，提供统一接入层，降低企业使用多种大模型的技术门槛和运维成本。二、核心功能特性1. 透明代理完全保留 OpenAI、Google Gemini 和 Anthropic Claude 等原生 API 格式，无需修改客户端代码即可无缝迁移。支持官方 OpenAI API、Azure OpenAI 以及其他 OpenAI 兼容服务。支持 Gemini Pro、Gemini Pro Vision 等模型的原生 API。支持 Claude 系列模型，支持高质量的对话和文本生成。2. 智能密钥管理支持密钥分组管理、自动轮换和故障恢复，提升密钥可用性与安全性。提供高性能密钥池，确保在高并发场景下密钥的稳定使用。3. 负载均衡支持多上游端点加权负载均衡，优化请求分发，提高服务整体可用性。自动管理密钥黑名单及恢复机制，减少因密钥失效导致的请求失败。4. 动态配置与热重载系统设置和分组配置支持热重载，修改后无需重启即可生效，提升运维效率。分组配置优先级高于系统设置，满足不同场景下的定制化需求。5. 企业级架构支持分布式主从部署和水平扩展，满足高并发场景下的性能需求。提供全面的监控功能，包括实时统计、健康检查和详细请求日志，便于问题排查与系统优化。6. 现代化管理与安全基于 Vue 3 的 Web 管理界面，直观易用，提升管理效率。管理端与代理端认证分离，支持全局和分组级别代理密钥，强化权限管控。提供优雅关闭、错误恢复和完善的安全机制，确保服务稳定运行。三、系统架构1. 架构设计graph TB subgraph 客户端层 A1[OpenAI 客户端] A2[Gemini 客户端] A3[Claude 客户端] end subgraph gpt-load 代理层 B1[API 网关] B2[认证模块] B3[负载均衡器] B4[密钥管理器] B5[健康检查] B6[配置中心] B7[Web 管理界面] end subgraph 上游服务层 C1[OpenAI API] C2[Azure OpenAI] C3[Gemini API] C4[Claude API] C5[其他兼容服务] end subgraph 数据存储层 D1[(MySQL/PostgreSQL)] D2[(SQLite)] D3[Redis 可选] end A1 --> B1 A2 --> B1 A3 --> B1 B1 --> B2 B2 --> B3 B3 --> B4 B4 --> C1 B4 --> C2 B4 --> C3 B4 --> C4 B4 --> C5 B3 --> B5 B5 --> B4 B7 --> B6 B6 --> B4 B6 --> D1 B6 --> D2 B6 --> D32. 请求处理流程sequenceDiagram participant C as 客户端 participant G as gpt-load 网关 participant A as 认证模块 participant L as 负载均衡器 participant K as 密钥管理器 participant P as 上游 AI 服务 C->>G: 发起 API 请求 G->>A: 验证代理密钥 A-->>G: 认证通过 G->>L: 获取可用端点 L->>K: 请求密钥 K-->>L: 返回健康密钥 L->>P: 转发请求 P-->>L: 返回响应 L-->>G: 转发响应 G-->>C: 返回结果3. 密钥管理机制graph LR subgraph 密钥池 A[密钥组 1] B[密钥组 2] C[密钥组 3] end subgraph 密钥状态 D[活跃密钥] E[冷却密钥] F[黑名单密钥] end subgraph 密钥操作 G[自动轮换] H[故障恢复] I[健康检查] end A --> D B --> D C --> D D --> E E --> D D --> F F --> H H --> D I --> D G --> D四、部署与安装1. 环境准备确保服务器已安装 Docker 和 Docker Compose。准备数据库（MySQL、PostgreSQL 或 SQLite）和 Redis（可选，用于缓存和分布式协调）。2. 下载配置文件mkdir -p gpt-load && cd gpt-load wget https://raw.githubusercontent.com/tbphp/gpt-load/refs/heads/main/docker-compose.yml wget -O .env https://raw.githubusercontent.com/tbphp/gpt-load/refs/heads/main/.env.example3. 修改配置文件编辑 .env 文件，修改 AUTH_KEY 为强密码，确保安全性。根据需要配置数据库连接（DATABASE_DSN）和 Redis 连接（REDIS_DSN）。4. 启动服务docker compose up -d默认使用 SQLite 数据库，适合轻量单机应用。如需使用 MySQL 或 PostgreSQL，请在 docker-compose.yml 文件中取消对应服务的注释，并配置好环境变量。5. 访问管理界面打开浏览器，访问 http://localhost:3001。使用修改后的 AUTH_KEY 登录管理界面。6. 配置 API 代理在管理界面创建服务分组（如 openai、gemini），并添加对应 AI 服务的 API 密钥。客户端通过代理端点调用，替换原生 API 地址为 http://localhost:3001/proxy/{group_name}，使用代理密钥认证。五、技术优势分析1. 透明代理设计完全保留上游 API 格式，客户端无需任何代码修改即可切换到代理层，大幅降低迁移成本。2. 多厂商统一接入同时支持 OpenAI、Azure OpenAI、Google Gemini、Anthropic Claude 等主流 AI 服务，企业可根据成本、性能、合规性等因素灵活选择服务商。3. 智能密钥管理密钥分组、自动轮换、故障恢复等机制，有效应对 API 配额限制、密钥失效等问题，提升服务稳定性。4. 负载均衡与高可用支持加权负载均衡、故障自动切换，配合分布式部署能力，满足企业级高并发场景需求。5. 运维友好热重载配置、Web 管理界面、详细监控日志，降低运维复杂度，提升问题排查效率。6. 安全加固管理端与代理端认证分离、多级代理密钥、优雅关闭机制，全面保障服务安全。六、应用场景1. AI 应用开发商需要集成多种大模型能力，希望统一管理 API 调用、控制成本、保障服务稳定性的场景。2. 企业内部 AI 平台企业内部构建 AI 能力平台，需要统一接入多种 AI 服务，并通过密钥管理、负载均衡等机制保障服务稳定性。3. AI 服务代理商为多个客户提供 AI 服务接入，需要通过密钥分组、负载均衡等机制实现多租户隔离和服务保障。4. 研发团队研发团队在开发过程中需要频繁切换不同 AI 服务进行测试和对比，gpt-load 提供了便捷的统一接入层。七、技术栈1. 后端技术Go 语言：高性能、并发能力强，适合构建代理服务。2. 前端技术Vue 3：现代化 Web 管理界面，提供良好的用户体验。3. 数据存储MySQL/PostgreSQL：生产环境推荐使用的关系型数据库。SQLite：轻量级单机部署场景。Redis：可选，用于缓存和分布式协调。4. 容器化部署Docker、Docker Compose：简化部署流程，支持快速交付。Kubernetes：支持容器编排平台部署，满足大规模集群需求。八、项目资源1. 开源地址GitHub：https://github.com/tbphp/gpt-load用户可以在此获取源代码、参与贡献或报告问题。2. 官网地址官网：https://www.gpt-load.com/基于 Next.js 14 构建，提供项目展示与文档导航。官网为开源项目提供了专业的展示平台，包含项目简介、功能特性、架构图、性能指标等内容。3. 文档地址详细文档可在官网或 GitHub 仓库的 README.md 文件中查看。文档涵盖了平台的功能特性、快速开始指南、配置说明、部署指南等内容，为用户提供全面的使用指导。九、总结gpt-load 作为一款企业级多渠道大模型 API 管理平台，通过透明代理、智能密钥管理、负载均衡等核心特性，为企业集成多种 AI 服务提供了高效、稳定的解决方案。其基于 Go 语言的高性能架构、Vue 3 现代化管理界面、热重载配置等设计，充分体现了企业级产品的设计理念。对于需要统一管理多种 AI 服务的企业和开发者而言，gpt-load 是一个值得考虑的开源选择。参考资料gpt-load 企业级多渠道大模型 API 管理平台gpt-load GitHub 仓库gpt-load 官方网站