2015 11 06 write sysv service - hanyong/note GitHub Wiki

创建和管理 System V 服务

System V 是个很古老的东西,曾在多个地方看到过,网上能找到的相关资料和教程却很少,找到一些链接,也不太有用。

/etc/init.d/ 目录创建一个文件, 设置可执行标志位, 即为一个 System V 服务, 文件名即为服务名. 此文件通常是一个服务脚本, 实现 start, stop, status 等操作命令. 使用 service 命令即可对该服务进行 start, stop, status 等控制操作, 如 service a.sh status, 其效果其实与直接调用 /etc/init.d/a.sh status 相同. 这样服务就算创建成功了, 但不会自动启动, 如何自动启动?且看下文。

/etc/ 目录下还有一些 rc*.d/ 文件夹, 如 rc3.d/, 每个文件夹对应一个 runlevel, 这些文件夹下包含一些特定命名规则的到服务脚本的软链接, 控制每个 runlevel 下默认起停的服务及顺序. 软链接命名规则如下, 第一个字母为 K 或 S, K 表示停止, S 表示启动, 随后为两位数字, 范围 '00' - '99', 表示起停服务的优先级, 值小的优先, 随后为服务名. linux 正常启动时 runlevel 通常为 2-5 的其中一个, 并且我们通常对 runlevel 2-5 使用一样的配置. 已知 ubuntu 正常启动时 runlevel 为 2, RHEL 5/6 console 模式为 3, GUI 为 5.

这些软链接不必手动维护, ubuntu 下可使用 update-rc.d 命令维护, 这个命名看起来有点像个文件夹名字, 其实确实是个命令. 常用用法如下:

  1. sudo update-rc.d a.sh defaults 99 1

创建各 runlevel 下的软链接, 默认 2-5 启动, 其他停止. 随后两个参数为启动、停止优先级,最后启动,最先停止.

  1. sudo update-rc.d -f a.sh remove 删除所有 runlevel 下的软链接, 但服务脚步本身不会删除,并且,如果服务脚本本身存在的话,不加 "-f" 参数会有一个警告,remove 动作不会执行, 添加 "-f" 参数可强制执行。

  2. sudo update-rc.d a.sh enable|disable

在 runlevel 2-5 下允许, 禁止服务。 其效果是将软链接名字在 K, S 间切换, 优先级修改为 (100 - 优先级), 晚启动的先停止. 这个命令需要在 runlevel 下已存在软链接时调用.

在一些古老的系统如 RHEL 5/6 上, 可以使用 chkconfig 管理这些链接. 使用 chkconfig 管理的脚本要求必须包含类似如下两行注释:

# chkconfig: - 99 01
# description: test

chkconfig 这一行指定服务默认启动的 runlevel (通常为 2345, "-" 表示不启动) 和启动、停止优先级, 具体说明可参考 man chkconfig。 服务脚步放到 /etc/init.d/ 目录下,并且包含这两行内容,就可以用 chkconfig 管理.

  1. chkconfig --list [name], 列出服务在各个 runlevel 下是否启动, 不指定 name 则列出所有服务.
  2. sudo chkconfig --add <name>,创建指定服务在各个 runlevel 下的软链接。
  3. sudo chkconfig --del <name>,删除指定服务在各个 runlevel 下的软链接。
  4. sudo chkconfig [--level N..] <name> on|off,在指定 runlevel 下启用/禁用服务,不指定 --level 则默认为 2345.

ubuntu 下默认已经没有这个命令,虽然在仓库上可以找到安装,但貌似功能有问题,请不要安装使用.

编写服务脚本时我们通常有一些通用逻辑要处理,如服务已经启动时不要重复启动,停止服务时通过指定 pid 文件停止进程, 可能先尝试优雅退出,不行再强制杀掉。 ubuntu 下有个命令 start-stop-daemon,可以帮助我们处理这些通用逻辑, 通过 start-stop-daemon 调用我们的应用脚本,我们应用脚本可以省去处理这些逻辑。

start-stop-daemon 有几个选项可以用来匹配服务进程,通常使用 pid 文件集合 exec 路径比较靠谱, 如:

-p a.sh.pid --exec /bin/sleep

  1. 默认情况 pid 文件需要应用自己创建, start-stop-daemon 期望应用进程会自动 detach 服务进程, 这时 start-stop-daemon 不知道服务进程 pid, pid 文件只能由应用自己创建。 有个缺点就是 pid 文件路径同时在应用本身和 start-stop-daemon 参数维护,两者需要保持一致。
  2. exec 路径通过 /proc/<pid>/exe 采集,对脚本进程会定位到解释器本地程序,这对非编译成本地程序的应用定位不够精确。

启动服务时如果服务已经启动,停止服务时服务未启动,start-stop-daemon 不做任何操作,默认会报错, 添加 --oknodo 可避免 start-stop-daemon 这种情况下报错。

start-stop-daemon 默认会改变当前目录到系统根目录 /,如果要指定应用目录,可以添加 --chdir 参数, 如:

--chdir $PWD

启动服务语法为 --start -a <命令> [-- 参数列表..]-a 参数指定启动应用程序名字或路径, 默认为 exec 路径, 但对脚本来说这两者是不一样的。

如果我们的应用脚本本身没有 detach 服务进程到后台的功能, 添加 -b, --background 参数还可以帮我们实现这个功能。 但要注意,使用这个功能时,start-stop-daemon 默认将应用 stdin, stdout, stderr 全部重定向到 /dev/null, 所有 stdout, stderr 输出信息将丢失。 并且 start-stop-daemon 不会检查服务进程有没有启动成功,服务进程没起来或者有任何报错都不会有任何提示, 所以这个功能是不推荐使用的,除非我们的应用也不使用输入输出了,也能平稳启动没问题了, 在 quick and dirty 的场合为了简化工作可以使用。 使用这个功能时,还可以使用 -m, --make-pidfile 参数,让 start-stop-daemon 帮你创建 pid 文件。 这两个参数通常应该一起使用 -bm。 不过话说回来,不使用输入输出和平稳启动都做了,detach 服务进程和创建 pid 文件也只是一步之遥了。 自己包装一下还能加一些有用的输入输出和检查报错,唯一的缺陷就是 pid 文件路径要在应用和 start-stop-daemon 参数间耦合一下。

需要以指定用户启动进程,可以添加 -c, --chuid username|uid[:group|gid] 参数实现。 这个功能需要 root 权限,否则启动服务进程会失败。 注意, 当使用了 -bm 参数时,即使启动服务进程失败, start-stop-daemon 不会做检查,返回码依然为成功 (即 0)。 System V 服务脚本自动启动时,启动用户即为 root,这个参数很适合在 System V 脚本中使用.

停止服务语法为 --stop,默认的停止方式是向服务进程发送一个 SIGTERM 信息。 可以使用 --signal 修改发送的信号。 通常我们可以使用功能更强大的 --retry 自定义停止动作, 参数为一系列信号和超时对,如 SIGTERM/30/SIGKILL/1. 可以使用 --status 参数检查服务进程是否存在。 当我们应用的停止动作需要执行其他命令或动作,而不只是发送信号时,使用 --stop 停止服务是不适用的, 我们可以配合自定义命令和 --status 检查实现停止服务功能。

我们也可以直接在我们的应用脚本中使用 start-stop-daemon,简化我们编写应用脚本的一些工作。

⚠️ **GitHub.com Fallback** ⚠️