在 Linux 系统中,后台服务通常由 systemd 管理。systemd 是现代 Linux 发行版的初始化系统和服务管理器,它通过单元文件(unit file)来定义服务的启动方式、依赖关系、重启策略等。当你需要将一个自己编写的程序、脚本或第三方软件变成开机自启的服务时,就需要编写一个 systemd 服务配置文件。
与传统的 SysV init 脚本相比,systemd 的服务文件语法简洁、声明式,支持依赖自动解析、资源限制、日志集成等功能,维护起来更高效。本节将带你创建一个真实可用的 systemd 服务。
1. 服务文件的基本结构
systemd 的服务文件以 .service 为后缀,通常放置在 /etc/systemd/system/ 目录下(用户自定义服务)或 /lib/systemd/system/ 下(软件包安装的服务,不应手动修改)。一个最简单的服务文件包含三个固定小节:[Unit]、[Service] 和 [Install]。
[Unit]
Description=我的自定义服务
After=network.target
[Service]
Type=simple
ExecStart=/usr/local/bin/my-app --config /etc/my-app.conf
Restart=on-failure
User=nobody
Group=nogroup
[Install]
WantedBy=multi-user.target
下面逐一解释各部分的作用。
2. [Unit] 节:描述与依赖
Description:对服务的简短说明,会出现在systemctl status的输出中。After:定义该服务应在哪些目标(target)或服务之后启动。例如network.target表示等网络就绪后再启动本服务,避免服务启动时网络不可用。对于有网络依赖的服务,常与Wants=network.target配合使用。Requires/Wants:前者表示强依赖(若被依赖服务启动失败,当前服务也失败),后者表示软依赖(被依赖服务启动失败不影响当前服务启动)。
通常,对于简单服务,After=network.target 就足够。
【实用场景】 如果你的服务需要数据库,可以写:
After=mysql.service
Wants=mysql.service
这意味着在 MySQL 之后启动,并且期望 MySQL 能被拉起来,但不会因 MySQL 失败而阻止本服务的启动尝试。
3. [Service] 节:执行方式与行为控制
这是核心配置,定义了 systemd 如何启动和监督你的进程。
Type:设置服务的启动类型,常用选项:simple(默认):systemd 认为ExecStart启动的进程就是主服务进程,不会跟踪其启动完成信号。适用于大多数不进行fork的前台程序。forking:适用于会fork到后台的传统守护进程。当父进程退出后,systemd 认为服务启动完成。需配合PIDFile指定 pid 文件路径。oneshot:用于执行一次性任务并退出的程序,可配合RemainAfterExit=yes让 systemd 认为服务一直处于活动状态。notify:服务启动完成后会通过sd_notify()发送通知信号,适合支持 systemd 通知的现代软件。
真实经验:如果你用 Go、Python 或 Node.js 编写了一个阻塞式运行的 HTTP 服务,直接让它在前台运行,使用 Type=simple 即可,最简单可靠。
ExecStart:启动服务时执行的命令,必须使用绝对路径。可带参数。如果想执行多条命令,可以用分号分隔,并用-前缀表示忽略错误(如ExecStartPre=-/usr/bin/mkdir -p /run/my-app)。
ExecStop:停止服务时执行的命令。如果没有指定,systemd 默认向主进程发送SIGTERM信号,等待一段时间后发送SIGKILL。你可以自定义清理脚本。
Restart:定义何时重启服务。常用值:no:不自动重启。always:无论什么退出原因都重启。on-failure:仅在进程退出码非 0、或捕获到非正常信号时重启。对长期运行的服务,推荐使用此项,避免预期内退出(如自己调用了exit(0))导致循环重启。on-abort:仅在收到未捕获的信号导致退出时重启。
实用技巧:与 RestartSec=5 配合,设置重启前的等待秒数,防止频繁重启消耗资源。
User/Group:指定以哪个用户和组的身份运行服务,提升安全性。强烈建议不要以 root 身份运行自定义服务,除非确有必要。可以创建一个系统用户(如systemd-sysusers或useradd -r myapp)。
WorkingDirectory:设置服务进程的工作目录,可选。
Environment/EnvironmentFile:设置环境变量,或指定一个包含键值对的文件。
StandardOutput/StandardError:设置标准输出和错误输出的目标,常见值有journal(发送到 systemd 日志)、syslog、null等。默认为journal,可使用journalctl -u 服务名查看。
一个小示例片段:
[Service]
Type=simple
ExecStart=/opt/app/server --port 8080
Restart=on-failure
RestartSec=5
User=appuser
Group=appgroup
Environment="NODE_ENV=production"
4. [Install] 节:定义如何安装为开机启动
该节负责告诉 systemd 当执行 systemctl enable 时,应将服务链接到哪个目标(target)的 wants 目录中。
WantedBy:最常见的值是multi-user.target,表示在多用户文本模式下启动(等同于传统运行级别 3)。对于带图形界面的桌面系统,某些服务可能需要graphical.target。Alias:为服务设置别名,较少使用。
配置好 WantedBy 后,运行:
sudo systemctl enable my-app.service
就会自动创建符号链接,下次开机时自动启动服务。
5. 完整示例:部署一个 Node.js Web 应用
假设我们有一个 Node.js 应用位于 /opt/webapp/server.js,需要以 web 用户运行,并监听 3000 端口。
步骤:
# 创建系统用户
sudo useradd -r -s /bin/false web
# 安装应用文件到 /opt/webapp(略)
创建服务文件 /etc/systemd/system/webapp.service:
[Unit]
Description=My Web Application
After=network.target
[Service]
Type=simple
User=web
Group=web
WorkingDirectory=/opt/webapp
ExecStart=/usr/bin/node /opt/webapp/server.js
Restart=on-failure
RestartSec=10
Environment="PORT=3000"
StandardOutput=journal
StandardError=journal
SyslogIdentifier=webapp
[Install]
WantedBy=multi-user.target
加载并启动:
sudo systemctl daemon-reload # 新创建或修改服务文件后必须执行
sudo systemctl start webapp
sudo systemctl enable webapp # 设置开机自启
查看状态和日志:
systemctl status webapp
journalctl -u webapp -f # 跟踪最新日志
6. 高级选项速查
PrivateTmp=true:为服务提供私有的/tmp和/var/tmp命名空间,增加安全性。ProtectSystem=full:将/usr、/boot、/etc设为只读(服务无法修改系统文件),strict则同时使/dev、/proc等虚拟文件系统只读。ProtectHome=true:服务不能访问/home、/root目录,适合不需要读取用户数据的服务。NoNewPrivileges=true:禁止服务通过setuid获得新权限。LimitNOFILE=65535:提高文件描述符数量限制(对于高并发服务很关键)。CPUQuota=200%:限制 CPU 使用额度,100% 对应一个 CPU 核。MemoryMax=512M:限制最大内存使用,超过后进程会被 OOM killer 终止。
这些安全强化选项可以大幅降低服务被入侵后的损害范围,在生产环境中非常有价值。
7. 排错技巧
- 修改服务文件后一定要运行
sudo systemctl daemon-reload,否则 systemd 仍使用旧的缓存。 - 启动失败时先运行
systemctl status <服务名>,它会显示最近的日志摘要和退出码。 - 使用
journalctl -xe -u <服务名>查看更详细的错误。 - 测试
ExecStart命令时,先在终端中手动执行以确保路径和参数正确。 - 如果服务启动后立即退出,检查是否使用了
forking类型但程序并没有后台化,或是否有PIDFile未指定。
自定义 systemd 服务是运维和开发人员的基本功。通过声明式配置,你可以统一管理所有后台进程的启动、监控和日志,把精力集中在业务逻辑上。掌握这些核心配置项,足以应对 90% 以上的服务管理场景。