把下载器做成常驻服务:用 systemd 把程序跑在 Linux 后台
本地跑下载脚本,关了终端就停;放服务器上想让它 7×24 常驻、开机自启、崩溃自动拉起——这时候就该用 systemd。它是现代 Linux 发行版的标准服务管理器。这篇文章从 unit 文件结构讲起,覆盖虚拟环境、日志、资源限制、安全加固、看门狗和多实例,让你把任何命令行程序(下载器、爬虫、转码队列)封装成一个靠谱的系统服务。
一、为什么是 systemd 而不是 nohup/screen
nohup ... &:关终端不退出,但重启就没了、也不会自动重启;screen/tmux:同理,得人盯着;- systemd:开机自启、挂了自动拉起、日志接 journald、资源限制一把抓、还能设看门狗。
二、准备你的程序
假设下载器在 /opt/downloader/main.py,用虚拟环境 /opt/downloader/venv。先确保它能直接跑且不依赖终端输入:
/opt/downloader/venv/bin/python /opt/downloader/main.py
确认它是长期运行、无交互的(别让它等 stdin)。
三、写 unit 文件
/etc/systemd/system/downloader.service:
[Unit]
Description=VidDown Downloader Service
After=network.target
Wants=network-online.target
[Service]
Type=simple
User=downloader
Group=downloader
WorkingDirectory=/opt/downloader
ExecStart=/opt/downloader/venv/bin/python /opt/downloader/main.py
Restart=on-failure
RestartSec=5
Environment=PYTHONUNBUFFERED=1
EnvironmentFile=/opt/downloader/.env
# 资源限制
MemoryLimit=1G
CPUQuota=80%
[Install]
WantedBy=multi-user.target
要点:
Type=simple:主进程即 ExecStart 起的那个;若程序会 fork 后台进程用Type=forking;支持 sd_notify 用Type=notify;Restart=on-failure:非正常退出 5 秒后自动重启;always则无论怎样都重启;User/Group:别用 root 跑业务程序,建普通用户更安全;PYTHONUNBUFFERED=1:日志实时进 journal,不被缓冲吞掉;EnvironmentFile:从文件读环境变量(如数据库连接)。
四、启用与日常操作
sudo systemctl daemon-reload
sudo systemctl enable downloader # 开机自启
sudo systemctl start downloader
sudo systemctl status downloader
看日志:
journalctl -u downloader -f # 实时跟随
journalctl -u downloader --since today
五、日志轮转
journald 自带按大小/时间轮转,无需额外配置。若你想把日志也落文件:
StandardOutput=append:/var/log/downloader/out.log
StandardError=append:/var/log/downloader/err.log
再配 /etc/logrotate.d/downloader 做切割。或直接依赖 journald + journalctl --vacuum-size=500M 限制总量。
六、安全加固(生产建议)
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
ReadWritePaths=/opt/downloader/data
这能大幅缩小被入侵时的破坏面。仅开放程序真正需要读写的路径。
七、看门狗(防假死)
若程序支持 systemd 看门狗协议,可设:
WatchdogSec=30
# 程序里定期调用 sd_notify("WATCHDOG=1")
超过 30 秒没"喂狗"就判定卡死并重启。不支持的话用 Restart 兜底。
八、多实例
复制 unit 改名字和端口,分别 enable:
cp downloader.service downloader-worker2.service
# 改 ExecStart 里的 --port 参数
systemctl enable downloader-worker2
九、与 Docker 的区别
| 维度 | systemd | Docker |
|---|---|---|
| 轻量 | 高 | 中(有容器开销) |
| 隔离 | 弱 | 强 |
| 迁移 | 绑定机器 | 易迁移 |
| 多服务编排 | 弱 | 强(compose/k8s) |
小机器单服务用 systemd 足够;要弹性扩缩、多实例编排再上 Docker/K8s。
十、常见翻车现场
| 现象 | 根因 | 解法 |
|---|---|---|
| 启动失败 | 路径/权限错 | journalctl -u downloader 看具体报错 |
| 跑一会自停 | 程序报错退出 | 查日志,修代码或放宽 Restart |
| 不读环境变量 | unit 没设 | 用 Environment= 或 EnvironmentFile= |
| 用了 root 有隐患 | 直接 root 跑 | 建专用用户 User= |
| 重启不生效 | 没 daemon-reload | 改 unit 后必跑 systemctl daemon-reload |
十一、常见问题
Q:程序要收命令行参数? 直接拼在 ExecStart 后:...python main.py --port 8000。
Q:怎么热更新代码? 改完代码 sudo systemctl restart downloader 即可。
Q:能限制它别吃满内存吗? 能,MemoryLimit=1G 超限会被 OOM kill 并按 Restart 重启。
Q:多实例怎么互不干扰? 复制 unit 文件、改名字/端口/工作目录,分别 enable 启动。
小结
systemd 是 Linux 后台服务的事实标准:写好 unit(注意 Type、Restart、User、环境变量),enable+start 即可常驻,日志、限资源、加固一条龙。单服务小机器首选,复杂编排再上容器。