KubeCon North America 2025 Review

KubeCon North America 2025 13号结束了,官网上也有了些会议资料。挑了几个感兴趣的话题总结下。

Dynamic Routing with Multi-Cluster Inference Gateway

https://kccncna2025.sched.com/event/27FeP/ai-inference-without-boundaries-dynamic-routing-with-multi-cluster-inference-gateway-rob-scott-google-daneyon-hansen-soloio?iframe=no&w=100%&sidebar=yes&bg=no

这段时间正好在做 AI 网关,这个话题可以说是“瞌睡了送枕头”。

推理服务和传统 API 流量相比,在 payload,响应时间,后端资源开销上都有着很大的差异(见下图)。 2781cd595fe30c8da311d124a8e4ee35_MD5

因此,推理网关需要做到后端负载感知的调度(传统 API 网关也有类似的方案,尤其是在后端机型不一样,普通的 rr 无法均匀负载时,做后端负载感知动态调权)。在 Gateway 和推理实例间引入了一个 EPP(Endpoint Picker)组件(注:EPP 现在也是 Kubernetes 做推理服务的一个通用组件),采集推理实例的指标来动态选择推理后端。 3462773fb295a9dabdabc886b1de43ca_MD5

benchmark 数据显示,使用推理网关相比传统负载均衡,推理实例间的负载更加均衡,请求排队更少,从而降低了响应时间。

在多集群场景下,这套方案需要解决 3 个问题:

  1. 服务发现:Cluster Inference Services 如何暴露给 Gateway?
  2. 后端选择:Gateway 如何在多集群间分配流量?
  3. 路由模式:流量如何从 Gateway 转发到集群?

第一个问题作者提了 3 个解决方法: fd7a6ffacbfd58fcd6482dd40b861461_MD5

不是关注重点,略过。

第二个问题,简单的 RR 和 Active-Passive 肯定就失去了推理网关负载感知的优势。所以,在 EPP 感知负载之外,Gateway 也得做负载感知。作者也提了两个方法: 1d3df4bac299f39b0c917b886bab2a27_MD5 f4bd518bc6dce2999d5805d5b2d46dac_MD5

从层级上来说,EPP Aggregate Metrics 方案更加简洁,毕竟在 EPP 上还得做二次调度。

最后一个问题,如果 EPP 能跨集群直接访问,direct routing 是最合适的方式,不行的话再加一层网关,使用 Cluster-Local Gateway 做暴露也能访问。

网关的限流方案

Note:本文由 Google Gemini DeepResearch 生成。

1.网关限流服务的战略必要性与流量控制架构

1.1. 网关在流量管理中的控制点角色与核心必要性

API 网关在现代微服务架构中扮演着至关重要的流量控制点角色,负责管理北向(Ingress)和南向(Egress)的流量。对流量实施限制(即限流)是保障上游服务稳定性、维持系统可用性和实现资源公平分配的核心防御机制。

限流服务的战略必要性主要体现在以下几个方面:

首先,限流是防止系统过载和雪崩效应的关键。当面临突发流量高峰时,如不加限制,后台服务器可能因资源耗尽而被压垮。通过限制客户端在指定时间窗内的请求次数,网关可以有效地保护应用编程接口(API)及其背后的微服务。其次,限流是抵御恶意攻击的有效手段,尤其是分布式拒绝服务攻击(DDoS)或防止未受限制的网络爬虫过度消耗计算资源。如果任由客户端无限制地访问上游服务,系统的可用性将受到负面影响。

从架构设计的角度看,网关作为请求进入系统的唯一入口,是实施全局限流策略的最佳位置,可以根据 IP、API 密钥、特定消费者(Consumer)或其他定制标准来应用限流规则。

1.2. 限流策略的粒度、层次结构与定制化响应

在实际应用中,灵活的限流服务需要支持精细的粒度和层次化配置模型,以满足不同业务和资源保护的需求。这种配置的优先级机制体现了一种 “微观精确控制”优于“宏观默认保护” 的设计哲学。

网关通常支持多层次的限流配置,包括:

  1. 特定 API 或消费者限流: 这是优先级最高的限流配置。例如,用户可以对单个关键 API 进行限流设置,或者对特定的消费者群体施加限制。这种配置会覆盖所有默认设置。这意味着在限流值冲突时,系统优先执行最明确定义或最严格的限制,确保关键资源的防护级别不受宏观策略的稀释。
  2. API 默认限流: 作用于应用下大部分 API 的通用配置。当没有针对单个 API 配置限流值时,将采用此默认值。
  3. 应用总限流 (App Total Limit): 这是对当前应用下所有 API 请求的总和设置的上限。应用总限流是防止资源被“虹吸”的最后防线。即使单个 API 的限流都没有达到,一旦应用的总请求量超出限流总和值,所有请求都将被限制。这种设计解决了在微服务架构中,攻击者通过对大量低 QPS API 进行频繁调用,最终耗尽整个应用共享资源预算的“长尾攻击”问题。

网关在触发限流时,必须向客户端返回明确的、用户友好的响应。例如,默认的限流响应可能包含 {"resultStatus":1002, "tips":"顾客太多,客官请稍候"} 。为了实现友好的降级体验,网关需要支持定制化的响应格式,允许用户配置 JSON 格式的返回内容,如通过 resultStatus 字段(例如,1002 表示限流)和 tips 字段向用户提供定制化的限流提示。

2. 常见限流算法的原理、适用性与分布式性能剖析

选择合适的限流算法是构建高性能网关服务的基石。不同的算法在精确度、突发处理能力和分布式系统中的扩展性方面存在显著差异。

2.1. 固定窗口计数器 (Fixed Window Counter, FWC)

固定窗口计数器是最简单、资源消耗最低的限流算法。它在一个固定的时间周期内(例如每 60 秒)累积请求计数,当周期结束时,计数器重置。

优势与局限性: FWC 的实现非常简单,查询和递增操作计算复杂度为 $O(1)$,因此延迟极低。然而,其主要局限在于边界突发问题。如果客户端在窗口结束前的一刻发出大量请求,并在下一个窗口开始后的第一时间再次发出同等量的请求,系统在短时间内实际放行的请求量可能高达阈值的两倍。这使得 FWC 难以应对流量的瞬间爆发,可能导致上游服务过载。

常见的大模型 OpenAPI 规范

AI Gateway 的对接开发中,一个重要的内容就是对接不同厂商推理服务的接口协议。目前,推理服务的接口协议主要分为以下几种类型:

  • 文本对话接口,如 OpenAI 的 chat completions 和 response API 等
  • 向量接口,向量接口用于将输入的文本或者图片、视频(多模态)等转换成向量表示,适用于搜索(文搜图、图搜图、图文混合搜索)、聚类、推荐等场景。

文本对话

文本对话 API 需要提供如下能力支持:

  1. 模型选择
  2. 用户、系统、模型输入内容角色区分
  3. 模型参数调整
  4. 工具调用
  5. MCP 支持
  6. 用量统计

目前使用最广泛的文本对话接口自然是 OpenAI 的 chat completions API。几乎所有的 LLM 服务提供商都支持 chat completions compatible 调用。

https://platform.openai.com/docs/api-reference/introduction

chat completions

基本调用:

curl https://api.openai.com/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -d '{
    "model": "gpt-5",
    "messages": [
      {
        "role": "developer",
        "content": "You are a helpful assistant."
      },
      {
        "role": "user",
        "content": "Hello!"
      }
    ]
  }'
  • model: 请求调用的模型
  • messages:构成对话的消息体。根据消息的来源角色,message 可以分为 developer/system(开发者,系统提供的 prompt),user(用户自身的输入)和 assistant(模型的响应,用于多轮对话时模型的上下文传递)。
    • content 分为两种类型,纯文本即为 string,非纯文本 content 为列表类型,内容根据 type(text,image_url,input_audio 等)有不同的字段
  • stream:采用正常 HTTP 响应还是 sse 响应
  • stream_options: 在 sse 响应时,有些模型默认不会输出 usage 信息,需要显式将 stream_options.include_usage 设置成 true
  • temperature:模型温度,取值范围 0 - 2,值越高,输出的 tokens 随机性越大
  • top_p:和 tempreature 一样对模型输出进行调整的参数,模型会考虑概率质量最高的top_p个tokens的结果。所以0.1意味着只考虑概率质量最高的10%的tokens。
  • reasoning_effort:模型的推理深度,比如对于 OpenAI 模型来说有 minimal,low,medium,high 等多种选择。
  • max_completion_tokens:最大输出 tokens 数,包括 output tokens 和 reasoning tokens。替代原来的 max_tokens 字段。
  • tools:告知大模型本地可调用的工具列表。工具里面定义了工具的名称、描述和 json schema 表示的参数描述。替代原来的 functions 字段。
  • tool_choice:告知模型对于工具的调用选择。none 表示不要调用任何工具直接生成 messages,auto 表示由模型自己决定是否调用 allowed_tools。required 表示模型必须调用至少一个工具。

除此之外,各家可以在 OpenAI 标准的 API 上扩展自己的字段,比如 cherry studio 会使用 thinking 字段来开启/关闭模型思考:

APISIX 插件体系

背景知识

APISIX 基于 Openresty 和 Nginx 开发,了解 APISIX 的插件体系也有必要先了解下 Nginx 和 Openresty 的基础知识。

Nginx 请求处理阶段

Nginx 将一个 HTTP 请求的处理过程精心划分为一系列有序的阶段(Phases),就像一个工厂的流水线。每个阶段都有特定的任务,不同的模块可以将自己的处理程序(handler)注册到感兴趣的阶段。这种设计使得 Nginx 的功能高度模块化、可扩展,并且处理流程非常清晰高效。

一个请求在 Nginx 中主要会经过以下 11 个处理阶段

1. NGX_HTTP_POST_READ_PHASE (请求读取后阶段)

  • 任务: 接收到完整的请求头(Request Header)之后,第一个被执行的阶段。
  • 常用模块ngx_http_realip_module
  • 作用举例: 在这个阶段,realip 模块会根据 X-Forwarded-For 或 X-Real-IP 等请求头,将客户端的真实 IP 地址替换掉代理服务器的 IP 地址。这对后续的访问控制和日志记录至关重要。

2. NGX_HTTP_SERVER_REWRITE_PHASE (Server 级别地址重写阶段)

  • 任务: 在 server 配置块中执行 URL 重写。
  • 常用模块/指令rewrite 指令(当它定义在 server 块中时)。
  • 作用举例: 在请求进入具体的 location 匹配之前,对 URL 进行全局的、初步的改写。例如,将所有 http 请求强制重定向到 https
server {
    listen 80;
    server_name example.com;
    # 这个 rewrite 就工作在 SERVER_REWRITE 阶段
    rewrite ^/(.*)$ https://example.com/$1 permanent; 
}

3. NGX_HTTP_FIND_CONFIG_PHASE (配置查找阶段)

  • 任务: 根据上个阶段处理完的 URI,查找并匹配对应的 location 配置块。
  • 核心功能: 这是 Nginx 路由的核心。Nginx 会用请求的 URI 与 location 指令定义的规则进行匹配,找到最合适的 location。这个阶段没有模块可以注册 handler,是 Nginx 核心自己完成的。

4. NGX_HTTP_REWRITE_PHASE (Location 级别地址重写阶段)

  • 任务: 在上一步匹配到的 location 块内部,执行 URL 重写。
  • 常用模块/指令rewrite 指令(当它定义在 location 块中时)、set 指令。
  • 作用举例: 对特定 location 的请求进行更精细的 URL 改写。这个阶段的 rewrite 可能会导致 Nginx 重新回到 FIND_CONFIG 阶段去匹配新的 location,可能会有循环,最多执行 10 次以防止死循环。
location /app/ {
    # 这个 rewrite 工作在 REWRITE 阶段
    rewrite ^/app/(.*)$ /v2/app/$1 break; 
}

5. NGX_HTTP_POST_REWRITE_PHASE (地址重写后阶段)

  • 任务: 防止 rewrite 阶段的重写指令导致死循环。如果 URI 在上一个阶段被重写,这个阶段会把重写后的 URI 交给 Nginx,让其重新开始 FIND_CONFIG 阶段,查找新的 location。这是一个内部阶段,用户通常不直接感知。

6. NGX_HTTP_PREACCESS_PHASE (访问权限控制前置阶段)

  • 任务: 在正式的访问权限检查之前,做一些准备工作。
  • 常用模块ngx_http_limit_conn_module (连接数限制), ngx_http_limit_req_module (请求速率限制)。
  • 作用举例: 在检查用户名密码之前,先检查客户端的请求速率是否过快,如果过快,直接拒绝,不必再执行后面的阶段。

7. NGX_HTTP_ACCESS_PHASE (访问权限控制阶段)

  • 任务: 对请求进行权限验证。
  • 常用模块/指令ngx_http_access_module (allowdeny), ngx_http_auth_basic_module (HTTP 基本认证)。
  • 作用举例: 检查客户端 IP 是否在允许列表中,或者验证用户提供的用户名和密码是否正确。如果这个阶段有任何模块拒绝了请求,处理就会立即中断并返回错误(如 403 Forbidden)。

8. NGX_HTTP_POST_ACCESS_PHASE (访问权限控制后置阶段)

  • 任务: 主要用于配合 ACCESS 阶段的 satisfy 指令。
  • 作用举例: 当 satisfy any; 被使用时,如果 ACCESS 阶段有模块允许了访问(例如 IP 匹配成功),POST_ACCESS 阶段的处理就可以被跳过。如果 ACCESS 阶段没有明确允许,这个阶段的处理(例如 HTTP 基本认证)就会被执行。

9. NGX_HTTP_TRY_FILES_PHASE (Try_files 阶段)

  • 任务try_files 指令专属的特殊阶段。
  • 常用模块/指令try_files
  • 作用举例: 按顺序检查文件或目录是否存在,如果找到,则内部重定向到该文件,如果都找不到,则执行最后一个参数(通常是内部重定向到一个 location 或返回一个状态码)。
location / {
    # 这个指令工作在 TRY_FILES 阶段
    try_files $uri $uri/ /index.html;
}

10. NGX_HTTP_CONTENT_PHASE (内容生成阶段)

  • 任务核心阶段,负责生成最终的响应内容并发送给客户端。
  • 重要特性: 每个 location 只有一个模块能成为“内容处理模块”(Content Handler)。
  • 常用模块:
    • ngx_http_static_module: 处理静态文件。
    • ngx_http_proxy_module: 将请求反向代理到后端服务器 (proxy_pass)。
    • ngx_http_fastcgi_module: 将请求转发给 FastCGI 应用(如 PHP-FPM)。
    • ngx_http_index_module: 处理目录索引文件。
    • return: 直接返回指定的状态码或内容。
  • 执行逻辑: Nginx 会调用在 location 中找到的内容处理模块。例如,如果配置了 proxy_pass,那么 proxy_module 就会接管请求。如果没有特定的内容处理模块,默认会由 static_module 尝试提供静态文件服务。

11. NGX_HTTP_LOG_PHASE (日志记录阶段)

  • 任务: 请求处理完成后,记录访问日志。
  • 常用模块/指令ngx_http_log_module (access_log)。
  • 作用举-例: 无论请求成功还是失败,这个阶段都会被执行(除非有严重错误),用于将请求的相关信息(如客户端IP、请求时间、状态码等)写入到日志文件中。

过滤器模块 (Filter Modules)

CONTENT 阶段生成了响应内容后,在发送给客户端之前,响应数据还会经过一系列的 过滤器(Filter) 链处理。过滤器负责对响应头和响应体进行修改或加工。

WebSocket Origin Header 校验失败

最近做 APISIX 线上服务时遇到一个场景:业务使用 websocket 转发时,在浏览器会出现 WebSocket close with status code 1006 的错误。打开调试工具查看发现在 websocket 握手时服务端返回了 403。

76f75e967736245ec923202987a22482_MD5

4efbf4b7a2093ed4f1d6e522139dc92e_MD5

非常奇怪的是,如果业务不经过 APISIX 直接访问后端 code-server 是没有问题的(中间也得经过一层 Ingress 转发)。

简单的流量模型如下:

                                                                                                                      
                                                                                                                      
                               +--------------+                              +--------------+          +-------------+
  https://domain.com:9443/xxx  |              |  http://domain.com:23480/xxx |              |          |             |
--------------------------------   APISIX     -------------------------------+   Ingress    -----------+ code-server |
                               |              |                              |              |          |             |
                               +--------------+                              +--------------+          +-------------+

经过对比发现,两者的请求头里面 Host 和 Origin 是存在差异的。尝试在 APISIX 中强制修改 Host 头部,问题没有解决。然后利用 proxy-rewrite 强制修改 Origin 头部,请求恢复正常。

    "plugins": {
      "proxy-rewrite": {
        "uri": "/anything",
        "headers": {
          "set": {
	        "Origin": "http://domain.com"
          }
        }
      }
    },

那么问题来了,为什么改完 Origin Header 就行了?code-server 是如何处理 Origin Header 的?为什么 Ingress 可以,APISIX 不行?