用 systemd 托管 Python Web 应用:Gunicorn + Flask 开机自启与崩溃自动重启实战
一、为什么不要再用 nohup 跑服务
刚买云服务器时,最常见的部署方式是:nohup python app.py > app.log 2>&1 &
它能跑,但问题很多:服务器重启后服务不会自动启动;进程异常退出后没人重新拉起;日志越写越大没有轮转;重启服务还得先 ps 找进程号再 kill。现在主流 Linux 发行版(Ubuntu、Debian、CentOS、EulerOS、openEuler 等)都使用 systemd,写一个几十行的 unit 文件,就能让应用享受和 nginx、sshd 一样的待遇:开机自启、失败重启、统一日志、权限隔离。
二、准备示例应用
项目放在 /opt/myapp,先创建独立运行用户:
sudo useradd --system --no-create-home --shell /usr/sbin/nologin myapp
sudo mkdir -p /opt/myapp
sudo chown -R myapp:myapp /opt/myapp
cd /opt/myapp
sudo -u myapp python3 -m venv venv
sudo -u myapp ./venv/bin/pip install flask gunicorn
/opt/myapp/app.py:
from flask import Flask, jsonify
import os, socket
app = Flask(__name__)
@app.route("/")
def index():
return jsonify(status="ok", host=socket.gethostname(), pid=os.getpid())
@app.route("/health")
def health():
return "healthy", 200
手动验证:sudo -u myapp ./venv/bin/gunicorn -w 2 -b 127.0.0.1:8000 app:app ,另开终端 curl http://127.0.0.1:8000/ 返回 JSON 即正常。
三、Gunicorn 配置文件 /opt/myapp/gunicorn.conf.py
import multiprocessing
bind = "127.0.0.1:8000"
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = "sync"
timeout = 30
graceful_timeout = 30
keepalive = 5
accesslog = "-"
errorlog = "-"
loglevel = "info"
accesslog 和 errorlog 设为 "-" 会写到标准输出,由 systemd 收进 journald,用 journalctl 统一查看。2核2G 的小服务器可把 workers 手动设为 3 左右。
四、systemd unit 文件 /etc/systemd/system/myapp.service
[Unit]
Description=MyApp Flask service (Gunicorn)
After=network-online.target
Wants=network-online.target
[Service]
Type=notify
User=myapp
Group=myapp
WorkingDirectory=/opt/myapp
Environment="PYTHONUNBUFFERED=1"
EnvironmentFile=-/opt/myapp/.env
ExecStart=/opt/myapp/venv/bin/gunicorn -c /opt/myapp/gunicorn.conf.py app:app
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=30
Restart=on-failure
RestartSec=5
StartLimitIntervalSec=60
StartLimitBurst=5
MemoryMax=512M
NoNewPrivileges=true
ProtectSystem=full
PrivateTmp=true
[Install]
WantedBy=multi-user.target
关键配置说明:
1. Type=notify:Gunicorn 20 以上支持 systemd 通知;版本过旧改成 Type=simple。
2. EnvironmentFile:数据库密码等敏感配置放 .env,前面的减号表示文件不存在也不报错,记得 chmod 600。
3. ExecReload:给主进程发 HUP,平滑重启 worker,不中断正在处理的请求。
4. Restart=on-failure 与 RestartSec=5:异常退出 5 秒后自动拉起;StartLimit 表示 60 秒内最多重启 5 次,防止致命 bug 无限重启。
5. MemoryMax:限制最多使用 512M,防止内存泄漏拖垮整机。
6. NoNewPrivileges、ProtectSystem、PrivateTmp:简单安全加固。五、启动并设置开机自启
sudo systemctl daemon-reload
sudo systemctl enable --now myapp
sudo systemctl status myapp
常用:systemctl restart / reload / stop myapp; systemctl is-enabled myapp 查看是否开机自启。
六、用 journalctl 查看日志
journalctl -u myapp -f
journalctl -u myapp --since "1 hour ago"
journalctl -u myapp -p err
journalctl -u myapp -n 100 --no-pager
如担心日志占满磁盘,编辑 /etc/systemd/journald.conf 设置 SystemMaxUse=500M,再 sudo systemctl restart systemd-journald。
七、验证崩溃自动重启
sudo systemctl show -p MainPID myapp
sudo kill -9 <上一步得到的 PID>
sleep 6
systemctl status myapp
服务会被重新拉起,PID 变化,日志有 "Scheduled restart job"。sudo reboot 后服务同样自动运行。
八、配合 Nginx 对外服务
Gunicorn 只监听 127.0.0.1,Nginx 站点配置加入:
location / {
proxy_pass http://127.0.0.1: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;
proxy_set_header X-Forwarded-Proto $scheme;
}
安全组只需放行 80 和 443。
九、一键更新部署脚本 /opt/myapp/deploy.sh
#!/usr/bin/env bash
set -euo pipefail
cd /opt/myapp
sudo -u myapp git pull --ff-only
sudo -u myapp ./venv/bin/pip install -r requirements.txt
sudo systemctl reload myapp
sleep 2
curl -fsS http://127.0.0.1:8000/health && echo " 部署成功"
十、常见问题排查
1. status=203/EXEC:ExecStart 路径错或无执行权限,检查虚拟环境路径。
2. status=217/USER:User 指定的用户不存在,先 useradd。
3. Permission denied:chown -R myapp:myapp /opt/myapp。
4. 改了 service 文件没生效:先 sudo systemctl daemon-reload。
5. 启动超时:Type=notify 但 Gunicorn 版本过旧,升级或改 Type=simple。
6. 反复重启后停止:触发了 StartLimitBurst,journalctl 找到根因,修复后 sudo systemctl reset-failed myapp 再启动。
总结
用 systemd 托管 Python 应用几乎没有额外成本:一个 unit 文件、几条命令,就能获得开机自启、崩溃自愈、统一日志、平滑重载和资源限制。同样适用于 FastAPI(worker_class 换成 uvicorn.workers.UvicornWorker)、Django、Celery worker 等后台进程。
- 点赞
- 收藏
- 关注作者
评论(0)