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.exe(C:\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 就等于没注册上。另外 appcmd 的 instanceMaxRequests 不接受 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 的同学们。

相关推荐
ZhangJun9514 小时前
Mobike 共享单车分析项目
人工智能·python·算法·kmeans·聚类·knn
XLYcmy14 小时前
PDF 论文处理器 — 优势与功能文档
大数据·python·网络安全·pdf·dify·rag·漏洞检测
小猴子爱上树14 小时前
跨马翻译:批量图片翻译+视频字幕+智能抠图,跨境电商在线图片翻译工具
大数据·人工智能·python·音视频
winfredzhang14 小时前
用 Python 手写一个“所见即所得“的图片水印工具:wxPython + Pillow 实战详解
python·pillow·rotate·crop·身份改水印
xUxIAOrUIII14 小时前
日常笔记-1005-1
linux·笔记·python·macos
yl453014 小时前
内蒙热门的废酸再生处理企业
大数据·python
IT方大同15 小时前
java输入输出+方法
java·开发语言·python
Java后端的Ai之路15 小时前
Python 进阶探索30 - Python中的装饰器
开发语言·人工智能·python·文件处理·装饰器模式
言乐615 小时前
Python基于关键词分拣快递(适用于电商与货运代理等)
开发语言·python·django·virtualenv·pygame
代码方舟15 小时前
Python数据工程:利用天远名下车辆车牌查询A优化个人债务重组与清偿能力评估合规体验
人工智能·python