API 网关:Kong 与 APISIX
如果说服务网格管的是"服务之间怎么说话"(东西向流量),那 API 网关管的是"外面的人怎么进来"(南北向流量)。它是所有外部请求踏入你系统的第一道门,也常常是最后一道——认证、限流、路由、灰度、协议转换、审计日志,全在这一层做。这一章讲两个最主流的选择:Kong(生态最成熟)和 APISIX(国内团队主导、配置热更新最快),外加限流和鉴权的具体实现。
15.1 网关到底该干什么:先划清职责边界
| 能力 | 说明 |
|---|---|
| 路由 Routing | 把 /api/order/** 转到订单服务,支持按路径、Host、Header、Method、Query 匹配。这是最基本的能力。 |
| 认证鉴权 AuthN | 校验 JWT 签名、API Key、OAuth2 / OIDC,把解析出的用户身份透传给下游(通常是 header)。注意:网关做的是"认证",业务授权(这个用户能不能改这个订单)必须在服务里做。 |
| 限流 Rate Limiting | 按 IP / 用户 / API / 租户限流,保护后端。单机限流 + 分布式限流两种,见 15.5。 |
| 灰度与流量染色 | 按 header、用户 ID、百分比把流量导向新版本——和 Istio 的金丝雀是同一类需求,只是发生在入口。 |
| 协议转换 | 外部 REST/JSON ↔ 内部 gRPC、WebSocket 代理、HTTP/2。让内部服务不必迁就外部协议。 |
| 请求/响应改写 | 加/删 header、重写路径、统一包装响应格式({code,msg,data})、给下游带 trace id。 |
| 可观测与审计 | 统一的访问日志、耗时统计、错误率;对外 API 的调用审计(谁在什么时候调了哪个接口)。这是网关最高价值的能力之一。 |
| 不该做的 | 业务逻辑(订单状态机、金额计算)、复杂的数据聚合、长耗时的处理。网关是"薄"的一层,塞业务进去会让它变成瓶颈和发布风险源。 |
论有了 Nginx / Ingress,为什么还要 API 网关
Nginx 是极其优秀的反向代理,做静态托管、简单负载均衡、TLS 终止,它几乎是最好的选择。但它的短板在于"动态"和"可编程":
① 配置更新要 reload。Nginx 改配置需要 reload(虽然平滑,但频率高了仍有代价),在"每天上百次路由变更"的微服务环境里不够顺滑。
② 没有"API 生命周期"的概念。Nginx 只知道 server/location;网关知道"API、消费者、订阅、插件、配额",能回答"哪个应用调了哪些 API、用了多少配额"。
③ 鉴权/限流/配额不是一等公民。Nginx 也能用 Lua(OpenResty)写,但那基本等于自己造一个网关。Kong 和 APISIX 本质就是"用 OpenResty 造好的、带插件生态的网关"。
结论:K8s 内部简单暴露服务用 Ingress(或 Gwy API)就够了;一旦需要面向外部提供 API(认证、配额、计费、审计、多租户),就该上 API 网关。两者不冲突,常常是"网关在最外层 + Ingress 在里面"的叠加结构。
15.2 Kong:插件生态最成熟的网关
Kong 基于 Nginx + OpenResty(Lua),2024 年起把核心逐步迁移到 Rust 写的 OpenResty 替代品上(版本以官网最新稳定版为准),但对象模型和用法一直很稳定。它的核心是五个对象,理解这五个就理解了 Kong。
| 对象 | 含义 |
|---|---|
| Service | 对上游服务的抽象(上游地址、协议、超时)。可以理解成"我要代理的那个后端"。 |
| Route | 匹配规则:什么样的请求(路径/Host/方法/Header)交给哪个 Service。 |
| Upstream + Target | 上游的负载均衡池:多个 Target(真实实例 IP:Port),Upstream 负责健康检查和均衡策略。 |
| Consumer | 调用方身份(一个应用/一个租户/一个开发者),用来挂配额和鉴权凭据。 |
| Plugin | 插件,可以挂在全局 / Service / Route / Consumer 四个层级上,粒度从粗到细。这是 Kong 的灵魂。 |
用 Admin API 建一条完整的 API(生产建议用声明式配置 + decK / GitOps,见下)
更好的做法:声明式配置 + GitOps(配置进 Git,变更走评审)
15.3 APISIX:配置热更新最快的网关
APISIX 同样基于 OpenResty,但架构上有一个关键差别:它把配置放在 etcd 里,而不是数据库 + 定时同步。所有节点 watch etcd,配置变更毫秒级生效,无需 reload。这是它在国内云原生圈流行的核心原因之一。(版本以官网最新稳定版为准;插件数量已超过 100 个,并支持 Wasm/Java/Python 等多语言插件。)
论为什么"etcd + 全内存路由树"能带来毫秒级生效
① 配置存在 etcd 里,而不是网关自己的数据库。etcd 天然支持 watch(长连接监听变化)和强一致,多个网关节点订阅同一份配置,不会出现各节点配置不一致。
② 路由匹配用 radix tree(基数树),并且整棵树常驻内存。配置一变,只需在内存里重建树(毫秒级),没有磁盘 IO,也不需要重启 worker。
③ 对比 Kong 传统模式(配置在 Postgres/Cassandra,节点定期轮询数据库同步,默认几分钟),APISIX 的变更实时性明显更好。Kong 现在也可以用 DB-less + declarative config 达到近似效果,但那份配置需要你自己推送到每个节点。
代价:APISIX 强依赖 etcd。etcd 挂了,网关还能继续用已有配置转发流量,但你改不了配置(新上线、限流调整都做不了)。所以 etcd 必须三节点集群部署,这是上 APISIX 时必须一并规划的事。
APISIX 的核心配置:Upstream、Route、插件(Admin API 写法)
① 网关自己成了单点。所有流量都过网关,网关一挂全站不可用。必须多实例 + 前面挂 SLB/Keepalived + 健康检查,并且网关实例要能随时被替换(无状态,配置来自 etcd / 声明式文件)。
② 配置下发"以为生效了"。Admin API 返回 200 只代表"配置写入成功",不代表"这个节点的 worker 已生效"。验证方法:用业务请求打一遍,或用 prometheus 指标 / 日志确认命中了新路由。Kong 用 DB 模式时还要注意节点同步间隔。
③ 插件里写了阻塞操作。网关是所有请求的必经之路,插件里一个 sleep、一次同步 HTTP 调用、一次慢查询,会把整个网关的吞吐拖垮。自定义插件里禁止同步阻塞,必须用 cosocket 做异步 IO。
④ 限流键选错了。只按 remote_addr 限流,在 NAT 出口或 CDN 后面等于把一整个公司/城市当成一个用户限。要按真实用户(JWT 里的 sub / UID),并让 CDN 传真实 IP 头(X-Forwarded-For),同时注意这个头必须由可信代理设置,否则能被伪造。
⑤ 请求体大小和超时没限制。攻击者上传一个 2GB 的 body,或者开一堆慢连接,网关的连接池和内存会被吃光(Slowloris)。必须设置 client_max_body_size、client_body_timeout、总超时,并开启连接数限制。
15.4 Kong 与 APISIX 怎么选
| 维度 | Kong | APISIX | Nginx / OpenResty | 云厂商网关 |
|---|---|---|---|---|
| 底座 | OpenResty(Lua)为主,部分组件 Rust | OpenResty(Lua)+ etcd | 纯 Nginx + 自写 Lua | 托管服务 |
| 配置存储 | Postgres / Cassandra / 声明式文件(DB-less) | etcd(watch 实时同步) | 配置文件(reload 生效) | 控制台 / OpenAPI |
| 配置生效速度 | DB 模式秒到分钟级;DB-less 需推送 | 毫秒级(无需 reload) | reload 生效(平滑但有开销) | 秒级 |
| 插件生态 | 最成熟,商业插件丰富,文档/社区大 | 数量多(100+),国内文档友好,支持多语言插件 | 等于自己开发 | 看厂商 |
| 运维复杂度 | 中(要养 DB 或做 DB-less) | 中(要养 etcd 集群) | 低(但功能要自己写) | 最低(但贵、可定制性差) |
| 适合谁 | 需要成熟商业支持、插件市场、企业级 API 管理 | K8s 环境、要求配置实时生效、国内团队 | 简单反向代理、静态+少量动态 | 不想运维、预算充足、需求标准 |
选三条判断
① 已经是 K8s 原生环境、追求配置秒级生效 → APISIX(它和 K8s Ingress/Gateway API 的集成很自然,etcd 本来就是 K8s 的底座)。
② 需要成熟的插件市场、商业支持、复杂的 API 生命周期管理(开发者门户、订阅、计费)→ Kong。它的企业版在这方面做得最深。
③ 需求真的只是"转发 + 一点点重写" → 别上网关,Nginx 或 K8s Gateway API 就够。网关的价值来自"我要对 API 做治理",没有这个需求它就是多一个要维护的组件。
15.5 限流:四种算法和一次正确的落地
| 算法 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| 固定窗口计数 | 每个时间窗口一个计数器,超过阈值拒绝,窗口结束清零 | 最简单、内存最小(一个计数) | 临界问题:窗口交界处可能放过接近 2 倍的请求(59 秒来 100 个 + 61 秒来 100 个) |
| 滑动窗口 | 把窗口切成小格(如 1 秒 10 格),统计最近 N 格的请求数 | 平滑、精度可控 | 实现稍复杂,需要存多格计数 |
| 漏桶 Leaky Bucket | 请求先进桶,以恒定速率流出,桶满则拒绝 | 输出速率绝对平滑,适合保护下游 | 无法应对合理突发(明明有余量也不让过) |
| 令牌桶 Token Bucket | 以固定速率往桶里放令牌,桶容量=允许的突发量,有令牌才能过 | 既能限平均速率,又允许突发,最常用 | 需要维护令牌数(有状态) |
分布式限流的正确姿势:Redis + Lua 令牌桶(原子、跨节点一致)
① 单机限流当成分布式限流。网关有 5 个实例,每个都配"100/秒",实际放过去的是 500/秒。分布式限流必须把计数放到 Redis/etcd,或者用"总配额 ÷ 实例数"的静态分配(简单但会被流量倾斜打偏)。
② 限流维度过粗。只有"全局限流"的话,一个大客户就能把配额吃光;只有"IP 限流"的话,NAT 后面的用户互相伤害。实践是分层限流:全局 → 租户/用户 → 单接口 → 单 IP。
③ 只限流量,不限并发。限流(QPS)保护的是吞吐,并发连接数限制保护的是连接资源。慢请求攻击(Slowloris)QPS 很低但会占满连接,必须配 limit-conn / 网关的并发上限。
④ Redis 挂了限流就"全放行"。要明确降级策略:放行(保护可用性,但可能压垮后端)还是拒绝(保护后端,但影响所有用户)?大多数业务选择"放行 + 告警",但核心接口宁愿拒绝。这个决定必须提前做,不能临场拍脑袋。
⑤ 通过 X-Forwarded-For 取客户端 IP 时不做校验。这个头是客户端可以伪造的,攻击者每次换一个值就能绕过基于 IP 的限流。必须只信任你前面的代理设置的最后一跳地址(Nginx 的 set_real_ip_from / 网关的 trusted proxies 配置)。
15.6 鉴权:网关该做到哪一步
| 方式 | 怎么做 | 适合 | 注意 |
|---|---|---|---|
| API Key | 调用方在 header 里带固定 key,网关查库/缓存校验 | 内部服务、简单的第三方接入 | key 泄漏即失守,要能随时吊销;不要放在 URL 里(会进日志、被浏览器历史记录) |
| JWT 校验 | 网关用公钥(JWKS)验签,解析出 sub/roles 并通过 header 透传给下游 | 用户态 API,前后端分离的标准做法 | JWT 一旦签发就无法主动失效(除非维护黑名单)。所以 TTL 要短 + 配 refresh token;敏感操作要在服务侧再校验一次 |
| OAuth2 / OIDC | 走标准授权码流程,网关校验 access token(通常也是 JWT) | 面向第三方开放平台、SSO | 要处理 token 的签发、刷新、撤销;scope 到具体接口的映射要清晰 |
| HMAC 签名 | 用密钥 + 时间戳 + 请求体算签名,网关验签并校验时间窗口 | 开放 API(支付回调、金融接口) | 必须带时间戳 + nonce 防重放,否则抓到报文就能无限重放。密钥分发和轮换要有机制 |
一条完整的"网关认证 + 身份透传"链路(这是最实用的模式)
① 把"网关认证"当成"业务授权"。网关确认了"这是用户 1001 的合法 token",但"用户 1001 能不能看订单 9999"必须由业务服务判断。只做网关鉴权的系统,只要把 URL 里的订单号改一下就能看到别人的订单——这就是最典型的越权漏洞(IDOR)。网关解决"身份",服务解决"权限",两者缺一不可。
② 信任下游可以直接伪造的 header。如果网关通过 X-User-Id 传身份,那么任何人都能自己造一个 X-User-Id: 1001 打到服务上——除非你保证"服务只能被网关访问"(网络隔离 + 只监听内网 + 服务网格里校验来源身份)。正确做法:网关在校验后覆写这些 header(而不是追加),并在服务侧把"直接暴露公网"这条路径彻底堵死。
① API 网关管南北向(外部怎么进来),服务网格管东西向(服务之间怎么说话)。职责不同,常同时存在。
② 网关该做:路由、认证、限流、灰度、协议转换、改写、审计;不该做:业务逻辑、复杂聚合、长耗时操作。
③ Kong 生态最成熟,对象模型清晰(Service/Route/Upstream/Consumer/Plugin),适合企业级 API 管理;APISIX 用 etcd 做配置中心,配置毫秒级生效,适合 K8s 环境,但 etcd 必须三节点高可用。
④ 限流选令牌桶(限平均速率又能应对突发);分布式限流必须把计数放 Redis/etcd,单机限流 × N 个实例 = 实际放行 N 倍。别忘了同时限并发。
⑤ 鉴权在网关,授权在服务。网关只回答"你是谁","你能不能做这件事"必须由业务判断,否则就是越权漏洞。
⑥ 上线前必须演练:网关多实例、配置变更可验证、限流降级策略已定、请求体大小与超时已设。这四件事没做,网关就是全站最大的风险点。