IIS + wfastcgi 部署 Flask 应用实战:从连续 500 到稳定 200 的完整踩坑实录

一、背景

我开发了一个 Flask 数据采集控制台(工银理财在售产品采集),数据源是工银理财官网,前端页面每 1.2 秒轮询一次 /api/status 展示采集进度。应用有两个"特殊体质":

  1. 任务状态全部在内存里 (后台线程采集 + 全局变量记录进度),所以必须单进程常驻,任何形式的进程回收/重启都会导致采集任务状态丢失;
    1. 代码本身没有任何改动空间(部署目标就是"不改代码")。
      部署目标:Windows Server + IIS,把 python web_app.py 跑起来的 5022 端口,正式变成 IIS 托管的站点。

二、方案选型:为什么是 IIS + wfastcgi

一开始考虑了两个方案:

方案 说明 结论
A:IIS + wfastcgi 微软官方推荐的 Python 托管方式,FastCGI 协议,应用代码零改动 ✅ 选用
B:宝塔面板反向代理 本机是 Windows 版宝塔,官方未集成 Python 项目管理器,反向代理还依赖 IIS URL Rewrite 模块(未安装) ❌ 仅作备选

核心结论:Windows 上部署 Flask,IIS + wfastcgi 是最正统的路线;宝塔的 Python 支持主要在 Linux 版,Windows 版基本指望不上。

三、部署步骤全记录

1. 环境与工具

  • Python 3.11.5(D:\Programs\Python\Python311\python.exe,PyCharm 同款解释器)
    • IIS(本机已装,有多个既有站点)
    • 全程用管理员 PowerShell + appcmd.exeC:\Windows\System32\inetsrv\appcmd.exe

注意:本机 PowerShell 是 5.1 老版本,不支持 && 串联命令 ,一直用 ;。另外 appcmd 传中文参数值会丢字(后面有专门一坑),涉及中文的配置我后来都绕开了。

2. 安装 wfastcgi

powershell 复制代码
D:\Programs\Python\Python311\python.exe -m pip install wfastcgi

安装后 wfastcgi.py 落在 Lib\site-packages\wfastcgi.py(版本 3.0.0)。

官方推荐 python -m wfastcgi enable 一键注册,但我执行时挂起超时了(它内部还会去改 IIS 配置),于是放弃,改为手动用 appcmd 注册,可控性反而更好。

3. 注册 FastCGI 应用(坑 1:必须 fullPath + arguments 一起注册)

powershell 复制代码
appcmd set config -section:system.webServer/fastCgi "/+[fullPath='D:\Programs\Python\Python311\python.exe',arguments='D:\Programs\Python\Python311\Lib\site-packages\wfastcgi.py',signalBeforeTerminateSeconds='30']" /commit:apphost

# 防回收三件套(采集任务常驻必需)
appcmd set config -section:system.webServer/fastCgi "/[fullPath='D:\Programs\Python\Python311\python.exe'].instanceMaxRequests:10000000" /commit:apphost
appcmd set config -section:system.webServer/fastCgi "/[fullPath='D:\Programs\Python\Python311\python.exe'].activityTimeout:600" /commit:apphost
appcmd set config -section:system.webServer/fastCgi "/[fullPath='D:\Programs\Python\Python311\python.exe'].requestTimeout:600" /commit:apphost

坑 1 详解 :只注册 fullPath 不带 arguments 时,站点启动报 0x80070585(无法在 fastCGI 配置中找到 handler scriptProcessor)。因为 wfastcgi 的 handler 匹配规则是 fullPath + arguments 必须同时匹配 ,漏掉 arguments 就等于没注册上。另外 appcmdinstanceMaxRequests 不接受 0(最小 1),10000000(1e7)等效"永不回收"。

4. 编写 web.config

放在项目根目录(应用物理路径下):

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <system.webServer>
      <handlers>
            <add name="Python FastCGI" path="*" verb="*" modules="FastCgiModule" resourceType="Unspecified"
                       scriptProcessor="D:\Programs\Python\Python311\python.exe|D:\Programs\Python\Python311\Lib\site-packages\wfastcgi.py" />
                           </handlers>
                               <appSettings>
                                     <add key="WSGI_HANDLER" value="web_app.app" />
                                           <add key="PYTHONPATH" value="D:\产品\MIS\FinancialProduct\src\icbc_wm" />
                                                 <add key="WSGI_LOG" value="D:\产品\MIS\FinancialProduct\src\icbc_wm\wfastcgi.log" />
                                                     </appSettings>
                                                       </system.webServer>
                                                       </configuration>
                                                       ```
三个 appSettings 的含义:

- `WSGI_HANDLER`:WSGI 应用入口(`模块名.app`);
- - `PYTHONPATH`:项目目录,让 `import web_app` 能找到代码;
- - `WSGI_LOG`:**排障神器**,wfastcgi 会把全生命周期和 Python 异常 traceback 写进这个日志文件(后面排障全靠它)。
### 5. 创建 IIS 站点(坑 2:绑定字符串必须三段式)

```powershell
appcmd add site /name:icbc_wm /physicalPath:"D:\产品\MIS\FinancialProduct\src\icbc_wm" /bindings:http/*:5022

创建成功但启动时报 0x80070057 参数不正确。折腾半天发现根因:IIS 绑定字符串必须是 协议://IP:端口:主机名 三段式,末尾的冒号不能省,正确写法:

powershell 复制代码
appcmd set site icbc_wm /bindings:http/*:5022:

6. 专用应用池(防回收核心配置)

powershell 复制代码
appcmd add apppool /name:icbc_wm
appcmd set apppool icbc_wm /managedRuntimeVersion:""          # 无托管代码
appcmd set apppool icbc_wm /processModel.idleTimeout:00:00:00  # 空闲不回收
appcmd set apppool icbc_wm /recycling.periodicRestart.time:00:00:00  # 定时不回收
appcmd set app icbc_wm/ /applicationPool:icbc_wm

采集任务在后台线程里跑、进度存内存,任何回收都是灾难,所以:空闲超时 0、定期回收 0、instanceMaxRequests 拉到 1e7。

7. 防火墙放行端口

powershell 复制代码
New-NetFirewallRule -DisplayName "icbc_wm 5022" -Direction Inbound -Action Allow -Protocol TCP -LocalPort 5022

8. 权限修复(坑 3:应用池身份读不了 Python)

启动后日志报 0x8007010b 目录无效、FastCGI 进程意外退出。排查发现:D:\Programs\Python\Python311 的 ACL 只有 Administrators/SYSTEM,应用池身份(IIS APPPOOL\icbc_wm)根本读不了 python.exe。授权:

powershell 复制代码
icacls "D:\Programs\Python\Python311" /grant "IIS APPPOOL\icbc_wm:(OI)(CI)RX" /T
icacls "D:\产品\MIS\FinancialProduct\src\icbc_wm" /grant "IIS APPPOOL\icbc_wm:(OI)(CI)M" /T

(Python 目录 10 万+文件,/T 递归跑了两分钟,属正常。)

9. 中文物理路径乱码(坑 4:最隐蔽的一坑)

权限修好后进程能启动了,但还是 500。这次用 WSGI_LOG 抓到了真相:

复制代码
FileNotFoundError: [WinError 3] 系统找不到指定的路径。: 'D:\\²úÆ·\\MIS\\FinancialProduct\\src\\icbc_wm\\'

"产品"被 IIS 转成了 ²úÆ· !这是 GBK 编码被按 Latin-1 解码的经典乱码------IIS 在把物理路径作为 APPL_PHYSICAL_PATH 传给 FastCGI 进程时,对非 ASCII 路径会编码损坏 ,wfastcgi 里 os.chdir(physical_path) 直接 FileNotFoundError。

解法:目录联接(junction)方案------建一个纯 ASCII 路径的联接指向真实目录,站点物理路径用 ASCII 路径,代码零拷贝:

powershell 复制代码
cmd /c mklink /J "D:\wwwroot\icbc_wm" "D:\产品\MIS\FinancialProduct\src\icbc_wm"
appcmd set vdir "icbc_wm/" /physicalPath:"D:\wwwroot\icbc_wm"

改完后 chdir 正常,进程顺利走到 import flask

10. 依赖版本兼容(坑 5:Flask 1.1.2 与 Jinja2 3.1.6 不兼容)

日志显示下一个错误:

复制代码
ImportError: cannot import name 'escape' from 'jinja2'

Flask 1.1.2 还在用 from jinja2 import escape 这种旧式导入,而环境里的 Jinja2 已经被升到 3.1.6(顶层 escape 被移除了)。验证一下:python -c "import flask" 直接复现同样报错------连手动 python web_app.py 都跑不起来了(之前能跑是因为旧进程没重启过)。

修复思路:这台机器的 Python 是共享环境(还装着 Flask-SocketIO、flask-cors 等,可能是别的项目在用),全局升级 Flask 风险大 。所以创建独立虚拟环境,只给这个应用用:

powershell 复制代码
# 1. 创建 venv
D:\Programs\Python\Python311\python.exe -m venv D:\wwwroot\icbc_wm_venv

# 2. 安装兼容组合(Flask 2.x 对旧代码完全兼容)
D:\wwwroot\icbc_wm_venv\Scripts\python.exe -m pip install flask==2.3.3 pymysql requests wfastcgi -i https://pypi.tuna.tsinghua.edu.cn/simple

# 3. 重新注册 FastCGI 到 venv 的 python
appcmd set config -section:system.webServer/fastCgi "/+[fullPath='D:\wwwroot\icbc_wm_venv\Scripts\python.exe',arguments='D:\wwwroot\icbc_wm_venv\Lib\site-packages\wfastcgi.py',signalBeforeTerminateSeconds='30']" /commit:apphost

# 4. venv 目录同样授权给应用池
icacls "D:\wwwroot\icbc_wm_venv" /grant "IIS APPPOOL\icbc_wm:(OI)(CI)RX" /T

# 5. web.config 的 scriptProcessor 改为 venv 路径

心得 :部署环境里如果历史包袱(旧 Flask、混合装的包)已经没法理清,venv 隔离是成本最低、最不容易误伤其他项目的方案

四、验收结果

测试项 结果
首页 GET / ✅ 200,页面正常渲染(含"工银理财"标题)
GET /api/status ✅ 200,{"running":false,...} 正常 JSON
GET /api/products ✅ 200,返回 1656 只产品真实数据(主表采集时间 2026-09-14)
回收应用池后再测 ✅ 依然全部 200,配置持久生效
站点/应用池/防火墙 ✅ 全部 Started / 已放行

五、排障方法论总结(心得)

这次部署最值钱的不是步骤,而是排障顺序

  1. 先抓日志,别猜 。wfastcgi 的 WSGI_LOG 环境变量是唯一能拿到 Python 侧完整 traceback 的通道。我前几个 500 全靠猜,效率极低;把 WSGI_LOG 加上后,每个错误都是一次定位。
    1. 区分"环境问题"和"IIS 问题" 。用同一个解释器手动 python -c "import flask" 复现,就能判断报错跟 Web 环境无关、是包组合本身坏了------这直接决定了修复方向。
    1. 中文是 Windows 部署的头号敌人。站点路径、命令行参数、环境变量值,任何带中文的地方都可能在不同环节被编码搞坏。能用 ASCII 就用 ASCII(junction、venv 都是这个思路)。
    1. 最小改动原则。共享 Python 环境别乱动,venv 隔离;系统配置改前先备份(我备份了 applicationHost.config)。
    1. 回收验证不可省。部署完回收一次应用池再测,确认配置是"持久生效"而不是"碰巧活着"。

六、上线后维护要点

  • 改代码:直接改原目录 D:\产品\MIS\FinancialProduct\src\icbc_wm\(junction 自动同步),然后 appcmd recycle apppool icbc_wm 即可;
    • wfastcgi.log 会持续增长,定期清理;
    • 应用池/站点配置都在 applicationHost.config,有备份 .bak_icbc

最终效果http://127.0.0.1:5022/ 由 IIS 托管,开机即用,无需手动 python web_app.py,采集任务单进程常驻不回收。希望这篇实录能帮到在 Windows 上部署 Flask 的同学们。

相关推荐
WiKiLeaks_successor1 小时前
Scipy库里的众数函数不严谨,我把它重构了。
python·scipy
傻啦嘿哟1 小时前
房产数据对比爬虫:同时爬取链家+贝壳+安居客,做房价横向对比
python
小静AI工程实验室1 小时前
JS 逆向接口 ID 变了?Python 与 Node.js 复现 JSON 大整数精度丢失
javascript·python·node.js
李航19831 小时前
用 DeepDraw 几何引擎开发建筑设计软件(五):创建选择工具
python·3d
Metaphor6922 小时前
使用 Python 设置 Excel 行列自适应 【代码示例】
python·excel
花酒锄作田10 小时前
FastAPI 使用 session 认证
python·fastapi
lsswear10 小时前
Python 并发 线程
开发语言·python
Ivanqhz11 小时前
MLIR OpBuilder
开发语言·python·mlir
威联通安全存储12 小时前
TS-h2287XU-RP 在家电制造总装与质检数据场景的部署
python·制造