跳到主要内容

在共享网关上构建租户感知策略

概览

APISIX 不提供 Tenant 资源,也没有内置的租户隔离模型。本方案组合使用 APISIX 的多项能力,通过一个共享网关为客户、团队或业务单元提供不同的身份认证、路由和流量策略。

这些模式只能区分请求处理和策略行为,不能隔离 Admin API 访问、配置存储或网关运行时资源。需要更强的管理或运行时隔离时,请使用独立的 APISIX 部署。

本方案组合使用:

  1. 消费者组——向相关消费者应用共享插件配置。
  2. 基于 Host、路径或已认证消费者的路由——将请求路由到租户专属上游。
  3. 消费者级限流——在策略组内执行不同的配额。
  4. proxy-rewrite——通过请求头将租户上下文转发给后端。

适用场景

  • 共享单一 API 网关的多个客户
  • 为不同团队提供独立策略和配额配置的内部平台
  • 需要租户感知路由和身份认证的 SaaS 应用
  • 需要将租户身份转发给后端服务

方法 A:使用消费者组共享租户策略

按租户或服务等级对消费者进行分组。每个组向其中的消费者提供共享插件配置,例如限流和请求转换。

1. 为租户策略集创建消费者组

# 免费层级——每个消费者每天 100 个请求
a6 consumer-group create -f - <<'EOF'
{
"id": "tenant-free",
"desc": "Free tier tenant",
"plugins": {
"limit-count": {
"count": 100,
"time_window": 86400,
"key_type": "var",
"key": "consumer_name",
"rejected_code": 429,
"rejected_msg": "Free tier quota exceeded"
}
}
}
EOF

# 专业层级——每个消费者每天 10000 个请求
a6 consumer-group create -f - <<'EOF'
{
"id": "tenant-pro",
"desc": "Pro tier tenant",
"plugins": {
"limit-count": {
"count": 10000,
"time_window": 86400,
"key_type": "var",
"key": "consumer_name",
"rejected_code": 429,
"rejected_msg": "Pro tier quota exceeded"
}
}
}
EOF

2. 创建消费者并分配到组

a6 consumer create -f - <<'EOF'
{
"username": "acme-corp",
"group_id": "tenant-pro",
"plugins": {
"key-auth": { "key": "acme-secret-key" }
}
}
EOF

a6 consumer create -f - <<'EOF'
{
"username": "startup-xyz",
"group_id": "tenant-free",
"plugins": {
"key-auth": { "key": "startup-xyz-key" }
}
}
EOF

3. 创建启用身份认证的共享路由

a6 route create -f - <<'EOF'
{
"id": "api-v1",
"uri": "/api/v1/*",
"upstream": {
"type": "roundrobin",
"nodes": { "api-backend:8080": 1 }
},
"plugins": {
"key-auth": {}
}
}
EOF

现在,acme-corp 每天可发送 10,000 个请求,startup-xyz 每天可发送 100 个请求, 两者都通过同一路由。

方法 B:基于 Host 的租户路由

根据 Host 请求头,将每个租户的流量路由到各自的后端。

1. 为每个租户创建上游

a6 upstream create -f - <<'EOF'
{
"id": "upstream-tenant-a",
"type": "roundrobin",
"nodes": { "tenant-a-backend:8080": 1 }
}
EOF

a6 upstream create -f - <<'EOF'
{
"id": "upstream-tenant-b",
"type": "roundrobin",
"nodes": { "tenant-b-backend:8080": 1 }
}
EOF

2. 创建基于 Host 的路由

a6 route create -f - <<'EOF'
{
"id": "tenant-a-route",
"host": "tenant-a.example.com",
"uri": "/*",
"upstream_id": "upstream-tenant-a",
"plugins": { "key-auth": {} }
}
EOF

a6 route create -f - <<'EOF'
{
"id": "tenant-b-route",
"host": "tenant-b.example.com",
"uri": "/*",
"upstream_id": "upstream-tenant-b",
"plugins": { "key-auth": {} }
}
EOF

方法 C:基于已认证身份的租户路由

使用已认证的 consumer_name 变量,通过 traffic-split 将流量路由到不同上游。身份认证插件会根据匹配到的消费者填充该 APISIX 变量,之后 traffic-split 才会运行,因此客户端无法通过伪造请求头来选择其他租户的上游。

a6 route create -f - <<'EOF'
{
"uri": "/api/*",
"plugins": {
"key-auth": {},
"traffic-split": {
"rules": [
{
"match": [{ "vars": [["consumer_name", "==", "acme-corp"]] }],
"weighted_upstreams": [
{ "upstream": { "type": "roundrobin", "nodes": { "tenant-a-backend:8080": 1 } }, "weight": 1 }
]
},
{
"match": [{ "vars": [["consumer_name", "==", "startup-xyz"]] }],
"weighted_upstreams": [
{ "upstream": { "type": "roundrobin", "nodes": { "tenant-b-backend:8080": 1 } }, "weight": 1 }
]
}
]
}
},
"upstream": {
"type": "roundrobin",
"nodes": { "default-backend:8080": 1 }
}
}
EOF

向后端转发租户上下文

使用 proxy-rewrite 将租户身份写入 HTTP 请求头,使后端能够识别请求所属的租户。

a6 route update api-v1 -f - <<'EOF'
{
"plugins": {
"key-auth": {},
"proxy-rewrite": {
"headers": {
"set": {
"X-Consumer-Name": "$consumer_name",
"X-Consumer-Group": "$consumer_group_id"
}
}
}
}
}
EOF

后端会收到 X-Consumer-Name: acme-corpX-Consumer-Group: tenant-pro 请求头。

声明式租户感知配置

使用 a6 config sync 以声明式方式管理租户组、消费者和路由:

# apisix-tenants.yaml
consumer_groups:
- id: tenant-free
desc: "Free tier"
plugins:
limit-count:
count: 100
time_window: 86400
key_type: var
key: consumer_name
- id: tenant-pro
desc: "Pro tier"
plugins:
limit-count:
count: 10000
time_window: 86400
key_type: var
key: consumer_name

consumers:
- username: acme-corp
group_id: tenant-pro
- username: startup-xyz
group_id: tenant-free

routes:
- id: api-v1
uri: "/api/v1/*"
upstream:
type: roundrobin
nodes:
"api-backend:8080": 1
plugins:
key-auth: {}
proxy-rewrite:
headers:
set:
X-Consumer-Name: "$consumer_name"
X-Consumer-Group: "$consumer_group_id"
# Preview changes
a6 config diff -f apisix-tenants.yaml

# Apply
a6 config sync -f apisix-tenants.yaml

消费者创建完成后,将每个租户的 key-auth 数据作为凭证创建。例如,将以下内容保存为 acme-credential.yaml

id: acme-key-auth
plugins:
key-auth:
key: acme-secret-key
a6 credential create --consumer acme-corp -f acme-credential.yaml

将免费等级的凭证保存为 startup-credential.yaml

id: startup-key-auth
plugins:
key-auth:
key: startup-xyz-key
a6 credential create --consumer startup-xyz -f startup-credential.yaml

注意事项

  • 消费者组不是隔离边界——消费者组只是在消费者之间复用插件配置。所有组仍共享同一个 APISIX 管理面、配置存储和网关运行时。
  • 凭证是独立资源——a6 config synca6 config dump 不管理消费者凭证子资源。请安全保管凭证文件,并使用 a6 credential 命令单独应用或恢复凭证。
  • 消费者组插件合并——消费者组配置的插件会与单个消费者配置的插件合并。如果两者定义了同一个插件,以消费者级配置为准。
  • group_id 是字符串——其值必须与现有消费者组 ID 完全一致。
  • 限流键——组合使用 key_type: "var"key: "consumer_name",可在同一消费者组内按消费者执行限流;否则限额会由组内所有消费者共享。
  • 租户路由身份——身份认证后,使用 consumer_nameconsumer_group_id 匹配路由。不要根据客户端提供的租户请求头进行路由,因为已认证的消费者可能会伪造其他租户的值。
  • 重写中的变量名称——$consumer_name$consumer_group_id 是 APISIX 内置变量,只有在身份认证完成后才可用。应确保身份认证插件(如 key-authjwt-auth)先于 proxy-rewrite 执行。

验证

# List consumer groups
a6 consumer-group list

# Verify consumer assignment
a6 consumer get acme-corp --output json | grep group_id

# Test rate limiting for free tier
for i in $(seq 1 101); do
curl -s -o /dev/null -w "%{http_code}\n" \
-H "apikey: startup-xyz-key" http://localhost:9080/api/v1/hello
done
# Request 101 should return 429

# Verify tenant headers reach backend
curl -H "apikey: acme-secret-key" http://localhost:9080/api/v1/headers
# Response should show X-Consumer-Name and X-Consumer-Group headers

本文根据 api7/a6 仓库中的 a6-recipe-multi-tenant/SKILL.md 生成。可在 AI Agent Skills 页面浏览全部 Skill。