rc-service 命令详解与参数实测(Alpine Linux)

在 Alpine Linux 3.20(OpenRC 0.54)上对 rc-service 的状态查询、start/stop/restart/zap 生命周期、条件修饰选项与 -C/-q/-v/-d/-Z 等参数逐项实测,并记录 crashed 恢复全流程。

Linux命令Linux系统命令行进程管理2026/10/17

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
OpenRC0.54-r1(openrc --version → openrc (OpenRC) 0.54)
busybox1.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(弱依赖,若在启动计划中就等它)——crond need logger 由 syslog provide logger 满足,nginx need 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 脚本确认服务类型,避免用统一阈值误报。

六、参考链接