起因
之前需要設(shè)定一個服務(wù)的開機啟動腳本,但是遺憾的是我沒有很好的 Linux 系統(tǒng)使用和配置經(jīng)驗,操作系統(tǒng)不熟,Shell 腳本不熟,網(wǎng)上的教程和例子不少但是限于自身的基礎(chǔ),其實參考性不大;基于現(xiàn)有經(jīng)驗和使用狀況,其實nginx的服務(wù)啟動和周期管理不就是很好的例子?
通過句讀 nginx 的 init 腳本,擴展shell編程的知識點和編程結(jié)構(gòu);
回顧
首先我需要什么?
有一個本地的可執(zhí)行文件,啟動之后可以提供有限的http服務(wù).我需要他想nginx的服務(wù)一樣,開機啟動,不受會話啟動關(guān)閉的影響;守護(hù)進(jìn)程,前后臺的概念在過程中熟悉,先去看看怎么實踐;
nginx 服務(wù)操作
啟動命令
sudo /etc/init.d/nginx start
關(guān)閉命令
sudo /etc/init.d/nginx stop
查看狀態(tài)
sudo /etc/init.d/nginx status
重啟
sudo /etc/init.d/nginx restart
邏輯上看,是使用root權(quán)限來執(zhí)行一段存在絕對路徑下的,腳本,并且傳入了一個參數(shù),start,stop,status restart等等來執(zhí)行對應(yīng)的操作,交互層面模仿這樣就可以
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 的數(shù)字貌似是運行級別,暫時先不管,被注釋的不會運行;
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
接下的部分是變量設(shè)定部分,先看下本機系統(tǒng)中的PATH變量原來是什么,然后這里是直接指定了PATH
DAEMON的意思本身就是守護(hù)進(jìn)程吧,這里指定的nginx的二進(jìn)制程序?看看對應(yīng)目錄下的文件是什么意思?文件是真實存在的,而且應(yīng)該就是nginx的執(zhí)行文件,那我自己的可執(zhí)行文件也要配置到這個變量中吧
NAME 和 DESC 應(yīng)該只是下面腳本中的變量替換,作為服務(wù)的名字和描述,也要改成自己服務(wù)的名字
默認(rèn)配置運行
# Include nginx defaults if available
if [ -r /etc/default/nginx ]; then
. /etc/default/nginx
fi
先要去看看 shell 當(dāng)中的 if 結(jié)構(gòu)是怎么寫的了,看不懂呀這個.....
shell 中的 if 結(jié)構(gòu)
分支結(jié)構(gòu)的基本形式是 if 后面跟上中括號,中括號里面放的是條件表達(dá)式,代表真假的布爾值,之后是一個分號和 then,真?zhèn)€分支代碼塊以 fi 結(jié)束
if [ ... ]; then
...
fi
方括號里的條件表達(dá)式的寫法有固定的格式,上文中的表達(dá)式具體的意思是檢測文件是不是可讀的-r /etc/default/nginx
;
再來看條件成立的時候執(zhí)行的代碼塊中的命令,一個點,空格,加上文件,表示執(zhí)行這個文件, source file ; ./file ; . file 這幾個命令貌似都是執(zhí)行文件,但是還是略有不同,具體的區(qū)別暫時不去深究吧
回到句讀中,這一段的意思大概就是,查看這個配置文件是不是可讀的,如果是的話那就執(zhí)行這個配置文件,來看看這配置文件的內(nèi)容
/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"
內(nèi)容都被注釋掉了,就說沒有執(zhí)行任何東西
測試執(zhí)行文件
test -x $DAEMON || exit 0
這行代碼里面有幾點需要看,test命令是什么? -x 參數(shù)的意思是? 一個豎線是管道,兩個豎線又是什么? exit 0 是表示退出?
- test 命令 : test 本身的作用是檢查文件類型并比較值,結(jié)合 -x 參數(shù)的作用就是檢查文件是否存在并且是不是有執(zhí)行的權(quán)限
- 短路邏輯或 || : exit 0 左邊的兩個豎線 || 跟一般含義一樣是邏輯運算符,表示短路的邏輯或,當(dāng) || 左邊的 test 命令返回假的時候,右邊的 exit 0 退出命令才會執(zhí)行,表示腳本退出;
結(jié)合上面的兩點來看,這個語句的意思就是,測試可執(zhí)行文件 DAEMON
是否存在兵器可以執(zhí)行,如果不是的話就退出程序;
環(huán)境初始化
. /lib/init/vars.sh
. /lib/lsb/init-functions
這是執(zhí)行了兩個文件,看名字,一個是初始化變量定義,一個是初始化函數(shù),具體內(nèi)容中的確是有執(zhí)行一些變量和函數(shù)的定義,下面用到的時候在回頭看吧
嘗試獲取 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 有關(guān),cat 命令打印內(nèi)容,之后是用 grep 篩選了什么,然后交給 awk 命令 來執(zhí)行,最后是 head 命令;一個個來看
- cat 命令很熟悉,就是吧文件的內(nèi)容打印到控制臺,也就是說,管道第一步是吧這個配置文件的內(nèi)容打印到控制臺
- grep 只知道是篩選,會返回篩選的內(nèi)容所在的行這樣子,參數(shù) -Ev 的具體含義?后面跟的明顯是正則表達(dá)式,應(yīng)該是有關(guān)系的~~~
- -E 參數(shù)的意思是后面跟隨的是正則表達(dá)式,而 -v 是表示反選,輸出的不是跟表達(dá)式匹配的,而是不匹配的;來看正則,意思會任意的空格開頭,而后是井號,意思是所有的注釋行?反選的話就是去掉所有的注釋
- 到這里也就明顯,第一步打印配置文件,第二步,刪除所有的注釋行,接下來來到了第三步 awk
- awk 命令沒有怎么接觸過,知道很強大;awk 是模式掃描和文本處理語言,這是一個語言?恩......好吧,看了一下, 本機的awk 實際上是一個指向 mawk 的鏈接,大概看了下語法,首先是指定要分割的分隔符集合,也就是 RS 等于的東西,然后針對分割之后的每行文本進(jìn)行處理,這里的意思就是,每一行都會被 RS 分割成兩部分應(yīng)該,第一個部分如果等于 PID 的話,那么就輸出第二部分; 這里就明了了,最后得到的就是存儲pid的文件;
- head 獲取文件的前幾行, -n1 就是獲取第一行
在解析完配置文件之后,得到的是儲存pid的文件,所以之后在檢測一下,變量 pid 的長度是不是0, 如果沒有找到配置的文件,那么就是用默認(rèn)的 /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 參數(shù)表示后面參數(shù)字符串的長度部位 0 的時候為真;所以這篇代碼不執(zhí)行, ulimit表示什么暫時不管
函數(shù)定義部分
#
# 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
}
從函數(shù)的名字可以看出這是服務(wù)啟動的時候執(zhí)行的函數(shù),從注釋中也看出了返回值的具體含義,0-服務(wù)已經(jīng)開啟,1-服務(wù)已經(jīng)運行,2-服務(wù)起不起來
start-stop-daemon 是一個系統(tǒng)的命令,專門用來啟動關(guān)閉服務(wù)的,那么剩下的就是配置參數(shù)了,唯一的問題就是一直沒有找到那個 DAEMON_OPTS 變量定義在哪里
接下來的函數(shù)們就實現(xiàn)各自的功能了 start stop 等等
腳本參數(shù)解析
最后的一部分就是一個多路分支的結(jié)構(gòu)了,根據(jù)輸入的第一個參數(shù)來決定執(zhí)行什么函數(shù)
總結(jié)
看來一個 daemon 的維護(hù)需要的東西還是蠻多的,如果沒有配置需要載入的話,就需要兩個文件,一個是 /etc/init/myservice ,還有一個是必須的 pid 文件, /run/myservice.pid 照著寫應(yīng)該就是OK的,具體的執(zhí)行可以交給系統(tǒng)內(nèi)部的命令
文件原本- nginx服務(wù)腳本 /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