← 返回首页

Python 应用的 Windows Server 部署方案

在 Linux 上部署 Python 服务通常用 systemd,但在 Windows Server 上有 几种不同的选择。本文对比了我实际用过的三种方案,帮助你在不同场景下做出选择。

方案对比

方案开机自启崩溃重启日志复杂度
NSSM 服务✅ 可配置✅ 自动
Task Scheduler需手动重定向
手动启动控制台极低

方案一:NSSM 注册 Windows 服务(推荐)

NSSM(Non-Sucking Service Manager)是一个免费工具,把任意可执行程序 包装成 Windows 服务,支持崩溃后自动重启、日志自动收集。

安装步骤

# 下载 nssm.exe 放到 C:\tools\nssm\
# 然后注册服务(管理员 CMD)
C:\tools\nssm\nssm.exe install cps_dashboard python
# 弹出 GUI:
#   Path: C:\Python312\python.exe
#   Startup dir: C:\cps_dashboard
#   Arguments: -m uvicorn main:app --host 0.0.0.0 --port 8000

常用操作

sc start cps_dashboard
sc stop  cps_dashboard
sc query cps_dashboard

优点

  • 服务崩溃后可配置自动重启(Recovery 选项)
  • 日志自动写到文件,无需手动重定向
  • 在 services.msc 中可视化管理

方案二:Task Scheduler 开机自启

适合对崩溃重启要求不高的场景,配置更简单。 缺点是进程崩溃后不会自动重启,需要手动干预。

$action = New-ScheduledTaskAction `
    -Execute "python" -Argument "main.py" `
    -WorkingDirectory "C:\jiyun_tallybook\server"

$trigger  = New-ScheduledTaskTrigger -AtStartup
$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit 0

Register-ScheduledTask `
    -TaskName "MyService" -Action $action `
    -Trigger $trigger -Settings $settings `
    -RunLevel Highest -User "SYSTEM" -Force
-ExecutionTimeLimit 0 关闭超时限制,否则任务会被自动终止。

日志重定向

Task Scheduler 默认不捕获输出,需要显式重定向:

Start-Process -FilePath "python" `
  -ArgumentList "main.py" `
  -WorkingDirectory "C:\myapp" `
  -RedirectStandardOutput "C:\myapp\logs\app.log" `
  -RedirectStandardError  "C:\myapp\logs\app.log" `
  -WindowStyle Hidden

自动化部署脚本

用 Paramiko 从本地开发机直接 SSH 上传文件并重启服务, 避免每次都要手动 RDP 进去操作:

import paramiko, os

HOST = "106.52.93.127"; USER = "Administrator"
KEY  = r"d:\workspace\deploy_key"

FILES = [
    ("main.py",       "C:/myapp/main.py"),
    ("routers/api.py","C:/myapp/routers/api.py"),
]

key    = paramiko.RSAKey.from_private_key_file(KEY)
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(HOST, 22, USER, pkey=key)

sftp = client.open_sftp()
for local, remote in FILES:
    sftp.put(local, remote)
    print(f"  ✓ {local}")
sftp.close()

client.exec_command("sc stop myservice")
import time; time.sleep(2)
client.exec_command("sc start myservice")
client.close()
print("Deploy done.")

端口冲突排查

启动失败时先查端口是否被占:

netstat -ano | findstr :8000
# 找到 PID 后查进程
tasklist | findstr