标签: Linux

  • systemd 服务管理实战:从 systemctl 到编写自己的 service 单元

    systemd 服务管理实战:从 systemctl 到编写自己的 service 单元

    配好一台 Linux 服务器,装完软件只是第一步 —— 真正决定服务器长期健康运行的,是把服务管好:开机自启、异常自动拉起、日志可查、配置可改不重启。在主流发行版(Ubuntu、Debian、RHEL、Arch)上,这套管理工作几乎都由 systemd 接管。它既是 init 进程,又是服务管家,还是日志中心。本文从日常操作讲到手写 service 单元,全是能直接上手的实战内容。

    systemd 是什么:三个核心概念

    systemd 是如今 Linux 世界的标准初始化系统(PID 1),它把系统的每一个”进程单元”抽象成 unit(单元)。先记住这三个词,后面全都会用到:

    • service:服务单元,后台常驻进程,如 nginx.servicesshd.service
    • target:目标的组合,相当于传统 SysV 的”运行级别”,如 multi-user.target(命令行)和 graphical.target(图形界面)
    • journald:systemd 自带的日志守护进程,用 journalctl 查看,统一收纳所有服务的标准输出和系统日志

    你不需要知道每个发行版用哪个 systemd 版本也能照常使用本文所有命令。当前主流:Ubuntu 24.04 LTS 用 systemd 255,Debian 12 用 systemd 252,Ubuntu 22.04 用 249,命令语法完全兼容。

    日常管理:systemctl 一页速查

    管理服务就是 systemctl + 动作 + 服务名,.service 后缀可以省略:

    systemctl start nginx        # 启动
    systemctl stop nginx         # 停止
    systemctl restart nginx      # 重启
    systemctl reload nginx       # 重载配置(不中断进程,前提是服务支持)
    systemctl status nginx       # 状态 + 最近日志,排查问题第一步
    systemctl enable nginx       # 开机自启
    systemctl disable nginx      # 取消开机自启
    systemctl enable --now nginx # 一键"开机自启 + 立即启动",最常用
    

    检查状态的三种姿势

    systemctl is-active nginx    # active / inactive / failed —— 适合脚本判断
    systemctl is-enabled nginx   # enabled / disabled
    systemctl is-failed nginx    # active / failed
    

    批量查看

    systemctl list-units --type=service --state=running   # 所有运行中的服务
    systemctl list-units --type=service --state=failed    # 挂掉的服务
    systemctl --failed                                    # 全部失败的 unit,开机后必查
    systemctl list-timers --all                           # 定时器及其下次触发时间
    systemctl list-unit-files --type=service              # 所有已安装的 unit 文件
    

    启动调优与系统控制

    systemd-analyze              # 开机总耗时
    systemd-analyze blame        # 每个服务分别耗时多少,找开机慢的元凶
    systemd-analyze critical-chain  # 启动关键路径,看卡在哪一环
    
    systemctl reboot             # 重启系统
    systemctl poweroff           # 关机
    systemctl get-default        # 当前默认 target(multi-user 或 graphical)
    systemctl set-default multi-user.target   # 命令行模式
    

    避坑enable 只设置自启不立即启动,start 只启动不自启。生产环境部署新服务,请永远使用 enable --now,别漏一半。

    排查故障:从 status 到 journalctl

    服务起不来是运维日常。标准的排查链是:

    systemctl status myapp          # 第一眼:状态、PID、退出的 exit code
    journalctl -u myapp -xeu myapp  # 日志全文:-x 带解释,-e 翻到末尾,-u 指定服务
    

    常见 exit code 含义:

    退出码 含义 常见原因
    203 Executable 不存在 ExecStart 路径写错或没权限执行
    200 加载 unit 失败 文件语法错误,先 daemon-reload
    216 用户/组不存在 User=/Group= 指向了不存在的账户

    修改了 unit 文件后必须执行 systemctl daemon-reload,否则 systemd 用的还是旧配置 —— 这是新手最常踩的坑。

    systemctl daemon-reload
    systemctl reset-failed myapp   # 清除 failed 状态,反复重启失败的会触发 rate limit
    

    实战:编写自己的 service 单元

    业务脚本、爬虫、API 服务,不要再用 nohup ... & 裸跑 —— 退出后没人帮你拉起来,开机也不自启。手写一个 service 单元只需三步。

    一个完整的示例

    假设你有一个 Python 应用 /opt/myapp/app.py,创建 /etc/systemd/system/myapp.service

    [Unit]
    Description=My Python Application
    After=network-online.target
    Wants=network-online.target
    
    [Service]
    Type=simple
    User=app
    Group=app
    WorkingDirectory=/opt/myapp
    EnvironmentFile=/etc/myapp/env.conf
    ExecStart=/usr/bin/python3 /opt/myapp/app.py
    Restart=on-failure
    RestartSec=3
    LimitNOFILE=65535
    
    [Install]
    WantedBy=multi-user.target
    

    然后一行启用:

    sudo systemctl daemon-reload
    sudo systemctl enable --now myapp
    

    关键字段逐条讲

    [Unit]

    • Description:给人类看的说明,systemctl status 时会显示
    • After=:声明依赖顺序,等网络就绪再启动,防止启动即报”连不上网”

    [Service] —— 核心:

    • Type=simple:默认值,ExecStart 起的进程就是主进程,多数场景选它
    • Type=forking:进程启动后会 fork 出子进程并自己退出(老派守护进程,如某些 Java 应用),需要配合 PIDFile=
    • User= / Group=永远用专用用户跑服务,不要用 root
    • Restart=on-failure:崩溃/非正常退出时自动拉起;线上服务建议配合 RestartSec=3 防抖。always 连正常退出也会拉起,慎用
    • EnvironmentFile=:把敏感配置(密码、token)放独立文件,权限收紧到 600,改配置不用动 unit
    • LimitNOFILE=:打开文件数上限,高并发服务必须调大,默认 1024 往往不够

    [Install]:只有写了 WantedBy= 的 unit 才能被 enable,开机自启就是把自己挂到对应 target 下。

    改配置不重启:drop-in 覆盖

    官方推荐用 systemctl edit 加覆盖片段,不改动系统原文件,系统升级后配置也不会被冲掉:

    sudo systemctl edit myapp
    # 在弹出的编辑器中写入:
    [Service]
    RestartSec=5
    

    保存后自动 daemon-reload,再 systemctl restart myapp 生效。查看合并后的最终配置:

    systemctl cat myapp    # 显示原文件 + 所有 drop-in 合并结果
    

    统一日志:journalctl 实战

    journald 把服务 stdout/stderr 和系统日志收进同一套日志,再也不用 cd /var/log 翻文件。

    常用过滤

    journalctl -u myapp              # 只看某个服务的日志
    journalctl -u myapp -f           # 实时跟踪,等价于 tail -f,排查首选
    journalctl -n 50                 # 最近 50 条
    journalctl -k                    # 内核日志
    journalctl -b                    # 本次开机以来的日志
    journalctl -b -1                 # 上次开机(需持久化,见下文)
    journalctl --list-boots          # 列出所有开机记录
    journalctl -p err                # 只看 err 及以上级别
    journalctl --since "1 hour ago"  # 时间过滤:today / "30min ago" / 具体时间点
    journalctl -u myapp --since today --until "10 minutes ago"   # 组合过滤
    journalctl --grep="OutOfMemory"  # 按正则搜索内容
    

    日志持久化:重启后日志不丢

    默认日志存在 /run/log/journal(内存盘),重启即清空。想要跨重启保留,只需创建持久化目录,systemd 检测到后自动切换:

    sudo mkdir -p /var/log/journal
    sudo systemd-tmpfiles --create --prefix /var/log/journal
    sudo systemctl restart systemd-journald
    

    磁盘占用控制

    日志跑久了会占磁盘,定期清理是基本功:

    journalctl --disk-usage               # 看占了多少
    sudo journalctl --vacuum-size=500M    # 清到 500MB 以下
    sudo journalctl --vacuum-time=30d     # 只保留最近 30 天
    

    更进一步:在 /etc/systemd/journald.conf.d/ 下建 00-storage.conf[Journal] + Storage=persistent / SystemMaxUse=200M,一次性限制日志上限,比反复手工清理省心。

    进阶:用 systemd timer 替代 cron

    cron 的痛点是没有统一日志、错过时间不补跑。timer 在 systemd 生态里日志、依赖、失败追踪全都有。示例:每天 3 点跑备份脚本。

    创建 /etc/systemd/system/backup.service

    [Unit]
    Description=Nightly backup job
    
    [Service]
    Type=oneshot
    ExecStart=/usr/local/bin/backup.sh
    

    创建 /etc/systemd/system/backup.timer

    [Unit]
    Description=Run backup daily at 3am
    
    [Timer]
    OnCalendar=daily
    Persistent=true
    
    [Install]
    WantedBy=timers.target
    

    启用方式与 service 相同:

    sudo systemctl enable --now backup.timer
    systemctl list-timers backup.timer   # 查看下次触发时间
    journalctl -u backup.service         # 查看每次执行日志
    

    Persistent=true 是杀手锏:机器当时关机没执行,下次开机自动补跑 —— 这是 cron 做不到的。OnCalendar= 语法与 cron 类似但更灵活,如 Mon..Fri *-*-* 09:00:00 表示工作日早上 9 点。

    实战总结

    • 部署服务:写 unit → daemon-reloadenable --nowsystemctl status 确认
    • 排查问题:systemctl status 看退出码 → journalctl -xeu 看日志
    • 服务异常:Restart=on-failure 自动拉起,别再用 nohup 裸跑
    • 改配置:systemctl edit 加 drop-in,别直接动 /usr/lib/systemd/
    • 开机必查:systemctl --failed,把红灯消灭在平时

    从”能跑”到”跑得稳”,systemd 就是那道分水岭。

    参考资料