ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

手把手编写systemd服务:自定义服务+开机自启+日志集成

手把手编写systemd服务:自定义服务+开机自启+日志集成 systemd已经是绝大多数 Linux 发行版的 init 系统Ubuntu 16.04 之后、CentOS 7 之后、Rocky 全系都跑在它上面。把自己写的脚本或程序注册成一个 systemd 服务能顺手解决三件事崩了自动重启、开机自动拉起、日志统一收进 journald。这一篇从写一个.service文件开始把systemctl、开机自启和日志查询一次讲透。一、先认清楚 systemctl 的几条核心命令systemd 没有单独的服务管理命令所有操作都走systemctl。先把最高频的几条记牢后面写文件才有参照。命令作用systemctl start 服务名立刻启动systemctl stop 服务名停止systemctl restart 服务名重启systemctl status 服务名看状态、PID、最近日志systemctl enable 服务名加入开机自启只建软链不立即启动systemctl disable 服务名取消开机自启systemctl daemon-reload改完 .service 文件后重载配置systemctl list-units --typeservice列出正在加载的服务服务名一般就是.service文件名去掉后缀。比如文件叫myapp.service后面命令里直接写myapp。二、手写一个 .service 文件自己写的服务文件放在/etc/systemd/system/下这是管理员自定义文件的位置优先级比发行版自带的/usr/lib/systemd/system/高升级也不会被覆盖。假设要把一个 Python 脚本跑成服务[Unit] DescriptionMy Demo Flask App Afternetwork.target [Service] Typesimple Userapprunner WorkingDirectory/opt/myapp ExecStart/opt/myapp/venv/bin/python app.py Restarton-failure RestartSec3 StandardOutputjournal StandardErrorjournal [Install] WantedBymulti-user.target把它存成/etc/systemd/system/myapp.service。三段[Unit]、[Service]、[Install]各管一摊。三、关键字段逐个说[Unit] 段描述与依赖字段说明Description一句话描述systemctl status和list-units会显示它After本服务在哪些单元之后启动只排序不保证它们成功Requires强依赖依赖单元起不来本服务也失败Wants弱依赖依赖单元起不起来不影响本服务生产更常用Afternetwork.target是最常见的写法意思是等网络起来我再启动。注意After只管顺序不会帮你确认网卡真的拿到 IP对启动顺序敏感的服务要自己在程序里做重试。[Service] 段怎么跑这个进程字段说明Typesimple主进程就是 ExecStart 那个命令不 fork最常用Typeforking程序会 fork 出后台主进程systemd 通过 PID 文件找它Typeoneshot跑完就退出适合一次性脚本ExecStart启动命令的绝对路径不能用 shell 管道、重定向ExecReloadsystemctl reload时执行的命令User以哪个用户身份跑强烈建议不用 rootWorkingDirectory工作目录相对路径会以它为基准Restart何时自动重启no/on-failure/always/on-abortRestartSec重启前等待秒数Environment设环境变量多条写多行一个容易踩的坑ExecStart不经过 shell写ExecStart/opt/myapp/run.sh python app.py这种带的会直接报错。需要 shell 逻辑就写成ExecStart/bin/bash -c ...或者把逻辑封进脚本文件里。[Install] 段开机自启的挂钩点字段说明WantedBymulti-user.target启到多用户模式时启动本服务绝大多数服务写这个WantedBy决定enable时往哪个 target 的.wants目录里建软链。不写这一段systemctl enable会直接拒绝。四、启动、自启、看状态文件写完后按顺序操作sudosystemctl daemon-reloadsudosystemctl start myappsudosystemctlenablemyappsudosystemctl status myapp第一条必须有。systemd 会缓存单元文件内容新建或改过.service之后不 reloadsystemctl 还在用旧配置。看状态的预期输出● myapp.service - My Demo Flask App Loaded: loaded (/etc/systemd/system/myapp.service; enabled; vendor preset: enabled) Active: active (running) since Wed 2026-09-16 10:23:41 CST; 12s ago Main PID: 18432 (python) Tasks: 2 (limit: 4616) Memory: 28.5M CPU: 180ms CGroup: /system.slice/myapp.service └─18432 /opt/myapp/venv/bin/python app.py第一行那个●变成红底白字的failed就是起失败了。loaded后面的enabled说明开机自启已挂上。enable干了什么可以用ls看一眼ls-l/etc/systemd/system/multi-user.target.wants/myapp.service预期输出lrwxrwxrwx. 1 root root 38 Sep 16 10:24 /etc/systemd/system/multi-user.target.wants/myapp.service - /etc/systemd/system/myapp.service就是一条软链。disable只是删掉这条软链不删服务文件本身。systemctl cat 与 drop-in 覆盖装第三方软件包时它自己会带一份.service到/usr/lib/systemd/system/。你想改其中一两个字段没必要把整个文件拷出来改——systemd 有 drop-in 覆盖机制。先看这个单元最终生效的完整配置systemctlcatmyapp预期输出节选# /etc/systemd/system/myapp.service [Unit] DescriptionMy Demo Flask App Afternetwork.target [Service] Typesimple Userapprunner ...systemctl cat会把原文件和所有 drop-in 片段按加载顺序拼在一起显示。要加覆盖sudosystemctl edit myapp它会打开/etc/systemd/system/myapp.service.d/override.conf写[Service] Restartalways MemoryMax512M保存后daemon-reload再restart。这样原文件不动升级软件包也不会把你的改动冲掉比手改/usr/lib/里那份干净。五、日志集成journalctl把进程的标准输出、标准错误交给 journald是上面StandardOutputjournal那两行做的事。之后查日志统一走journalctl不用自己搭 logrotate。命令作用journalctl -u myapp看本服务全部日志时间正序journalctl -u myapp -f实时跟踪等于 tail -fjournalctl -u myapp --since 10 minutes ago只看最近 10 分钟journalctl -u myapp -p err只看 error 以上级别journalctl -xe排错高频组合本次启动以来 带解释journalctl-umyapp-f预期输出-- Logs begin at Wed 2026-09-16 09:00:12 CST. -- Sep 16 10:23:41 web01 systemd[1]: Started My Demo Flask App. Sep 16 10:23:42 web01 python[18432]: * Serving Flask app app Sep 16 10:23:42 web01 python[18432]: * Running on http://127.0.0.1:5000如果程序自己往/var/log/xxx.log写文件就别重复用StandardOutputjournal了二选一否则同一份日志写两处。日志默认占多大、留多久由/etc/systemd/journald.conf里的SystemMaxUse控制比如SystemMaxUse500M。满了会自动删最老的。想让日志重启后还在默认就在确认/var/log/journal/目录存在不存在就只存内存重启即丢。六、⚠️ 常见错误改了 .service 不 daemon-reload改完直接restartsystemd 还按旧文件跑半天怀疑人生。养成改完就 reload 的肌肉记忆。ExecStart 写相对路径或不带绝对路径ExecStartpython app.py这种起不来systemd 对路径要求严格写全路径。Type 选错写个 fork 出去的老程序却用Typesimplesystemd 以为主进程秒退判服务失败又拉起死循环。不确定程序类型就先Typesimple试。权限问题服务以Userapprunner跑却让它读/root下的配置文件日志里报 Permission denied。把数据目录 chown 到对应用户。想当然以为 enable 会启动服务enable只挂软链不启动。要立刻生效得start或者用systemctl enable --now myapp一步到位。七、知识扩展target 与服务依赖multi-user.target这个词很多人照抄却不知道是什么。systemd 用 target 替代了传统 SysV 的运行级别runlevel。常见对应关系target对应 runlevel含义poweroff.target0关机rescue.target1单用户救援模式multi-user.target3多用户命令行graphical.target5多用户图形界面reboot.target6重启WantedBymulti-user.target的意思是当系统要进入多用户模式时请把我也拉起来。服务器基本没有图形界面最终都进multi-user.target所以绝大多数服务挂它就对了。想看当前系统默认进哪个 targetsystemctl get-default预期输出multi-user.target依赖关系再补一刀。After、Wants、Requires三个字段最容易混After只管排序不参与依赖决策。Wants弱依赖对方失败不影响我推荐日常用。Requires强依赖对方起不来我直接不启动慎用一个组件挂一串全挂。真要做复杂编排用systemctl list-dependencies myapp能把整棵依赖树打出来比对着文件猜快得多。
返回列表