导读:本站很多教程的结尾都有一句"注册成systemd服务",但从来没正面讲过systemd本身。它是现代Linux的init系统,管着机器上所有进程的生老病死,而绝大多数VPS玩家对它的认知停留在systemctl restart一条命令。这篇补上这块地基:unit文件怎么写、开机自启背后的原理、崩溃自动拉起怎么配、日志去哪看、服务卡死怎么杀。看完你会发现:之前用nohup挂后台、用screen保进程、写crontab轮询重启的老办法,systemd全都提供了更体面的替代。
一、先搞懂定位:systemd是所有进程的祖宗
传统认知里Linux用init进程按运行级别拉起服务,2015年后主流发行版(Debian、Ubuntu、CentOS、RHEL)全部换成了systemd。它是PID为1的那个进程,机器上其他所有进程都是它的子孙。
这个身份带来三个特权,正是我们需要的:
第一,开机自动拉起。systemd按依赖关系图并行启动所有enabled的服务,这是"开机自启"的底层实现——你执行的systemctl enable,只是在依赖图里登记了一下。
第二,监管进程生死。服务进程退出了,systemd第一时间知道,可以按你的配置决定重启还是放任。
第三,接管输出。服务进程打印到stdout/stderr的内容,systemd全部收进journal日志体系,不再散落在nohup.out或者丢失在终端里。
对比一下老三样:nohup挂后台,SSH断线进程是活了,但机器重启就没了,崩了也没人管;screen保会话,本质是手动看门,人不在就没人看;crontab每分钟轮询拉起,土法炼钢,空窗期一分钟。systemd一个不落全解决,而且是发行版原生方案。
二、五个高频命令:日常管理的手感
先把手感练出来,五条命令覆盖日常九成场景:
systemctl start 应用名 —— 启动 systemctl stop 应用名 —— 停止 systemctl restart 应用名 —— 重启(改完配置必做) systemctl status 应用名 —— 看状态(最重要的命令,没有之一) systemctl enable 应用名 —— 注册开机自启
一个高频疑问:enable和start是什么关系?enable是登记(下次开机自动起),start是立即启动。两个都要执行才是"现在跑起来+以后开机也跑"。对应的关闭组合是disable(取消登记)和stop(立即停止)。还有个贴心的复合参数:enable --now等于enable加start一步到位。
status的输出要会读:Active行显示active (running)是健康,active (exited)表示一次性任务跑完了(正常),failed就是出事了——下面几行通常直接给出退出码和最近的报错日志,八成问题在status输出里就有答案。
三、unit文件实战:给应用发身份证
systemd管理的基本单位叫unit,服务类unit的文件放在 /etc/systemd/system/ 目录下,扩展名.service。写一个最实用的例子——把一个Node应用管起来:
# /etc/systemd/system/myapp.service [Unit] Description=我的Node应用 After=network-online.target Wants=network-online.target [Service] Type=simple User=www-data WorkingDirectory=/opt/myapp ExecStart=/usr/bin/node /opt/myapp/server.js Restart=always RestartSec=5 Environment=NODE_ENV=production Environment=PORT=3000 [Install] WantedBy=multi-user.target
逐块拆解。[Unit]段是元信息:Description是给人看的备注;After和Wants声明依赖——本站反代教程讲过服务要等网络就绪才起得来,After=network-online.target就是干这个的,漏写的典型症状是开机后服务起不来,手动start又正常,"重启玄学病"的头号病因。
[Service]段是核心:User指定运行身份(别用root跑应用,本站安全加固文章的第一课);WorkingDirectory定工作目录;ExecStart是启动命令,注意必须写绝对路径,systemd不继承你的PATH环境变量;Restart=always加RestartSec=5是灵魂——进程无论怎么退出(崩溃、被杀、OOM),5秒后自动拉起,"崩了能自己站起来"就是这两行的功劳;Environment逐行注入环境变量,数据库密码之类的敏感配置走这里比写进代码强。
[Install]段的WantedBy=multi-user.target照抄即可,含义是"常规多用户模式下启用"。
写完文件,三步激活:
systemctl daemon-reload (让systemd重新读取unit文件,改完必执行) systemctl enable --now myapp systemctl status myapp
四、Type字段:四种启动姿势别选错
unit文件里最容易选错的是Type,四种类型对应四类程序:
Type=simple:ExecStart起的进程就是主进程,前台的普通程序(Node、Python、Go写的服务)用这个。最常用,拿不准就选它。
Type=forking:程序会自己fork到后台然后退出前台进程(老式C程序、传统的Nginx master)。systemd把退出的那个前台进程当成"启动完成"信号。现代软件大多默认前台模式,用得越来越少。
Type=oneshot:跑完就退的一次性任务,配合RemainAfterExit=yes可以让status显示active。开机执行初始化脚本、磁盘挂载这类动作用它。
Type=notify:程序主动调用sd_notify通知systemd"我准备好了"。软件自己适配了这个协议(比如systemd自家的resolved)才用,个人写的服务别碰。
一个判别技巧:不确定程序什么类型,先按simple跑,status看active (running)是否稳定、端口是否在监听,不对再调。
五、journalctl:日志的中央水库
systemd接管的进程,日志全部汇入journal,查看工具是journalctl。这是排障时的第一入口,五条命令记住:
journalctl -u myapp —— 看某服务的全部日志(-u指定unit) journalctl -u myapp -f —— 实时追踪(相当于tail -f,看实时输出必备) journalctl -u myapp --since today —— 只看今天的 journalctl -u myapp -n 100 —— 只看最近100行 journalctl -p err -b —— 本次开机以来的全部错误级日志(机器级体检)
对比传统方式的优势:时间戳自动对齐、按服务过滤、按优先级过滤、二进制存储抗篡改。以前各种应用日志东一个西一个,现在journalctl一个入口查全部。
磁盘方面有个默认值要知道:journal默认最多占磁盘10%或4G(看版本),超了自动滚动删除。小鸡磁盘紧张可以收紧,编辑 /etc/systemd/journald.conf 设 SystemMaxUse=200M,然后 systemctl restart systemd-journald 生效——这是本站磁盘治理思路在日志维度的延伸。
六、崩溃自动拉起的正确姿势与边界
Restart=always虽好,但"无限重启"有边界情况要处理。
高频崩溃风暴:程序一启动就崩(配置错了、数据库连不上),5秒重启一次,日志疯狂滚动。用StartLimitIntervalSec和StartLimitBurst设置熔断:
StartLimitIntervalSec=300 StartLimitBurst=5
含义:300秒内最多允许启动5次,超过就进入failed状态停止尝试,避免雪崩。此时需要人工systemctl reset-failed清除计数后再start。
优雅停止:服务收到stop时,systemd先发SIGTERM请求退出,默认等90秒还没退就SIGKILL强杀。要自定义这两个参数:
KillSignal=SIGTERM TimeoutStopSec=30
数据库类服务停止慢,把超时给足;写了优雅退出逻辑的应用,确保信号处理正确。
OOM保护:机器内存耗尽时内核会挑进程杀,如果你的服务"随机消失",journal里又查无此人,八成是OOM Killer干的。两条对策:unit里加OOMScoreAdjust=-500降低被选中概率(负值越低越不容易被杀),或者按本站小内存文章的思路从根上治理内存。被OOM杀掉在journalctl -k(内核日志)里会留有记录,查证命令:journalctl -k | grep -i oom。
七、模板unit:一套配置管十个实例
进阶但极实用的技巧:模板unit。文件名带@符号(如myapp@.service),实例名跟在@后面,unit内用%i引用。场景:一台机器跑三个bot,每个监听不同端口,不想写三份配置:
# /etc/systemd/system/mybot@.service [Unit] Description=Bot实例%i After=network-online.target [Service] ExecStart=/usr/bin/python3 /opt/bot/bot.py --config /opt/bot/%i.conf Restart=always User=bot [Install] WantedBy=multi-user.target
启动三个实例:
systemctl enable --now mybot@alpha systemctl enable --now mybot@beta systemctl enable --now mybot@gamma
%i分别展开成alpha、beta、gamma,一份模板三份实例,扩到十个也只是再加配置文件。哪吒探针这类一机多实例的场景同样适用。
八、把Docker容器也纳入管理
本站Compose教程讲过的容器栈,机器重启后docker.service会自动拉起(因为Docker的unit里有Restart策略),但如果某个容器本身没设restart: unless-stopped,机器重启后它不会跟着起来。
让systemd直管Compose栈也是常见姿势,写个包装unit:
# /etc/systemd/system/selfhosted.service [Unit] Description=自托管应用栈 Requires=docker.service After=docker.service [Service] Type=oneshot RemainAfterExit=yes WorkingDirectory=/opt/selfhosted ExecStart=/usr/bin/docker compose up -d ExecStop=/usr/bin/docker compose down [Install] WantedBy=multi-user.target
Type=oneshot加RemainAfterExit的组合拳:up -d本身秒退,但服务状态保持active,stop时执行down。这样systemctl status selfhosted一眼看清整栈状态,也纳入了统一的启停管理。
结语
nohup、screen、crontab轮询这些民间偏方,在systemd面前都是历史文物。一份十几行的unit文件,换来开机自启、崩溃自愈、日志归口、状态可视四件套,这是发行版给你配好的免费保姆。本站过往教程里那些"建议注册成服务"的伏笔,这篇就是统一答案。把机器上的常驻进程全部systemd化之后,你会体验到一种久违的踏实:断电重启也好、程序抽风也罢,机器总能自己回到正轨。


