一个小脚本解决大问题,这才是运维利器
你有没有遇到过这样的场景:服务器重启后,某个关键服务没起来,业务直接中断;或者每次手动启动一堆监控脚本、日志清理任务,重复操作又累又容易出错?其实,一个不到20行的开机启动脚本,就能把这些问题一劳永逸地解决掉。它不依赖复杂工具,不增加系统负担,却能稳稳托住你的线上服务——这才是真正接地气、可落地、见效快的运维利器。
这篇文章不讲高深理论,也不堆砌术语。我们就用最朴实的方式,带你从零写出一个可靠的开机自启脚本,并在CentOS和Ubuntu上都验证通过。无论你是刚接触Linux的新手,还是每天和服务器打交道的老运维,都能照着做、马上用、不出错。
1. 先搞懂一件事:开机启动不是“随便放个脚本就行”
很多人以为,只要把脚本丢进某个目录,系统就会自动运行它。但现实是:Linux启动过程有明确的阶段划分,脚本必须放在对的位置、起对的名字、满足基本规范,才能被正确识别和执行。
核心就两点:
- 脚本得放在
/etc/init.d/目录下(这是传统SysV init系统的标准位置,CentOS 6/7 和 Ubuntu 16.04+ 的兼容模式都支持) - 脚本本身要带基础控制逻辑:至少能响应
start、stop、status这三个命令,否则系统无法管理它
别担心,这个要求一点都不难。下面我们就写一个真正可用的mytest.sh,它会做一件简单但很典型的事:开机时自动创建一个标记文件,并记录启动时间。
1.1 写一个“能说话”的启动脚本
打开终端,用你喜欢的编辑器(比如nano或vim),创建/etc/init.d/mytest.sh:
#!/bin/bash # chkconfig: 2345 99 01 # description: A simple test service for boot startup # processname: mytest # 定义服务名称和日志路径 SERVICE_NAME="mytest" LOG_FILE="/var/log/mytest.log" MARK_FILE="/tmp/mytest_booted" # 启动函数 start() { echo "Starting $SERVICE_NAME..." echo "Boot time: $(date)" > "$MARK_FILE" echo "$(date): Service started" >> "$LOG_FILE" touch /var/lock/subsys/$SERVICE_NAME } # 停止函数 stop() { echo "Stopping $SERVICE_NAME..." rm -f "$MARK_FILE" echo "$(date): Service stopped" >> "$LOG_FILE" rm -f /var/lock/subsys/$SERVICE_NAME } # 状态函数 status() { if [ -f /var/lock/subsys/$SERVICE_NAME ]; then echo "$SERVICE_NAME is running" else echo "$SERVICE_NAME is not running" fi } # 根据参数调用对应函数 case "$1" in start) start ;; stop) stop ;; restart) stop start ;; status) status ;; *) echo "Usage: $0 {start|stop|restart|status}" exit 1 ;; esac为什么这个脚本能被系统识别?
第二行# chkconfig: 2345 99 01是关键——它告诉chkconfig工具:这个服务应该在运行级别2、3、4、5下启动,启动序号99(靠后),停止序号01(靠前)。虽然现代系统不一定用chkconfig,但保留它能提升兼容性。
第三行# description:是服务描述,方便后续查看。
最重要的是:它实现了start/stop/status三个标准动作,这是所有启动脚本的“身份证”。
1.2 设置权限并测试本地运行
脚本写完后,必须给它可执行权限:
sudo chmod +x /etc/init.d/mytest.sh现在就可以不重启,直接测试它是否工作正常:
# 手动启动 sudo /etc/init.d/mytest.sh start # 查看状态 sudo /etc/init.d/mytest.sh status # 检查标记文件和日志 cat /tmp/mytest_booted tail -n 1 /var/log/mytest.log如果看到类似mytest is running和正确的日期输出,说明脚本本身完全没问题。这一步非常关键——永远先确保脚本本地能跑通,再谈开机启动。
2. 系统怎么知道该什么时候运行它?运行级别和rc目录的秘密
Linux启动时,并不是一股脑儿把所有脚本全拉起来。它按“运行级别”(Runlevel)分阶段加载服务。你可以把它理解成“开机流程图”里的不同关卡:
- Runlevel 0:关机
- Runlevel 1:单用户模式(维护用)
- Runlevel 2~5:多用户模式,其中3 和 5 是最常见的默认级别(3是纯命令行,5带图形界面)
- Runlevel 6:重启
那系统怎么决定在哪个级别加载你的脚本?答案就在/etc/rcX.d/目录里。
2.1 查看当前系统的默认运行级别
运行这条命令:
runlevel你会看到类似N 5的输出。前面的N表示“之前没有运行级别”,后面的5就是当前级别——也就是系统启动时默认进入的级别。
小贴士:CentOS 7+/Ubuntu 18.04+ 默认使用 systemd,但为了兼容老脚本,它们仍保留了 SysV init 的模拟层。
runlevel命令依然有效,/etc/rc5.d/目录也真实存在。
2.2/etc/rc5.d/目录里到底有什么?
进入该目录看看:
cd /etc/rc5.d/ ls -l你会发现一堆以S或K开头的链接,比如S10network、S20sshd、K99grub2。它们的命名规则非常清晰:
Sxx:Start,表示“启动服务”,xx是两位数字,代表执行顺序(01最早,99最晚)Kxx:Kill,表示“停止服务”,同样按数字顺序执行- 所有这些链接,最终都指向
/etc/init.d/下的真实脚本
所以,让我们的mytest.sh开机启动,本质就是:在/etc/rc5.d/下创建一个指向它的Sxx链接。
3. 创建软链接:三步搞定开机自启
这一步极简,但名字和顺序很重要。
3.1 为什么选S99mytest而不是S01mytest?
因为S99表示“最后启动”。你的脚本很可能依赖网络、磁盘挂载、数据库等基础服务。如果它排在第一个(S01),而网络还没起来,脚本就会失败。S99是安全的选择——等绝大多数服务都就绪了,再轮到它。
当然,如果你的服务是基础设施级的(比如自定义的网络配置),可以适当调前,但99%的场景,S99最稳妥。
3.2 执行创建命令
sudo ln -s /etc/init.d/mytest.sh /etc/rc5.d/S99mytest注意:链接名是S99mytest,不是S99test。我们用服务名mytest,更清晰,避免歧义。
验证是否成功:
ls -l /etc/rc5.d/S99mytest你应该看到类似这样的输出:
S99mytest -> /etc/init.d/mytest.sh这就意味着:下次系统启动到 runlevel 5 时,它一定会执行/etc/init.d/mytest.sh start。
4. 实战验证:不重启也能测效果
别急着reboot。我们可以用更安全、更快捷的方式模拟开机启动流程。
4.1 手动触发 rc5.d 的全部 S 脚本
sudo /etc/init.d/rc 5这条命令会“假装”系统正切换到 runlevel 5,并依次执行/etc/rc5.d/下所有Sxx*脚本。你的S99mytest就在其中。
执行后,检查结果:
# 看服务是否被标记为运行中 sudo /etc/init.d/mytest.sh status # 看标记文件是否存在且内容正确 cat /tmp/mytest_booted # 看日志是否追加了新记录 tail -n 1 /var/log/mytest.log如果一切正常,恭喜你——脚本已正式纳入系统启动流程,只等下次真正重启生效。
4.2 (可选)用 chkconfig 管理(CentOS 用户推荐)
如果你用的是 CentOS,还可以用更高级的工具统一管理:
# 添加服务到 chkconfig sudo chkconfig --add mytest # 设置开机自启(对所有多用户级别生效) sudo chkconfig mytest on # 查看状态 sudo chkconfig --list | grep mytest它会自动为你在/etc/rc3.d/和/etc/rc5.d/下都创建S99mytest链接,省去手动操作。
5. 常见问题与避坑指南
写脚本容易,但让它长期稳定运行,需要避开几个经典陷阱。这些都是我在上百台服务器上踩过的坑,现在直接告诉你答案。
5.1 “脚本执行了,但什么都没发生?”——路径和环境变量问题
很多脚本里写了python3 /home/user/script.py,结果开机失败。原因很简单:/home分区可能还没挂载,或者PATH环境变量不包含/usr/local/bin。
解决方案:
- 所有路径用绝对路径(
/usr/bin/python3,不是python3) - 在脚本开头显式设置 PATH:
export PATH="/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin"
5.2 “日志里全是 Permission denied”——权限和用户问题
脚本默认以root身份运行,但如果它试图往普通用户目录写文件,就会失败。
解决方案:
- 日志、临时文件统一放在
/var/log/、/tmp/、/var/run/这类 root 可写的系统目录 - 如果必须操作用户数据,用
sudo -u username command切换用户
5.3 “重启后发现脚本没运行”——检查锁文件和依赖
有时脚本启动逻辑里有判断,比如“如果锁文件存在就不重复执行”,但锁文件路径错了,或上次异常退出没清理干净。
解决方案:
- 启动前先清理旧锁:
rm -f /var/lock/subsys/mytest - 加入简单防重逻辑(已在示例脚本中体现)
- 用
systemctl list-dependencies --reverse mytest.service(如转为 systemd)或检查/etc/rc5.d/链接是否真实存在
6. 进阶思考:这个小脚本能做什么大事情?
别小看这个“创建标记文件”的例子。它是一切自动化运维的起点。把里面的echo换成真实命令,它立刻就能承担重任:
- 自动拉起 Docker 容器:
docker-compose -f /opt/app/docker-compose.yml up -d - 同步配置文件:
rsync -avz /etc/nginx/ user@backup-server:/backup/nginx/ - 发送企业微信告警:
curl 'https://qyapi.weixin.qq.com/...' --data '{"msgtype":"text","text":{"content":"Server rebooted OK"}}' - 初始化数据库连接池:
mysql -u root -e "CREATE DATABASE IF NOT EXISTS app_db;"
关键是:它脱离了人工干预,成为系统自身的一部分。当故障发生、半夜告警、流量突增时,你不需要登录服务器,它已经默默完成了该做的事。
7. 总结:小脚本,大智慧
回看整个过程,我们只做了四件事:
- 写一个带
start/stop/status的标准脚本 - 查清系统运行级别(通常是5)
- 在
/etc/rc5.d/下创建S99mytest软链接 - 用
/etc/init.d/rc 5快速验证,再reboot终极确认
没有安装新软件,没有修改内核,不依赖云平台,甚至不用联网。它纯粹依靠 Linux 最底层、最稳定的启动机制,却解决了运维中最高频、最琐碎、最容易出错的问题。
真正的运维利器,从来不是功能最炫的工具,而是最简单、最可靠、最经得起时间考验的那一行命令、一个脚本、一次配置。当你能把这种“小而确定”的能力,沉淀成团队的标准实践,你就已经走在了自动化运维的正确道路上。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。