首页
/ Kong 开源贡献指南:PR 规范、Commit 约定、测试体系与 Lua 代码风格

Kong 开源贡献指南:PR 规范、Commit 约定、测试体系与 Lua 代码风格

2026-09-05 13:42:33作者:殷蕙予

本文基于 Kong 仓库根目录的 CONTRIBUTING.md 展开,系统梳理向 Kong(The API and AI Gateway)提交代码的完整流程:从问题上报渠道、Pull Request 提交前检查清单、Git 分支命名与 Commit Message 格式,到静态 Lint、busted 测试体系、changelog 编写要求、面向 LuaJIT 的性能编码实践,以及一套完整的 Lua 代码风格规范。读完后,你可以按官方约定独立提交一个符合仓库要求的补丁,并理解每个规范背后在仓库中的实际落点(如 Makefile.luacheckrcspec/ 测试目录结构、changelog/ 目录)。

一、寻求帮助与上报问题的渠道

在动手写补丁之前,CONTRIBUTING.md 首先区分了两类支持渠道:

1. 企业版(Enterprise Edition)支持

Kong Enterprise 客户应通过企业支持渠道提单,而不是在社区渠道提问;P1 级别的故障应拨打 24/7 企业支持热线。有意成为 Enterprise 客户则联系官方销售。

2. 社区版(Community Edition)支持

对于社区版的使用问题,官方建议:

  • 使用 GitHub Discussions 提问;
  • 加入社区 Slack 进行实时交流;
  • 不要在 GitHub Issues 中提出一般性使用问题或求助——Issues 仅保留给真正的 bug 报告;
  • 公开论坛 Kong Nation 适合提问、答疑和跟踪最新公告。

3. 如何报告一个 Bug

在 GitHub 仓库提交 issue 时,必须遵循 issue 模板,并包含以下四项信息:

  1. 问题摘要(A summary of the issue);
  2. 帮助复现问题的步骤列表;
  3. 遇到问题时的 Kong 版本;
  4. 你的 Kong 配置,或其中与问题相关的部分。

报告 bug 的同时,你也被欢迎直接提交修复补丁。功能请求(feature request)同样通过 issue 提交,且应尽可能详细。

二、贡献形式:不写代码也可以参与

除了代码增强和 bug 修复,CONTRIBUTING.md 列出了多种贡献方式:

  • 报告 bug;
  • 在支持渠道帮助社区成员;
  • 修复代码中的错别字;
  • 修复官方文档中的错别字、补充示例或澄清说明;
  • 对提出的特性和设计提供反馈;
  • Review Pull Requests。

提议新插件(重要边界)

官方明确说明:一般不会接受把新插件合并进本仓库。当前仓库中随 Kong 分发的插件集合是“基础插件集”,面向所有 Kong 安装环境。专用功能应以独立仓库中的插件形式存在。如果你需要写插件,官方建议:

  1. 先阅读 Plugin Development Guide;
  2. 将插件托管在公开仓库,并通过 LuaRocks 分发;
  3. 为插件增加曝光度:添加到 Kong Hub、在社区论坛发布公告。

这个边界也解释了仓库中 kong/plugins/ 目录的构成——从源码结构看,该目录下是随核心分发的一等公民插件(如 key-authjwtrate-limitingai-proxy 等),而外部插件机制则由 kong/runloop/plugin_servers/spec/02-integration/10-external-plugins/ 下的测试用例支撑。

三、提交补丁(Submitting a Patch)

小改动比大改动更容易被快速合并。如果你计划开发较大的特性,官方建议先在 GitHub Discussions 中沟通。提交 PR 前,必须完成以下检查清单:

  • 提交历史干净:变更是原子的,且遵循 commit message 格式;
  • Rebase 到基线分支git rebase 保证提交历史干净且线性;
  • 静态 Lint 通过:运行 make lintluacheck .
  • 测试通过:运行 make testmake test-all 或针对你的变更选择合适的测试套件;
  • 不要在 PR 中更新 CHANGELOG.md:该文件在发布流程中自动重新生成和维护。

如果 Reviewer 要求你修改补丁,请务必配合——CONTRIBUTING.md 特别强调:推动你的补丁前进是你的责任。PR 被接受后(修复 bug、增加功能或显著改善可用性),你就成为官方贡献者,可以申请 Contributor Badge,并且作为外部贡献者,你的名字会出现在后续版本的 changelog 中。

Git 分支命名规范

如果你有仓库的推送权限,请遵循以下分支命名约定:

前缀 用途
feat/foo-bar 新特性
fix/foo-bar Bug 修复
tests/foo-bar 仅涉及测试套件的变更
refactor/foo-bar 不改变行为的重构
style/foo-bar 风格问题
docs/foo-bar README.md、CONTRIBUTING.md 等文档更新
chore/foo-bar 不涉及功能源码的变更
perf/foo-bar 性能改进

Commit 原子性(Atomicity)

补丁中的提交必须组织为“逻辑工作单元”。一个 PR 可以包含一个或多个 commit,但每个 commit 内部不能混入无关变更。官方给出的典型反例:

你在修一个 bug 时顺手发现了另一个 bug——**不要在同一个 commit 里修两个 bug!**完成第一个 bug 的工作、提交补丁,之后再回来处理第二个 bug。无关的风格修复、重构同理。

判断标准是换位思考:Review 你的人,仅凭阅读你的 commit 历史,能否理解你的变更和理由?能否在某个 commit 里看到无关改动?答案应该是“不能”。遵循后文的 commit message 格式也有助于保持原子性。

四、Commit Message 格式

为了维护健康的 Git 历史,Kong 要求 commit message 满足以下硬性规则:

  • 使用现在时(present tense);
  • 必须以 typescope 为前缀;
  • 标题行(header)不超过 50 个字符
  • header 与 body 之间必须有一个空行
  • body 中的每一行不超过 72 个字符

整体遵循 conventional-commits 格式,模板如下:

<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>

Type(类型)

接受的 type 有九种:

  • feat:新特性;
  • fix:Bug 修复;
  • hotfix:发布过程中的紧急 bug 修复;
  • tests:纯粹与测试套件相关的变更(修测试、加测试、提升可靠性等);
  • docs:README.md、CONTRIBUTING.md 或类似文档的变更;
  • style:不影响代码含义的变更(空白裁剪、格式化等);
  • perf:显著改进性能的代码变更;
  • refactor:既不是修 bug 也不是加特性、且大到不能算作 perf 的代码变更;
  • chore:不属于重构的代码清理类维护变更、构建流程更新、依赖升级,或辅助工具与库的更新(LuaRocks、GitHub Actions 等)。

Scope(作用域)

Scope 指变更影响的代码库部分,由作者自行判断,常见的有:

  • proxy:影响请求代理的变更;
  • router:影响路由器的变更(路由器负责把请求匹配到配置好的 API);
  • admin:Admin API 的变更;
  • balancer:内部负载均衡器相关;
  • core:影响核心大部分、同时触及 proxybalancerdns 等多处的变更;
  • dns:内部 DNS 解析相关;
  • dao:与 DAO(数据层接口)相关的变更;
  • cli:CLI 变更;
  • cache:配置实体(数据层实体)缓存相关;
  • deps:更新依赖时使用(与 chore 前缀搭配);
  • conf:配置相关变更(新配置项、改进等);
  • <plugin-name>:插件名,如 basic-authldap
  • *:变更同时影响过多部分时使用(应尽量避免)。

这些 scope 与仓库目录结构可以直接对应:proxy 对应 kong/runloop/handler.lua 等运行循环逻辑,router 对应 kong/router/(含 atc.luatraditional.luaexpressions.lua 等),admin 对应 kong/api/dao 对应 kong/db/dao/dns 对应 kong/dns/cli 对应 kong/cmd/

Subject(标题)

Subject 应是对变更的简练描述,且必须:

  • 使用现在时、祈使句:写 "fix typo",而不是 "fixed" 或 "fixes";
  • 首字母大写:写 "fix typo",而不是 "Fix typo";
  • 以句号结尾。

Body(正文)

Body 应包含变更的详细说明。如果变更比较重大,应解释其动机和所选实现方案,并给出理由。每行不超过 72 个字符。

Footer(页脚)

Footer 是链接相关材料的理想位置:相关 GitHub issues、PR、被修复的 bug 报告等。

完整示例

CONTRIBUTING.md 给出了两个可直接借鉴的示例:

fix(admin): send HTTP 405 on unsupported method

The appropriate status code when the request method is not supported
on an endpoint is 405. We previously used to send HTTP 404, which
is not appropriate. This updates the Admin API helpers to properly
return 405 on such user errors.

* return 405 when the method is not supported in the Admin API helpers
* add a new test case in the Admin API test suite

Fix #678

另一个:

tests(proxy): add a new test case for URI encoding

When proxying upstream, the URI sent by Kong should be the one
received from the client, even if it was percent-encoded.

This adds a new test case which was missing, to ensure it is
the case.

五、静态 Lint:Luacheck 与仓库配置

Kong 使用 Luacheck 对 Lua 代码做静态检查,两种方式任选:

$ make lint

或:

$ luacheck .

Makefile 的源码可以看到 lint 目标的真实构成(第 152–155 行):它不仅运行 luacheck -q .,还会用 grep 检查测试文件中不允许残留的调试标记——spec/ 下的 #only / #o 标签和 t/ 下的 --- ONLY block,一旦发现即让 lint 失败。这防止了开发者把“只跑单个用例”的本地调试标记误提交进仓库。

仓库根目录的 .luacheckrc 定义了检查规则,几个关键点:

  • 基础标准是 std = "ngx_lua",即 OpenResty/lua-nginx-module 的 API 集合;测试文件(spec/**/*.lua**/*_test.lua)使用 ngx_lua+busted 标准,这与仓库使用 busted 作为测试框架一致;
  • 白名单全局变量仅三个:_KONGkongngx.IS_CLI——从源码结构看,这反映了 Kong 代码中合法的全局入口;
  • not_globals 禁用了 string.lentable.getn 等已被 Lua 5.1+ 淘汰的函数;
  • ignore = { "6." } 忽略了空白类告警(风格由代码规范而非 linter 强制);
  • 对个别文件单独放宽 read_globals,例如 kong/tools/sandbox/kong.lua 允许 table.pack/table.unpackkong/plugins/ldap-auth/*.lua 允许 bit.modstring.pack/string.unpack——因为 LuaJIT 的 5.1 语义下这些 API 并非总是存在。

六、编写测试:busted 三大套件

Kong 使用 busted 编写测试。CONTRIBUTING.md 要求:你的补丁必须包含相应测试套件的更新或新增。仓库的测试分为三大套件(对应 spec/ 下的目录编号):

目录 内容 Makefile 目标
spec/01-unit 单元测试(测试某个 Lua 模块或函数) make test
spec/02-integration 集成测试:启动 Kong(连接运行中的数据库),执行 Admin API 与代理请求并验证输出 make test-integration
spec/03-plugins 内置插件的测试(单元测试 + 集成测试) make test-plugins
spec/(全部) 一次性运行所有套件 make test-all

Makefile 中这些目标实际调用 bin/busted(由 dev 目标通过 LuaRocks 安装的 busted 2.2.0 等开发依赖提供);另有一个未列入文档的贡献者利器 make test-custom test_spec=<path>,可单独运行某个 spec 文件,适合在开发循环中快速验证。

写测试的几条守则:

  • 使用合适的 describe / it 块,让被测对象一目了然;
  • 保持测试原子性:一个测试不应同时断言两个无关行为;
  • 涉及数据层的测试要针对所有支持的数据库运行。

关于类型断言,官方给出了明确的“好/坏”对照:

-- bad
assert.Nil(foo)
assert.True(bar)

-- good
assert.is_nil(foo)
assert.is_true(bar)

比较 table 时:

-- bad (most of the time)
assert.equal(t1, t2)

-- good
assert.same(t1, t2)

除 busted 套件外,仓库还有一类基于 prove/TAP 的测试:t/ 目录下的 .t 文件(如 t/05-mlcache/ 中针对 resty.mlcache 的 15 个用例,t/01-pdk/ 中按 PDK 模块组织的用例)。Makefile 中的 pdk-phase-checks 目标会运行 t/01*/*/00-phase*.t 并生成 luacov 报告,用于验证 PDK 各接口在 Nginx 生命周期各阶段(rewrite/access/log 等)的调用合法性——这是贡献 PDK 相关代码时值得了解的验证机制。测试环境方面,spec/README.md 说明测试实例会忽略 KONG_xxx 环境变量以保证确定性,需用 KONG_TEST_xxx 形式覆盖;集成/插件测试可编辑 spec/kong_tests.conf 指向你自己的 Postgres/Cassandra 实例(详见 DEVELOPER.md)。

七、编写 Changelog:YAML 条目制

CONTRIBUTING.md 要求 PR 附带 changelog 条目,并指向 changelog/README.md。从仓库实际结构看,这套机制的工作方式是:

  1. 每个 PR 在 changelog/unreleased/kong/ 下新增一个 .yml 文件(当前仓库该目录下已有 77 个待发版的条目);
  2. 文件字段遵循 changelog/changelog-template.yaml
message: # "Description of your change" (required)
type: # One of "feature", "bugfix", "dependency", "deprecation", "breaking_change", "performance" (required)
scope: # One of "Core", "Plugin", "PDK", "Admin API", "Performance", "Configuration", "Clustering", "Portal", "CLI Command" (optional)
  1. 一个真实条目(来自 changelog/unreleased/kong/)长这样:
message: |
  **ai**: Added support for boto3 SDKs for the Bedrock provider, and for Google GenAI SDKs for the Gemini provider.
type: "feature"
scope: "Plugin"
  1. 发布时,changelog/ 下按版本归档了各版本的条目目录(如 changelog/3.5.0/changelog/3.6.0/ 直至 changelog/3.9.0/),changelog/Makefile 负责汇总生成正式 changelog PR,changelog/verify-prs 脚本则用于在两个版本间校验“每个功能 PR 是否都有对应 changelog 文件”。

这也解释了 PR 检查清单中“不要手动更新 CHANGELOG.md”的原因:CHANGELOG.md 是由这套 YAML 条目在发布流程中自动生成的产物。

八、编写高性能代码:面向 LuaJIT 的最佳实践

Kong 运行在 LuaJIT 上,而不是 Lua-PUC。CONTRIBUTING.md 据此给出了一套 LuaJIT 性能守则:

  • 不要创建全局变量;
  • 热路径上不要使用 NYI(Not Yet Implemented)函数;
  • 优先使用 FFI,而不是传统的 Lua C API 绑定;
  • 尽量预分配 table 槽位以避免 rehash:
-- bad
local t = {}
for i = 1, 100 do
  t[i] = i
end

-- good
local new_tab = require "table.new"
local t = new_tab(100, 0)
for i = 1, 100 do
  t[i] = i
end
  • 缓存热路径用到的全局变量,缓存名用原名中的 . 换成 _;对 OpenResty 内建 API,局部化时可以省掉 ngx. 前缀:
-- bad
for i = 1, 100 do
  t[i] = math.random()
end

-- good
local math_random = math.random
for i = 1, 100 do
  t[i] = math_random()
end
local req_get_post_args = ngx.req.get_post_args

非热路径可以不做局部化,例如错误分支中的 ngx.log(ngx.ERR, ...) 就没必要缓存。

  • 缓存 table 的长度和下标,避免不必要的 CPU 周期:
-- bad
for i = 1, 100 do
  t[#t + 1] = other_tab[#other_tab]
end

-- good
local n = 0
local n_other_tab = #other_tab
for i = 1, 100 do
  n = n + 1
  t[n] = other_tab[n_other_tab]
end

文档最后强调:最重要的仍然是设计一个高效的算法——再多的语言级技巧,也弥补不了糟糕的算法。从源码结构看,这些建议在仓库中是被实际执行的:kong/tools/table.lua 提供了 table 工具函数,kong/resty/mlcache/kong/runloop/ 等热路径代码普遍采用局部化缓存模式,可作为风格参照。

九、Contributor Badge

如果你的 PR 被接受(修复 bug、增加功能,或显著改善 Kong 的使用/理解),你将符合领取数字 Contributor Badge 的条件,可填写 Contributors Submissions form 申请。徽章有效期 1 年,到期后可通过提交新的贡献续期。

十、Lua 代码风格规范

为保证代码库健康一致,CONTRIBUTING.md 给出了一套(非穷尽的)Lua 风格约定。这套风格明显承袭 OpenResty 与 Nginx 的社区习惯,总则只有两条:行宽不超过 80 字符;缩进使用 2 个空格。遇到不确定的写法,去代码库里找类似案例保持一致;仓库中确实存在不满足规范的历史代码,欢迎贡献把它们改到推荐风格

模块(Modules)

模块内逻辑块之间用两行空行分隔:

local foo = require "kong.foo"


local _M = {}


function _M.bar()
  -- do thing...
end


function _M.baz()
  -- do thing...
end


return _M

变量(Variables)

命名使用 snake_case;常量使用大写:

-- bad
local myString = "hello world"

-- good
local my_string = "hello world"
-- bad
local max_len = 100

-- good
local MAX_LEN = 100

表(Tables)

使用构造器语法,并带尾逗号

-- bad
local t = {}
t.foo = "hello"
t.bar = "world"

-- good
local t = {
  foo = "hello",
  bar = "world", -- note the trailing comma
}

单行构造器中,花括号和赋值两侧要有空格

-- bad
local t = {foo="hello",bar="world"}

-- good
local t = { foo = "hello", bar = "world" }

遍历数组优先用 ipairs() 而非手写 for i = 1, #t,可读性更好:

-- bad
for i = 1, #t do
  ...
end

-- good
for _, v in ipairs(t) do
  ...
end

字符串(Strings)

  • 一律优先使用双引号(普通文件和 *_by_lua_block 指令内都是如此):
-- bad
local str = 'hello'

-- good
local str = "hello"
  • 字符串内含双引号时,优先用长括号字符串:
-- bad
local str = "message: \"hello\""

-- good
local str = [[message: "hello"]]
  • 拼接操作符 .. 两侧要有空格
-- bad
local str = "hello ".."world"

-- good
local str = "hello " .. "world"
  • 过长字符串要换行,并用 .. 连接:
-- bad
local str = "It is a very very very long string, that should be broken into multiple lines."

-- good
local str = "It is a very very very long string, " ..
            "that should be broken into multiple lines."

函数(Functions)

优先使用函数语法而非变量语法:

-- bad
local foo = function()

end

-- good
local function foo()

end

尽早校验、尽早返回:

-- bad
local function check_name(name)
  local valid = #name > 3
  valid = valid and #name < 30

  -- other validations

  return valid
end

-- good
local function check_name(name)
  if #name <= 3 or #name >= 30 then
    return false
  end

  -- other validations

  return true
end

遵循 Lua 的多返回值错误约定:可恢复错误用 nil + 错误描述字符串返回:

-- bad
local function check()
  local ok, err = do_thing()
  if not ok then
    return false, { message = err }
  end

  return true
end

-- good
local function check()
  local ok, err = do_thing()
  if not ok then
    return nil, "could not do thing: " .. err
  end

  return true
end

函数调用导致行宽超过 80 字符时,溢出的参数对齐到第一个参数

-- bad
local str = string.format("SELECT * FROM users WHERE first_name = '%s'", first_name)

-- good
local str = string.format("SELECT * FROM users WHERE first_name = '%s'",
                          first_name)

条件表达式(Conditional expressions)

避免单行条件,缩进子分支:

-- bad
if err then return nil, err end

-- good
if err then
  return nil, err
end

判断“是否赋值”时使用简写,除非你关心 nilfalse 的区别:

-- bad
if str ~= nil then

end

-- good
if str then

end

多分支且分支跨多行时,elseifelse 上方必须留空行;单行分支则不需要:

-- bad
if foo then
  do_stuff()
  keep_doing_stuff()
elseif bar then
  do_other_stuff()
  keep_doing_other_stuff()
else
  error()
end

-- good
if thing then
  do_stuff()
  keep_doing_stuff()

elseif bar then
  do_other_stuff()
  keep_doing_other_stuff()

else
  error()
end
--- good
if foo then
  do_stuff()
else
  error("failed!")
end

注意:若某分支较长,则所有分支(包括单行 else)都在其上方留空行。

当分支已经 return 时,不要再写后续分支,把其余逻辑写到父层级:

-- bad
if not str then
  return nil, "bad value"
else
  do_thing(str)
end

-- good
if not str then
  return nil, "bad value"
end

do_thing(str)

赋值或返回时,能用三元表达式提升可读性就用:

-- bad
local foo
if bar then
  foo = "hello"

else
  foo = "world"
end

-- good
local foo = bar and "hello" or "world"

表达式超宽时,续行对齐表达式开头:

-- bad
if thing_one < 1 and long_and_complicated_function(arg1, arg2) < 10 or thing_two > 10 then

end

-- good
if thing_one < 1 and long_and_complicated_function(arg1, arg2) < 10
   or thing_two > 10
then

end

调用 ngx.log() 传入变量时,优先用 vararg 风格,而不是 .. 拼接(后者在变量为 nil 时会抛异常):

-- bad
ngx.log(ngx.DEBUG, "if `my_var` is nil, this code throws an exception: " .. my_var)

-- good
ngx.log(ngx.DEBUG, "if `my_var` is nil, this code is fine: ", my_var)

十一、贡献前速查清单

把上述要点压缩成一张提交前 checklist:

  1. 分支名符合 feat/fix/tests/refactor/style/docs/chore/perf 前缀约定;
  2. 每个 commit 原子、message 为 type(scope): subject 格式,标题 ≤50 字符、body 每行 ≤72 字符、现在时、小写、无句号;
  3. make lint(Luacheck + 调试标记检查)通过;
  4. make test / make test-integration / make test-plugins 按变更范围通过,且补丁包含 busted 测试(断言用 assert.is_nil/assert.same 等规范写法);
  5. changelog/unreleased/kong/ 下按模板提交 YAML changelog 条目,且不动 CHANGELOG.md
  6. 热路径代码遵循 LuaJIT 守则(无全局变量、局部化缓存、预分配 table);
  7. 代码风格符合 80 列 / 2 空格缩进及本文第十节的各细则;
  8. 已 rebase 到基线分支,提交历史线性干净。

对于想深入了解开发环境搭建的读者,DEVELOPER.md 描述了从源码构建开发环境(make dev、Bazel venv、start_services 拉起数据库、kong migrations bootstrapkong start)的完整流程,本文的 lint 与测试目标均可在该环境下直接运行。

登录后查看全文
热门项目推荐
相关项目推荐