主题
APISIX 生产落地实战:从 Spring Cloud Gateway 到 API 网关统一治理
作者:yanshaodong | 发布时间:2026-08-18 | 分类:中间件 / API 网关
前言
网关是微服务架构里绕不开的一环。它扛着所有流量的入口,一旦出问题,影响面就是整个系统。
我们团队的业务从单体拆成微服务后,最初用的是 Spring Cloud Gateway(下文简称 SCG)。它和 Spring 技术栈天然契合,起步够快,但运行一段时间后,几个问题逐渐暴露出来:配置要改就得重新发布、插件生态不够用、高并发下性能吃紧、多协议(gRPC/TCP)支持乏力。
最终我们把网关整体迁移到了 Apache APISIX。这篇文章不打算写成「APISIX 是什么」的入门科普,而是复盘一次真实的网关迁移全流程:为什么换、怎么部署、怎么迁移、怎么运维,以及我们踩过的坑。
如果你正在纠结网关选型,或者计划把 APISIX 跑上生产,这篇应该能帮到你。
一、为什么要动网关
先说说把我们「逼」到换网关的几个真实痛点。
1. 灰度发布与流量治理能力弱
业务迭代时最理想的上线方式是灰度发布:先放一小部分流量到新版本,观察稳定后再逐步放量。SCG 要支持灰度、金丝雀、按比例切流,原生能力有限,通常得结合 Nacos 权重、自定义负载均衡策略或额外 Filter 来实现,开发和维护成本都不低;限流、熔断等流量治理策略的精细化控制同样需要自研。
2. 插件生态不够用
SCG 的扩展靠 Filter,能力本身够用,但社区里现成的、可复用的插件相对有限。很多通用能力——比如细粒度的限流、请求改写、可观测性增强——都得自己写,维护成本高。
3. 性能有天花板
SCG 基于 WebFlux 非阻塞模型,单机性能其实不差,但在高并发场景下,JVM 的 GC 抖动、内存占用,以及和业务服务争夺资源的问题,让我们在做容量规划时越来越被动。
4. 多协议支持乏力
业务里逐渐出现了 gRPC 内部调用、TCP 长连接等诉求,SCG 主要面向 HTTP,这些场景要另起炉灶。
这些问题叠加起来,让我们下定决心换一个「流量治理能力强、插件丰富、性能更强、多协议」的网关。
二、网关选型:为什么是 APISIX
我们把市面上主流的几款网关放到一起对比:
| 维度 | Nginx / OpenResty | Kong | Spring Cloud Gateway | APISIX |
|---|---|---|---|---|
| 动态配置 | 需 reload 或自研 | 支持(PostgreSQL) | 需刷新(配合配置中心) | 支持(etcd,实时生效) |
| 灰度/流量治理 | 自研 | 有限 | 自研 | traffic-split 等插件开箱即用 |
| 插件体系 | 自研 Lua/模块 | 插件丰富 | Filter 机制 | 80+ 内置插件 |
| 多协议 | HTTP/TCP | HTTP/TCP | 主要 HTTP | HTTP/gRPC/TCP/UDP/MQTT |
| 性能 | 高(基准) | 较高 | 中(JVM 开销) | 高(OpenResty) |
| 配置存储 | 文件 | PostgreSQL | 配置中心/Nacos | etcd |
| 社区与生态 | 成熟 | 成熟 | Spring 生态 | Apache 顶级项目,活跃 |
| 运维成本 | 中 | 高 | 中 | 低 |
选型结论
APISIX 在几个关键维度上恰好命中我们的诉求:
- 流量治理与动态配置:灰度发布、金丝雀、按比例切流通过
traffic-split等插件开箱即用,配置写入 etcd 后秒级生效、无需重启——这是打动我们的第一点。 - 插件生态:80+ 内置插件覆盖认证、限流、熔断、灰度、可观测性等,大部分需求开箱即用。
- 性能:基于 OpenResty(Nginx + LuaJIT),单核即可扛住很高的 QPS,且不依赖 JVM,资源占用可控。
- 技术栈契合:我们已有 Docker Swarm 编排 + etcd + Nacos 的基础设施,APISIX 的依赖正好落在现有体系内,不需要额外引入新组件。
补充一句:APISIX 由支流科技(API7.ai)开源,2019 年进入 Apache 孵化器,2020 年毕业成为 Apache 顶级项目,社区活跃度是选型时的加分项。
三、APISIX 架构与核心概念速览
理解 APISIX 的架构,是后面部署和运维的基础。它由三层组成:
核心概念
| 概念 | 说明 |
|---|---|
| Route(路由) | 匹配规则 + 目标,是 APISIX 的核心。包含匹配条件(uri、host、method 等)和绑定的插件 |
| Upstream(上游) | 一组上游服务节点 + 负载均衡算法(轮询、一致性哈希等) |
| Service(服务) | 上游的抽象,路由可以指向 Service,Service 再指向 Upstream,便于批量管理 |
| Consumer(消费者) | API 的调用方,用于认证、限流等场景,通常配合 key-auth、jwt-auth 等插件 |
| Plugin(插件) | 功能扩展单元,可作用于全局 / 路由 / 服务 / 消费者 |
etcd 数据模型
APISIX 的所有配置(路由、上游、插件、消费者等)都以 key-value 形式存储在 etcd 中。例如一条路由对应 /apisix/routes/{id} 这样的 key。APISIX 通过 etcd 的 watch 机制监听配置变更,一旦有更新,数据面会实时加载新配置,全程无需 reload。这正是「动态配置」的底层原理。
这也意味着:etcd 的可用性直接决定了 APISIX 的可用性,所以生产部署时 etcd 必须做高可用。
四、生产部署:Docker Swarm 下的高可用
我们的生产环境用 Docker Swarm 做容器编排,APISIX 和 etcd 都以 Swarm Service 的形式部署。
部署拓扑
关键点:APISIX 无状态,随流量水平扩展;etcd 有状态,必须集群化并保证数据不丢。我们的三台节点 gateway-101/102/103 上,各部署一个 APISIX 实例和一个 etcd 节点,组成 3 节点 etcd 集群。同机部署简化了拓扑,同时 etcd 的 3 个奇数节点正好满足 Raft 选举的多数派要求。
docker-compose.swarm.yml 关键配置
yaml
version: "3.8"
services:
apisix:
image: apache/apisix:3.15.0-debian
deploy:
replicas: 3
restart_policy:
condition: any
update_config:
parallelism: 1
delay: 10s
ports:
- "9080:9080" # HTTP
- "9443:9443" # HTTPS
environment:
- TZ=Asia/Shanghai
volumes:
# 挂载主配置(etcd 地址、admin key 等)
- ./apisix/config.yaml:/usr/local/apisix/conf/config.yaml:ro
# 日志目录持久化
- apisix-logs:/usr/local/apisix/logs
networks:
- apisix-net
healthcheck:
test: ["CMD", "curl", "-f", "http://127.0.0.1:9080/healthcheck"]
interval: 10s
timeout: 5s
retries: 3
etcd:
image: bitnami/etcd:3.5
deploy:
replicas: 3
placement:
# 固定 etcd 到指定节点,保证数据目录稳定
constraints: [node.labels.etcd == true]
environment:
- ETCD_NAME=etcd-{{.Task.Slot}}
- ETCD_ADVERTISE_CLIENT_URLS=http://etcd:2379
- ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379
- ETCD_INITIAL_CLUSTER_TOKEN=apisix-etcd-cluster
- ETCD_INITIAL_CLUSTER=etcd-1=http://etcd-1:2380,etcd-2=http://etcd-2:2380,etcd-3=http://etcd-3:2380
- ETCD_INITIAL_CLUSTER_STATE=new
- ETCD_DATA_DIR=/bitnami/etcd/data
volumes:
- etcd-data:/bitnami/etcd/data
networks:
- apisix-net
volumes:
apisix-logs:
etcd-data:
networks:
apisix-net:
driver: overlay说明:etcd 集群在 Swarm 里做高可用需要配合
placement.constraints固定节点、持久化数据卷,并正确配置ETCD_INITIAL_CLUSTER。这是部署里最需要细心的一环,后面「踩坑复盘」会展开讲。生产环境的完整编排还包含 Nacos、证书和多环境隔离,这里只给出核心骨架。
APISIX 主配置 config.yaml 关键项
yaml
apisix:
node_listen: 9080 # HTTP 监听端口
enable_ipv6: false
enable_control: true
deployment:
role: traditional # 使用 etcd 存储配置
role_traditional:
config_provider: etcd
etcd:
host: # etcd 集群地址
- "http://etcd:2379"
prefix: "/apisix" # 配置 key 前缀
timeout: 30
# 插件加载配置(按需启用,控制资源占用)
plugins:
- real-ip
- prometheus
- limit-req
- limit-count
- skywalking
- traffic-split
- jwt-auth
- key-auth运维细节
- 健康检查探针:APISIX 的
/healthcheck接口默认不经过认证,适合做容器探针。我们用它做 Swarm 的 healthcheck,异常时自动重建实例。 - 日志双路输出:APISIX 默认写文件,容器化场景下还要同时输出到 stdout,方便日志采集。通过
error_log和access_log配置/dev/stdout与文件双写(具体见下文踩坑第 3 条)。 - 无状态水平扩展:只要 etcd 地址配置正确,新增 APISIX 实例无需任何额外动作,Swarm 扩容后自动加入集群。
五、网关迁移实战
迁移的核心思路是灰度切流 + 可快速回滚,绝不一次性全量切换。
1. 路由迁移:SCG → APISIX
SCG 的路由是 YAML/Java 配置,迁移到 APISIX 后变成 etcd 中的动态配置,通过 Admin API 或声明式 YAML 管理。
SCG 原始配置:
yaml
spring:
cloud:
gateway:
routes:
- id: order-service
uri: lb://order-service
predicates:
- Path=/api/order/**迁移后的 APISIX 路由(Admin API 方式):
bash
curl http://127.0.0.1:9180/apisix/admin/routes/order-service \
-H "X-API-KEY: ${ADMIN_KEY}" \
-X PUT -d '
{
"uri": "/api/order/*",
"upstream": {
"type": "roundrobin",
"nodes": {
"order-service:8080": 1
}
}
}'迁移时建议先用脚本批量把 SCG 路由导出,再转成 APISIX 的路由配置,避免手抄出错。
2. 认证鉴权迁移
SCG 里的鉴权 Filter,迁移到 APISIX 对应插件:
| SCG 能力 | APISIX 插件 |
|---|---|
| JWT 校验 | jwt-auth |
| API Key / AppKey | key-auth |
| Basic Auth | basic-auth |
| MD5 签名校验 | 自定义插件(或结合 body-transformer) |
| IP 白名单 | ip-restriction |
我们内部用 MD5 签名鉴权(AppKey + 时间戳 + 签名),APISIX 没有现成插件,就基于 Lua 写了一个自定义插件。自定义插件开发会在下文高级能力章节提一下。
3. 灰度切流与回滚
切流策略:先在 APISIX 建好全部路由 → 用 Swarm 的负载均衡/或 DNS 层把少量流量切到 APISIX → 观察监控指标 → 逐步放量 → 稳定后下线 SCG。
bash
# 灰度期间可随时回滚:把流量切回 SCG 即可,APISIX 侧无需改动
# APISIX 侧用 traffic-split 插件按比例切流到不同上游
curl http://127.0.0.1:9180/apisix/admin/routes/order-service \
-H "X-API-KEY: ${ADMIN_KEY}" -X PATCH -d '
{
"plugins": {
"traffic-split": {
"rules": [
{ "weighted_upstreams": [ { "upstream_id": 1, "weight": 10 } ] }
]
}
}
}'回滚的底气在于:APISIX 配置在 etcd 里,改配置秒级生效,且可以用 Admin API 快速还原。这是动态配置带来的最大红利。
六、可观测性建设
网关是可观测性的关键节点,我们搭了「Prometheus + SkyWalking + Grafana」组合。
1. Prometheus 指标采集
APISIX 通过 prometheus 插件暴露 /apisix/prometheus/metrics 指标,Prometheus 抓取即可。
关键点——Swarm 服务发现:Swarm 里服务是动态调度的,IP 不固定,我们用 Prometheus 的 dns_sd_configs 做服务发现,靠 Swarm 内置 DNS 把服务名解析成实例 IP。
yaml
# prometheus.yml
scrape_configs:
- job_name: 'apisix'
dns_sd_configs:
- names:
- 'tasks.apisix' # Swarm 服务名,解析到所有副本
type: 'A'
port: 9091
refresh_interval: 15s
metrics_path: /apisix/prometheus/metrics
relabel_configs:
# 用 job label 标识服务,便于 Grafana 聚合筛选
- source_labels: [__meta_dns_name]
target_label: service
dns_sd_configs是我们在 Swarm 环境里的关键技巧:不用手工维护 IP 列表,服务扩缩容后 Prometheus 自动发现新实例。
2. SkyWalking 链路追踪
APISIX 的 skywalking 插件把网关作为链路的起点,将 trace 信息透传给下游服务,形成完整链路。只需在 config.yaml 的 plugins 里启用,并配置 SkyWalking OAP 地址。
yaml
plugins:
- skywalking
plugin_attr:
skywalking:
service_name: apisix
service_instance_name: apisix-instance-{{.Task.Slot}}
endpoint_addr: http://skywalking-oap:128003. Grafana 监控大盘
Grafana 接入 Prometheus 数据源后,导入 APISIX 官方 Dashboard(或自建面板),重点监控:QPS、平均/分位延迟、错误率、上游健康状态、etcd 延迟。
七、高级能力(简要)
生产上用到的几个进阶能力,这里点到为止,后续可单独展开。
- 限流熔断:
limit-req(请求数限流)、limit-count(计数限流)、limit-conn(并发限流)、api-breaker(熔断)。 - 灰度发布:
traffic-split插件按权重切流,配合 canary 实现金丝雀发布。 - 多协议:gRPC、TCP/UDP 代理,APISIX 天然支持。
- 自定义插件:基于 Lua 编写,我们用它实现了 MD5 签名鉴权,开发成本远低于 SCG 写 Java Filter。
八、踩坑复盘
这是本文最想分享的部分。迁移过程中踩过的坑,整理成表:
| # | 坑 | 现象 | 根因 | 解决 |
|---|---|---|---|---|
| 1 | etcd 数据丢失 | 重启后路由全部丢失,网关 404 | etcd 数据卷未持久化到稳定存储 | etcd 挂持久化卷 + 定期备份 etcd 快照 |
| 2 | 配置热更新失效 | 改了路由不生效,必须重启 | etcd watch 连接断开后未重连 | 检查 etcd 集群稳定性,APISIX 与 etcd 网络链路 |
| 3 | 容器日志丢失 | 容器重建后日志没了 | 日志只写文件,未输出 stdout | access_log/error_log 同时写文件 + /dev/stdout |
| 4 | worker 数不合理 | 高并发下 CPU 打不满 | worker_processes 未按核数配置 | 设为 auto,按容器核数自动分配 |
| 5 | 探针被打认证拦截 | healthcheck 一直失败触发重启 | 探针路径未排除认证 | 使用 /healthcheck 或配置探针路径跳过认证 |
| 6 | X-Forwarded-Proto 被覆盖 | 后端收到 secure=false,HTTPS 场景静默降级为 http | CLB 终结 TLS 后明文转发,APISIX 模板层用 $scheme 覆盖协议头,且 trusted_addresses 未匹配实际网络拓扑 | 按网络模式配置 trusted_addresses / real_ip_from,使来源 IP 命中信任网段 |
6. X-Forwarded-Proto 被覆盖导致 HTTPS 静默降级
这是一次线上 HTTPS 域突然全部 secure=false 的排查复盘,根因不在证书、不在后端,而在 APISIX 对 X-Forwarded-Proto 头的处理机制与网络拓扑不匹配。
现象
- 生产 HTTPS 域名访问某授权服务,后端收到的
X-Forwarded-Proto始终为http,导致secure=false; - 同链路、同应用、同配置,测试环境却正常(
secure=true); - 仅环境不同 → 环境差异导致行为分裂。
机制:新版 APISIX 的两段式处理
当 CLB(或 Nginx)终结 TLS、以 HTTP 明文转发到 APISIX 的 9080 端口时,APISIX 侧的 $scheme 变量为 http。APISIX 新版(sanitize X-Forwarded-* 头,官方 PR #13803)对 X-Forwarded-Proto 的处理是两段式:
① nginx 模板层(无条件清洗)
more_set_input_headers "X-Forwarded-Proto: $scheme";
→ 先强制替换为 $scheme(http),覆盖上游(如 CLB)传来的 https
② Lua 层(按信任判断恢复)
trusted_addresses_util.is_trusted(realip_remote_addr)
→ 来源可信时,恢复客户端/上游传入的 https
→ 来源不可信时,保持 $scheme(http)也就是说:新版 APISIX 默认不信任任何外部传入的 X-Forwarded- 头*,必须显式在 trusted_addresses 中声明可信来源,否则 HTTPS 场景会静默降级为 http。
根因:信任网段没匹配实际网络拓扑
realip_remote_addr(APISIX 眼中请求的真实来源 IP)取决于 Docker 网络模式,不同模式下「可信对象」完全不同:
| 网络模式 | CLB→APISIX 连接 | APISIX 眼中 realip_remote_addr | 应信任的网段 |
|---|---|---|---|
| host 网络 | host 直连,无 NAT | CLB 真实回源 IP(如 10.10.0.0/16 示意) | CLB 回源网段 |
| bridge 网络 | 经 docker0 网关(172.17.0.1)NAT | docker 网关 172.17.0.1 | docker 网关网段(如 172.16.0.0/12 示意) |
| Swarm ingress | 经 ingress 网关(10.255.0.2 示意)NAT | ingress 网关 | ingress 网段(如 10.255.0.0/16 示意) |
上表网段均为 示意,请以你环境
docker network inspect/ access log 中$realip_remote_addr的实际值为准。
失败链路:host 网络 → realip_remote_addr = CLB 回源 IP → 不在 trusted_addresses(若只配了 127.0.0.1 或过小网段)→ 判定不可信 → https 被丢弃,保持 http → secure=false。而测试用 bridge 网络,来源恰好命中 docker 网关网段,于是「碰巧」可信、https 被恢复。
修复:按网络模式配置信任网段
核心原则:trusted_addresses 与 nginx_config.http.real_ip_from 必须一致,且覆盖 APISIX 实际看到的来源 IP。
host 模式(信任 CLB 回源网段):
yaml
apisix:
node_listen: 9080
trusted_addresses:
- 127.0.0.1
- 10.10.0.0/16 # 示意:host 模式下信任 CLB 回源网段
nginx_config:
http:
real_ip_from:
- 127.0.0.1
- 10.10.0.0/16 # 与 trusted_addresses 保持一致bridge 模式(信任 docker 网关网段):
yaml
apisix:
node_listen: 9080
trusted_addresses:
- 127.0.0.1
- 172.16.0.0/12 # 示意:bridge 模式下信任 docker 网关网段
nginx_config:
http:
real_ip_from:
- 127.0.0.1
- 172.16.0.0/12Swarm ingress 模式(信任 ingress 网关网段):
yaml
apisix:
node_listen: 9080
trusted_addresses:
- 127.0.0.1
- 10.255.0.0/16 # 示意:Swarm ingress routing mesh 网关网段
nginx_config:
http:
real_ip_from:
- 127.0.0.1
- 10.255.0.0/16备选方案(不推荐作为长期方案):在路由级
proxy-rewrite插件里set强制X-Forwarded-Proto: https。优点是不动全局配置,缺点是硬编码 https,一旦该域出现 HTTP 入口会被误判;且只解决协议头、不解决真实客户端 IP(限流/审计/白名单仍受损)。
验证方法
- 修复前佐证:查看 APISIX access log 中
$realip_remote_addr,若显示的是 CLB/网关 IP 而非真实用户 IP,即信任判断失效的直接证据; - 修复后验证:
- 访问 HTTPS 域名,后端收到的
X-Forwarded-Proto应为https(对应secure=true); - access log 中
$realip_remote_addr恢复为真实客户端 IP; - 顺带确认
limit-req/limit-count/ip-restriction等依赖真实 IP 的功能按预期生效。
- 访问 HTTPS 域名,后端收到的
预防建议
- 信任网段必须匹配实际网络拓扑:host / bridge / ingress 三种模式的可信对象不同,不能套用同一网段,须以 access log 中
$realip_remote_addr实际值为准; - 配置注释必须与实际一致:注释写「最小权限 10.10.61.0/24」、实际配成
10.0.0.0/24会误导排查,且过小网段根本覆盖不到 CLB 回源网段; - X-Forwarded-Proto 失效的连带影响:限流、审计、IP 白名单等依赖真实 IP 的功能会同时受损,排查时一并检查;
- 迁移网络模式时同步改信任网段:生产由 host 升级为 Swarm ingress 后,来源 IP 从 CLB 回源 IP 变为 ingress 网关 IP,信任网段必须相应调整,否则老问题复发。
九、总结与展望
迁移收益
- 流量治理开箱即用:灰度发布、金丝雀、按比例切流通过插件即可实现,不再需要自研。
- 性能提升:网关层资源占用下降,容量规划更从容。
- 能力补齐:限流、灰度、多协议、可观测性开箱即用,少写了很多自研代码。
- 运维简化:无状态水平扩展 + 动态配置,日常变更风险大幅降低。
后续规划
- 深化多协议(gRPC/TCP)网关能力。
- 自研更多业务插件,沉淀团队网关中台。
- 探索 APISIX 与服务网格(Istio)的结合。