Read OSS

Kong Gateway 架构:API 网关如何在 Nginx 内部运行

中级

前置知识

  • 了解反向代理和 API 网关的基本概念
  • 熟悉 Nginx 的基本概念(Worker 进程、配置指令、upstream/downstream)
  • 掌握基础 Lua 语法(table、metatable、require/模块系统)

Kong Gateway 架构:API 网关如何在 Nginx 内部运行

大多数 API 网关本质上是独立的应用程序,只是顺带承担了 HTTP 流量代理的职责。Kong 则不同——它不只是运行在 Nginx 前面,而是活在 Nginx 内部。Kong 的每一行请求处理逻辑,都以 Lua 代码的形式运行在 Nginx Worker 进程中,并在 Nginx 请求生命周期的精确节点被触发执行。这种架构让 Kong 既拥有 Nginx 事件循环的原生性能,又具备完全可编程插件系统所带来的灵活性。

本文是七篇系列文章的第一篇,将介绍 Kong 如何嵌入 Nginx、如何将 Lua 处理函数映射到 Nginx 各阶段、代码库的组织方式,以及支撑所有后续请求处理的运行时环境是如何被引导启动的。

OpenResty:将 Nginx 变成 Lua 应用服务器

Kong 并不直接使用 Nginx,而是使用 OpenResty——一个将 Nginx 与 lua-nginx-module 打包在一起的发行版,使 Lua 代码得以在 Nginx Worker 进程内部执行。OpenResty 将 Nginx 的内部阶段以 *_by_lua_block 指令的形式暴露出来,作为 Lua 代码在请求处理特定时机运行的钩子。

这并非一个临时拼凑的嵌入式脚本方案。lua-nginx-module 提供了基于协程的 cosocket API,让 Lua 代码能够在不离开 Nginx 事件循环的前提下执行非阻塞 I/O 操作,包括数据库查询、HTTP 调用和 DNS 查询。每个需要调用上游 API 或查询数据库的 Kong 插件,都通过这个 cosocket 层来完成,这意味着单个 Worker 进程可以同时处理数以千计的并发连接。

关键点在于:Kong 的 Lua 代码运行在 Nginx 进程内部,而非与其并列运行。这里没有进程间通信的开销,也没有序列化边界。ngx 全局对象随处可用,共享内存区域(lua_shared_dict)则提供了高效的跨 Worker 数据共享机制。

flowchart TD
    subgraph "Nginx Master Process"
        A[Master: manages workers]
    end
    subgraph "Nginx Worker Process 1"
        B[Event Loop]
        C[lua-nginx-module]
        D[Kong Lua Code]
        B --> C --> D
    end
    subgraph "Nginx Worker Process N"
        E[Event Loop]
        F[lua-nginx-module]
        G[Kong Lua Code]
        E --> F --> G
    end
    A --> B
    A --> E
    H[lua_shared_dict: kong_db_cache] <-.-> D
    H <-.-> G

阶段驱动的生命周期:Kong 接入 Nginx 的方式

kong/templates/nginx_kong.lua 是 Kong 与 Nginx 进行接线的地方。每个 *_by_lua_block 指令都委托给全局 Kong 表上的对应方法:

init_by_lua_block {
    Kong = require 'kong'
    Kong.init()
}
init_worker_by_lua_block {
    Kong.init_worker()
}

在 server 块的后续部分,请求处理阶段以同样的方式在 第 145–163 行 完成接线:

rewrite_by_lua_block { Kong.rewrite() }
access_by_lua_block { Kong.access() }
header_filter_by_lua_block { Kong.header_filter() }
body_filter_by_lua_block { Kong.body_filter() }
log_by_lua_block { Kong.log() }

balancer 则通过 upstream 块中 第 85 行balancer_by_lua_block 完成接线。完整的阶段映射关系如下:

Nginx 指令 Kong 处理函数 职责
init_by_lua_block Kong.init() 加载 Schema、连接数据库、构建路由器
init_worker_by_lua_block Kong.init_worker() 设置定时器、缓存预热、集群初始化
ssl_certificate_by_lua_block Kong.ssl_certificate() 动态 TLS 证书选择
rewrite_by_lua_block Kong.rewrite() 工作空间检测、全局插件执行
access_by_lua_block Kong.access() 路由匹配、认证鉴权、插件执行
balancer_by_lua_block Kong.balancer() 上游目标选择、重试机制
header_filter_by_lua_block Kong.header_filter() 响应头处理
body_filter_by_lua_block Kong.body_filter() 响应体转换
log_by_lua_block Kong.log() 日志记录、指标采集、资源清理

所有这些处理函数都定义在 kong/init.lua 中,这是整个代码库的核心文件。

flowchart LR
    A[Client Request] --> B[rewrite]
    B --> C[access]
    C --> D[balancer]
    D --> E[Upstream]
    E --> F[header_filter]
    F --> G[body_filter]
    G --> H[log]
    H --> I[Client Response]

目录结构与模块映射

kong/ 目录按职责划分组织。理解这份模块地图是读懂代码库的必要前提:

目录 用途
kong/cmd/ CLI 命令(startstopreloadmigrations
kong/conf_loader/ 配置的加载、校验与合并
kong/db/ 数据库层:Schema、DAO、存储策略(Postgres、LMDB)
kong/runloop/ 核心请求处理:handler、router、balancer、插件迭代器
kong/router/ 路由匹配引擎(传统模式与表达式模式)
kong/pdk/ 插件开发工具包(Plugin Development Kit)——面向插件的稳定 API 接口
kong/plugins/ 内置插件实现(共 45 个)
kong/clustering/ 混合模式:CP/DP 通信、配置同步
kong/llm/ AI 网关:LLM 提供商驱动与适配器
kong/api/ Admin API 端点生成与路由
kong/tools/ 工具模块(gzip、字符串处理、UUID、时间等)
kong/templates/ Nginx 配置模板与默认配置

内置插件的完整列表枚举在 kong/constants.lua 中——共 45 个插件名称,涵盖从 jwtkey-auth 到较新的 ai-proxyai-prompt-guard 等各类插件。这份列表在初始化时驱动插件发现流程:

local plugins = {
  "jwt", "acl", "correlation-id", "cors", "oauth2",
  -- ... 36 more plugins ...
  "ai-proxy", "ai-prompt-decorator", "standard-webhooks", "redirect"
}

提示: constants.lua 非常值得收藏。除了插件列表,它还定义了 CORE_ENTITIES(Schema 加载顺序有讲究——依赖项优先)、ENTITY_CACHE_STORE 映射、协议定义以及集群相关常量。

全局 Kong 对象与运行时补丁

在任何请求处理开始之前,Kong 需要准备两样东西:一个作为共享命名空间的全局 kong 对象,以及一组用于调整 Lua/ngx 行为的运行时补丁。

全局 kong 对象在 kong/global.lua 中通过 _GLOBAL.new() 创建:

function _GLOBAL.new()
  return {
    version = KONG_VERSION,
    version_num = KONG_VERSION_NUM,
    configuration = nil,
  }
end

这个初始的裸表会在启动过程中被逐步填充。当 Kong 开始处理请求时,kong.dbkong.cachekong.core_cachekong.worker_eventskong.cluster_eventskong.dnskong.router 等对象都已挂载其上。PDK 命名空间(kong.requestkong.responsekong.service 等)则通过 第 161 行_GLOBAL.init_pdk() 完成初始化。

sequenceDiagram
    participant init.lua
    participant global.lua
    participant PDK
    init.lua->>global.lua: _GLOBAL.new()
    global.lua-->>init.lua: bare kong table {version, version_num}
    init.lua->>global.lua: _GLOBAL.init_pdk(kong, config)
    global.lua->>PDK: PDK.new(config, kong)
    PDK-->>global.lua: attaches kong.request, kong.response, etc.
    init.lua->>init.lua: kong.db = DB.new(config)
    init.lua->>init.lua: kong.dns = dns_client

kong/globalpatches.lua 中的运行时补丁在首次 require 时一次性应用。其中有三处补丁尤为值得关注:

  1. init 阶段的阻塞式 sleep(第 51–83 行):ngx.sleep 本身是非阻塞的,但在 init_worker 阶段,基于协程的 sleep 尚不可用。Kong 将其替换为 LuaSocket 的阻塞式 socket.sleep()——但仅在 ngx.get_phase() 返回 initinit_worker 时才生效。

  2. cJSON 精度cjson_safe.encode_number_precision(16) 确保浮点数在经过 JSON 序列化与反序列化后不会丢失精度。

  3. Protobuf 默认值pb.option("decode_default_array") 确保 protobuf 消息中的空数组在 JSON 编码时解码为 [] 而非 nil

提示: globalpatches 文件中也设置了 _G._KONG——一个仅包含 _NAME_VERSION 的精简表。注意不要将 _G._KONG(全局元数据标记)与 kong PDK 对象混淆。

请求生命周期概览

了解了整体架构之后,让我们从最高层面追踪一次 HTTP 请求的完整流程。客户端向 Kong 的代理端口发送 GET /api/users 请求:

  1. Rewrite 阶段Kong.rewrite()):初始化请求上下文(ngx.ctx),记录时间戳,检测工作空间,执行全局作用域插件。

  2. Access 阶段Kong.access()):runloop 的 access.before() 执行路由器,将请求匹配到对应的 Route 和 Service。插件按优先级依次执行——认证插件校验凭证,限流插件检查配额,转换插件修改请求内容。同时准备好上游连接所需的 balancer 数据。

  3. Balancer 阶段Kong.balancer()):选择具体的上游目标(IP:port),处理 DNS 解析,设置超时时间,管理连接池。发生重试时,此阶段会重新执行。

  4. 上游转发:Nginx 将请求转发至选定的上游目标。

  5. Header filter 阶段Kong.header_filter()):处理上游返回的响应头。插件可以对响应头进行添加、删除或修改。

  6. Body filter 阶段Kong.body_filter()):以流式分块的方式处理响应体。对于分块传输的响应,此阶段可能被多次调用。

  7. Log 阶段Kong.log()):完成所有计时数据的收尾。日志插件(http-log、file-log、datadog)对请求记录进行序列化并分发。最后释放请求上下文。

sequenceDiagram
    participant Client
    participant Rewrite as Kong.rewrite()
    participant Access as Kong.access()
    participant Balancer as Kong.balancer()
    participant Upstream
    participant HdrFilter as Kong.header_filter()
    participant BodyFilter as Kong.body_filter()
    participant Log as Kong.log()

    Client->>Rewrite: GET /api/users
    Rewrite->>Access: workspace set
    Access->>Access: Router match → Route + Service
    Access->>Access: Plugins execute (auth, rate-limit, ...)
    Access->>Balancer: balancer_data prepared
    Balancer->>Upstream: IP:port selected
    Upstream-->>HdrFilter: 200 OK + headers
    HdrFilter-->>BodyFilter: modified headers
    BodyFilter-->>Log: body chunks streamed
    Log-->>Client: response delivered

每个阶段处理函数都遵循同一模式,这在 kong/init.lua 中清晰可见:设置计时标记、调用 runloop.<phase>.before()、遍历执行插件、调用 runloop.<phase>.after()、记录耗时。这种"三明治"模式是 Kong 可扩展性的核心骨架——runloop 提供基础设施,插件提供具体行为。

下一步

本文奠定了理解 Kong 的基础:Kong 是运行在 Nginx Worker 进程内部的 Lua 代码,并在请求处理的每个阶段都设有钩子。在第二篇文章中,我们将完整追踪 Kong 的启动流程——从输入 kong start 命令开始,经过 CLI 分发、配置加载、Nginx 模板渲染,直到 init/init_worker 阶段将 Kong 准备好开始处理流量。