当前位置:首页>python>部署一个 Python 应用有多麻烦?从 SSH 到 systemd 的部署

部署一个 Python 应用有多麻烦?从 SSH 到 systemd 的部署

  • 2026-09-11 00:43:07
部署一个 Python 应用有多麻烦?从 SSH 到 systemd 的部署

在本地写了个 ML Playground,同事想试一下,让他跑我电脑前来操作也太业余了,干脆部署到服务器上。本以为装个 Python、跑个 uvicorn 就完事,结果从拿到服务器 IP 到跑成开机自启的服务,折腾了小半天。

这篇把整个过程记下来,从最原始的 SSH 连上去,到用 systemd 做成一个"挂了自动重启、开机自动拉起"的生产级服务。

以三篇文章走通一个完整的 Web 应用部署路线。

本篇从零徒手部署(SSH → scp → venv → nohup → systemd),工作中你不会用这种方式部署新项目,但 Docker 藏起来的就是这层,理解了它,出问题才知道从哪查。

Docker 容器化把代码和环境打包成镜像,这是行业默认标准,换服务器、加节点、回退版本都不用重复搭环境。

Docker Compose把 api + 数据库 + 反向代理串起来,真实项目很少只有单个服务,Compose 用一个 YAML 文件定义所有服务,一行命令启动全部。

关于环境:本文操作基于 Windows WSL2 上的 Ubuntu 22.04,不需要实体服务器。Docker 直接在 WSL 内安装(apt install docker.io),Windows 用户启用 WSL2 即可跟着操作。

一、起点:一个能跑的 FastAPI 应用

ML Playground是一个简单的 FastAPI 应用,目录结构不复杂:

ml-learning/├── projects/│   └── ml_playground/│       ├── app.py              # FastAPI 入口│       ├── core.py             # 算法注册中心│       ├── ui.py               # Gradio 前端│       └── datasets.py         # 内置数据集加载├── models/                     # 14 个 numpy 算法实现├── data/                       # Titanic、Mall 等 CSV├── requirements.txt└── ...

本地已经跑过很多次了,一行命令就能启动:

pip install -r requirements.txtuvicorn projects.ml_playground.app:app --host 0.0.0.0 --port 8000

现在问题很简单:怎么让它跑在服务器上,别人也能访问?答案没有那么简单。整个过程可以拆成五步。

ML Playground 界面。本地跑好的应用,目标是部署到服务器上让同事也能用

二、第一步:SSH 连上去

拿到服务器 IP 之后,第一步当然是连上去:

ssh root@123.123.123.123

第一次连会让你确认主机指纹,输入 yes,再输密码。

每次 SSH 都要输密码,既繁琐又不安全,后面如果要写自动部署脚本,密码登录根本行不通。

配置 SSH 密钥对可以一劳永逸:

ssh-copy-id root@123.123.123.123

这条命令把公钥追加到服务器 ~/.ssh/authorized_keys 里。之后 SSH 连接时,服务器用你的公钥验证身份,自动放行,不再需要密码。

左半部分:密码登录,三步手动操作,每次都要重复;右半部分:密钥登录,配置一次,后续自动登录。

三、第二步:把代码传上去

连上服务器了,代码还在本地。传文件最直接的方法是 scp:

# 把整个项目根目录传上去(projects/、models/、data/、requirements.txt 一起)scp -r projects/ models/ data/ requirements.txt root@123.123.123.123:~/ml-learning/

但 scp 每次全量传输,项目大了之后很慢。日常更新代码时,用 rsync 只传变化的部分:

rsync -avz --progress \    projects/ models/ data/ requirements.txt \    root@123.123.123.123:~/ml-learning/

-a 保留文件属性,-z 压缩传输,-v 显示详情。第一次传输差别不大,但后续更新时 rsync 比 scp 快一个数量级,因为只传改过的文件。

以下说的是源路径(要传的目录)的斜杠行为,目标路径带不带斜杠效果一样:

ml-learning/(带斜杠)和 ml-learning(不带斜杠)行为不同:

  • ml-learning/:复制目录里的内容到目标路径下
  • ml-learning(无斜杠):把整个目录复制到目标路径下,结果是 ~/ml-learning/ml-learning/...

日常同步推荐带斜杠,符合直觉。

ML Playground 有 14 个算法模型文件,经常改一个就要重新部署。首次部署用 scp(命令简单),日常更新用 rsync(只传改动)。

左侧本地机器(项目目录树)、右侧服务器(已接收的文件)。scp 全量传输适合首次,rsync 增量传输适合日常更新。

四、第三步:装环境,这一步开始出问题

SSH 进去了,代码传上去了,接下来装 Python 环境:

sudo apt updatesudo apt install -y python3 python3-pip python3-venv

然后创建虚拟环境,装项目依赖:

cd ~/ml-learningpython3 -m venv .venvsource .venv/bin/activatepip install -r requirements.txt

每个项目独立环境,互相不干扰。

五、第四步:跑起来,然后发现它活不长

环境装好了,启动应用:

nohup .venv/bin/python -m uvicorn projects.ml_playground.app:app --host 0.0.0.0 --port 8000 &

nohup 保证 SSH 断开后进程继续跑,& 把进程放到后台。直接用 .venv/bin/python 调起虚拟环境里的 Python,不依赖 source activate。

验证一下:

curl http://localhost:8000/docs

Swagger 页面返回了。关掉 SSH 终端,在本地浏览器打开 http://123.123.123.123:8000 也能打开。

看起来搞定了,同事试了一下说还行,但是过两天又挂了,同事跟我说打不开了,再跑。

nohup 只管"SSH 断开后继续跑",管不了"进程崩溃后重新跑"和"机器重启后自动跑"。 它是个临时方案,不是服务管理工具。

左栏 nohup(临时方案:一行命令能跑,但重启消失、崩溃不自愈);右栏 systemd(生产方案:开机自启、崩溃自动重启、日志集中管理)。

六、第五步:systemd,让它自己活着

nohup 适合临时测试,生产环境需要更可靠的方案。Linux 自带的 systemd 是操作系统的服务管理器,让程序开机自启、崩溃自动重启。

创建一个服务文件,告诉 systemd 怎么管理你的应用:

sudo vim /etc/systemd/system/ml-playground.service
点击展开 systemd 服务文件完整内容
[Unit]Description=ML Playground APIAfter=network.target[Service]Type=simpleUser=rootWorkingDirectory=/root/ml-learningExecStart=/root/ml-learning/.venv/bin/python -m uvicorn projects.ml_playground.app:app --host 0.0.0.0 --port 8000Restart=alwaysRestartSec=5[Install]WantedBy=multi-user.target

几个关键配置说明:

  • WorkingDirectory=/root/ml-learning 指定项目根目录(与前面 cd ~/ml-learning 一致)
  • ExecStart 用虚拟环境里的 python -m uvicorn,模块路径 projects.ml_playground.app:app 与第一节启动命令一致
  • Restart=always + RestartSec=5:挂了等 5 秒自动重启
  • WantedBy=multi-user.target:开机自启

然后加载并启动:

sudo systemctl daemon-reload          # 重新加载配置sudo systemctl enable ml-playground   # 开机自启sudo systemctl start ml-playground    # 启动服务sudo systemctl status ml-playground   # 查看状态

看到 active (running) 就说明成功了,从这以后不管服务器重启多少次、进程崩溃多少次,systemd 都会自动把它拉起来。

七、出问题了怎么看

服务运行过程中出了问题,第一件事是看日志。这几个命令可以记下来:

# systemd 日志(包含 uvicorn 的 stdout)sudo journalctl -u ml-playground -n 50 --no-pager  # 最近 50 行sudo journalctl -u ml-playground -f                 # 实时跟踪# 看谁占了端口ss -tlnp | grep 8000# 看进程是否活着ps aux | grep uvicorn

这几个命令不用记住每条参数,记住三条即可:日志用 journalctl、端口用 ss、进程用 ps,具体参数忘了就 --help。

journalctl(systemd 日志,等价于 tail -f 应用日志)、ss/ps(系统状态),三组工具覆盖 90% 的排查场景。

八、回头看

这一套走下来,核心就是五步:

但这套流程有一个本质问题:换一台机器,以上步骤全部重来。

哪怕两台都是 Ubuntu 22.04,也要重新 SSH、重新 apt、重新 pip install、重新配 systemd。换成 CentOS 的话,apt 变 yum,systemd 路径都不一样,几乎等于重新学一遍。

systemd 管的是进程挂了自动重启,管不了从零搭环境。下一篇 Docker 容器化:一次构建,到处运行 把代码和环境打包在一起,解决这个问题。


部署环境: Ubuntu 22.04 LTS(Windows WSL2)

最新文章

随机文章