起因
之前需要设定一个服务的开机启动脚本,但是遗憾的是我没有很好的 Linux 系统使用和配置经验,操作系统不熟,Shell 脚本不熟,网上的教程和例子不少但是限于自身的基础,其实参考性不大;基于现有经验和使用状况,其实nginx的服务启动和周期管理不就是很好的例子?
通过句读 nginx 的 init 脚本,扩展shell编程的知识点和编程结构;
回顾
首先我需要什么?
有一个本地的可执行文件,启动之后可以提供有限的http服务.我需要他想nginx的服务一样,开机启动,不受会话启动关闭的影响;守护进程,前后台的概念在过程中熟悉,先去看看怎么实践;
nginx 服务操作
启动命令
sudo /etc/init.d/nginx start
关闭命令
sudo /etc/init.d/nginx stop
查看状态
sudo /etc/init.d/nginx status
重启
sudo /etc/init.d/nginx restart
逻辑上看,是使用root权限来执行一段存在绝对路径下的,脚本,并且传入了一个参数,start,stop,status restart等等来执行对应的操作,交互层面模仿这样就可以
sudo /etc/init.d/myservice {start|stop|status|restart}
开始句读
开头部分
#!/bin/sh
### BEGIN INIT INFO
# Provides: nginx
# Required-Start: $local_fs $remote_fs $network $syslog $named
# Required-Stop: $local_fs $remote_fs $network $syslog $named
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: starts the nginx web server
# Description: starts nginx using start-stop-daemon
### END INIT INFO
第一行是 shell 脚本的魔法字符串,指定解释器,好像所有的脚本都这样子;而后是一大段注释,init info 启动信息,不是很明白,0 到 6 的数字貌似是运行级别,暂时先不管,被注释的不会运行;
20170605 These comments are definitely not useless.
They are used to define LSB info, why I found it? because it has warning when I add it to init daemon use this
sudo update-rc.d myservice defaults
变量定义
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
DAEMON=/usr/sbin/nginx
NAME=nginx
DESC=nginx
接下的部分是变量设定部分,先看下本机系统中的PATH变量原来是什么,然后这里是直接指定了PATH
DAEMON的意思本身就是守护进程吧,这里指定的nginx的二进制程序?看看对应目录下的文件是什么意思?文件是真实存在的,而且应该就是nginx的执行文件,那我自己的可执行文件也要配置到这个变量中吧
NAME 和 DESC 应该只是下面脚本中的变量替换,作为服务的名字和描述,也要改成自己服务的名字
默认配置运行
# Include nginx defaults if available
if [ -r /etc/default/nginx ]; then
. /etc/default/nginx
fi
先要去看看 shell 当中的 if 结构是怎么写的了,看不懂呀这个.....
shell 中的 if 结构
分支结构的基本形式是 if 后面跟上中括号,中括号里面放的是条件表达式,代表真假的布尔值,之后是一个分号和 then,真个分支代码块以 fi 结束
if [ ... ]; then
...
fi
方括号里的条件表达式的写法有固定的格式,上文中的表达式具体的意思是检测文件是不是可读的-r /etc/default/nginx
;
再来看条件成立的时候执行的代码块中的命令,一个点,空格,加上文件,表示执行这个文件, source file ; ./file ; . file 这几个命令貌似都是执行文件,但是还是略有不同,具体的区别暂时不去深究吧
回到句读中,这一段的意思大概就是,查看这个配置文件是不是可读的,如果是的话那就执行这个配置文件,来看看这配置文件的内容
/etc/defaults/nginx
# Note: You may want to look at the following page before setting the ULIMIT.
# http://wiki.nginx.org/CoreModule#worker_rlimit_nofile
# Set the ulimit variable if you need defaults to change.
# Example: ULIMIT="-n 4096"
#ULIMIT="-n 4096"
内容都被注释掉了,就说没有执行任何东西
测试执行文件
test -x $DAEMON || exit 0
这行代码里面有几点需要看,test命令是什么? -x 参数的意思是? 一个竖线是管道,两个竖线又是什么? exit 0 是表示退出?
- test 命令 : test 本身的作用是检查文件类型并比较值,结合 -x 参数的作用就是检查文件是否存在并且是不是有执行的权限
- 短路逻辑或 || : exit 0 左边的两个竖线 || 跟一般含义一样是逻辑运算符,表示短路的逻辑或,当 || 左边的 test 命令返回假的时候,右边的 exit 0 退出命令才会执行,表示脚本退出;
结合上面的两点来看,这个语句的意思就是,测试可执行文件 DAEMON
是否存在兵器可以执行,如果不是的话就退出程序;
环境初始化
. /lib/init/vars.sh
. /lib/lsb/init-functions
这是执行了两个文件,看名字,一个是初始化变量定义,一个是初始化函数,具体内容中的确是有执行一些变量和函数的定义,下面用到的时候在回头看吧
尝试获取 PID
# Try to extract nginx pidfile
PID=$(cat /etc/nginx/nginx.conf | grep -Ev '^\s*#' | awk 'BEGIN { RS="[;{}]" } { if ($1 == "pid") print $2 }' | head -n1)
if [ -z "$PID" ]
then
PID=/run/nginx.pid
fi
首先肯定和这个文件 /etc/nginx/nginx.conf 有关,cat 命令打印内容,之后是用 grep 筛选了什么,然后交给 awk 命令 来执行,最后是 head 命令;一个个来看
- cat 命令很熟悉,就是吧文件的内容打印到控制台,也就是说,管道第一步是吧这个配置文件的内容打印到控制台
- grep 只知道是筛选,会返回筛选的内容所在的行这样子,参数 -Ev 的具体含义?后面跟的明显是正则表达式,应该是有关系的~~~
- -E 参数的意思是后面跟随的是正则表达式,而 -v 是表示反选,输出的不是跟表达式匹配的,而是不匹配的;来看正则,意思会任意的空格开头,而后是井号,意思是所有的注释行?反选的话就是去掉所有的注释
- 到这里也就明显,第一步打印配置文件,第二步,删除所有的注释行,接下来来到了第三步 awk
- awk 命令没有怎么接触过,知道很强大;awk 是模式扫描和文本处理语言,这是一个语言?恩......好吧,看了一下, 本机的awk 实际上是一个指向 mawk 的链接,大概看了下语法,首先是指定要分割的分隔符集合,也就是 RS 等于的东西,然后针对分割之后的每行文本进行处理,这里的意思就是,每一行都会被 RS 分割成两部分应该,第一个部分如果等于 PID 的话,那么就输出第二部分; 这里就明了了,最后得到的就是存储pid的文件;
- head 获取文件的前几行, -n1 就是获取第一行
在解析完配置文件之后,得到的是储存pid的文件,所以之后在检测一下,变量 pid 的长度是不是0, 如果没有找到配置的文件,那么就是用默认的 /run/nginx.pid
检查ulimit
# Check if the ULIMIT is set in /etc/default/nginx
if [ -n "$ULIMIT" ]; then
# Set the ulimits
ulimit $ULIMIT
fi
因为这个文件中的ulimit配置选项被注释掉了, -n 参数表示后面参数字符串的长度部位 0 的时候为真;所以这篇代码不执行, ulimit表示什么暂时不管
函数定义部分
#
# Function that starts the daemon/service
#
do_start()
{
# Return
# 0 if daemon has been started
# 1 if daemon was already running
# 2 if daemon could not be started
start-stop-daemon --start --quiet --pidfile $PID --exec $DAEMON --test > /dev/null \
|| return 1
start-stop-daemon --start --quiet --pidfile $PID --exec $DAEMON -- \
$DAEMON_OPTS 2>/dev/null \
|| return 2
}
从函数的名字可以看出这是服务启动的时候执行的函数,从注释中也看出了返回值的具体含义,0-服务已经开启,1-服务已经运行,2-服务起不起来
start-stop-daemon 是一个系统的命令,专门用来启动关闭服务的,那么剩下的就是配置参数了,唯一的问题就是一直没有找到那个 DAEMON_OPTS 变量定义在哪里
接下来的函数们就实现各自的功能了 start stop 等等
脚本参数解析
最后的一部分就是一个多路分支的结构了,根据输入的第一个参数来决定执行什么函数
总结
看来一个 daemon 的维护需要的东西还是蛮多的,如果没有配置需要载入的话,就需要两个文件,一个是 /etc/init/myservice ,还有一个是必须的 pid 文件, /run/myservice.pid 照着写应该就是OK的,具体的执行可以交给系统内部的命令
文件原本- nginx服务脚本 /etc/init.d/nginx
#!/bin/sh
### BEGIN INIT INFO
# Provides: nginx
# Required-Start: $local_fs $remote_fs $network $syslog $named
# Required-Stop: $local_fs $remote_fs $network $syslog $named
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: starts the nginx web server
# Description: starts nginx using start-stop-daemon
### END INIT INFO
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
DAEMON=/usr/sbin/nginx
NAME=nginx
DESC=nginx
# Include nginx defaults if available
if [ -r /etc/default/nginx ]; then
. /etc/default/nginx
fi
test -x $DAEMON || exit 0
. /lib/init/vars.sh
. /lib/lsb/init-functions
# Try to extract nginx pidfile
PID=$(cat /etc/nginx/nginx.conf | grep -Ev '^\s*#' | awk 'BEGIN { RS="[;{}]" } { if ($1 == "pid") print $2 }' | head -n1)
if [ -z "$PID" ]
then
PID=/run/nginx.pid
fi
# Check if the ULIMIT is set in /etc/default/nginx
if [ -n "$ULIMIT" ]; then
# Set the ulimits
ulimit $ULIMIT
fi
#
# Function that starts the daemon/service
#
do_start()
{
# Return
# 0 if daemon has been started
# 1 if daemon was already running
# 2 if daemon could not be started
start-stop-daemon --start --quiet --pidfile $PID --exec $DAEMON --test > /dev/null \
|| return 1
start-stop-daemon --start --quiet --pidfile $PID --exec $DAEMON -- \
$DAEMON_OPTS 2>/dev/null \
|| return 2
}
test_nginx_config() {
$DAEMON -t $DAEMON_OPTS >/dev/null 2>&1
}
#
# Function that stops the daemon/service
#
do_stop()
{
# Return
# 0 if daemon has been stopped
# 1 if daemon was already stopped
# 2 if daemon could not be stopped
# other if a failure occurred
start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PID --name $NAME
RETVAL="$?"
sleep 1
return "$RETVAL"
}
#
# Function that sends a SIGHUP to the daemon/service
#
do_reload() {
start-stop-daemon --stop --signal HUP --quiet --pidfile $PID --name $NAME
return 0
}
#
# Rotate log files
#
do_rotate() {
start-stop-daemon --stop --signal USR1 --quiet --pidfile $PID --name $NAME
return 0
}
#
# Online upgrade nginx executable
#
# "Upgrading Executable on the Fly"
# http://nginx.org/en/docs/control.html
#
do_upgrade() {
# Return
# 0 if nginx has been successfully upgraded
# 1 if nginx is not running
# 2 if the pid files were not created on time
# 3 if the old master could not be killed
if start-stop-daemon --stop --signal USR2 --quiet --pidfile $PID --name $NAME; then
# Wait for both old and new master to write their pid file
while [ ! -s "${PID}.oldbin" ] || [ ! -s "${PID}" ]; do
cnt=`expr $cnt + 1`
if [ $cnt -gt 10 ]; then
return 2
fi
sleep 1
done
# Everything is ready, gracefully stop the old master
if start-stop-daemon --stop --signal QUIT --quiet --pidfile "${PID}.oldbin" --name $NAME; then
return 0
else
return 3
fi
else
return 1
fi
}
case "$1" in
start)
[ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
do_start
case "$?" in
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
esac
;;
stop)
[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
do_stop
case "$?" in
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
esac
;;
restart)
log_daemon_msg "Restarting $DESC" "$NAME"
# Check configuration before stopping nginx
if ! test_nginx_config; then
log_end_msg 1 # Configuration error
exit 0
fi
do_stop
case "$?" in
0|1)
do_start
case "$?" in
0) log_end_msg 0 ;;
1) log_end_msg 1 ;; # Old process is still running
*) log_end_msg 1 ;; # Failed to start
esac
;;
*)
# Failed to stop
log_end_msg 1
;;
esac
;;
reload|force-reload)
log_daemon_msg "Reloading $DESC configuration" "$NAME"
# Check configuration before reload nginx
#
# This is not entirely correct since the on-disk nginx binary
# may differ from the in-memory one, but that's not common.
# We prefer to check the configuration and return an error
# to the administrator.
if ! test_nginx_config; then
log_end_msg 1 # Configuration error
exit 0
fi
do_reload
log_end_msg $?
;;
configtest|testconfig)
log_daemon_msg "Testing $DESC configuration"
test_nginx_config
log_end_msg $?
;;
status)
status_of_proc -p $PID "$DAEMON" "$NAME" && exit 0 || exit $?
;;
upgrade)
log_daemon_msg "Upgrading binary" "$NAME"
do_upgrade
log_end_msg 0
;;
rotate)
log_daemon_msg "Re-opening $DESC log files" "$NAME"
do_rotate
log_end_msg $?
;;
*)
echo "Usage: $NAME {start|stop|restart|reload|force-reload|status|configtest|rotate|upgrade}" >&2
exit 3
;;
esac