混合模式:控制平面、数据平面与配置同步
前置知识
- ›第 1–5 篇(完整架构解析,涵盖数据库层)
- ›了解 WebSocket 协议基础
- ›熟悉分布式系统概念(最终一致性)
混合模式:控制平面、数据平面与配置同步
生产环境中的 Kong 部署很少使用单节点。混合模式架构将职责明确分离:控制平面(CP)通过 Admin API 和数据库管理配置,而数据平面(DP)则使用从 CP 接收到的配置来代理流量。这种分离意味着数据平面无需连接数据库,可以运行在隔离的网络段中,也可以独立扩缩容。
本文将追踪 CP/DP 的完整通信链路——从初始化阶段的角色检测,到基于 WebSocket 的配置推送,再到更新的增量同步机制。
混合模式架构与角色检测
正如第 2 篇所介绍的,角色检测在 Kong.init() 的早期阶段完成,通过 第 201–218 行 的简单配置判断实现:
is_data_plane = function(config) return config.role == "data_plane" end
is_control_plane = function(config) return config.role == "control_plane" end
role 配置项(通过 KONG_ROLE 环境变量或 kong.conf 设置)决定了整个初始化路径的走向:
- 控制平面:连接 Postgres,运行 Admin API,跳过路由构建(不代理流量),为 DP 启动 WebSocket 服务器
- 数据平面:以 DB-less 模式运行(LMDB),跳过 Admin API,向 CP 发起 WebSocket 客户端连接,代理流量
- 传统模式:连接 Postgres,运行全部功能(Admin API + 代理)
集群模块在 Kong.init() 的 第 701–713 行 完成初始化:
if is_http_module and (is_data_plane(config) or is_control_plane(config)) then
kong.clustering = require("kong.clustering").new(config)
if config.cluster_rpc then
kong.rpc = require("kong.clustering.rpc.manager").new(config, kong.node.get_id())
if config.cluster_rpc_sync then
kong.sync = require("kong.clustering.services.sync").new(db, is_control_plane(config))
end
end
end
flowchart TD
subgraph "Control Plane"
A[Admin API] --> B[(PostgreSQL)]
B --> C[Config Export]
C --> D[WebSocket Server]
end
subgraph "Data Plane 1"
E[WebSocket Client] --> F[Declarative Config Loader]
F --> G[(LMDB)]
G --> H[Router + Plugins]
H --> I[Proxy Traffic]
end
subgraph "Data Plane 2"
J[WebSocket Client] --> K[Declarative Config Loader]
K --> L[(LMDB)]
L --> M[Router + Plugins]
M --> N[Proxy Traffic]
end
D <-->|mTLS| E
D <-->|mTLS| J
控制平面:通过 WebSocket 广播配置
控制平面模块在 kong/clustering/init.lua 的第 80 行完成实例化:
function _M:init_cp_worker(basic_info)
events.init()
self.instance = require("kong.clustering.control_plane").new(self)
self.instance:init_worker(basic_info)
end
kong/clustering/control_plane.lua 模块负责管理接受 DP 连接的 WebSocket 服务器。整体流程如下:
- DP 通过 WebSocket 连接到 CP 的集群监听端口
- CP 通过 mTLS 验证 DP 的客户端证书(
validate_client_cert,位于 第 57–62 行) - CP 检查 CP 与 DP 版本之间的插件/过滤器兼容性
- CP 导出当前配置,并将其压缩后作为 payload 发送
- 当配置发生变更(通过 Admin API 或数据库迁移)时,CP 将更新后的配置推送给所有已连接的 DP
导出过程会将数据库中的所有实体序列化为声明式配置格式,针对旧版 DP 进行兼容性转换,并使用 gzip 压缩 payload。第 67 行 的函数如下:
local function handle_export_deflated_reconfigure_payload(self)
local ok, p_err, err = pcall(self.export_deflated_reconfigure_payload, self)
return ok, p_err or err
end
CP 会与每个 DP 维持 ping/pong 心跳机制(间隔 30 秒,由 constants.lua 中的 CLUSTERING_PING_INTERVAL 定义)。若某个 DP 心跳超时,它将在 clustering_data_planes 实体中被标记为离线,可通过 Admin API 的 GET /clustering/data-planes 接口查看其状态。
sequenceDiagram
participant DP as Data Plane
participant CP as Control Plane
participant DB as PostgreSQL
DP->>CP: WebSocket connect + mTLS cert
CP->>CP: validate_client_cert()
CP->>CP: Check plugin compatibility
CP->>DB: Export all entities
CP->>CP: Serialize + gzip compress
CP->>DP: RECONFIGURE payload
DP->>DP: Apply declarative config
DP->>CP: PONG (heartbeat)
Note over CP: Admin API changes config
CP->>DB: Write changes
CP->>CP: Re-export config
CP->>DP: RECONFIGURE (updated)
DP->>DP: Rebuild router + plugins
数据平面:接收并应用配置
数据平面的逻辑位于 kong/clustering/data_plane.lua。DP 会创建一个 WebSocket 客户端,使用集群证书连接到 CP:
function _M.new(clustering)
local self = {
declarative_config = kong.db.declarative_config,
conf = clustering.conf,
cert = clustering.cert,
cert_key = clustering.cert_key,
}
return setmetatable(self, _MT)
end
当 DP 收到 RECONFIGURE 消息时,会依次执行以下步骤:
- 解压:通过
inflate_gzip解压 gzip payload - 解析:将 JSON 解析为 Lua 表
- 校验:对照声明式配置 schema 进行验证
- 加载:通过声明式配置 pipeline 写入 LMDB(与第 5 篇介绍的基于文件的 DB-less 配置使用同一套 pipeline)
- 重建:重建路由器与插件迭代器
kong/runloop/handler.lua 中的重配置处理器会记录耗时:
local reconfigure_time = get_monotonic_ms() - reconfigure_started_at
if ok then
log(INFO, "declarative reconfigure took ", reconfigure_time,
" ms on worker #", worker_id)
end
每个 worker 都会独立处理重配置事件。第 949 行 的 events.register_events(reconfigure_handler) 调用为 worker 事件注册了处理器——当某个 worker 通过 WebSocket 从 CP 收到配置后,会发布一个事件,触发所有 worker 同步重建。
提示: DP 会存储当前配置的哈希值,并与收到的 payload 进行比对。如果哈希一致,则跳过本次重配置,避免在 CP 推送相同配置时(例如 CP 重启后)触发不必要的路由重建。
RPC 框架与增量同步
传统的 CP→DP 同步存在一个局限:每次配置变更都会发送完整配置。对于拥有数千条路由的部署环境,每次变更都可能产生数兆字节的 payload。
Kong 在 kong/clustering/rpc/manager.lua 中引入了更新的 RPC 框架,基于 WebSocket 实现了 CP 与 DP 之间的双向通信,使用 JSON-RPC v2 协议。RPC manager 负责维护客户端连接并协商能力:
function _M.new(conf, node_id)
local self = {
clients = {},
client_capabilities = {},
node_id = node_id,
conf = conf,
cluster_cert = assert(clustering_tls.get_cluster_cert(conf)),
cluster_cert_key = assert(clustering_tls.get_cluster_cert_key(conf)),
callbacks = callbacks.new(),
}
基于 RPC 框架构建的增量同步系统位于 kong/clustering/services/sync/init.lua,支持基于差量的配置更新:
function _M.new(db, is_cp)
local strategy = strategy.new(db)
local self = {
db = db,
strategy = strategy,
rpc = rpc.new(strategy),
is_cp = is_cp,
}
if is_cp then
self.hooks = require("kong.clustering.services.sync.hooks").new(strategy)
end
return setmetatable(self, _MT)
end
同步系统在 CP 侧通过 DAO hook 追踪变更。每当某个实体被创建、更新或删除时,hook 便会记录一条 delta。DP 则定期调用 kong.sync.v2 RPC,仅拉取自上次已知版本以来的变更内容。
DP 侧的 第 41–80 行 注册了 RPC 就绪事件并开始同步:
worker_events.register(function(capabilities_list)
for _, v in ipairs(capabilities_list) do
if v == "kong.sync.v2" then
has_sync_v2 = true
break
end
end
end, "clustering:jsonrpc", "connected")
flowchart LR
subgraph "Full Sync (v1)"
A[CP] -->|Entire config payload| B[DP]
end
subgraph "Incremental Sync (v2/RPC)"
C[CP] -->|Delta: +route, -plugin, ~service| D[DP]
D -->|"kong.sync.v2 RPC: last_version=42"| C
end
增量同步由 cluster_rpc 和 cluster_rpc_sync 两个配置项控制。两者同时启用时,Kong 将使用 RPC 框架进行同步,而不再采用传统的全量配置推送方式。
安全性:平面间的 mTLS
CP 与 DP 之间的所有通信均通过双向 TLS 加密。cluster_cert 和 cluster_cert_key 配置项指定了用于客户端和服务端双向认证的证书。CP 会用自己的 CA 验证 DP 的证书,反之亦然。
验证逻辑位于 kong/clustering/init.lua:
function _M:validate_client_cert(cert_pem)
cert_pem = cert_pem or ngx_var.ssl_client_raw_cert
return validate_client_cert(self.conf, self.cert, cert_pem)
end
这种双向认证机制确保只有经过授权的数据平面才能从控制平面接收配置——这一点至关重要,因为配置中可能包含 API 密钥和上游凭证等敏感数据。
第 7 篇将探索 Kong 最新的重要子系统:AI 网关能力——它支持对来自多个提供商的 LLM 请求进行代理和转换。