ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Uvicorn+FastAPI本地部署指南:解决外部访问与端口难题

Uvicorn+FastAPI本地部署指南:解决外部访问与端口难题 先聊个实际场景最近身边不少朋友在折腾本地大模型什么 Ollama、Dify、DeepSeek 都装好了API 也在本机调通了可一到要让同事电脑、自己手机或者局域网另一台机器访问的时候就卡住了——http://localhost:8000敲进去只有自己能打开换成本机局域网 IP 直接超时。同样的戏码也发生在用 FastAPI 写接口的人身上开发环境跑得好好的一旦要让测试环境、其他服务或者生产服务器去调它就各种连不上。这篇东西我从头到尾拆一遍 Uvicorn FastAPI 本地部署这件事重点放在怎么让外部设备真正访问到你起的服务顺便把那些排行榜上反复出现的坑比如 uvicorn 一直占用 8000、防火墙挡连接、局域网能通但公网不通一次性讲透。我的目标读者很明确刚开始用 FastAPI 写接口的新手以及那些把 AI 大模型或数据处理服务部署在本地、又需要对外提供 Web API 的人。这篇文章不堆概念直接给能落地的步骤每一步都讲清楚为什么这么做。1. 先搞懂 Uvicorn 和 FastAPI 各自扮演什么角色很多教程一上来就让你pip install fastapi uvicorn然后跑个 hello world 就完事了。但真到部署的时候你得先理解这两个东西到底谁负责什么。简单说FastAPI 是你写接口逻辑用的框架它定义路由、参数校验、数据序列化而 Uvicorn 是一个 ASGI 服务器负责真正监听网络端口、接收 HTTP 请求、把请求交给 FastAPI 处理、再把响应返回给客户端。1.1 没弄懂这个区别的典型后果你单独写个 FastAPI 应用文件如果没有 Uvicorn 这类服务器去加载并运行它那这个应用就只是个等待被调用的对象根本不会自己监听端口。反过来如果你只用 Uvicorn 去运行一个普通的 WSGI 应用比如 Flask虽然能跑但很多 ASGI 特性发挥不出来。所以 Uvicorn 和 FastAPI 的正确关系是Uvicorn 服务器FastAPI 应用两者配合才能对外提供 Web 服务。我记得有个同事第一次接触 FastAPI看到网上说FastAPI 自带开发服务器结果直接python main.py发现命令行报错No module named uvicorn他又去搜怎么安装 uvicorn装了一半又来问我为什么uvicorn main:app提示找不到模块。其实核心逻辑很简单FastAPI 官方文档推荐用 Uvicorn 来跑是因为 Uvicorn 是目前 ASGI 服务器里性能和稳定性都相当好的选择而且它是用 uvloop 和 httptools 这两个高性能库封装的底层事件循环比纯 Python 实现快不少。1.2 一个最小可运行的 API 长什么样我把项目先拆成最简单的结构后面再展开怎么做生产级部署myapi/ ├── main.py # 应用入口定义 FastAPI 实例和路由 └── requirements.txtmain.py里写这么一段from fastapi import FastAPI app FastAPI(titleDemo API) app.get(/) def read_root(): return {message: Hello, FastAPI Uvicorn} app.get(/health) def health_check(): return {status: alive}requirements.txt里写上fastapi0.115.6 uvicorn[standard]0.34.0然后安装依赖、启动服务pip install -r requirements.txt uvicorn main:app --host 0.0.0.0 --port 8000注意命令里的main:app意思是从main.py里导入名为app的对象。这个对象是 FastAPI 实例Uvicorn 会把 HTTP 请求按 ASGI 协议转发给它。这一步写好之后你在浏览器访问http://127.0.0.1:8000/health能看到{status:alive}说明本机服务已经起来了。2. 外部访问失败的第一道坎host 绑定 0.0.0.0 而不是 127.0.0.1这是排行榜上怎么才能外部访问这类问题里最常见也是最本质的原因。Uvicorn 默认的--host参数是127.0.0.1意思是只监听本机回环地址。回环地址的设备接口只在你自己这台电脑上存在局域网里的其他设备无法通过你的局域网 IP 路由到这个服务。2.1 127.0.0.1、0.0.0.0 和具体 IP 的区别我用一个比方来解释你开了一个家庭派对门口挂了个牌子写着仅限本人进入。127.0.0.1就是这个牌子——只有你本机能进你邻居、快递员一概不能进。0.0.0.0相当于把牌子换成欢迎所有人进来只要走这门它表示监听本机所有网络接口上的请求。你还可以更精确地用某个具体 IP如--host 192.168.1.100表示只允许从这块网卡进来的连接。所以想让局域网其他设备通过http://192.168.1.100:8000访问你的 FastAPI 服务启动命令至少要改成uvicorn main:app --host 0.0.0.0 --port 8000改完之后原地址http://127.0.0.1:8000依然能访问因为它绑定的网络接口也包含了回环接口所以本地调试习惯不受影响。2.2 用本机局域网 IP 自测启动改成0.0.0.0之后先别急着让其他设备连你自己本机先用局域网 IP 验证一次。查询本机局域网 IP# Linux / macOS ip addr show | grep inet # 或者 ifconfig | grep inet # Windows ipconfig通常在192.168.x.x或10.x.x.x网段找到你的地址。然后在浏览器或 curl 里测试curl http://192.168.1.100:8000/health如果这条命令通了说明服务至少已经从仅本机可见变成了局域网可见。这是整个外部访问链路里面最先要解决的网络绑定问题。很多人的服务已经跑在 0.0.0.0 上了但 Windows 防火墙默认阻止了外部连接于是卡在第二步。3. 防火墙策略明明 host 是 0.0.0.0 了局域网还是连不上服务绑定0.0.0.0之后局域网其他设备访问仍然超时这种情况十有八九是防火墙挡住了入口。操作系统层面的防火墙会检查每个入站连接如果端口没有暴露TCP 握手的 SYN 包直接丢弃表现就是客户端一直卡在连接阶段直到超时。3.1 Windows 防火墙放行端口Windows 上跑 Uvicorn 时第一次启动通常会弹出一个对话框问你是否允许 Python 通过防火墙。如果你点了取消或者压根没看到弹窗那就得手动放行。操作路径是控制面板 - Windows Defender 防火墙 - 高级设置 - 入站规则 - 新建规则 - 端口 - TCP - 特定本地端口填 8000 - 允许连接 - 配置文件全选 - 命名。这里有个小细节值得注意只放行 TCP 8000 就行Uvicorn 用的是 TCP 协议不需要开 UDP。而且如果你换了端口比如从 8000 改成 9000防火墙规则还要再改一次别指望端口变了规则自动生效。3.2 Linux 防火墙firewalld 和 ufwLinux 服务器更加常见。Debian/Ubuntu 系默认可能是 ufwCentOS/RHEL 系可能是 firewalld。先看规则加没加# ufw sudo ufw status # firewalld sudo firewall-cmd --list-all如果是 ufw放行端口sudo ufw allow 8000/tcp如果是 firewalldsudo firewall-cmd --zonepublic --add-port8000/tcp --permanent sudo firewall-cmd --reload很多云服务器的控制台安全组也要单独检查。阿里云、腾讯云这类平台默认会有一层安全组策略需要到控制台去添加入方向规则允许 TCP 8000 端口。这个和系统防火墙是两层独立的机制一层不开就访问不了。我自己踩过最尴尬的一次服务器 firewall 放行了ufw 也放行了但安全组忘了改局域网访问测试了两小时才发现问题。3.3 云服务器安全组和系统防火墙的双层注意点本地局域网部署和云服务器部署的排查路径略有不同。如果你是在一台有公网 IP 的云服务器上跑 Uvicorn那防火墙链路是客户端 - 公网线路 - 云安全组 - 系统 iptables/firewalld - Uvicorn 进程。任何一个环节把端口挡了连接都到不了应用层。所以排查外部连接问题时我习惯按照由外到内逐层检查的顺序1. 确认服务进程在监听 0.0.0.0:8000 2. 确认系统防火墙放行了 8000/tcp 3. 确认云安全组如果有放行 8000/tcp 4. 如果都不行用 tcpdump 抓包看 SYN 是否到达服务器4. uvicorn 一直占用 8000端口冲突的完整排查链路热搜词里uvicorn 一直占用8000出现频率很高这属于部署过程中绕不开的经典问题。第一次在服务器上启动 Uvicorn 时报[Errno 98] Address already in use或者在 Windows 上报[WinError 10048] Only one usage of each socket address很多人第一反应是重启服务器但基本没用因为重启后那个进程可能又自动起来了。4.1 定位谁占用了端口排查思路很简单找到当前监听 8000 端口的进程看它是什么再决定是杀掉还是换端口。# Linux ss -tlnp | grep 8000 # 或者 lsof -i :8000 # Windows netstat -ano | findstr :8000 # 输出的最后一列是 PID再用 tasklist 查进程名 # macOS lsof -i :80004.2 常见占用源和处理方式根据我的经验8000 端口被占用的常见情况有这么几种场景占用进程处理方式之前启动的 Uvicorn 没杀掉python/uvicorn结束进程并确认无残留子进程运行了 Django/Flask 等其他开发服务器python换成 8001 端口或停掉旧服务AI 工具如某些 WebUI默认占用了 8000其他服务换端口这是最省事的系统服务或 Docker 端口映射docker-proxy 等检查容器并调整映射我遇到过最坑的一种情况是之前用nohup uvicorn main:app 启动的服务终端关了但进程还在后台运行。由于没有日志提醒重新启动又提示端口占用查ss -tlnp才发现是之前遗留的进程。杀掉它kill -9 PID如果进程一直杀不掉用kill -9强制结束。但我不建议动不动就kill -9杀 Uvicorn 主进程——Uvicorn 正常退出会清理端口资源强制杀掉可能留下 socket 残留概率比较低但存在再加上如果有--workers 4这类参数会有一组子进程只杀主进程worker 子进程可能进入失控状态。最稳妥的做法是如果服务是 systemd 管的用systemctl restart如果只是手动起的kill -TERM 父PID先尝试优雅退出杀不掉再升级到kill -9。4.3 换端口是不是更优解与其纠结杀掉占用进程有时候换端口更省心。但换端口不能瞎换里面有几个细节要注意端口范围要在 1024 以上1024 以下通常需要 root 权限商用服务尽量避免常见的 8000、8080、8888 这些热门端口很容易被其他软件抢占。我自己喜欢用 18000、18001 这类不那么常见但好记的端口监听端口一旦变更所有客户端调用地址都得跟着变如果有前端代码写死了端口记得同步更新防火墙规则也要一起调整这个前面说过了。如果你希望 Uvicorn 在端口冲突时自动换端口可以在启动命令里加上多个端口参数比如uvicorn main:app --host 0.0.0.0 --port 8000 --port 8001不过这种配置实际意义不大因为服务重启后端口可能一直在变反而增加外部调用方的心智负担。更推荐固定端口加 systemd 守护。5. 从手动启动走向常驻服务systemd 守护 Uvicorn如果你把 FastAPI 服务部署到一台服务器上手动uvicorn main:app --host 0.0.0.0 --port 8000这种方式只能在前台跑一旦 SSH 断开终端关闭服务就跟着没了。虽然可以挂nohup或者screen但都不够可靠——进程崩溃了不会自动拉起机器重启了也不会自动恢复。生产环境推荐用 systemd 来管理它。5.1 一个可用的 systemd 服务文件假设你的项目放在/opt/myapi依赖安装在系统的 Python 环境中或者你自己有 venv路径对应改。创建一个/etc/systemd/system/myapi.service文件[Unit] DescriptionFastAPI Demo Service Afternetwork.target [Service] Userwww-data WorkingDirectory/opt/myapi ExecStart/usr/bin/python3 -m uvicorn main:app --host 0.0.0.0 --port 8000 Restartalways RestartSec3 EnvironmentPYTHONUNBUFFERED1 [Install] WantedBymulti-user.target然后重新加载并启动sudo systemctl daemon-reload sudo systemctl enable --now myapi sudo systemctl status myapiRestartalways是生产部署的关键配置。它保证进程意外退出时systemd 会在 3 秒后重新拉起。这在本地部署 AI 模型服务时尤其重要因为模型推理进程有时会因显存不足或内存溢出退出如果没人盯着手动重启会很被动。5.2 用虚拟环境隔离依赖刚才那个 ExecStart 用的是系统 Python实际项目中我建议使用 venv 或者 conda 环境避免依赖冲突。特别是你同时跑很多 AI 相关的 Python 服务时比如 Ollama 的 Python SDK、FastAPI、数据处理的 pandas、深度学习框架依赖之间冲突概率很高。修改 ExecStart 指向虚拟环境目录即可ExecStart/opt/myapi/venv/bin/python -m uvicorn main:app --host 0.0.0.0 --port 8000注意不要直接ExecStart/opt/myapi/venv/bin/uvicorn ...虽然也能跑但用python -m uvicorn能保证和当前 Python 解释器配套避免 PATH 里多个 Python 版本把你搞晕。5.3 多 worker 配置要谨慎网上很多教程建议加--workers 4来提升并发能力但在本地部署阶段我反而不是很推荐。原因有两个第一--workers在 Uvicorn 中是启动多个进程每个进程都会加载一遍 FastAPI 应用。如果你的应用里有大的 AI 模型加载比如本地部署了语言模型那每个 worker 都会占一份显存。8G 显存的机器模型加载两份可能直接 OOM。第二并发能力不是靠随便加 worker 就能提升的还要看你的应用有没有状态共享问题。FastAPI 默认你是无状态的但如果用了内存存储比如内存里缓存了向量库索引多 worker 之间数据不共享就会出现请求打到了 worker A 有缓存打到 worker B 就没有这种诡异表现。如果需要多进程先确认你的应用是无状态的再考虑加--workers。本地部署阶段--workers 2通常已经够用没必要盲目上 8 个。6. 让局域网以外的设备能访问公网部署的几条路径前面讲的都是局域网内外部访问。但很多时候你需要的不是局域网而是让外网设备比如另一座城市的电脑、你手机 4G 网络也能访问到本地部署的服务。这个场景有几个可行路径按操作难度从低到高排列。6.1 云服务器反向代理最稳的方案如果你的本机或者内网服务器已经跑着 FastAPI但带宽和稳定性不够或者不想把内网服务直接暴露到公网那么最推荐的方式是在一台有公网 IP 的云服务器上部署 Nginx再由 Nginx 把请求转发到你内网的 Uvicorn 服务。举个例子你在云服务器上有域名api.example.comNginx 配置server { listen 80; server_name api.example.com; location / { proxy_pass http://192.168.1.100:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }这样外部用户访问http://api.example.com实际上是 Nginx 接收请求再转发到你内网的 FastAPI。这种架构的好处是Nginx 可以同时做 HTTPS 终结、负载均衡、静态文件服务你的 FastAPI 服务只需要专心处理业务逻辑。6.2 内网穿透工具没有公网 IP 时的替代方案如果你的服务运行在家里或者公司内网没有公网 IP也申请不到端口映射权限那就得用内网穿透类工具。这类工具的原理是内网机器主动建立一个到公网中转服务器的连接外部用户访问中转服务器的某个端口中转服务器再通过这个已经建立的隧道把请求转发到你的内网服务。常见工具有 frp、ngrok、cpolar 等。以 frp 为例你在一台有公网 IP 的服务器上运行 frpsserver在内网运行 frpcclient就行了。frpc.ini 里配置[common] server_addr 你的云服务器IP server_port 7000 [web] type tcp local_ip 127.0.0.1 local_port 8000 remote_port 8000然后外部用户直接访问你的云服务器 IP:8000流量经过 frp 隧道导到内网 FastAPI。这里要提醒一句内网穿透工具让服务暴露到了公网安全风险也同步放大。如果还是 HTTP 明文传输能抓到数据包的人都能看到请求内容。强烈建议穿透后再套一层 HTTPS或者至少放一个 Token 校验在前面。6.3 安全底线公开到公网之前必须做的事不管用哪种方式把 FastAPI 暴露到公网至少要做好几件事否则我可以负责任地说不出 72 小时就有人来扫描你的服务。第一不要把 Uvicorn 直接裸露在公网上。Uvicorn 是 ASGI 服务器不是安全边界。最优架构是公网 - NginxHTTPS- FastAPI。如果非要直连 Uvicorn也要确保服务本身有鉴权。第二给 API 加认证。FastAPI 里可以用 OAuth2 密码模式、JWT、简单的 API Key。最简单粗暴的版本是依赖注入加 Header 校验from fastapi import FastAPI, Header, HTTPException app FastAPI() app.get(/secure) def secure_read(x_api_key: str Header(...)): if x_api_key ! 你的密钥: raise HTTPException(status_code403, detailForbidden) return {data: secret}第三限制来源 IP。用 Nginx 限制到只有特定 IP 段能访问或者 Uvicorn 层面配合 allow_origins 做跨域限制。这一步看具体需求如果服务只给同事用直接把办公室出口 IP 加到白名单里就行。7. 本地部署 AI 大模型场景的额外心得这段时间热搜词里密集出现本地部署 DeepSeekOllama 本地部署Dify 本地部署这类关键词可见很多人其实不是写业务接口而是在把大模型工具链部署到本地后需要暴露 API。这个场景和普通的 FastAPI 部署有几个明显不同的注意点我单独拉出来讲。7.1 Uvicorn 只是入口模型加载才是重点用 FastAPI 封装本地大模型推理服务的时候Uvicorn 的配置反而简单难点在模型加载上。比如你部署 Llama 系列或者 DeepSeek 量化模型通常流程是启动时加载模型到显存/内存每个 HTTP 请求进来后调用模型进行推理返回结果。一个常见的错误是每个请求都重新加载模型这样性能会奇差无比。正确做法是启动时加载一次全程复用from fastapi import FastAPI from transformers import AutoModelForCausalLM, AutoTokenizer app FastAPI() tokenizer AutoTokenizer.from_pretrained(./deepseek-local) model AutoModelForCausalLM.from_pretrained(./deepseek-local) app.post(/generate) def generate(prompt: str): inputs tokenizer(prompt, return_tensorspt) outputs model.generate(**inputs, max_new_tokens512) return {text: tokenizer.decode(outputs[0], skip_special_tokensTrue)}Ollama 这类工具把模型加载细节封装掉了你只要用它的 HTTP API 就行。但如果你是自己用 FastAPI 封装 Transformers 模型记得把模型加载放到全局变量而不是函数内部。7.2 模型推理超时和 Uvicorn 的 timeout 设置大模型推理通常耗时较长特别是没有 GPU、纯 CPU 跑的情况下生成几百个 token 可能需要几十秒甚至几分钟。而 Uvicorn 默认对请求处理没有超时限制超时主要由中间的代理层控制。如果你在 Nginx 后面Nginx 默认的proxy_read_timeout是 60 秒大模型推理一慢就会被掐断。所以要修改 Nginx 超时时间location /generate { proxy_pass http://127.0.0.1:8000; proxy_read_timeout 300s; proxy_send_timeout 300s; proxy_connect_timeout 60s; }如果是公网穿透frp 也有可能因为长时间没有数据交互而判定连接空闲断开需要适当调大心跳间隔。这类隐蔽的问题排查起来很费劲我一开始还以为是 Uvicorn 的问题后来抓包才发现是中间层超时。7.3 显存占用和并发请求的矛盾本地部署大模型服务时还容易遇到一个看着像是 Uvicorn 崩了其实是 OOM的问题。并发请求一多显存瞬间溢满Python 进程直接崩溃。这时候检查服务日志会发现有 CUDA out of memory 的报错。解决办法有几个方向模型加载时加上半精度或 int8 量化减少显存占用Uvicorn 只开单 worker在应用层做并发控制比如用信号量限制同时只有 1 个推理任务执行。比如import asyncio from fastapi import FastAPI app FastAPI() semaphore asyncio.Semaphore(1) app.post(/generate) async def generate(prompt: str): async with semaphore: # 你的推理代码 return {text: result}这样即使外部并发请求很多推理部分仍然是串行的不会打爆显存。代价是高峰请求排队但对本地部署场景来说稳定比吞吐量重要得多。8. 从踩坑经验里提炼出的几条部署建议前面七章把从本机到局域网再到公网的完整链路讲完了最后分享几个我没法归类到某一节但特别重要的经验都是实际部署中反复遇到的问题。8.1 日志管理不能省手动uvicorn main:app跑起来日志直接打印到终端。一旦升级成 systemd 服务日志就进入 journald。如果服务出了问题对应去看日志journalctl -u myapi -fUvicorn 自带访问日志默认会打印每个请求的状态码。调试阶段可以开着生产环境如果想要日志少一点可以加参数--no-access-log但我的建议是生产环境不要关访问日志。一旦出现安全问题或者异常请求访问日志是最直接的溯源依据。8.2 启动脚本容易忽略 PYTHONUNBUFFERED之前 systemd 那个文件里我写了EnvironmentPYTHONUNBUFFERED1。这个不是随手加的。默认情况下 Python 的输出是块缓冲的日志不会实时刷到 stdoutsystemd 里看日志会有延迟。加了PYTHONUNBUFFERED1后强制逐行输出排查问题的时候能看到即时的打印信息。8.3 善用 Uvicorn 的 --reload 但仅限开发环境FastAPI 开发时改代码后要手动重启服务很烦人所以--reload参数很好用uvicorn main:app --host 0.0.0.0 --port 8000 --reload开启后文件变化自动重启。但生产环境千万不要加--reload这不仅仅是性能问题更重要的是它会让运行状态变得不可控系统可能在不知道改了哪个文件的情况下无声重启而且--reload默认会启动一个监控进程资源占用也会多一份。8.4 本地部署的 FastAPI 也要考虑 CORS如果你的 FastAPI 是给前端页面调的而前端跑在另一个域名或端口就会遇到跨域问题。FastAPI 处理这个很简单from fastapi.middleware.cors import CORSMiddleware app.add_middleware( CORSMiddleware, allow_origins[http://localhost:5173], allow_methods[*], allow_headers[*], )但这里有个安全细节开发时可以allow_origins[*]生产环境最好收敛到具体域名。如果你把 API 开放给所有人而这个 API 又恰好有写操作那么任何恶意网页都能从用户浏览器发起跨域请求这是 CSRF 攻击的高发场景。现在回头看最让我踩得深的一个坑就是把所有精力花在如何让外部访问的第一步——改 host 和防火墙却忽视了部署架构的完整链条服务绑定、防火墙、安全组、进程守护、代理层、超时设置。每一步单独看都不难但串起来之后任何一个环节遗漏都会导致访问失败。这篇东西写出来本质上就是把我自己踩过的坑和验证过的方案整理成了一份可以照着操作的清单。你在部署的时候如果又碰到新问题记住一个原则先从网络链路逐层排查再从进程状态和服务日志里找线索别急着重启机器大多数问题都不是重启能解决的。
返回列表