rc-update 命令详解与参数实测(Alpine Linux)
在 Alpine Linux 3.20(OpenRC 0.54)上对 rc-update 的 add/del/show 子命令与 -a/-s/-u/-q/-C 等选项逐项实测,讲清 boot/default/sysinit/shutdown 四层 runlevel 体系与开机自启的软链原理。
rc-update 命令详解与参数实测(Alpine Linux)
目标:讲清 OpenRC 开机自启管理器
rc-update的全部子命令与选项,从软链机制到 boot/default/sysinit/shutdown 四层 runlevel 体系,再到栈式 runlevel、依赖树刷新等进阶用法,全部命令与回显已在 Alpine Linux 3.20.10(OpenRC 0.54)实测通过。
一、环境信息
| 项目 | 值 |
|---|---|
| 操作系统 | Alpine Linux 3.20.10(PRETTY_NAME="Alpine Linux v3.20") |
| 内核 | 6.8.0-138-generic |
| 架构 | x86_64 |
| OpenRC | 0.54-r1(openrc --version → openrc (OpenRC) 0.54) |
| busybox | 1.36.1-r31(提供 busybox-openrc 内置服务的 init 脚本) |
| 测试服务 | nginx 1.26.3-r0、busybox crond/syslog |
| 测试日期 | 2026-10-17 |
Alpine Linux 以 OpenRC 为默认 init 体系。OpenRC 管理服务分两个独立维度:当前状态由 rc-service 负责(start/stop/status,见姊妹篇《rc-service 命令详解与参数实测(Alpine Linux)》),开机自启则由本文主角 rc-update 负责。
二、命令概览
2.1 开机自启原理:runlevel 与软链
OpenRC 的「开机自启」没有任何魔法,本质就是把 /etc/init.d/ 下的服务脚本软链到 /etc/runlevels/<runlevel>/ 目录。OpenRC 引导时按 sysinit → boot → default 的顺序遍历这些目录,把其中的服务做依赖排序后依次拉起;关机时反向执行 shutdown 层。rc-update 的全部工作,就是替你增删查这些软链。
先看目录骨架(/sbin/rc-update 是命令本体):
$ which rc-service rc-update openrc
/sbin/rc-service
/sbin/rc-update
/sbin/openrc
$ ls -l /etc/runlevels/
total 20
drwxr-xr-x 2 root root 4096 Oct 17 00:32 boot
drwxr-xr-x 2 root root 4096 Oct 17 00:32 default
drwxr-xr-x 2 root root 4096 Oct 17 00:32 nonetwork
drwxr-xr-x 2 root root 4096 Oct 17 00:32 shutdown
drwxr-xr-x 2 root root 4096 Oct 17 00:32 sysinit
五个 runlevel 的职责分工:
| runlevel | 引导阶段 | 典型成员 | 说明 |
|---|---|---|---|
| sysinit | 内核刚接管系统 | devfs、sysfs、cgroups、mdev/udev | 挂载内核伪文件系统、设备管理,最先执行 |
| boot | 系统本地初始化 | root、fsck、localmount、hostname、hwclock、sysctl、modules | 在网络之前完成本地环境准备 |
| default | 常规运行态 | networking、sshd、crond、nginx 及各应用服务 | 「开机自启」通常指的目标层,多用户服务都加这里 |
| shutdown | 关机流程 | killprocs、mount-ro、savecache | 系统关停时反向执行 |
| nonetwork | 单机维护 | 无预置成员 | default 的无网变体,供救援/单机场景叠加 |
💡 技术拓展:Linux 的四种主流 init 体系对比。init 是 PID 1,决定「服务怎么定义、怎么启动、怎么管自启」:
| init 体系 | 服务定义 | 启动模型 | 自启管理命令 |
|---|---|---|---|
| sysvinit | /etc/init.d/ LSB 脚本 | 编号串行,无依赖图 | chkconfig / update-rc.d |
| OpenRC(本文) | /etc/init.d/ 脚本 + depend() 依赖声明 | 依赖排序后并行,runlevel 分层 | rc-update(Alpine、Gentoo、Devuan) |
| systemd | unit 文件 + cgroup 跟踪 | socket/D-Bus 激活,依赖图 | systemctl enable/disable |
| runit | /service/ 监督脚本 | 目录即服务,进程监督重启 | 软链 /var/service(Void Linux) |
与 systemd 把自启记录写进 unit 文件不同,OpenRC 的 runlevel 就是纯目录+软链,可以直接 ls -l /etc/runlevels/default/ 看真相——这也是它简单、可审计、易救援的原因。
2.2 语法与 --help 全量回显
rc-update 只有三个子命令(add/del/show),配四个全局选项:
$ rc-update --help
Usage: rc-update [options] add <service> [<runlevel>...]
or: rc-update [options] del <service> [<runlevel>...]
or: rc-update [options] [show [<runlevel>...]]
Options: [ asuChqVv ]
-a, --all Process all runlevels
-s, --stack Stack a runlevel instead of a service
-u, --update Force an update of the dependency tree
-h, --help Display this help output
-C, --nocolor Disable color output
-V, --version Display software version
-v, --verbose Run verbosely
-q, --quiet Run quietly (repeat to suppress errors)
2.3 测试数据准备
与姊妹篇同一套环境(安装命令与 init 脚本原文见《rc-service 命令详解与参数实测(Alpine Linux)》2.3 节):
$ apk add openrc busybox-openrc nginx
三个测试服务的角色:crond(add/del 主角)、nginx(裸 add 的 runlevel 回落演示)、syslog(boot 层演示)。初始状态 runlevel 全空,可以从零观测每条命令的效果。
⚠️ 前置要求:rc-update 的 -u(依赖树刷新)依赖 OpenRC 引导建立的 /run/openrc/ 运行态目录。本文实测在 OpenRC 为 init 的真实 Alpine 服务器语境下进行;救援盘/chroot 等未引导环境的初始化方法见姊妹篇 5.1 节。
三、参数逐项实测
3.1 rc-update show:查看自启注册表(不带参数等价于 show)
$ rc-update
$ echo $?
0
$ rc-update show
$ echo $?
0
全新系统的 runlevel 全空,两条命令都无输出、退出码 0。加了服务后再看输出格式(见 3.3 节)。
3.2 show -v:把未注册的服务也列出来
$ rc-update show -v
acpid |
agetty |
binfmt |
bootmisc |
cgroups |
consolefont |
crond |
devfs |
dmesg |
firstboot |
fsck |
hostname |
hwclock |
hwdrivers |
killprocs |
klogd |
loadkmap |
local |
localmount |
loopback |
machine-id |
modloop |
modules |
mount-ro |
mtab |
net-online |
netmount |
networking |
nginx |
ntpd |
numlock |
osclock |
procfs |
rdate |
rfkill |
root |
runsvdir |
s6-svscan |
save-keymaps |
save-termencoding |
savecache |
seedrng |
staticroute |
swap |
swclock |
sysctl |
sysfs |
sysfsconf |
syslog |
termencoding |
watchdog |
不带 -v 时只列出已注册服务;-v 把 /etc/init.d/ 里全部 51 个服务都铺出来,右列留出 runlevel 标注位——这是盘点「装了哪些服务、哪些还没配自启」的常用手段(服务清单与《rc-service》3.11 节的 -l 同源)。
3.3 add <service> <runlevel>:注册开机自启
$ rc-update add crond default
* service crond added to runlevel default
$ echo $?
0
$ ls -l /etc/runlevels/default/
total 0
lrwxrwxrwx 1 root root 17 Oct 17 00:32 crond -> /etc/init.d/crond
输出确认 + 软链实锤:/etc/runlevels/default/crond -> /etc/init.d/crond,这就是「开机自启」的全部实现。重复添加是幂等的:
$ rc-update add crond default
* rc-update: crond already installed in runlevel `default'; skipping
$ echo $?
0
3.4 add 省略 runlevel:回落到「当前运行级」,无运行级时落 sysinit
任务书之外的重要发现——add 的 runlevel 参数可以省略:
$ rc-update add nginx
* service nginx added to runlevel sysinit
$ echo $?
0
$ ls -l /etc/runlevels/sysinit/ /etc/runlevels/default/
/etc/runlevels/default/:
total 0
lrwxrwxrwx 1 root root 17 Oct 17 00:32 crond -> /etc/init.d/crond
/etc/runlevels/sysinit/:
total 0
lrwxrwxrwx 1 root root 17 Oct 17 00:32 nginx -> /etc/init.d/nginx
nginx 被加进了 sysinit!规则是:省略 runlevel 时取「当前运行级」(/run/openrc/softlevel 记录的值),正常引导的 default 运行级服务器上等价于 add nginx default;本环境无当前运行级记录,回落到兜底值 sysinit。交互敲 rc-update add nginx 少打一个 default 是常见手滑,用 show 能立即发现。清理:
$ rc-update del nginx sysinit
* service nginx deleted from runlevel sysinit
$ echo $?
0
⚠️ 反直觉提醒:sysinit 层加应用服务没有意义(该层在伪文件系统就绪后即执行完退出),虽然 rc-update 不做拦截,但这个服务永远不会在 default 阶段被拉起。
3.5 show [<runlevel>...]:按列解读注册表
添加后先看全貌(列为全部 runlevel,| 后按列标注归属):
$ rc-update show
crond | default
nginx | sysinit
$ echo $?
0
只看某一层、或多层过滤:
$ rc-update show default
crond | default
$ echo $?
0
$ rc-update show sysinit boot
nginx | sysinit
$ echo $?
0
加入 boot 层的 syslog 后三列对照更直观(先注册 syslog):
$ rc-update add syslog boot
* service syslog added to runlevel boot
$ echo $?
0
$ ls -l /etc/runlevels/boot/
total 0
lrwxrwxrwx 1 root root 18 Oct 17 00:32 syslog -> /etc/init.d/syslog
$ rc-update show
crond | default
nginx | sysinit
syslog | boot
同一服务可以同时注册多层(下一层未完成前不会被重复执行,OpenRC 引擎按依赖图去重):
$ rc-update add crond boot
* service crond added to runlevel boot
$ echo $?
0
$ rc-update show
crond | boot default
3.6 del <service> [<runlevel>...]:注销自启
$ rc-update del syslog boot
* service syslog deleted from runlevel boot
$ echo $?
0
$ rc-update del crond default boot
* service crond deleted from runlevel default
* service crond deleted from runlevel boot
$ echo $?
0
del 支持一次指定多个 runlevel,逐一删除并逐行报告。删不存在的注册会报错:
$ rc-update del nosuch default
* rc-update: service `nosuch' is not in the runlevel `default'
$ echo $?
1
3.7 -a/--all:对全部 runlevel 批量操作
-a 让 add/del 作用于所有 runlevel。del 场景最实用——不记得服务注册在哪层时一键摘除(先重建双 runlevel 场景):
$ rc-update add crond default
* service crond added to runlevel default
$ rc-update add crond boot
* service crond added to runlevel boot
$ rc-update -a del crond
* rc-update: service `crond' is not in the runlevel `nonetwork'
* rc-update: service `crond' is not in the runlevel `shutdown'
* service crond deleted from runlevel default
* rc-update: service `crond' is not in the runlevel `sysinit'
* service crond deleted from runlevel boot
$ echo $?
1
-a del 遍历全部五个 runlevel:不含目标服务的层逐条告警(nonetwork/shutdown/sysinit),含的层执行删除(default/boot);只要有一条告警,整体退出码为 1。注意遍历顺序是目录序(实测两次输出顺序不同),脚本里不要依赖行序。验证删净:
$ rc-update show
$ echo $?
0
-a 与 add 组合会被拒绝(语义不明,官方不允许):
$ rc-update -a add crond default
* rc-update: the -a option is invalid with add
$ echo $?
1
3.8 -s/--stack:栈式 runlevel(把一个 runlevel 叠进另一个)
-s 切换 add/del 的操作对象:不再是「服务 → runlevel」,而是「runlevel → runlevel」,把一个 runlevel 整体叠加为另一个的成员:
$ rc-update -s add nonetwork default
* runlevel nonetwork added to runlevel default
$ echo $?
0
$ ls -la /etc/runlevels/default/
total 8
drwxr-xr-x 2 root root 4096 Oct 17 00:32 .
drwxr-xr-x 7 root root 4096 Oct 17 00:32 ..
lrwxrwxrwx 1 root root 12 Oct 17 00:32 nonetwork -> ../nonetwork
栈的存储也是软链:default/nonetwork -> ../nonetwork。引导 default 层时,OpenRC 会递归展开 nonetwork 里的全部成员。移除:
$ rc-update -s del nonetwork default
* runlevel nonetwork deleted from runlevel default
$ echo $?
0
$ ls -la /etc/runlevels/default/
total 8
drwxr-xr-x 2 root root 4096 Oct 17 00:32 .
drwxr-xr-x 7 root root 4096 Oct 17 00:32 ..
两条防护性限制(实测回显):
$ rc-update -s add default default
* rc-update: cannot stack `default' onto itself
$ echo $?
1
$ rc-update -s add nonetwork boot
* rc-update: cannot stack the sysinit runlevel
$ echo $?
1
自身叠自身直接拒绝;往 boot 层叠栈被拦(提示文案笼统地写作 "sysinit",实际 sysinit 与 boot 都不允许叠栈——引导早期层必须保持扁平可控)。
3.9 -u/--update:强制刷新依赖树,且会短路后续子命令
$ rc-update -u
Service `acpid' needs non existent service `dev'
Service `hwdrivers' needs non existent service `dev'
Service `machine-id' needs non existent service `dev'
Service `watchdog' needs non existent service `dev'
* Caching service dependencies ... [ ok ]
$ echo $?
0
-u 立即重算服务依赖图并写入 /run/openrc/deptree 缓存。那 4 行 "needs non existent service dev'" 是最小环境特有的提示:acpid/hwdrivers 等脚本 need dev,而本环境未装 mdev/udev 设备管理服务;真实服务器装齐设备服务后不会有这些行。依赖缓存刷新后,rc-status --servicelist、服务启动排序等才反映最新注册状态——**add/del 之后建议跟一条 rc-update -u`**。
一个实测陷阱:-u 处理完直接返回,同一命令行里跟在后面的子命令不会执行(官方设计为 update 即动作):
$ rc-update -u show
Service `acpid' needs non existent service `dev'
Service `hwdrivers' needs non existent service `dev'
Service `machine-id' needs non existent service `dev'
Service `watchdog' needs non existent service `dev'
* Caching service dependencies ... [ ok ]
$ echo $?
0
想「刷新后再看列表」要分两条命令执行。
3.10 show 与悬空软链:注册表只认服务脚本
手工制造一个指向不存在脚本的软链(模拟服务被 apk delete 但自启残留的典型事故):
$ ln -s /etc/init.d/nosuchsvc /etc/runlevels/default/nosuchsvc
$ ls -l /etc/runlevels/default/
total 0
lrwxrwxrwx 1 root root 21 Oct 17 00:32 nosuchsvc -> /etc/init.d/nosuchsvc
$ rc-update show
$ echo $?
0
show 不显示悬空软链(无法解析成服务就不进列表),但它在引导时会被引擎警告。用 del 清理即可:
$ rc-update del nosuchsvc default
* service nosuchsvc deleted from runlevel default
$ echo $?
0
$ ls -l /etc/runlevels/default/
total 0
💡 Gentoo 老版本 rc-update 曾有 fix 子命令专扫悬空链,OpenRC 0.54 已移除(实测见 3.12 节),现在清理残留就是 rc-update del <svc> <runlevel> 一条条做,或直接 rm 软链。
3.11 -q/--quiet:静默执行
$ rc-update -q add syslog default
$ echo $?
0
$ rc-update show
syslog | default
成功的 add 被完全静默,只留退出码 0;注册表确认 syslog 已进入 default。适合批量脚本。未加 -q 的对照(同一服务的普通删除):
$ rc-update del syslog default
* service syslog deleted from runlevel default
$ echo $?
0
3.12 -C/--nocolor、无效子命令与 -h/-V
rc-update 的输出( * service ... added ...)在终端下同样着色,-C 强制纯文本(重定向/管道时本就无色,效果一致):
$ rc-update -C add syslog boot
* service syslog added to runlevel boot
$ echo $?
0
$ rc-update del syslog boot
* service syslog deleted from runlevel boot
$ echo $?
0
0.54 只认 add/del/show 三个子命令,网上老文档提到的其他写法都会被拒(均为实测):
$ rc-update show-default
* rc-update: invalid command `show-default'
$ echo $?
1
$ rc-update show-sysinit
* rc-update: invalid command `show-sysinit'
$ echo $?
1
$ rc-update fix
* rc-update: invalid command `fix'
$ echo $?
1
$ rc-update --version
rc-update (OpenRC) 0.54
$ rc-update -V
rc-update (OpenRC) 0.54
3.13 -v/--verbose 与 show 的列宽
-v 配合 show 即 3.2 节的全量清单;配 add/del 时输出与默认一致(add/del 本就单行报告)。列宽规则:服务名列左对齐、宽度随最长服务名自动扩展(save-termencoding 把列撑到 18 字符),runlevel 标注从 | 右侧按固定列位书写,多层注册按 sysinit → boot → default → shutdown 顺序排列。
四、实战示例
4.1 配置一组服务的开机自启
装完 nginx 与 crond 后的最小自启配置(注意:add 不等于 start,本节命令执行完服务并不会立即运行,要立即生效需另行 rc-service start):
$ rc-update add networking default
* service networking added to runlevel default
$ rc-update add nginx default
* service nginx added to runlevel default
$ rc-update add crond default
* service crond added to runlevel default
$ rc-update -u
Service `acpid' needs non existent service `dev'
Service `hwdrivers' needs non existent service `dev'
Service `machine-id' needs non existent service `dev'
Service `watchdog' needs non existent service `dev'
* Caching service dependencies ... [ ok ]
$ rc-update show
crond | default
networking | default
nginx | default
(-u 输出里 4 条 dev 提示的含义见 3.9 节:本环境未装设备管理服务所致,真实服务器装齐后没有这些行。)
为什么 networking 也要加?nginx 的 init 脚本 need net,而 net 由 networking 提供——开机时如果 networking 不在 default 层,nginx 会因依赖无法满足而起不来(这条教训来自实测:networking 启动失败时 nginx 的 start 直接报 "cannot start nginx as networking would not start")。
4.2 更换自启实现:del + add 组合
把 crond 从「default 层自启」调整为「boot 层尽早自启」(示例演示操作链,实际取舍见 5.2 节;另两行是 4.1 注册的 networking/nginx,正好对照):
$ rc-update del crond default
* service crond deleted from runlevel default
$ rc-update add crond boot
* service crond added to runlevel boot
$ rc-update show
crond | boot
networking | default
nginx | default
4.3 批量审查自启面
接手陌生机器时的一组只读命令(沿用 4.2 完成后的注册表状态):
$ rc-update show
crond | boot
networking | default
nginx | default
$ rc-update show | wc -l
3
$ rc-update show -v | grep -c '|'
51
show 看全貌与分层归属,行数统计回答「配了自启的服务有几个」,show -v 的全量清单(51 个服务全在列)对比即可发现「装了却没配自启」的服务,再结合 rc-service -l 交叉核对。
五、注意事项与常见问题
5.1 add 不等于 start,del 不等于 stop
rc-update 只改「下次开机」的注册表(软链),对正在运行的服务没有任何即时影响:rc-update add crond default 执行完 crond 并不会跑起来(要用 rc-service crond start);rc-update del 也不会停掉在跑的服务(要用 rc-service crond stop)。两篇命令的分工:rc-update 管「要不要自启」,rc-service 管「现在跑不跑」。
5.2 服务该加 default 还是 boot?
经验法则:在根文件系统挂载完成前必须就绪的系统级服务进 boot(如早期日志、时钟同步基础),需要网络或依赖较复杂的常驻服务进 default。放错层的典型症状:服务在 boot 层因依赖(如 net)未就绪而启动失败。多数发行版包自带的服务脚本已由打包者把 boot 层成员(bootmisc、syslog 等基础件)预设好,应用服务一律 default 即可。
5.3 省略 runlevel 的裸 add 会落到哪?
见 3.4 节实测:省略 runlevel 时取当前运行级;引导完成的服务器上是 default(softlevel 记录为 default),救援环境/chroot 中可能回落 sysinit。生产操作永远显式写 runlevel,并在执行后用 rc-update show 复核。
5.4 注册表与软链可以互相直接操作吗?
可以。rc-update 本质就是软链管理器,ln -s /etc/init.d/crond /etc/runlevels/default/crond 与 rc-update add crond default 结果等价(3.3 节的软链实拍就是证据)。但手工操作有三个坑:悬空链不会被告警(3.10 节)、重复添加不会去重提示、依赖树不自动刷新。常规运维走 rc-update,救援时才手工 ln/rm。
5.5 add 之后为什么还要 rc-update -u?
add/del 只动软链;OpenRC 引导与服务启动时读取的依赖图缓存 /run/openrc/deptree 要靠 -u 重算。不加的话,新注册服务的依赖排序可能沿用旧缓存(正常引导流程会在 runlevel 切换时自动刷新,手工操作后手动刷一次最稳妥)。注意 -u 会短路同一命令行里的后续子命令(3.9 节实测)。
5.6 runlevel 里那些奇怪的层(nonetwork/shutdown)怎么用?
nonetwork 是 default 的无网变体:rc-update -s add nonetwork default 把它叠进 default 后,其成员会随 default 一起启动,专门放置「单机维护模式才需要」的服务。shutdown 层的成员(killprocs、mount-ro、savecache)由 OpenRC 在关机时按依赖反向执行,一般不动它。sysinit/boot 两层不允许叠栈(3.8 节实测报错)。
5.7 老教程里的 show-<runlevel>、fix 子命令为什么不存在?
Gentoo 时代 rc-update 曾支持 show-default、fix 等扩展,OpenRC 0.5x 已精简为 add/del/show 三子命令(3.12 节实测 invalid command 回显)。查特定层的注册直接用位置参数:rc-update show default、rc-update show boot。
六、参考链接
- OpenRC 官方仓库(源码与版本发布):https://github.com/OpenRC/openrc
- Gentoo Wiki:OpenRC(runlevel 与栈式 runlevel 详解):https://wiki.gentoo.org/wiki/OpenRC
- Alpine Wiki:OpenRC(含 rc-update 用法示例):https://wiki.alpinelinux.org/wiki/OpenRC
- rc-update(8) 在线手册页(Debian 版 OpenRC):https://manpages.debian.org/openrc/rc-update.8.en.html
- Alpine Wiki:init 系统与 rc 脚本编写:https://wiki.alpinelinux.org/wiki/Writing_Init_Scripts