Apache APISIX:从 etcd 路由实验到集群发布治理
发布平台把一条 APISIX Route 更新为 GET,页面提示成功,随后 POST 请求开始返回 404。工程师只看了更新请求的 2xx,没有比较更新前后的对象:对 Route 做字段 PATCH 会保留其他字段,但对子路径或数组 PATCH 会替换那一整段。管理请求成功只能证明写入被接受,不能证明目标对象仍表达原来的意图。
另一个现场里,两个 APISIX 实例都健康,只有一部分请求返回 503。Route 引用了已被清理的 Plugin Config;etcd 仍把 Route 正常同步到每个实例,真正失败发生在请求装配插件配置时。APISIX 的可靠性来自一条完整证据链:管理身份写入对象,etcd 保存 revision 并通过 watch 分发,数据面在内存中构造路由与插件,真实请求再命中某个 Upstream。任何一段都不能用前一段的成功代替。
一条请求如何变成一次上游调用
Route 负责匹配客户端请求并选择插件与转发目标;Service 抽取多个 Route 共用的 API 配置;Upstream 保存节点、负载均衡、重试、超时和健康检查;Plugin Config 复用一组插件配置。Upstream 可以直接内嵌在 Route/Service,也可以作为独立对象由 upstream_id 引用。Route 自己的 Upstream 配置优先于 Service 上的配置,排障时必须检查最终合并结果,而不是只查看 Service。
插件可挂在 Route、Service、Consumer、Consumer Group、Plugin Config 或 Global Rule。非全局对象上的同名插件不是简单叠加,最终只保留优先级最高的一份;Global Rule 与局部同名插件则都会执行。插件在 rewrite、access、before_proxy、响应过滤和 log 等阶段运行,执行顺序取决于插件阶段与优先级,不取决于 JSON 字段书写顺序。
APISIX 常见状态模式如下:
| 模式 | 状态位置 | 发布方式 | 数据面行为 | 主要风险 |
|---|---|---|---|---|
| traditional | etcd;APISIX 同时承担管理与代理角色 | Admin API 写 etcd | watch 变更并更新内存路由 | Admin 面与流量面故障域相近 |
| decoupled | control plane 写入的 etcd | control plane 的 Admin API | data plane 读取/监听 etcd,但不承载 Admin API | 管理职责被隔离,etcd 网络、角色配置和版本组合仍需治理 |
| Standalone 文件驱动 | apisix.yaml/apisix.json | 原子替换文件 | 轮询文件并热更新内存,不替换 worker | 多节点文件分发与原子性由外部系统保证 |
| Standalone API 驱动 | 进程内存与外部期望状态 | 专用 API 全量提交 | 内存中替换完整配置 | 仍属实验能力,持久化和重放责任在外部 |
文件驱动 Standalone 不需要 etcd,适合配置规模可控、由 Git/制品完整发布的环境。实验性的 API 驱动 Standalone 不能因为“提供了 API”就视为成熟管理平面;采用前必须验证并发写入、进程重启、失败重放和版本兼容。具体状态以目标发行线的 部署模式文档 为准。
用 Compose 建立可辨认基线
先从 Apache APISIX 下载与验签入口 选择目标发行线,从官方镜像与 etcd 发布页选择完整 tag/digest,并核对 CPU 架构、配置 schema 和兼容性。源代码发布还要按 ASF 指引验证签名或 SHA512。系统包、Docker、Helm 与源码安装的依赖和升级动作不同;下面的 Compose 只用于隔离实验,不代表生产拓扑。
创建 apisix-config.yaml:
apisix:
node_listen: 9080
enable_admin: true
deployment:
role: traditional
role_traditional:
config_provider: etcd
admin:
allow_admin:
- 0.0.0.0/0
admin_key:
- name: lab-admin
key: ${{ADMIN_KEY}}
role: admin
etcd:
host:
- http://etcd:2379实验容器需要从 Compose 网络访问 Admin API,因而临时放宽 allow_admin;宿主端口必须只绑定回环,实验结束立即销毁。生产配置应改为管理网 CIDR、随机高熵 key、HTTPS/mTLS、独立自动化身份和 etcd mTLS,不能复制这段 key 或 CIDR。
创建 compose.yaml:
services:
etcd:
image: ${ETCD_IMAGE:?set ETCD_IMAGE to a fixed tag or digest}
command:
- /usr/local/bin/etcd
- --name=etcd
- --data-dir=/etcd-data
- --listen-client-urls=http://0.0.0.0:2379
- --advertise-client-urls=http://etcd:2379
volumes:
- apisix-etcd:/etcd-data
networks: [apisix-lab]
blue:
image: ${ECHO_IMAGE:?set ECHO_IMAGE to a fixed tag or digest}
command: ["-listen=:5678", "-text=upstream=blue"]
networks: [apisix-lab]
green:
image: ${ECHO_IMAGE:?set ECHO_IMAGE to a fixed tag or digest}
command: ["-listen=:5678", "-text=upstream=green"]
networks: [apisix-lab]
apisix:
image: ${APISIX_IMAGE:?set APISIX_IMAGE to a fixed tag or digest}
environment:
ADMIN_KEY: ${ADMIN_KEY:?set a one-time lab key}
volumes:
- ./apisix-config.yaml:/usr/local/apisix/conf/config.yaml:ro
ports:
- "127.0.0.1:9080:9080"
- "127.0.0.1:9180:9180"
depends_on: [etcd, blue, green]
networks: [apisix-lab]
networks:
apisix-lab:
name: apisix-lab
volumes:
apisix-etcd:
name: apisix40-etcd不同 etcd 镜像的二进制路径可能不同,先在目标镜像中核对 etcd --version 和入口命令;不要为了让示例启动而改用未验证的浮动镜像。随后启动并保留版本证据:
export APISIX_IMAGE='apache/apisix:<verified-tag-or-digest>'
export ETCD_IMAGE='<official-etcd-image>:<verified-tag-or-digest>'
export ECHO_IMAGE='hashicorp/http-echo:<verified-tag-or-digest>'
export ADMIN_KEY="$(openssl rand -hex 32)"
docker compose -p apisix40 config
docker compose -p apisix40 up -d
for i in $(seq 1 30); do
if curl -fsS -H "X-API-KEY: ${ADMIN_KEY}" \
http://127.0.0.1:9180/apisix/admin/routes > apisix-routes.ready.json; then
break
fi
sleep 2
done
test -s apisix-routes.ready.json
docker compose -p apisix40 ps
docker compose -p apisix40 images
docker compose -p apisix40 exec apisix apisix version
docker compose -p apisix40 logs --no-color apisixdepends_on 不等待 etcd 可写或 APISIX 完成初始化,所以要用有限次数的已认证 Admin 请求建立就绪门槛;超时后保留 ps 与日志并停止实验,不能无限重试掩盖错误。只有 APISIX 与 etcd 都稳定运行、9080/9180 只监听回环、日志没有配置解析或 etcd 连接错误,才进入对象创建。images、apisix version 和配置文件摘要共同构成版本基线;快速启动脚本若关闭 Admin API 鉴权,只能放在一次性受控环境,不能直接进入共享环境。
${{ADMIN_KEY}} 由 APISIX 在进程内展开,不是 Compose 模板;Compose 只负责把一次性实验 key 注入容器。环境变量仍可能通过容器元数据被有 Docker 管理权限的人读取,所以它只比把固定 key 提交进仓库更好,并不等于生产 Secret 方案。生产环境应由 Secret 管理器或受控挂载在启动时提供,且禁止把 docker inspect、渲染后的配置和 Admin 请求头写入普通流水线日志。
逐层创建 Upstream、Service 与 Route
先创建两个独立 Upstream。对象 ID 使用可读的合成名称,响应体与 HTTP 状态码保存为发布证据:
curl -i -X PUT 'http://127.0.0.1:9180/apisix/admin/upstreams/up-blue' \
-H "X-API-KEY: ${ADMIN_KEY}" -H 'Content-Type: application/json' \
-d '{"type":"roundrobin","nodes":{"blue:5678":1}}'
curl -i -X PUT 'http://127.0.0.1:9180/apisix/admin/upstreams/up-green' \
-H "X-API-KEY: ${ADMIN_KEY}" -H 'Content-Type: application/json' \
-d '{"type":"roundrobin","nodes":{"green:5678":1}}'再用 Service 抽取上游引用,Route 只负责匹配:
curl -i -X PUT 'http://127.0.0.1:9180/apisix/admin/services/svc-blue' \
-H "X-API-KEY: ${ADMIN_KEY}" -H 'Content-Type: application/json' \
-d '{"upstream_id":"up-blue"}'
curl -i -X PUT 'http://127.0.0.1:9180/apisix/admin/services/svc-green' \
-H "X-API-KEY: ${ADMIN_KEY}" -H 'Content-Type: application/json' \
-d '{"upstream_id":"up-green"}'
curl -i -X PUT 'http://127.0.0.1:9180/apisix/admin/routes/route-blue' \
-H "X-API-KEY: ${ADMIN_KEY}" -H 'Content-Type: application/json' \
-d '{"uri":"/api/blue","methods":["GET"],"service_id":"svc-blue"}'
curl -i -X PUT 'http://127.0.0.1:9180/apisix/admin/routes/route-green' \
-H "X-API-KEY: ${ADMIN_KEY}" -H 'Content-Type: application/json' \
-d '{"uri":"/api/green","methods":["GET"],"service_id":"svc-green"}'PUT 使用客户端指定 ID 创建或替换完整对象。POST 通常让服务端分配 ID;PATCH 更新部分字段;字段设为 null 可删除,子路径 PATCH 会替换该子树,数组也应按整体替换理解。每次写入后都 GET 对象并保存响应中的 key、value 和 revision/index 类证据,字段名以目标版本实际响应为准。
执行正向与反向请求:
curl -i http://127.0.0.1:9080/api/blue
curl -i http://127.0.0.1:9080/api/green
curl -i -X POST http://127.0.0.1:9080/api/blue
curl -i http://127.0.0.1:9080/api/missing
curl -i -H 'X-API-KEY: wrong-key' \
http://127.0.0.1:9180/apisix/admin/routes两个成功响应必须分别出现 upstream=blue 与 upstream=green。错误 Method 和未知路径应停在路由层,上游日志中不应出现对应请求;错误 Admin key 必须被拒绝且对象保持不变。精确状态码可能受目标版本和路由配置影响,验收应固定为“预期类别 + 无上游副作用 + 配置 revision 未变化”,不要只断言一个数字。
用 PATCH 反例看见配置数据结构
先导出 route-blue,再只增加一个 Method:
curl -sS -H "X-API-KEY: ${ADMIN_KEY}" \
'http://127.0.0.1:9180/apisix/admin/routes/route-blue' \
> route-blue.before.json
curl -i -X PATCH 'http://127.0.0.1:9180/apisix/admin/routes/route-blue' \
-H "X-API-KEY: ${ADMIN_KEY}" -H 'Content-Type: application/json' \
-d '{"methods":["GET","HEAD"]}'
curl -sS -H "X-API-KEY: ${ADMIN_KEY}" \
'http://127.0.0.1:9180/apisix/admin/routes/route-blue' \
> route-blue.after.json比较前后对象,应看到 uri 与 service_id 被保留,而 methods 数组成为提交的完整新数组。对子路径执行 PATCH 时,提交值会替换那个子路径,不会与旧数组智能合并。生产自动化应采用“GET 当前对象 -> 生成期望完整对象 -> schema 校验/差异审查 -> 条件化发布 -> GET 与请求复核”,并在并发写场景使用目标版本提供的 revision/比较能力,避免后写者静默覆盖先写者。
Admin API 在不同发行线中的响应包装可能不同;无论对象位于 .value 还是响应根部,都不能把 key、revision、时间等服务端元数据原样 PUT 回去。本实验的原始声明只有 uri、methods、service_id,因此显式重建这三个字段,再执行完整对象回退:
jq '(if has("value") then .value else . end) | {uri, methods, service_id}' \
route-blue.before.json > route-blue.rollback.json
curl -i -X PUT 'http://127.0.0.1:9180/apisix/admin/routes/route-blue' \
-H "X-API-KEY: ${ADMIN_KEY}" -H 'Content-Type: application/json' \
--data-binary @route-blue.rollback.json
curl -sS -H "X-API-KEY: ${ADMIN_KEY}" \
'http://127.0.0.1:9180/apisix/admin/routes/route-blue'
curl -I http://127.0.0.1:9080/api/blue
curl -i http://127.0.0.1:9080/api/blue回退后的这三个声明字段应与 before 一致,GET 恢复成功而新增的 HEAD 不再作为允许方法到达 blue 上游。生产对象不能照抄这个三字段过滤器,应从版本化期望状态重建完整对象;若目标版本响应不是 .value 包装,也必须先检查 JSON 再调整提取路径,不能让 jq 产生的 null 进入 PUT。
接着创建一份可复用 Plugin Config,把请求标识与本地固定窗口限流放在一起:
curl -i -X PUT 'http://127.0.0.1:9180/apisix/admin/plugin_configs/pc-lab' \
-H "X-API-KEY: ${ADMIN_KEY}" -H 'Content-Type: application/json' \
-d '{
"plugins": {
"request-id": {
"header_name": "X-Lab-Request-Id",
"include_in_response": true
},
"limit-count": {
"count": 2,
"time_window": 60,
"rejected_code": 429,
"key_type": "var",
"key": "remote_addr",
"policy": "local"
}
}
}'
curl -i -X PATCH 'http://127.0.0.1:9180/apisix/admin/routes/route-blue' \
-H "X-API-KEY: ${ADMIN_KEY}" -H 'Content-Type: application/json' \
-d '{"plugin_config_id":"pc-lab"}'
for i in 1 2 3; do
curl -i http://127.0.0.1:9080/api/blue
done前两次请求应到达 blue,并在响应中出现不同的 X-Lab-Request-Id;同一窗口内第三次应被 429 截断,blue 上游请求总数只增加 2。客户端若主动发送同名 request ID,插件不会覆盖它,因此生产链路不能直接把该值当可信审计身份:应在受信入口清理外部同名 Header,或同时记录网关生成的不可伪造关联字段。policy: local 的计数器属于单节点,两个 APISIX 节点会各自拥有配额;需要集群共享配额时再评估 Redis/Redis Cluster,并把 Redis TLS、认证、超时、故障时 fail-open/fail-close、热点 key 和成本纳入实验,不能把“单节点第三次 429”外推成集群总配额。
再构造一个引用故障,比单纯 404 更能暴露对象关系:创建 Route 引用一个不存在的 Plugin Config,然后请求它。目标版本应在请求阶段返回 503 类失败;恢复时先创建所引用的 Plugin Config,或把 Route 恢复到上一份完整对象。删除共享 Plugin Config 之前必须反查所有 Route 引用,否则配置删除成功会把故障推迟到数据面。
curl -i -X PUT 'http://127.0.0.1:9180/apisix/admin/routes/route-broken-plugin' \
-H "X-API-KEY: ${ADMIN_KEY}" -H 'Content-Type: application/json' \
-d '{"uri":"/api/broken-plugin","service_id":"svc-blue","plugin_config_id":"missing-plugin-config"}'
curl -i http://127.0.0.1:9080/api/broken-plugin保存 Admin API 返回、Route GET、APISIX error log、上游无请求证据和恢复后的代理请求,才能证明问题位于引用装配层。
etcd 为什么既是配置总线也是故障域
Admin API 不直接改每个 worker 的路由表。etcd 保存对象,MVCC revision 标识状态演进,APISIX 通过 watch 获得变化并更新内存结构。事务比较可帮助发布者避免覆盖不再是预期 revision 的对象;watch 中断后,客户端需要从可用 revision 恢复或重新同步。etcd compact、配额、磁盘延迟、leader 变更和网络分区都会改变管理写入与配置传播,但已有内存配置是否继续服务,要由目标模式和故障实验确认。
因此,集群验收至少要有三组证据:Admin API 写入响应及对象 revision、每个 APISIX 节点的同步/错误日志、经每个数据节点发出的真实请求。只看 etcd 有 key,不能证明 worker 已装载;只看 APISIX 健康,不能证明它拥有最新 revision;只看负载均衡后的成功请求,可能漏掉某个落后节点。
生产 etcd 应作为独立基础设施治理,使用受支持版本组合、奇数成员、跨故障域部署、客户端/peer TLS、身份认证、持久磁盘、配额告警和定期快照恢复。Helm Chart 可能提供内置 etcd,但默认依赖、镜像、持久化和副本数会随 Chart 变化;渲染目标 Chart 后逐项核对,不能把“Chart 能安装”理解为生产 etcd 已设计完成。
把管理角色与流量角色真正拆开
decoupled 不是给同一组 Pod 换两个标签,而是让管理入口与公网流量落在不同进程、网络策略和伸缩组。Control Plane 只监听 Admin API 并写 etcd;Data Plane 只监听业务流量并从 etcd 读取配置。两侧必须使用相同的 etcd.prefix、受支持的 APISIX/etcd 组合和一致的自定义插件集合:
# control-plane config.yaml
apisix:
enable_admin: true
deployment:
role: control_plane
role_control_plane:
config_provider: etcd
admin:
allow_admin: ["10.20.0.0/16"]
admin_key:
- name: cicd
key: ${{ADMIN_KEY}}
role: admin
etcd:
host: ["https://etcd-1.internal:2379", "https://etcd-2.internal:2379", "https://etcd-3.internal:2379"]
prefix: /apisix-prod
tls:
cert: /run/secrets/etcd-client.crt
key: /run/secrets/etcd-client.key# data-plane config.yaml
apisix:
node_listen: 9080
enable_admin: false
deployment:
role: data_plane
role_data_plane:
config_provider: etcd
etcd:
host: ["https://etcd-1.internal:2379", "https://etcd-2.internal:2379", "https://etcd-3.internal:2379"]
prefix: /apisix-prod
tls:
cert: /run/secrets/etcd-client.crt
key: /run/secrets/etcd-client.key证书链还要通过 apisix.ssl.ssl_trusted_certificate 指向可信 CA;服务端证书校验、证书文件权限及目标发行线的完整 TLS 字段,应从该发行线配置 schema 确认。不要在 Data Plane 保留 9180 的安全组入口,也不要把 etcd 客户端私钥做进镜像层。APISIX Admin key 提供的是管理入口认证,并不提供按 Route、租户或动作划分的细粒度授权;变更审批、双人复核和不可抵赖审计需要在 API 前置代理、发布平台与外部日志系统完成。
同步实验需要绕过外部负载均衡逐节点执行。先在 Control Plane 发布 route-blue 并记录 Admin 响应,再分别请求每个 Data Plane;随后阻断一个 Data Plane 到 etcd 的新连接,发布只存在于 green 的新 Route。未阻断节点应出现新行为,受阻节点若仍服务旧 Route,只能证明内存中的最后状态可用。恢复网络后必须观察该节点日志、对象行为和新 Route 首次成功时间收敛;若只恢复旧流量而新 Route 仍 404,应继续查 prefix、证书、etcd 权限、watch compaction 与节点实际配置,不能靠重启把传播故障抹掉。
Standalone 适合什么发布模型
文件驱动配置在 config.yaml 中设置数据角色和 YAML provider:
deployment:
role: data_plane
role_data_plane:
config_provider: yamlRoute、Service、Upstream 等完整期望状态写入 conf/apisix.yaml。发布器先在临时路径生成文件并校验,再用同一文件系统内的原子 rename 替换,APISIX 轮询后热更新内存配置。不要逐行覆盖正在读取的文件,也不要手工维护 APISIX 生成的 conf/nginx.conf。
验证需要三轮:合法文件使 blue/green 请求发生预期变化;语法或 schema 错误文件不会把当前可用状态静默清空,并在 error log 留下明确证据;进程重启后仍从磁盘恢复同一摘要。多个节点之间没有 etcd watch,文件分发器必须证明每个节点收到同一 SHA-256。环境变量展开还可能改变 YAML 类型,例如 "001"、"true" 或 URL 应以字符串形式保留;目标版本升级时要加入类型敏感回归。
Standalone 的优点是状态可直接版本化、数据面不依赖 etcd;代价是全量文件发布、节点分发、并发控制和回退由团队承担。配置需要高频增量写入、多个管理客户端或动态消费者生命周期时,etcd-backed 模式通常更合适。
接入项目时明确谁拥有哪一段策略
网关配置仓库至少保存 Route、Service、Upstream、Plugin Config 的所有者和引用图,并为每次发布生成期望对象摘要。业务仓库保存 API 契约、上游超时预算、幂等语义和应用侧身份校验。两者在流水线中用合成环境汇合:先直接访问 blue/green 建立上游基线,再发布 APISIX 对象,执行正确路由、错误 Method、错误凭据、缺失引用和上游不可达测试。
应用只接受来自受信网关网络的转发头,进入业务前清除客户端伪造值;网关认证得到的 Consumer 身份不自动等于业务用户授权。Upstream 的 timeout.connect、timeout.send、timeout.read 分别改变建连、发送请求与等待响应的失败窗口,retries 决定失败后可能增加的尝试次数;字段单位、默认值和允许范围必须从目标版本 schema 读取。对非幂等 POST,不要仅因为 Upstream 支持 retries 就开启重试;必须确认连接失败发生在发送请求体之前,或上游具有幂等键/去重机制,并把总超时预算分配给连接、单次响应与重试。负向实验应让一个合成节点拒绝连接或延迟响应,核对上游实际调用次数、APISIX error log 与客户端总耗时,证明配置没有制造超出预算的重放。
Route 的 status 适合显式停用对象:停用后对象仍存在但不应继续匹配,恢复旧值即可回切。ttl 则会让临时对象到期后从存储中删除,适合限时实验,不适合无人负责的长期 API;采用它时要保存到期时间、对象消失的 GET 证据和路由不再命中的请求证据,不能把自动删除误判为同步故障。
复用插件配置能减少漂移,也扩大共享对象的影响面。修改 Plugin Config 前列出引用 Route,执行样本请求,逐步发布并准备旧对象 PUT 回退。Global Rule 影响所有匹配流量,必须使用更高等级审批、流量回放和紧急禁用路径。
按停止位置做故障分层
| 现象 | 首查证据 | 可能层次 | 再验证 |
|---|---|---|---|
| Admin API 拒绝 | socket、TLS、CIDR、X-API-KEY、审计日志 | 管理网络或身份 | 正确身份仅从受控网络成功,旧/错 key 持续失败 |
| 写入成功但请求仍旧 | 对象 revision、节点日志、节点级请求 | etcd watch、节点落后或请求命中另一集群 | 所有节点对象摘要与行为收敛 |
| 404 且上游无日志 | Route 的 Host/Path/Method/status | 路由匹配或未装载 | 直接 GET Route 并用同一请求逐节点探测 |
| 503 且引用对象缺失 | Route/Service/Plugin Config 引用图 | 对象装配 | 恢复引用对象后请求成功,未引用路径不受影响 |
| 502/503 且有 upstream 错误 | DNS、节点地址、TLS/SNI、健康状态 | 上游连接 | 直连基线和经网关请求同时验证 |
| 超时或尾延迟突增 | APISIX latency、重试次数、上游耗时 | 超时预算、连接池、重试放大 | 关闭单一变量后总时长与请求次数符合预算 |
健康检查也需要机制证据。只有 Upstream 被请求命中后检查才会开始;纯被动检查摘除的节点因为不再收到请求,不能自行证明恢复,通常要配合主动检查。探测 Host、Path、TLS 或期望状态码写错时,健康后端会被误摘。即使没有健康节点,目标版本也可能继续尝试上游,不能把健康检查当成绝对熔断器。
生产架构、容量与选型
单个 APISIX 实例加单 etcd 只适合学习和短时联调。生产入口前需要外部负载均衡,多 APISIX 实例跨故障域部署;etcd 独立成奇数成员集群并避免与所有网关共享同一故障域。管理 API 不经公网入口暴露,管理网络与代理网络分离。需要进一步隔离配置变更与流量时,再采用 decoupled 角色;需要极简数据面依赖且全量发布可接受时,选择文件驱动 Standalone。
容量不能只用裸代理 RPS 表示。至少测量客户端与上游连接数、TLS 握手、请求/响应体、插件阶段耗时、日志/Trace 出口、Upstream 重试、配置对象数量、worker RSS、etcd 写入与 watch 延迟,以及失去一个实例后的余量。逐级放大真实对象和流量,验收“错误率与尾延迟维持目标、RSS 不随发布轮次单调增长、配置传播时间稳定、N-1 后仍有余量”,阈值由业务 SLO 和基线测量决定。
选型时把 APISIX 与团队能力绑定:已经能可靠运行 etcd、需要动态对象和丰富插件时,etcd-backed 模式有优势;只需要可审计的全量配置且团队善于不可变发布时,Standalone 更轻;大量自定义 Lua/Wasm/外部 runner 会提高进程内故障与供应链成本。Kubernetes Ingress Controller 是独立组件与版本线,采用它时还要单独治理 CRD/API、控制器权限和 APISIX 核心兼容,不能把 Chart 版本当作整套系统版本。
升级、备份与可执行回退
升级前记录源/目标 APISIX、安装方式、镜像/Chart digest、OpenResty、插件/runner、etcd、config.yaml、配置模式和对象摘要,逐项阅读目标发行线 CHANGELOG 的不兼容标记。APISIX 没有覆盖任意版本跨度的一条通用升级命令;跨大版本尤其不能让新旧实例直接写同一 etcd prefix 并期待自动转换。
etcd-backed 部署先冻结管理写入,再保存 etcd 快照、启动配置、Helm values、证书/Secret 引用、自定义插件和制品 digest。用匹配 etcd 大版本的工具检查快照,并在隔离目录/隔离集群恢复;恢复目标使用不同地址和 APISIX prefix,核对 Route、Service、Upstream、SSL、Consumer、Plugin Config 数量与正反请求。快照文件存在不等于可恢复。
新版本先连接隔离 prefix 或恢复副本,完成对象转换和回归,再双跑并逐步切流。失败时把入口切回仍保持旧数据与旧镜像的集群。helm rollback 只能恢复 Kubernetes 清单,不能自动逆转 etcd 数据结构、外部 Secret 或插件依赖;数据库/配置已经迁移后仅换旧 Pod 不是回滚。
Standalone 文件模式保留上一份 config.yaml、apisix.yaml/apisix.json 与插件制品。新文件在目标版本中验证后原子发布;失败时原子恢复旧文件并重复两个上游与反例探针。API 驱动 Standalone 的内存不能充当备份,外部配置源必须能从空进程重放完整期望状态。
权限、凭据、数据、许可与成本
Admin API key、etcd 客户端证书、Secret 后端身份和 CI 发布凭据分别使用独立服务身份,不共享人员 key。轮换采用“创建新凭据 -> 验证新凭据 -> 切换发布者 -> 撤销旧凭据 -> 证明旧凭据失败”,并检查 shell history、日志、镜像层、ConfigMap 和普通 Helm values 没有残留。Secret 引用解析后未必再次通过目标字段 schema,轮换后必须执行运行时负向验证,不能只看配置保存成功。
访问日志、Trace、Admin API 响应、etcd 快照和配置导出可能含内部地址、Consumer、证书或请求数据。为 Header、Query、Body 和插件错误日志建立脱敏规则,限制保留期与下载权限;备份加密并与解密密钥分离;测试只用合成租户和 example.com 域名。
Apache APISIX 核心仓库的许可证结论不能自动覆盖基础镜像、Helm 依赖、自定义插件、外部 runner、服务发现和 Secret 后端。上线前分别检查目标 artifact 的 LICENSE/NOTICE、SBOM、漏洞与来源签名。商业托管、支持服务和基础设施价格随供应方变化,只在采购时读取当前合同;自建成本则至少包含 APISIX 实例、etcd、负载均衡、跨区/出口、日志/Trace、备份、证书系统和插件维护。
按引用关系精确清理
先导出合成对象并确认 ID,然后按 Route -> Service -> Upstream 删除;错误引用实验的 Route 也要单独删除:
for id in route-broken-plugin route-blue route-green; do
curl -i -X DELETE \
-H "X-API-KEY: ${ADMIN_KEY}" \
"http://127.0.0.1:9180/apisix/admin/routes/${id}"
done
curl -i -X DELETE \
-H "X-API-KEY: ${ADMIN_KEY}" \
'http://127.0.0.1:9180/apisix/admin/plugin_configs/pc-lab'
for id in svc-blue svc-green; do
curl -i -X DELETE \
-H "X-API-KEY: ${ADMIN_KEY}" \
"http://127.0.0.1:9180/apisix/admin/services/${id}"
done
for id in up-blue up-green; do
curl -i -X DELETE \
-H "X-API-KEY: ${ADMIN_KEY}" \
"http://127.0.0.1:9180/apisix/admin/upstreams/${id}"
done再次 GET 对象并请求 /api/blue、/api/green,应证明对象不存在且两个上游无新增日志。随后确认 Compose 项目资源,最后才删除专用 etcd volume:
docker compose -p apisix40 ps
docker compose -p apisix40 logs --no-color apisix > apisix40.log
docker compose -p apisix40 down --remove-orphans
docker ps -a --filter label=com.docker.compose.project=apisix40
docker network ls --filter label=com.docker.compose.project=apisix40
docker volume inspect apisix40-etcd
docker volume rm apisix40-etcddocker volume rm 只可用于已确认名称、用途和所有者的实验卷;共享 etcd、未知 volume 或生产快照绝不能按这段命令处理。这个 Compose 没有把 etcd 的 2379 映射到宿主机,清理后应确认 9080、9180 不再监听,项目容器与网络均不存在,专用 volume 已删除;不要用“宿主机从未监听 2379”冒充 etcd 已清理。随后删除合成配置与脱敏日志,撤销实验 key/证书,并复查负载均衡、DNS、对象存储快照和日志索引是否继续计费。生产退役还要先证明没有 Route/Consumer/证书引用,满足审计保留后再销毁备份与密钥。
