[nginx]句读nginx服务启动脚本

起因

之前需要设定一个服务的开机启动脚本,但是遗憾的是我没有很好的 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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容