资讯动态

用 systemd 托管后台服务:Restart 策略、日志接管、资源限制与开机自启全踩一遍

发布时间:2026/9/5 9:33:19 来源:尧图企业网站定制
用 systemd 托管后台服务:Restart 策略、日志接管、资源限制与开机自启全踩一遍你写了个 Python/Go/Node 的后台服务,部署到 Linux 上。第一版怎么跑?大概率是nohup ./app ,或者塞进screen/tmux里。然后你会陆续踩到:进程崩了没人拉起来、服务器重启后服务没了、日志全糊在一个越来越大的nohup.out里、想看它有没有活着只能ps aux | grep。这些问题 systemd 全都替你解决了,而且它就是现代 Linux(CentOS 7/Ubuntu 16.04/Debian 8)的标配,不用装任何东西。这篇文章从一个能跑但很糟的启动方式出发,一步步把服务交给 systemd 托管,把 Restart、日志、资源限制、开机自启这几件事讲透。nohup 方式糟在哪先看典型的「土办法」:nohuppython3 /opt/myapp/server.py/var/log/myapp.log21它的问题:崩了不会自动重启。进程 OOM 或抛异常退出,服务就没了,你得盯着。重启服务器就丢。机器重启后没人再执行这条命令。日志不切割。myapp.log无限增长,迟早撑爆磁盘。管理全靠手动。停止要先ps找 PID 再kill,查状态靠猜。没有资源隔离。一个服务内存泄漏能把整台机器拖死。systemd 把服务定义成一个「unit 文件」,以上五点全内建支持。写第一个 service 文件systemd 的服务定义放在/etc/systemd/system/下,文件名以.service结尾。建一个/etc/systemd/system/myapp.service:[Unit] DescriptionMy App Server # 等网络就绪后再启动,依赖网络的服务几乎都要这行 Afternetwork-online.target Wantsnetwork-online.target [Service] Typesimple # 用哪个用户跑,别用 root 跑业务进程 Userappuser Groupappuser WorkingDirectory/opt/myapp # ExecStart 必须写绝对路径,systemd 不吃 PATH 里的相对命令 ExecStart/usr/bin/python3 /opt/myapp/server.py # 崩了就拉起来 Restarton-failure RestartSec3s [Install] # systemctl enable 时挂到这个 target,实现开机自启 WantedBymulti-user.target三个段落各管一摊:[Unit]:描述和依赖关系(什么之前/之后启动)。[Service]:进程本身怎么跑、怎么重启。[Install]:enable时怎么挂载,决定开机自启。写完让 systemd 重新读配置,然后启动:sudosystemctl daemon-reload# 改动 .service 文件后必须执行sudosystemctl start myapp# 启动sudosystemctl status myapp# 看状态(是否 active running、最近日志)sudosystemctlenablemyapp# 设置开机自启enable之后,服务器重启会自动拉起,前面第 2 个痛点解决。Type 到底该填 simple 还是别的Type决定 systemd 怎么判断「服务算启动成功了」,填错会导致依赖它的服务时序错乱。最常用的三个:Typesimple(默认):ExecStart一旦 fork 出进程,立刻认为启动完成。适合前台运行、不 daemon 化的程序(大多数现代服务:Go 二进制、python server.py、node app.js)。Typeforking:程序自己会 fork 到后台、父进程退出。传统守护进程(如某些 C 写的 daemon)用它,通常要配PIDFile。Typenotify:程序启动完成后主动用sd_notify通知 systemd「我好了」。最精确,但要程序支持(如 nginx、systemd 原生集成的服务)。判断口诀:你的程序是不是在前台一直跑、不自己 daemon 化?是就simple。绝大多数用脚本/二进制直接跑的服务都是 simple。如果你用了--daemon之类让程序自己退到后台的参数,反而要用forking——但更推荐去掉那个参数、让 systemd 管前台进程。Restart 策略:别无脑用 alwaysRestart控制进程退出后要不要重启,几个常用值:no(默认):从不重启。on-failure:非正常退出(退出码非 0、被信号杀死、超时)才重启。推荐默认选它。always:不管怎么退出都重启,包括正常退出(退出码 0)。on-abnormal:只在被信号杀死或超时时重启。为什么推荐on-failure而不是always?因为always会连「你主动正常关闭」也拉起来——比如一次性任务跑完正常退出(exit 0),always会把它无限重启。业务常驻服务用on-failure最合适:崩了(异常)才救,主动停了就别捣乱。光有Restart还不够,得防「崩溃-重启-再崩溃」的死循环把 CPU 打满。用启动限流:[Service] Restarton-failure RestartSec3s # 10 秒窗口内最多重启 5 次,超了就放弃并标记 failed StartLimitIntervalSec10s StartLimitBurst5超过阈值后 systemd 会停手并把服务标为failed,而不是无限空转。这时systemctl status会显示start-limit-hit,你就知道该去查根因而不是让它自己抽搐。日志:直接交给 journaldsystemd 服务的标准输出/标准错误默认被journald接管,不用你自己重定向文件、也不用配 logrotate。查日志:journalctl-umyapp# 该服务全部日志journalctl-umyapp-f# 实时跟踪(类似 tail -f)journalctl-umyapp--since10 min ago# 最近 10 分钟journalctl-umyapp-perr# 只看 error 及以上级别journalctl-umyapp--sincetoday# 今天的journald 自带按大小/时间滚动清理,不会像nohup.out那样无限增长(默认上限可在/etc/systemd/journald.conf里用SystemMaxUse调,比如SystemMaxUse500M)。一个实操细节:很多语言的运行时默认对 stdout 做缓冲,导致日志不实时刷进 journald。Python 尤其明显,要关掉缓冲:[Service] # 让 Python 的 stdout/stderr 不缓冲,日志实时进 journald EnvironmentPYTHONUNBUFFERED1 ExecStart/usr/bin/python3 /opt/myapp/server.py或者在命令行加python3 -u。不加这个,你会疑惑「明明在打日志,journalctl 里却半天不出东西」。资源限制:一个服务别拖垮整台机器systemd 基于 cgroup,能直接给服务上内存/CPU 限制,不用 Docker 也能做资源隔离:[Service] # 内存超过 512M 就被内核 OOM 掉这个服务(而不是随机杀别人) MemoryMax512M # 高水位软限制,超了开始回收但不立刻杀 MemoryHigh400M # CPU 最多用 1.5 个核(150%) CPUQuota150% # 最多开 100 个任务(线程/进程),防 fork 炸弹 TasksMax100MemoryMax是硬上限:进程超了会被 OOM killer 精准干掉这个服务,而不是让内核在整机内存耗尽时随机挑一个进程杀。这就把前面第 5 个痛点(一个服务泄漏拖死全机)堵上了。改完记得daemon-reloadrestart。想临时看某个服务实时吃了多少资源:systemctl status myapp# 状态里会显示 Memory、CPU、Tasks 当前用量systemd-cgtop# 类似 top,按 cgroup 看各服务资源占用一个更完整的生产级模板把上面的点拼起来,加几个常用的安全加固项:[Unit] DescriptionMy App Server Afternetwork-online.target Wantsnetwork-online.target [Service] Typesimple Userappuser Groupappuser WorkingDirectory/opt/myapp EnvironmentPYTHONUNBUFFERED1 EnvironmentAPP_ENVproduction # 从文件读环境变量,密钥别写死在 unit 文件里 EnvironmentFile/opt/myapp/.env ExecStart/usr/bin/python3 /opt/myapp/server.py # 优雅停机:先发 SIGTERM,给 30 秒收尾再 SIGKILL KillSignalSIGTERM TimeoutStopSec30s Restarton-failure RestartSec3s StartLimitIntervalSec10s StartLimitBurst5 MemoryMax512M CPUQuota150% TasksMax100 # 安全加固:只读系统目录、禁止提权、私有 /tmp NoNewPrivilegestrue ProtectSystemstrict ProtectHometrue PrivateTmptrue # strict 下若服务要写某目录,显式放开 ReadWritePaths/opt/myapp/data [Install] WantedBymulti-user.target几个值得说的:EnvironmentFile把密钥放独立文件(权限设600),别硬编码进 unit。TimeoutStopSec30sKillSignalSIGTERM:systemctl stop时先发 SIGTERM,你的程序捕获它做优雅关闭(处理完在途请求),30 秒还没退才强杀。这对有状态服务很重要。ProtectSystemstrict/NoNewPrivileges这类是「零成本」的安全加固,让服务即使被攻破也难以动系统文件,配ReadWritePaths放开确实需要写的目录即可。排查:服务起不来先看这三样systemctl status myapp# 概览:active/failed、退出码、最近几行日志journalctl-umyapp-n50--no-pager# 最近 50 行完整日志,看真实报错systemctlcatmyapp# 打印当前生效的 unit 内容,确认没写错/没忘 daemon-reload最高频的三个低级错误:改了.service忘了daemon-reload(改动没生效)、ExecStart写了相对路径(找不到命令)、User指定的用户对WorkingDirectory没权限(启动即失败)。statusjournalctl基本一眼定位。小结systemd 是现代 Linux 自带的服务管理器,用一个.service文件就替代了 nohup 手动重启 logrotate cgroup 一整套,不用装任何东西。核心四要素:Restarton-failure(崩了才救)配StartLimitBurst防重启风暴;stdout 交给 journald 用journalctl -u查;MemoryMax/CPUQuota做资源隔离;enableWantedBymulti-user.target实现开机自启。Type绝大多数前台服务填simple;Python 记得PYTHONUNBUFFERED1否则日志不实时。改完.service一定daemon-reload;起不来先看systemctl statusjournalctl -u。一句话记忆:别再nohup ——写个二十行的 unit 文件,自动重启、开机自启、日志、限流、资源上限一次全有了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价