通过在 OpenResty 的配置文件中定义不同的 location
块,将匹配特定 URL 路径的请求转发到不同的后端 FastAPI 应用(即使它们运行在不同的端口或甚至是不同的服务器/容器上)。
核心思路:
多个 FastAPI 应用实例:
- 你的每个 FastAPI 应用(例如 “Tool App”, “ServiceB App”)会独立运行,监听在各自的端口上。
- 例如:
- Tool App 运行在
127.0.0.1:8001
(或 Docker 容器tool_app_container:8001
) - ServiceB App 运行在
127.0.0.1:8002
(或 Docker 容器serviceb_app_container:8002
)
- Tool App 运行在
OpenResty 配置:
- 在 OpenResty 的
server
配置块中,为每个你想映射的 URL 路径创建一个location
块。 - 在每个
location
块内部,使用proxy_pass
指令将请求转发到对应的 FastAPI 应用的地址和端口。
- 在 OpenResty 的
OpenResty 配置示例
假设你有以下需求:
XXX.com/tool/
下的所有请求 -> Tool App (监听在8001
端口)XXX.com/serviceB/
下的所有请求 -> ServiceB App (监听在8002
端口)XXX.com/static/
-> 静态文件XXX.com/
-> 前端单页应用 (SPA) 或其他默认 FastAPI 应用 (监听在8000
端口)
# /usr/local/openresty/nginx/conf/nginx.conf 或 /etc/nginx/conf.d/your-site.conf
# 为每个 FastAPI 应用定义上游服务器组 (推荐,方便管理和扩展)
upstream fastapi_default_app {
server 127.0.0.1:8000; # 或者 Docker 服务名:端口,例如 default_fastapi_service:8000
# server unix:/path/to/default_app.sock;
}
upstream fastapi_tool_app {
server 127.0.0.1:8001; # 或者 Docker 服务名:端口,例如 tool_fastapi_service:8001
# server unix:/path/to/tool_app.sock;
}
upstream fastapi_serviceb_app {
server 127.0.0.1:8002; # 或者 Docker 服务名:端口,例如 serviceb_fastapi_service:8002
# server unix:/path/to/serviceb_app.sock;
}
server {
listen 80;
server_name XXX.com; # 替换为你的域名
# 通用的代理头部设置,可以在 server 级别或 http 级别设置
proxy_http_version 1.1; # 推荐,支持 keepalive
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade; # 用于 WebSocket
proxy_set_header Connection "upgrade"; # 用于 WebSocket
# 静态文件
location /static/ {
alias /path/to/your/project/static/;
expires 30d;
add_header Cache-Control public;
}
# 路由到 Tool App
location /tool/ { # 注意末尾的斜杠
# proxy_pass 指令末尾的斜杠很重要,它会影响路径如何传递给后端
# 当 location 和 proxy_pass 末尾都有斜杠时,location 匹配的路径前缀会被移除
# 例如:XXX.com/tool/myaction -> 后端收到 /myaction
proxy_pass http://fastapi_tool_app/;
}
# 路由到 ServiceB App
location /serviceB/ {
proxy_pass http://fastapi_serviceb_app/;
}
# 如果有其他特定路径
# location /another-path/ {
# proxy_pass http://another_backend_service/;
# }
# 默认的 FastAPI 应用或前端 SPA
location / {
# 如果是 FastAPI 应用作为默认
# proxy_pass http://fastapi_default_app;
# 如果是前端 SPA
root /path/to/your/frontend/dist;
try_files $uri $uri/ /index.html;
}
# Lua 脚本可以在这些 location 块内或 server 块级别使用
# access_by_lua_block { ... }
}
关于 proxy_pass
和路径重写的重要说明:
location /path/
(带斜杠) 和proxy_pass http://backend/
(带斜杠):
当请求XXX.com/path/subpath
时,proxy_pass
会将/path/
从请求 URI 中移除,然后将/subpath
附加到http://backend/
后面,所以后端收到的是/subpath
。这通常是你想要的行为,因为后端应用不需要关心外部的/path/
前缀。location /path/
(带斜杠) 和proxy_pass http://backend
(不带斜杠):
当请求XXX.com/path/subpath
时,整个原始请求 URI (/path/subpath
) 会被附加到http://backend
后面,后端收到的是/path/subpath
。location /path
(不带斜杠) 和proxy_pass http://backend
(不带斜杠):
当请求XXX.com/path/subpath
时,后端收到的是/path/subpath
。
选择哪种方式取决于你的 FastAPI 应用是如何配置路由的。通常推荐第一种方式(location /prefix/
和 proxy_pass http://backend/
),这样 FastAPI 应用内部的路由可以从根路径 /
开始定义,而不需要包含 /prefix
。
FastAPI 应用端的考虑:
如果 OpenResty 在转发时没有移除路径前缀(例如,location /tool/
转发后,FastAPI 仍然收到 /tool/myaction
),那么你的 FastAPI 路由需要包含这个前缀:
# tool_app.py
from fastapi import FastAPI
app = FastAPI() # 默认情况下,FastAPI 不知道它被代理在 /tool/ 之下
@app.get("/tool/items/{item_id}") # 路由包含 /tool
async def read_tool_item(item_id: int):
return {"item_id": item_id, "app": "Tool App"}
如果 OpenResty 在转发时移除了路径前缀(例如,使用 location /tool/ { proxy_pass http://backend/; }
),那么 FastAPI 应用内部的路由就可以从根路径开始定义:
# tool_app.py
from fastapi import FastAPI
# 如果希望 FastAPI 生成的 URL (例如在 OpenAPI 文档中) 包含 /tool 前缀
# 即使它在运行时不知道这个前缀,可以使用 root_path
# app = FastAPI(root_path="/tool") # 这样 OpenAPI 文档和重定向会正确
app = FastAPI() # 或者不设置 root_path,如果不需要它自动处理前缀
@app.get("/items/{item_id}") # 路由从 / 开始,因为 /tool/ 已被 OpenResty 处理
async def read_item(item_id: int):
return {"item_id": item_id, "app": "Tool App"}
在多数情况下,让代理(OpenResty)处理路径前缀的剥离,使后端应用(FastAPI)保持简单,是一个更好的做法。如果FastAPI需要生成包含此前缀的URL(例如在OpenAPI文档或重定向中),可以使用FastAPI(root_path="/yourprefix")
。
使用 Docker Compose 管理多个 FastAPI 服务和 OpenResty
这是一个简化的 docker-compose.yml
示例,展示了如何组织:
version: '3.8'
services:
fastapi_default:
build: ./path_to_default_fastapi_app
container_name: fastapi_default_container
# command: gunicorn -w 2 -k uvicorn.workers.UvicornWorker main:app -b 0.0.0.0:8000
expose:
- "8000"
networks:
- web_internal_network
fastapi_tool:
build: ./path_to_tool_fastapi_app
container_name: fastapi_tool_container
# command: gunicorn -w 2 -k uvicorn.workers.UvicornWorker main:app -b 0.0.0.0:8001
expose: # 假设它在容器内监听 8001
- "8001" # 注意:这里是容器内部端口,OpenResty会通过服务名和这个端口访问
networks:
- web_internal_network
fastapi_serviceb:
build: ./path_to_serviceb_fastapi_app
container_name: fastapi_serviceb_container
# command: gunicorn -w 2 -k uvicorn.workers.UvicornWorker main:app -b 0.0.0.0:8002
expose:
- "8002"
networks:
- web_internal_network
openresty:
image: openresty/openresty:alpine # 或者你自定义的 OpenResty 镜像
container_name: openresty_proxy_container
ports:
- "80:80"
- "443:443"
volumes:
- ./openresty_configs/nginx.conf:/usr/local/openresty/nginx/conf/nginx.conf:ro
- ./openresty_configs/conf.d/:/etc/nginx/conf.d/:ro
- ./static_files/:/var/www/static/:ro
# - /path/to/ssl_certs/:/etc/ssl/certs/:ro
depends_on:
- fastapi_default
- fastapi_tool
- fastapi_serviceb
networks:
- web_internal_network
networks:
web_internal_network:
driver: bridge
在上述 Docker Compose 配置中,OpenResty 的 nginx.conf
或 conf.d/your-site.conf
文件中的 upstream
或 proxy_pass
指令应该使用 Docker 的服务名:
# 在 OpenResty 的配置文件中
upstream fastapi_default_app {
server fastapi_default:8000; # 服务名:容器内端口
}
upstream fastapi_tool_app {
server fastapi_tool:8001; # 服务名:容器内端口
}
upstream fastapi_serviceb_app {
server fastapi_serviceb:8002; # 服务名:容器内端口
}
# ... 然后在 location 块中使用这些 upstream
location /tool/ {
proxy_pass http://fastapi_tool_app/;
}
# ...