rc-service 命令详解与参数实测(Alpine Linux)
在 Alpine Linux 3.20(OpenRC 0.54)上对 rc-service 的状态查询、start/stop/restart/zap 生命周期、条件修饰选项与 -C/-q/-v/-d/-Z 等参数逐项实测,并记录 crashed 恢复全流程。
rc-service 命令详解与参数实测(Alpine Linux)
目标:讲清 OpenRC 服务管理前端
rc-service的定位、参数与退出码,覆盖状态查询、start/stop/restart/zap 生命周期、crashed 状态判定与恢复、条件修饰选项、静默/冗余/调试/dry-run 等输出控制,全部命令与回显已在 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 |
| 辅助工具 | util-linux-misc 2.40.1-r1(提供 script 命令,用于 -C 颜色对比) |
| 测试日期 | 2026-10-17 |
Alpine Linux 自 2.x 起一直以 OpenRC 为默认 init 体系,rc-service 是 OpenRC 面向单个服务的管理入口(与之配套:rc-update 管开机自启注册、rc-status 看全局状态一览,后两者见同专栏姊妹篇)。所有服务的定义文件集中在 /etc/init.d/ 目录,可执行位、shell 脚本形态,俗称 init 脚本或 service 脚本。
💡 技术拓展:Linux 的四种主流 init 体系对比。init 是内核引导后启动的第一个用户态进程(PID 1),负责把系统拉到可用状态并管理常驻服务:
| init 体系 | 服务定义 | 启动模型 | 代表发行版 |
|---|---|---|---|
| sysvinit | /etc/init.d/ LSB 脚本 | 按编号串行,无依赖图 | Debian 老版本、RHEL 6 及更早 |
| OpenRC(本文) | /etc/init.d/ 脚本 + depend() 依赖声明 | 依赖排序后并行启动,runlevel 分层 | Alpine Linux、Gentoo、Devuan、KISS 系 |
| systemd | /usr/lib/systemd/system/ unit 文件 | socket/D-Bus 激活 + cgroup 全程跟踪 | RHEL/CentOS、Ubuntu、Fedora、Debian、Arch |
| runit | /service/ 目录监督脚本 | 目录即服务,进程监督重启 | Void Linux、Artix |
OpenRC 的设计哲学是「轻量 + 依赖图」:不接管进程 supervision(靠 start-stop-daemon 与 pidfile),不侵入 cgroup,因此内存占用极小、可移植性强,这也是 Alpine 选择它的原因。本文的主角 rc-service 就是向 OpenRC 引擎(openrc-run)提交「对某个服务执行某个动作」的命令行入口。
二、命令概览
2.1 命令定位与语法
$ which rc-service rc-update rc-status openrc openrc-run
/sbin/rc-service
/sbin/rc-update
/bin/rc-status
/sbin/openrc
/sbin/openrc-run
rc-service 位于 /sbin/,语法为:
rc-service [options] [-i] <service> <cmd>...
or: rc-service [options] -e <service>
or: rc-service [options] -l
or: rc-service [options] -r <service>
四种形态分别对应:对服务 <service> 执行命令 <cmd>(最常用)、测试服务是否存在(-e)、列出全部可用服务(-l)、把服务名解析成 init 脚本路径(-r)。<cmd> 不限于固定清单——rc-service 会把未知命令透传给底层引擎,init 脚本里自定义的函数(如 nginx 的 checkconfig)同样可以直接调用。
2.2 --help 全量回显
$ rc-service --help
Usage: rc-service [options] [-i] <service> <cmd>...
or: rc-service [options] -e <service>
or: rc-service [options] -l
or: rc-service [options] -r <service>
Options: [ cdDe:ilr:INsSZChqVv ]
-d, --debug set xtrace when running the command
-D, --nodeps ignore dependencies
-e, --exists <arg> tests if the service exists or not
-c, --ifcrashed if the service is crashed run the command
-i, --ifexists if the service exists run the command
-I, --ifinactive if the service is inactive run the command
-N, --ifnotstarted if the service is not started run the command
-s, --ifstarted if the service is started run the command
-S, --ifstopped if the service is stopped run the command
-l, --list list all available services
-r, --resolve <arg> resolve the service name to an init script
-Z, --dry-run dry run (show what would happen)
-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)
参数分为三组:条件修饰选项(-c/-i/-I/-N/-s/-S,先判断服务状态再决定是否执行命令)、输出控制(-C/-v/-q/-d/-Z)与独立形态(-e/-l/-r)。下文逐项实测。
2.3 测试数据准备
安装 OpenRC 与两个带 init 脚本的服务包(nginx 包自带 /etc/init.d/nginx,busybox-openrc 提供 crond/syslog 等基础服务脚本):
$ apk add openrc busybox-openrc nginx
网络服务需要基本的网卡定义,写入标准 loopback 配置(真实服务器上由 setup-alpine 或 /etc/network/interfaces 手工维护):
$ cat /etc/network/interfaces
auto lo
iface lo inet loopback
本文用三个服务做测试对象,init 脚本原文如下(/etc/init.d/ 下均为可执行 shell 脚本):
$ cat /etc/init.d/crond
#!/sbin/openrc-run
name="busybox $RC_SVCNAME"
command="/usr/sbin/$SVCNAME"
pidfile="/run/$RC_SVCNAME.pid"
command_args="$CRON_OPTS -f"
command_background=true
depend() {
need localmount
need logger
}
$ cat /etc/init.d/nginx
#!/sbin/openrc-run
description="Nginx http and reverse proxy server"
extra_commands="checkconfig"
extra_started_commands="reload reopen upgrade"
cfgfile=${cfgfile:-/etc/nginx/nginx.conf}
pidfile=/run/nginx/nginx.pid
command=${command:-/usr/sbin/nginx}
command_args="-c $cfgfile"
required_files="$cfgfile"
depend() {
need net
use dns logger netmount
}
start_pre() {
checkpath --directory --owner nginx:nginx ${pidfile%/*}
$command $command_args -t -q
}
checkconfig() {
ebegin "Checking $RC_SVCNAME configuration"
start_pre
eend $?
}
reload() {
ebegin "Reloading $RC_SVCNAME configuration"
start_pre && start-stop-daemon --signal HUP --pidfile $pidfile
eend $?
}
三个脚本正好展示了 init 脚本的关键要素,后文实测会反复用到:
command/pidfile/command_background:告诉 OpenRC 启动什么进程、pidfile 在哪、是否后台化;depend()中的need(强依赖,失败则本服务不能启动)与use(弱依赖,若在启动计划中就等它)——crondneed logger由 syslogprovide logger满足,nginxneed net由 networking 满足,这就是为什么启动 crond 会连带拉起 syslog;extra_started_commands="reload reopen upgrade":仅在服务已启动时允许执行的自定义命令;extra_commands="checkconfig"则任何状态都可执行。
⚠️ 前置要求:rc-service 的服务状态记录在 /run/openrc/ 运行态目录中,由 OpenRC 引导流程建立。本文所有实测均在 OpenRC 为 init 的真实 Alpine 服务器语境下进行;若在救援盘/chroot 等未由 OpenRC 引导的环境中操作,需先做运行态初始化,详见 5.1 节。
三、参数逐项实测
3.1 不带参数执行:必须指定服务
$ rc-service
* rc-service: you need to specify a service
$ echo $?
1
除 -e/-l/-r 三种独立形态外,rc-service 必须拿到服务名,否则直接报错退出 1。
3.2 rc-service <service> status:查询状态与退出码
$ rc-service crond status
* status: stopped
$ echo $?
3
status 是排查服务问题的第一入口,输出两样东西:状态文本与状态对应的进程检查结论。退出码是脚本可编程使用的,实测两个基本状态:
| 服务状态 | 含义 | status 退出码(实测) |
|---|---|---|
| stopped | 已停止(服务未标记启动,或标记了但进程已全部消失) | 3 |
| started | 运行中(标记 started 且 pidfile 指向的进程存活) | 0 |
| crashed | 崩溃(标记 started 但 pidfile 指向的进程已死) | 32(见 3.4 节实测) |
| starting / stopping | 正在启动 / 正在停止(瞬态,出现于并行启动或卡住的停止流程) | —(OpenRC 约定 8 / 4) |
| inactive | 已激活但不驻留(如 oneshot 类服务执行完毕后) | —(OpenRC 约定 16) |
脚本中惯用法就是直接 if rc-service crond status >/dev/null; then ...,靠退出码分支而无需解析文本。
3.3 start:启动服务与依赖链解析
$ rc-service crond start
* Checking local filesystems ... [ ok ]
* Remounting filesystems ... [ ok ]
* Mounting local filesystems ... [ ok ]
* Loading modules ...modprobe: can't change directory to '/lib/modules': No such file or directory
modprobe: can't change directory to '/lib/modules': No such file or directory
[ ok ]
* Setting system clock using the hardware clock [UTC] ... [ ok ]
* Setting hostname ... [ ok ]
* Starting busybox syslog ... [ ok ]
* Starting busybox crond ... [ ok ]
$ echo $?
0
尽管命令只点名了 crond,OpenRC 先做依赖解析:crond need localmount(其上还有 root/fsck 链)与 need logger(由 syslog 提供),于是把整条依赖链按序启动,最后才启动 crond。中途 Loading modules 的两行 modprobe 报错出现在无 /lib/modules 的最小系统上,但服务脚本对它容错,链条继续并以 [ ok ] 收尾——[ ok ]/[ !! ]/[ WARNING ] 标签即执行结果的直观标记。
验证进程真实存在,并确认幂等语义(对已启动服务再 start 只是警告、不报错):
$ ps aux | grep "[c]rond"
698 root 0:00 /usr/sbin/crond -c /etc/crontabs -f
$ rc-service crond status
* status: started
$ echo $?
0
$ rc-service crond start
* WARNING: crond has already been started
$ echo $?
0
start 是声明式的:服务已在运行则跳过,退出码仍为 0,因此可以放心地在初始化脚本里重复执行。
3.4 状态实战:crashed 的判定与恢复
crashed 是 OpenRC 状态机里最容易被误解的一环:服务标记仍是 started,但 pidfile 指向的进程已经死亡。下面构造一次真实事故——master 进程被 SIGKILL:
$ rc-service nginx start
* Starting nginx ... [ ok ]
$ kill -9 $(cat /run/nginx/nginx.pid)
$ echo $?
0
$ rc-service nginx status
* status: crashed
$ echo $?
32
status 退出码立刻变成 32,监控脚本可以据此告警。但注意——master 死了,它 fork 出的 worker 进程还活着并继续监听 80 端口:
$ ps aux | grep "[n]ginx"
2210 nginx 0:00 nginx: worker process
2211 nginx 0:00 nginx: worker process
2212 nginx 0:00 nginx: worker process
2213 nginx 0:00 nginx: worker process
2214 nginx 0:00 nginx: worker process
2215 nginx 0:00 nginx: worker process
2217 nginx 0:00 nginx: worker process
2218 nginx 0:00 nginx: worker process
2219 nginx 0:00 nginx: worker process
2220 nginx 0:00 nginx: worker process
2221 nginx 0:00 nginx: worker process
2222 nginx 0:00 nginx: worker process
标准的恢复动作分四步,每一步都有真实的坑(详见 5.3 节 FAQ)。第一步 zap 把状态标记重置回 stopped:
$ rc-service nginx zap
* Manually resetting nginx to stopped state
$ echo $?
0
$ rc-service nginx status
* status: stopped
$ echo $?
3
第二步若直接重启,会撞上孤儿 worker 占用的 80 端口:
$ rc-service nginx start
* Starting nginx ...nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address in use)
nginx: [emerg] bind() to [::]:80 failed (98: Address in use)
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address in use)
nginx: [emerg] bind() to [::]:80 failed (98: Address in use)
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address in use)
nginx: [emerg] bind() to [::]:80 failed (98: Address in use)
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address in use)
nginx: [emerg] bind() to [::]:80 failed (98: Address in use)
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address in use)
nginx: [emerg] bind() to [::]:80 failed (98: Address in use)
nginx: [emerg] still could not bind()
* start-stop-daemon: failed to start `/usr/sbin/nginx'
* Failed to start nginx
[ !! ]
* ERROR: nginx failed to start
$ echo $?
1
更隐蔽的是,此时孤儿 worker 仍在对外提供 HTTP 服务——健康检查可能全是绿的,但 OpenRC 视角服务已死:
$ wget -S -q -O /dev/null http://127.0.0.1/ 2>&1 | head -3
HTTP/1.1 404 Not Found
wget: server returned error: HTTP/1.1 404 Not Found
第三步清理孤儿 worker(先用 pgrep 确认目标,再统一发 TERM;kill 后瞬间 ps 里显示 [nginx] 方括号形态,是进程已收到信号、正在退出等待 init 回收的僵尸态,稍候即消失):
$ pgrep nginx
2210
2211
2212
2213
2214
2215
2217
2218
2219
2220
2221
2222
$ kill $(pgrep nginx)
$ echo $?
0
$ ps aux | grep "[n]ginx"; echo $?
2210 nginx 0:00 [nginx]
2211 nginx 0:00 [nginx]
2212 nginx 0:00 [nginx]
2213 nginx 0:00 [nginx]
2214 nginx 0:00 [nginx]
2215 nginx 0:00 [nginx]
2217 nginx 0:00 [nginx]
2218 nginx 0:00 [nginx]
2219 nginx 0:00 [nginx]
2220 nginx 0:00 [nginx]
2221 nginx 0:00 [nginx]
2222 nginx 0:00 [nginx]
1
(末尾 1 是 grep 无存活匹配的退出码。)第四步,端口释放后重启成功:
$ rc-service nginx start
* Starting nginx ... [ ok ]
$ wget -S -q -O /dev/null http://127.0.0.1/ 2>&1 | head -3
HTTP/1.1 404 Not Found
wget: server returned error: HTTP/1.1 404 Not Found
$ rc-service nginx status
* status: started
$ echo $?
0
3.5 stop:停止服务
$ rc-service crond stop
* Stopping busybox crond ... [ ok ]
$ echo $?
0
$ rc-service crond status
* status: stopped
$ echo $?
3
与 start 对称,stop 也是幂等的,对已停止服务只给 WARNING:
$ rc-service crond stop
* WARNING: crond is already stopped
$ echo $?
0
停止成功后可再确认无进程残留:
$ ps aux | grep "[c]rond"; echo $?
1
3.6 restart:重启服务
$ rc-service crond restart
* Stopping busybox crond ... [ ok ]
* Starting busybox crond ... [ ok ]
$ echo $?
0
restart 内部是先 stop 再 start(含依赖图重算),两个动作都展示各自的 [ ok ]。
3.7 zap:只重置状态标记,不动进程
zap 是排障利器也是危险动作:它不执行 stop、不杀任何进程,只把服务的状态标记(/run/openrc/started/ 下的记录)抹掉。构造一个「进程活着但 OpenRC 认为已停止」的状态:
$ rc-service crond start
* Starting busybox crond ... [ ok ]
$ ps aux | grep "[c]rond"
1063 root 0:00 /usr/sbin/crond -c /etc/crontabs -f
$ rc-service crond zap
* Manually resetting crond to stopped state
$ echo $?
0
$ rc-service crond status
* status: stopped
$ echo $?
3
$ ps aux | grep "[c]rond"
1063 root 0:00 /usr/sbin/crond -c /etc/crontabs -f
状态已经是 stopped,进程却还在跑。此后再执行 rc-service crond start 会被 start-stop-daemon 的 pidfile 检查拒绝(新实例起不来,残留实例也无人接管):
$ rc-service crond status
* status: stopped
$ rc-service crond start
* Starting busybox crond ...
* start-stop-daemon: /usr/sbin/crond is already running
* Failed to start busybox crond
[ !! ]
* ERROR: crond failed to start
$ echo $?
1
所以 zap 之后必须手工处置遗留进程:
$ kill $(cat /run/crond.pid)
$ echo $?
0
$ ps aux | grep "[c]rond"; echo $?
1
$ rc-service crond start
* Starting busybox crond ... [ ok ]
$ echo $?
0
zap 的正确用途就是 3.4 节的 crashed 恢复:进程确认已死、但状态标记卡在 started 时,用它把状态机拨回正轨。对仍在运行的服务执行 zap 几乎总是错误的。
3.8 条件修饰选项:-s/--ifstarted、-S/--ifstopped、-N/--ifnotstarted、-I/--ifinactive、-c/--ifcrashed、-i/--ifexists
这组选项把「判断状态 → 执行命令」合成一步,条件不满足时静默跳过(无输出、退出码 0),天然适合写幂等脚本。以「crond 运行中、nginx 已停止」为基线逐一实测:
$ rc-service crond status
* status: started
$ rc-service nginx status
* status: stopped
-s/--ifstarted:服务已启动才执行。crond 满足条件,命令照常执行并回显状态:
$ rc-service --ifstarted crond status
* status: started
$ echo $?
0
nginx 不满足「已启动」,被静默跳过:
$ rc-service --ifstarted nginx status
$ echo $?
0
-S/--ifstopped:服务已停止才执行,与 -s 相反:
$ rc-service --ifstopped crond status
$ echo $?
0
$ rc-service --ifstopped nginx status
* status: stopped
$ echo $?
3
-N/--ifnotstarted:服务未启动才执行。最实用的场景是幂等启动——第一个实例由它拉起,第二个调用被静默跳过:
$ rc-service --ifnotstarted crond start
$ echo $?
0
$ rc-service crond status
* status: started
$ echo $?
0
-I/--ifinactive:服务处于 inactive 状态才执行,对常驻服务一律静默跳过:
$ rc-service --ifinactive crond status
$ echo $?
0
-c/--ifcrashed:仅在 crashed 时执行。3.4 节 nginx 处于 crashed 时它执行了 status,恢复正常后同样静默跳过:
$ rc-service --ifcrashed crond status
$ echo $?
0
-i/--ifexists:服务存在即执行(不关心状态)。对比服务不存在时的行为差异:
$ rc-service nosuch status
* rc-service: service `nosuch' does not exist
$ echo $?
1
$ rc-service --ifexists nosuch status
$ echo $?
0
写批量巡检脚本时,-i 能把「服务未安装」从错误降级为跳过。
3.9 -e/--exists:测试服务是否存在
$ rc-service -e crond
$ echo $?
0
$ rc-service -e nosuch
$ echo $?
1
不产生任何输出,纯退出码语义,是 if rc-service -e nginx; then 式判断的标准工具。
3.10 -r/--resolve:把服务名解析为 init 脚本路径
$ rc-service -r crond
/etc/init.d/crond
$ echo $?
0
$ rc-service -r nginx
/etc/init.d/nginx
$ echo $?
0
$ rc-service -r nosuch
$ echo $?
1
脚本中要对 init 脚本本身做操作(读取变量、软链检查)时用它拿路径,避免硬编码。
3.11 -l/--list:列出全部可用服务
$ rc-service -l | wc -l
51
$ rc-service -l
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
清单来自 /etc/init.d/ 目录中所有可执行的服务脚本(不含 functions.sh 辅助库,故 52 个文件对应 51 个服务)。
3.12 describe:服务的描述与扩展命令清点
$ rc-service nginx describe
* Nginx http and reverse proxy server
* checkconfig: no description
* reload: no description
* reopen: no description
* upgrade: no description
* cgroup_cleanup: Kill all processes in the cgroup
$ echo $?
0
describe 列出 init 脚本的 description 与全部扩展命令(extra_commands/extra_started_commands 及内置的 cgroup_cleanup)。对比无 description 的服务:
$ rc-service crond describe
* No description for crond
* healthcheck: no description
* unhealthy: no description
* cgroup_cleanup: Kill all processes in the cgroup
由此可知 crond 还带有 healthcheck/unhealthy 扩展命令——这正是发现服务能力的第一手段。
3.13 自定义命令:checkconfig、reload 与命令透传规则
init 脚本里定义的函数可以直接作为 <cmd> 执行。nginx 的 checkconfig 属于 extra_commands(任何状态可用),做配置语法预检:
$ rc-service nginx checkconfig
* Checking nginx configuration ... [ ok ]
$ echo $?
0
reload 属于 extra_started_commands,服务必须已启动:
$ rc-service nginx reload
* Reloading nginx configuration ... [ ok ]
$ echo $?
0
对已停止的服务执行 reload 会被拒:
$ rc-service nginx stop
* Stopping nginx ... [ ok ]
$ rc-service nginx reload
* nginx: cannot `reload' as it has not been started
$ echo $?
1
未知命令会透传到底层并被拒绝:
$ rc-service crond show
* crond: unknown function `show'
$ echo $?
1
💡 OpenRC 还为 restart 家族提供了「条件版」:conditionalrestart(别名 condrestart)只在服务已启动时才执行重启,停止状态下静默跳过,适合升级脚本中对「可能没在跑」的服务做无损重启:
$ rc-service crond conditionalrestart
$ echo $?
0
$ rc-service crond start
* Starting busybox crond ... [ ok ]
$ rc-service crond conditionalrestart
* Stopping busybox crond ... [ ok ]
* Starting busybox crond ... [ ok ]
$ echo $?
0
$ rc-service crond condrestart
* Stopping busybox crond ... [ ok ]
* Starting busybox crond ... [ ok ]
$ echo $?
0
注意裸写的 conditional 并不是有效命令(部分文档以讹传讹),实测报 unknown function:
$ rc-service crond conditional
* crond: unknown function `conditional'
$ echo $?
1
3.14 -D/--nodeps:忽略依赖关系
$ rc-service --nodeps crond start
* Starting busybox crond ... [ ok ]
$ echo $?
0
与 3.3 节普通 start 对比:输出里没有依赖链的任何动作(无 localmount/syslog 等),只启动点名服务本身。status 一类只读命令本就不依赖解析,加 --nodeps 行为一致:
$ rc-service --nodeps crond status
* status: started
$ echo $?
0
⚠️ --nodeps start 只应在你确切知道依赖已就绪时使用(例如依赖服务由别的手段拉起),否则可能启动一个看似成功、实则依赖缺失的服务。
3.15 -Z/--dry-run:预演将要执行的动作
$ rc-service -Z crond start
* start: crond
$ echo $?
0
$ rc-service -Z crond restart
* start: crond
$ echo $?
0
$ rc-service -Z crond status
* status: stopped
$ echo $?
3
-Z 只打印「将要执行什么」(如 start: crond),不真正触碰服务。注意 restart 的 dry-run 显示的首个动作是 start——restart 在已停止的服务上退化为一次 start。验证 dry-run 后服务状态未被改变:
$ rc-service crond status
* status: stopped
$ echo $?
3
3.16 -q/--quiet:静默输出(重复两次抑制错误)
对 status 这类回显型命令,单个 -q 即可全静默(退出码保留,供脚本判断):
$ rc-service -q crond status
$ echo $?
0
$ rc-service crond stop
* Stopping busybox crond ... [ ok ]
$ rc-service -q crond status
$ echo $?
3
服务名错误这类报错,单次 -q 仍会显示,重复 -qq 才一并抑制:
$ rc-service -q nosuch status
* rc-service: service `nosuch' does not exist
$ echo $?
1
$ rc-service -qq nosuch status
$ echo $?
1
3.17 -v/--verbose:冗余输出
$ rc-service -v crond status
* Executing: /lib/rc/sh/openrc-run.sh /lib/rc/sh/openrc-run.sh /etc/init.d/crond status
* status: stopped
$ echo $?
3
比普通模式多出一行实际执行的引擎命令,用于确认底层调用链(真正的执行体是 /lib/rc/sh/openrc-run.sh 包装的 /etc/init.d/<svc>)。
3.18 -d/--debug:xtrace 调试跟踪
$ rc-service -d crond status 2>&1 | head -40
+ sourcex -e /etc/rc.conf
+ '[' -e '=' -e ]
+ shift
+ '[' -e /etc/rc.conf ]
+ . /etc/rc.conf
+ rc_tty_number=12
+ respawn_delay=2
+ respawn_max=5
+ respawn_period=1800
+ '[' -d /etc/rc.conf.d ]
+ _conf_d=/etc/init.d/../conf.d
+ _c=crond
+ '[' -n crond -a crond '!=' crond ]
+ unset _c
+ sourcex -e /etc/init.d/../conf.d/crond.sysinit
+ '[' -e '=' -e ]
+ shift
+ '[' -e /etc/init.d/../conf.d/crond.sysinit ]
+ return 1
+ sourcex -e /etc/init.d/../conf.d/crond
+ '[' -e '=' -e ]
+ shift
+ '[' -e /etc/init.d/../conf.d/crond ]
+ . /etc/init.d/../conf.d/crond
+ CRON_OPTS='-c /etc/crontabs'
+ unset _conf_d
+ RC_VRF_EXEC=
+ '[' -n ]
+ '[' -n ]
+ sourcex /lib/rc/sh/runit.sh
+ '[' /lib/rc/sh/runit.sh '=' -e ]
+ . /lib/rc/sh/runit.sh
+ sourcex /lib/rc/sh/s6.sh
+ '[' /lib/rc/sh/s6.sh '=' -e ]
+ . /lib/rc/sh/s6.sh
+ '[' -z ]
+ s6_service_path=/var/svc.d/crond
+ sourcex /lib/rc/sh/start-stop-daemon.sh
+ '[' /lib/rc/sh/start-stop-daemon.sh '=' -e ]
+ . /lib/rc/sh/start-stop-daemon.sh
-d 等价于给整个执行过程开 set -x,能看清 init 脚本加载顺序:/etc/rc.conf → conf.d/<svc>.sysinit(本例不存在,return 1)→ conf.d/<svc>(读到 CRON_OPTS),随后依次 source runit.sh/s6.sh/start-stop-daemon.sh 等后端实现。排障定位「服务脚本变量为何没生效」时极有用。
3.19 -C/--nocolor:禁用彩色输出
OpenRC 的输出在终端里默认带色:绿色 */[ ok ]、红色 */[ !! ]。但生效有三个前提:stdout 是终端、TERM 环境变量非空、未被显式禁用。用 script 命令制造伪终端实测,TERM 为空时即使是终端也不着色:
$ script -qec 'echo TERM=$TERM; tty' /dev/null
TERM=
/dev/pts/0
显式指定 TERM 后,用一次 restart 同时捕获两种着色:启动行是加粗绿色星号(^[[1m^[[32m),失败行是红色(^[[31m)。落盘文件中含 ANSI 色码的行有 4 行,cat -v 可以直接看到原始转义字节(行尾的 ^M 是伪终端 CRLF 的痕迹):
$ script -qec 'TERM=xterm rc-service nginx restart' /dev/null > /tmp/with_color.txt
$ grep -c $'\033' /tmp/with_color.txt
4
$ cat -v /tmp/with_color.txt
^[[1m^[[32m*^[[m Starting nginx^[[K ...^M
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address in use)^M
nginx: [emerg] bind() to [::]:80 failed (98: Address in use)^M
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address in use)^M
nginx: [emerg] bind() to [::]:80 failed (98: Address in use)^M
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address in use)^M
nginx: [emerg] bind() to [::]:80 failed (98: Address in use)^M
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address in use)^M
nginx: [emerg] bind() to [::]:80 failed (98: Address in use)^M
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address in use)^M
nginx: [emerg] bind() to [::]:80 failed (98: Address in use)^M
nginx: [emerg] still could not bind()^M
^[[1m^[[31m*^[[m start-stop-daemon: failed to start `/usr/sbin/nginx'^[[K^M
^[[1m^[[31m*^[[m Failed to start nginx^[[K^M
[ !! ]^M
^[[1m^[[31m*^[[m ERROR: nginx failed to start^[[K^M
加 -C 后,同样条件下色码归零——这正是该选项的价值:把输出写进日志文件、管道给 grep/awk、或在 CI 中保证输出纯净:
$ script -qec 'TERM=xterm rc-service -C nginx restart' /dev/null > /tmp/nocolor.txt
$ grep -c $'\033' /tmp/nocolor.txt
0
$ cat -v /tmp/nocolor.txt
* Starting nginx ...nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address in use)^M
nginx: [emerg] bind() to [::]:80 failed (98: Address in use)^M
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address in use)^M
nginx: [emerg] bind() to [::]:80 failed (98: Address in use)^M
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address in use)^M
nginx: [emerg] bind() to [::]:80 failed (98: Address in use)^M
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address in use)^M
nginx: [emerg] bind() to [::]:80 failed (98: Address in use)^M
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address in use)^M
nginx: [emerg] bind() to [::]:80 failed (98: Address in use)^M
nginx: [emerg] still could not bind()^M
^M
* start-stop-daemon: failed to start `/usr/sbin/nginx'^M
^M
* Failed to start nginx^M
[ !! ]^M
* ERROR: nginx failed to start^M
对比还能发现一个细节:着色模式下每行末尾带 ^[[K(擦除到行尾的控制序列,用于并行启动时整行刷新),-C 模式连它也一并省去。若想全局关色,可在 /etc/rc.conf 设置 rc_nocolor=YES(该文件中注释原文:# Set rc_nocolor to yes if you do not want colors displayed in OpenRC)。
3.20 -h/--help 与 -V/--version
$ rc-service --version
rc-service (OpenRC) 0.54
$ rc-service -V
rc-service (OpenRC) 0.54
-h 输出 2.2 节的完整帮助。--version 便于在变更单里记录组件版本。
四、实战示例
4.1 上线一个 nginx:从预检到重载的完整闭环
$ rc-service nginx checkconfig
* Checking nginx configuration ... [ ok ]
$ rc-service nginx start
* Starting networking ...
* lo ... [ ok ]
* Starting nginx ... [ ok ]
$ rc-service nginx status
* status: started
$ wget -S -q -O /dev/null http://127.0.0.1/ 2>&1 | head -3
HTTP/1.1 404 Not Found
wget: server returned error: HTTP/1.1 404 Not Found
$ rc-service nginx reload
* Reloading nginx configuration ... [ ok ]
$ rc-service nginx stop
* Stopping nginx ... [ ok ]
注意 start 输出:nginx need net,OpenRC 先把 networking(本例只有 lo)拉起再启动 nginx。改完配置后 checkconfig 先行、reload 平滑生效,是零中断运维的最短路径。
4.2 crashed 告警后的标准处置
接到「status 退出码 32」告警后的完整动作序列(细节与原理见 3.4 节):
$ rc-service nginx status
* status: crashed
$ rc-service nginx zap
* Manually resetting nginx to stopped state
$ kill $(pgrep nginx)
$ rc-service nginx start
* Starting nginx ... [ ok ]
$ rc-service nginx status
* status: started
四步口诀:确认 crashed → zap 重置标记 → 清理遗留进程 → 重启并验证。
4.3 幂等的脚本化启停
初始化脚本/CI 中推荐全部用条件修饰选项,重复执行无副作用:
$ rc-service --ifnotstarted crond start
$ rc-service --ifnotstarted crond start
$ rc-service crond status
* status: started
$ rc-service --ifstarted crond stop
* Stopping busybox crond ... [ ok ]
$ rc-service --ifstarted crond stop
$ rc-service --ifstarted crond status
$ echo $?
0
第二次 --ifnotstarted start 与第二次 --ifstarted stop 都被静默跳过;配合 -q 可让脚本输出彻底干净。
4.4 盘点服务资产
$ rc-service -l | wc -l
51
$ rc-service -r nginx
/etc/init.d/nginx
$ rc-service nginx describe
* Nginx http and reverse proxy server
* checkconfig: no description
* reload: no description
* reopen: no description
* upgrade: no description
* cgroup_cleanup: Kill all processes in the cgroup
接手陌生 Alpine 机器时,先用这一组命令摸清「有哪些服务、脚本在哪、各自支持什么动作」。
五、注意事项与常见问题
5.1 ⚠️ 前置要求:OpenRC 需处于引导环境
rc-service 读写 /run/openrc/ 下的运行态(状态标记、依赖缓存、锁目录),这些由 OpenRC 引导流程(openrc-init → openrc sysinit/boot)建立。在未由 OpenRC 引导的系统上直接执行会得到官方的明确警告(实测回显):
$ rc-service crond status
* You are attempting to run an openrc service on a
* system which openrc did not boot.
* You may be inside a chroot or you may have used
* another initialization system to boot this system.
* In this situation, you will get unpredictable results!
* If you really want to do this, issue the following command:
* touch /run/openrc/softlevel
救援盘/chroot 恢复系统的场景确实需要绕过这层保护,按提示补 softlevel 标记只是第一步;完整可用的初始化(实测有效)是把状态目录一并建齐再补标记:
# mkdir -p /run/openrc/{exclusive,starting,started,stopping,stopped,inactive,crashed,failed,hotplugged,daemons}
# touch /run/openrc/softlevel
(花括号展开是 bash 语法;busybox sh 下需展开为逐条 mkdir,效果相同。)
其中 exclusive/ 锁目录尤其关键:实测缺它时 openrc-run 会把「锁不可用」误报成服务已在启动中。做完上述初始化后,rc-service status/start/stop/restart、rc-update add/del/show 等操作即可正常执行。这类环境属于降级场景:服务脚本内部的 need 依赖可能指向未安装的服务(如最小环境没有设备管理服务时会出现 "Service 'hwdrivers' needs non existent service 'dev'" 一类提示),功能可用但依赖提示需自行甄别。
5.2 rc-service 与 runlevel 无关,别用它管自启
rc-service 只面对服务的当前状态:只要 /etc/init.d/<svc> 存在,无论有没有被加入 runlevel,status/start/stop 都可用(从未加入自启的 nginx 照样能 start)。开机自启是 rc-update 的职责(写入 /etc/runlevels/<runlevel>/ 的软链),两套机制互不覆盖。反过来,rc-update add 也不会启动服务,详见姊妹篇《rc-update 命令详解与参数实测(Alpine Linux)》。
5.3 zap 只重标记、不杀进程——孤儿进程陷阱
3.4/3.7 节实测已证明:zap 之后进程照常存活。对 nginx 这类多进程服务,master 被 SIGKILL 后 worker 成为孤儿并继续占用端口、继续对外服务(wget 仍返回 404),此时监控若只看 HTTP 探活会完全失真,若只看 rc-service 状态也会失真——两个视角必须交叉验证。清理孤儿 worker 的安全做法是先 pgrep 确认 PID 列表再 kill;慎用 pkill -f 加含空格的关键字,-f 按完整命令行匹配,容易把自己的 shell 一起匹配掉。
5.4 幂等语义与退出码约定
start 对已启动服务、stop 对已停止服务都只报 WARNING 且退出码 0(3.3/3.5 节),因此这两条可以无脑重放;而「条件不满足被跳过」与「条件满足执行完毕」的退出码也同为 0,--ifstarted 系列只表达意图不表达结果——需要精确结果时用裸 status 的退出码(0 started / 3 stopped / 32 crashed)。
5.5 颜色输出的三个条件
3.19 节实测结论:色彩 = 终端 × TERM 非空 × 未禁用。三者缺一输出都是纯文本。管道/重定向天然无色(无需加 -C);CI 与日志采集建议显式 -C,不依赖环境;全局开关在 /etc/rc.conf 的 rc_nocolor。
5.6 status 的 crashed/inactive 与脚本的对应关系
crashed 判定依据是「pidfile 记录的进程是否存活」,因此只对带 pidfile 的服务有意义;oneshot 型脚本(无 command、用 start() 自定义函数)执行后进入 inactive 而非 started。给监控写探测逻辑前,先 rc-service <svc> describe 与阅读 init 脚本确认服务类型,避免用统一阈值误报。
六、参考链接
- OpenRC 官方仓库(源码与版本发布):https://github.com/OpenRC/openrc
- Gentoo Wiki:OpenRC(体系与 runlevel 详解):https://wiki.gentoo.org/wiki/OpenRC
- Alpine Wiki:OpenRC(Alpine 视角的使用指南):https://wiki.alpinelinux.org/wiki/OpenRC
- rc-service(8) 在线手册页(Debian 版 OpenRC):https://manpages.debian.org/openrc/rc-service.8.en.html
- Alpine Wiki:init 系统与 rc 脚本编写:https://wiki.alpinelinux.org/wiki/Writing_Init_Scripts