如果想开机就执行一个脚本,怎么做?
在 Linux 服务器运维中,经常需要让某些脚本或程序在系统启动时自动运行——比如挂载远程存储、建立反向隧道连接 CI/CD 服务器、启动监控代理等。过去大家习惯往 /etc/rc.local 里堆命令,但这种方式既无法管理依赖顺序,也无法追踪运行状态。
如今主流 Linux 发行版都已采用 systemd 作为初始化系统,通过编写 service 单元文件来实现开机自启,是更规范、更可控的做法。本文将从实际操作出发,逐步解析其工作原理,并整理常见配置参数与排错经验。
一、操作实录
假设需要在服务器开机时自动执行一个连接 Jenkins 的脚本 /root/connect.sh,完整操作如下:
1. 创建 service 文件
sudo vim /etc/systemd/system/dosomething.service
写入以下内容:
[Unit]
Description=Run Something at boot
After=network.target
[Service]
Type=oneshot
ExecStart=/root/run.sh
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
2. 重新加载 systemd 配置
sudo systemctl daemon-reexec
3. 启用开机自启
sudo systemctl enable dosomething.service
💡 完成以上三步后,系统每次启动都会自动执行 /root/run.sh。
二、技术原理
systemd 概述
systemd 是现代 Linux 发行版(Ubuntu 16.04+、Debian 8+、CentOS 7+ 等)的系统和服务管理器,在系统引导过程中作为第一个进程(PID 1)运行,负责初始化整个用户空间。它取代了传统的 SysV init 系统,主要优势包括:
🚀 并行启动:多个服务同时拉起,缩短启动时间
🔗 依赖管理:通过声明式配置精确控制启动顺序
🧩 统一接口:systemctl 命令统一管理所有服务
📊 生命周期跟踪:每个服务都有明确的状态(active、inactive、failed 等)
Service 文件结构解析
▎[Unit] 段:元数据与启动顺序
Description=Do Something at boot
After=network.target
-
- Description:服务的可读描述,出现在
systemctl status 输出中。 - After:定义启动顺序约束。它表示"在
network.target 完成之后再启动本服务",属于弱依赖——只保证顺序,不保证网络一定可用。如果脚本中需要真正可用的网络连接(如访问远程 HTTP 接口),应使用更严格的配置:
After=network-online.target
Wants=network-online.target
Wants 会主动拉起 network-online.target,确保网络接口完成 IP 分配和路由配置。
▎[Service] 段:运行行为定义
Type=oneshot
ExecStart=/root/connect.sh
RemainAfterExit=yes
-
- Type=oneshot:适用于执行完即退出的任务。systemd 会等待
ExecStart 指定的进程完全退出后,才认为该服务"启动完成"。 - ExecStart:指定要执行的主命令。必须是绝对路径。
- RemainAfterExit=yes:关键参数。由于 oneshot 类型的进程执行完就会退出,如果不设置此项,systemd 会判定服务启动失败。设为
yes 后,即使进程已退出,systemd 仍将其标记为 active (exited) 状态。 - 常见的 Type 类型对比:
| 类型 |
适用场景 |
simple |
前台持续运行的进程(默认值) |
forking |
传统守护进程,父进程 fork 子进程后自行退出 |
| oneshot |
一次性任务,执行完毕即退出 |
notify |
进程通过 sd_notify() 向 systemd 发送就绪信号 |
dbus |
通过 D-Bus 名称注册来判定就绪 |
idle |
等待其他任务全部启动完毕后执行 |
▎[Install] 段:安装信息
WantedBy=multi-user.target
这定义了服务被 enable 时的归属目标。multi-user.target 对应传统的运行级别 3(多用户命令行模式)。执行 systemctl enable 后,systemd 会在 /etc/systemd/system/multi-user.target.wants/ 目录下创建一个指向该 service 文件的符号链接。系统进入该 target 时,会自动启动所有被链接的服务。
常见的 WantedBy 目标:
| 目标 |
含义 |
multi-user.target |
多用户命令行模式 |
graphical.target |
图形界面模式 |
default.target |
系统默认目标(通常是上述两者之一) |
daemon-reexec 与 daemon-reload 的区别
文中使用了 daemon-reexec,它会完全重启 systemd 守护进程本身,重新执行 systemd 二进制文件。在日常操作中,更常用的是:
sudo systemctl daemon-reload
| 命令 |
作用范围 |
典型使用场景 |
daemon-reload |
仅重新加载 unit 文件配置 |
新增或修改 service/timer 等单元文件后 |
daemon-reexec |
重启 systemd 进程本身 |
systemd 软件包升级后 |
绝大多数情况下 daemon-reload 已经足够,daemon-reexec 的开销更大。
enable 的本质
systemctl enable 并非将配置"写入某个开机脚本",而是在对应的 .wants 目录中建立符号链接。可以通过下面命令验证:
ls -la /etc/systemd/system/multi-user.target.wants/connect_jenkins.service
# lrwxrwxrwx 1 root root 41 Jan 15 10:00 ... -> /etc/systemd/system/connect_jenkins.service
相应地,systemctl disable 只是删除这个符号链接,不会删除原始的 service 文件。
三、常用参数配置
3.1 环境变量注入
[Service]
Environment="JENKINS_URL=https://jenkins.example.com"
Environment="API_TOKEN=abc123xyz"
EnvironmentFile=/etc/sysconfig/connect_jenkins
ExecStart=/root/connect.sh
Environment 直接定义单个变量,EnvironmentFile 则可以批量从一个文件中读取(文件格式为 KEY=VALUE,以 # 开头的行视为注释)。
3.2 指定运行用户
[Service]
User=jenkins
Group=jenkins
ExecStart=/root/connect.sh
⚠️ 注意:如果将脚本存放在 /root/ 目录下,同时指定非 root 用户运行,会因权限不足导致 Permission denied。建议将脚本移至 /usr/local/bin/ 等全局可读路径。
3.3 自动重启策略
对于需要长期运行的进程(如隧道、守护程序),应配置重启策略:
[Service]
Type=simple
ExecStart=/usr/bin/my-daemon
Restart=always
RestartSec=10
Restart 字段的可选值:
| 值 |
触发重启的条件 |
no | 从不重启(默认) |
on-success | 仅在进程正常退出(exit code 0)时 |
on-failure | 仅在非正常退出时(非零退出码、被信号杀死等) |
on-abnormal | 被信号杀死或超时 |
| always | 无论何种原因退出都重启 |
3.4 超时与启动频率限制
[Service]
TimeoutStartSec=30
TimeoutStopSec=15
StartLimitIntervalSec=300
StartLimitBurst=5
-
- TimeoutStartSec:服务启动阶段的超时时间。如果进程在该时间内未发出就绪信号(对于
Type=notify)或未退出(对于 Type=oneshot),systemd 会认为启动失败并终止进程。 -
- StartLimitIntervalSec 和 StartLimitBurst:联合限制单位时间内的启动次数,防止故障服务无限循环重启消耗系统资源。
-
3.5 日志查看
systemd 自动将所有服务的标准输出和标准错误收集到 journald:
journalctl -u connect_jenkins.service # 查看全部日志
journalctl -u connect_jenkins.service -f # 实时追踪(类似 tail -f)
journalctl -u connect_jenkins.service --since "1 hour ago"
3.6 延迟启动
如果希望脚本在开机后延迟一段时间再执行,有两种方式:
方式一:在 service 中使用 ExecStartPre
[Service]
ExecStartPre=/bin/sleep 30
ExecStart=/root/connect.sh
方式二:使用 systemd timer(推荐)
# /etc/systemd/system/connect_jenkins.timer
[Unit]
Description=Delay Connect Jenkins startup
[Timer]
OnBootSec=60s
Unit=connect_jenkins.service
[Install]
WantedBy=timers.target
然后启用 timer 而非直接启用 service:
systemctl enable connect_jenkins.timer
systemctl start connect_jenkins.timer
timer 方式的优势在于独立于 service 文件,时间控制更灵活(支持日历定时、单调定时等),且不会污染 service 的启动逻辑。
四、常见报错与排查
报错 1:exit code 1 / 启动失败
connect_jenkins.service: Failed with result 'exit-code'.
Process: 1234 ExecStart=/root/connect.sh (code=exited, status=1/FAILURE)
排查步骤:
# 查看详细日志
journalctl -u connect_jenkins.service --no-pager
# 手动以 bash -x 调试脚本
bash -x /root/connect.sh
# 确认脚本首行包含正确的 shebang
head -1 /root/connect.sh
# 应输出 #!/bin/bash 或 #!/bin/sh
常见原因汇总:
| 原因 |
解决方法 |
| 脚本无执行权限 | chmod +x /root/connect.sh |
| 脚本中使用相对路径 | 全部改为绝对路径 |
| 依赖的环境变量未设置 | 在 service 文件中用 Environment= 声明 |
| 脚本依赖的命令不在 PATH 中 | 用绝对路径调用或设置 Environment=PATH=... |
报错 2:Permission denied
Failed to execute command: Permission denied
# 确保脚本有可执行权限
chmod 755 /root/connect.sh
# 确认文件权限
ls -la /root/connect.sh
报错 3:网络未就绪
脚本尝试连接远程服务器,但 DNS 解析失败或连接被拒绝,尽管配置了 After=network.target。
原因:network.target 仅表示网络服务进程已启动,不代表网络接口已完成 IP 配置或网关可达。
解决:改用 network-online.target:
[Unit]
Description=Run Connect Jenkins at boot
Wants=network-online.target
After=network-online.target
同时确保等待网络在线的组件已启用(取决于发行版):
# Debian/Ubuntu
systemctl enable systemd-networkd-wait-online.service
# CentOS/RHEL
systemctl enable NetworkManager-wait-online.service
报错 4:start-limit-hit
Start request repeated too quickly.
Failed with result 'start-limit-hit'.
原因:服务在短时间内反复启动失败,触发了 systemd 的启动频率保护机制。
# 重置失败计数
systemctl reset-failed connect_jenkins.service
# 修复根本问题后再启动
systemctl start connect_jenkins.service
报错 5:enable 提示文件不存在
Failed to enable unit: Unit file connect_jenkins.service does not exist.
# 确认文件位置
ls /etc/systemd/system/connect_jenkins.service
# 重新加载配置
systemctl daemon-reload
# 再次尝试 enable
systemctl enable connect_jenkins.service
排错速查表
| 检查项 |
命令 |
| 服务是否已加载 | systemctl list-unit-files | grep ... |
| 当前运行状态 | systemctl status ...service |
| 最近相关日志 | journalctl -xe -u ...service |
| 配置语法校验 | systemd-analyze verify ... |
| 手动执行测试 | /root/connect.sh |
五、总结
通过 systemd service 文件实现开机自启,其核心流程可以概括为:
编写 service 文件 → daemon-reload 加载配置 → enable 建立开机链接 → 系统启动时按依赖顺序执行
对于一次性脚本任务,Type=oneshot 配合 RemainAfterExit=yes 是最合适的组合。如果脚本实际需要维持一个长期连接(如 SSH 反向隧道),则应考虑改为 Type=simple 并添加 Restart=always,确保进程异常退出后能自动恢复。
掌握 systemd service 的配置与排错方法,是 Linux 系统管理的基础技能。相比传统的 rc.local 方式,systemd 提供了更完善的依赖管理、状态追踪和日志集成能力,值得在各类自动化运维场景中推广使用。