如何寫SysV服務管理腳本

本文目錄:

1.1 SysV腳本的特性
1.2 SysV腳本要具備的能力
1.3 start函數分析
1.4 stop函數分析
1.5 reload函數分析
1.6 status、restart、force-reload等
1.7 結束語

SysV服務管理腳本和/etc/rc.d/init.d/functions文件中的幾個重要函數(包括daemon,killproc,status以及幾個和pid有關的函數)"關係匪淺"。本人已對該文件做了極詳細的分析和說明,參考functions文件詳細分析和說明

1.1 SysV腳本的特性

SysV風格的服務啓動腳本有以下幾個特性:

  1. 一般都放在/etc/rc.d/init.d目錄下。
  2. 這類腳本要求能接受start、stop、restart、status等參數來管理服務進程。
  3. 基本上都會加載/etc/rc.d/init.d/functions文件,因爲該文件中定義了幾個對進程管理非常有用的函數。
  4. 基本上都會加載/etc/sysconfig目錄下的同名文件。此目錄下的服務同名文件一般都是爲服務管理腳本提供選項參數的。例如/etc/sysconfig/httpd。
  5. 在腳本的頂端,需要加上# chkconfig# description兩行。chkconfig行定義的是該腳本被chkconfig工具管理時的主要依據,包括開機和關機時的啓動、關閉順序,以及運行在哪些運行級別。description是該腳本的描述性語句。雖然這兩行以"#"開頭,但必不可少。

例如,/etc/init.d/httpd腳本的前面幾行內容如下:

#!/bin/bash
#
# httpd        Startup script for the Apache HTTP Server
#
# chkconfig: - 85 15
# description: The Apache HTTP Server is an efficient and extensible  \
#              server implementing the current HTTP standards.
# processname: httpd
# config: /etc/httpd/conf/httpd.conf
# config: /etc/sysconfig/httpd
# pidfile: /var/run/httpd/httpd.pid
#

# Source function library.
. /etc/rc.d/init.d/functions

if [ -f /etc/sysconfig/httpd ]; then     # 判斷後再加載
    . /etc/sysconfig/httpd
fi

1.2 SysV腳本要具備的能力

要使用腳本管理服務進程,該腳本還要求具備以下能力,且處理邏輯越完善,腳本就越完美。

  1. 啓動進程時:
    • 要求能夠檢測進程是否已在運行。這包括檢測pid文件是否存在、/proc目錄下是否有進程pid值對應的目錄。
    • 應該爲程序創建鎖文件,路徑一般在/var/lock/subsys目錄下。
    • 如果使用daemon函數啓動進程,允許"--user"指定程序的運行身份。
    • 有些進程啓動時需要依賴於其他進程,如NFS啓動時依賴於rpcbind服務、mountd服務等,所以在NFS腳本中必須能夠檢測並啓動這些依賴服務。
  2. 關閉進程時:
    • 要求能夠檢測進程是否已在運行。同樣是檢測pid文件是否存在,/proc目錄下是否有pid對應的目錄。要注意,只有/proc目錄下沒有了對應目錄,才表示進程已死,但pid文件仍可能存在,例如kill -9就會出現這種問題。
    • 可以使用functions文件中的killproc函數殺進程,也可以直接使用kill或killall。
    • 爲了讓腳本更完善,殺進程時應該多次檢測進程是否真的已經殺死。
    • 殺死進程的最後,必須要刪除pid文件和鎖文件。
    • 對於有依賴性的服務,考慮是否也應該殺死它們。
  3. 服務重讀配置文件時(reload):
    • 對於非終端進程,發送HUP信號的作用是重讀配置文件,而不會中斷進程。
    • 爲了標準,應該找出"master"進程的pid,並向其發送HUP信號。一般來說,服務的子進程或線程不會也沒必要讀取配置文件。爲了方便或投籃,可以直接向所有進程發送HUP信號。
    • 最好在發送HUP信號前,也檢查進程是否已在運行。當然,對於reload來說,這無所謂。
    • 如果待管理程序支持配置文件的語法檢查,在發送HUP信號前,應該檢查語法是否錯誤。
    • 實在無法實現重讀配置文件的功能,應該讓其和restart的功能一致,一般這也是"force-reload"的功能。
  4. 重啓服務時:
    • 一般來說,就是先stop,再start。
  5. 查看status時:
    • 除非有額外的狀態顯示需求,否則/etc/init.d/functions中的status函數已經足夠完美了。

以上並沒有說明,管理多實例服務時的情況。這需要考慮額外的因素,例如程序自身是否支持多實例,支持的話是否應該寫多個服務腳本分別管理各程序,配置文件是否要共享,pid文件是否能共享,搜索pid時如何避免搜索出非自身實例的pid,還要注意分配鎖文件。這樣的腳本寫起來可能並不難,但這些因素必須要考慮。本文暫不介紹多實例的SysV腳本,因爲和程序自身關聯性比較強。

有了以上內容,並理解了functions文件中的函數,再看/etc/init.d/下的服務啓動腳本,絕大多數都感覺很簡單。因爲它們的思路和框架都是一致的。

因爲網上以及/etc/init.d/下服務啓動腳本示例太多了,所以本文不單獨寫這類腳本,而是從幾個腳本中抽出比較經典的部分,分別介紹start,stop,reload和status的寫法。

1.3 start函數分析

以httpd的服務管理腳本/etc/init.d/httpd爲例。

start() {
    echo -n $"Starting $prog: "
    LANG=$HTTPD_LANG daemon --pidfile=${pidfile} $httpd $OPTIONS
    RETVAL=$?
    echo
    [ $RETVAL = 0 ] && touch ${lockfile}
    return $RETVAL
}

函數首先輸出"Starting $prog"信息,再使用daemon啓動"$httpd"程序。

在daemon語句中,"--pidfile"是daemon的參數,該參數爲daemon檢測pid文件是否存在,"$httpd"進程是否已在運行。注意,這個"--pidfile"是寫在"$httpd"前面的,表示這是daemon的參數,而非"$httpd"的啓動參數。

檢測完成後,啓動程序。程序的啓動命令從"$httpd"參數開始,"$OPTIONS"是"$httpd"的啓動選項。一般出現"$OPTIONS"這個字眼,很可能加載了/etc/sysconfig目錄下的同名文件,目的是提供程序啓動參數。

如果啓動成功,則會daemon函數會調用functions中的success函數顯示"[ OK ]",否則會顯示"[ FAILED ]"。

最後,如果啓動成功,則會創建該進程的鎖文件"$lockfile"。鎖文件一般都在/var/lock/subsys目錄下。

很多時候,管理的進程也有"--pidfile"類似的選項。例如下面的啓動語句:

daemon --pidfile $pidfile $processname --pidfile=$pidfile

兩個"--pidfile"選項,但他們的作用是不一樣的。第一個"--pidfile"是daemon函數的參數,以便daemon能夠檢測該文件中的pid進程是否已在運行。第二個"--pidfile"是"$processname"的啓動參數,啓動時會創建此文件作爲pid文件。

再看一個不使用daemon函數管理進程啓動動作的示例。以下是/etc/init.d/sshd中的start函數內容。

start()
{
        [ -x $SSHD ] || exit 5
        [ -f /etc/ssh/sshd_config ] || exit 6
        # Create keys if necessary
        if [ "x${AUTOCREATE_SERVER_KEYS}" != xNO ]; then
                do_rsa_keygen
                if [ "x${AUTOCREATE_SERVER_KEYS}" != xRSAONLY ]; then
                        do_rsa1_keygen
                        do_dsa_keygen
                fi
        fi

        echo -n $"Starting $prog: "
        $SSHD $OPTIONS && success || failure
        RETVAL=$?
        [ $RETVAL -eq 0 ] && touch $lockfile
        echo
        return $RETVAL
}

前面多了一大段,這和服務啓動腳本的框架無關,是程序自身要求的,但作用很簡單。無非就是判斷下程序是否可執行,配置文件是否存在,是否要創建服務端主機驗證階段的密鑰對,也就是/etc/ssh/ssh_host_{rsa,dsa}_key等幾個文件。

再下面纔是服務啓動腳本中的通用邏輯部分。輸出一段信息,然後啓動程序,創建鎖文件。但這裏沒有使用daemon函數管理,所以這裏配合了success和failure函數以便人性化顯示"[ OK ]"或"[ FAILED ]"。

1.4 stop函數分析

仍然以/etc/init.d/httpd中的stop函數爲例。

# When stopping httpd, a delay (of default 10 second) is required
# before SIGKILLing the httpd parent; this gives enough time for the
# httpd parent to SIGKILL any errant children.
stop() {
    status -p ${pidfile} $httpd > /dev/null
    if [[ $? = 0 ]]; then
        echo -n $"Stopping $prog: "
        killproc -p ${pidfile} -d ${STOP_TIMEOUT} $httpd
    else
        echo -n $"Stopping $prog: "
        success
    fi
    RETVAL=$?
    echo
    [ $RETVAL = 0 ] && rm -f ${lockfile} ${pidfile}
}

前面加了一段註釋,大致意思是說這裏殺死進程的行爲和httpd自帶的apachectl工具停止服務的命令"apachectl -k stop"的行爲是不同的。之所以我要把這一段也貼上來,也就是爲了說明這一點。有些服務程序自帶進程管理工具,亦或是使用functions中的函數,完全由我們自己決定。

再看stop函數的邏輯。首先使用"status"函數檢查進程的狀態,如果進程已在運行,則使用killproc函數殺掉它,否則表示進程未運行或進程已死,但pid文件還存在。所以,在最後刪掉pidfile和lockfile。

需要注意的是,killproc殺進程時,能保證pidfile同時被刪除。但它不負責lockfile,而且執行stop之前曾手動執行了"kill -9"殺進程,那麼進程雖然已死,但pid文件卻存在。因此也仍需手動rm刪除pidfile。

killproc的調用方法爲:

killproc [-p $pidfile] -[d $delay] $processname [-signal]

它的邏輯和執行過程是這樣的:

  1. 根據pidfile找出要殺的pid,如果沒有指定pidfile,則默認從/var/run/$base.pid讀取;
  2. 如果指定了要發送的信號,則killproc通過kill命令發送給定信號。0.5秒後檢查/proc目錄下是否還有對應目錄存在,有則說明進程殺死失敗,返回"[ FAILED ]"信息,否則表示成功,於是刪除pid文件。
  3. 如果沒有指定要發送的信號,則killproc先發送TERM信號(即kill -15),然後在給定的延遲時間delay內,每隔一秒檢查一次/proc下是否有對應目錄,如果發現沒有,則表示進程殺死成功,於是刪除pid文件(其實這種情況不用刪,因爲TERM信號會自動做收尾動作)。但如果delay都超時了,還發現進程存在,則發送KILL信號強制殺死進程,最後刪除pid文件。

現在再理解killproc -p ${pidfile} -d ${STOP_TIMEOUT} $httpd就很簡單了。

再看/etc/init.d/sshd腳本中的stop。

stop()
{
    echo -n $"Stopping $prog: "
    killproc -p $PID_FILE $SSHD
    RETVAL=$?
    # if we are in halt or reboot runlevel kill all running sessions
    # so the TCP connections are closed cleanly
    if [ "x$runlevel" = x0 -o "x$runlevel" = x6 ] ; then
        trap '' TERM
        killall $prog 2>/dev/null
        trap TERM
    fi
    [ $RETVAL -eq 0 ] && rm -f $lockfile
    echo
}

更直接,直接就killproc。但是後面還設置了runlevel的判斷情況,這就屬於程序自身屬性了,和服務管理腳本的邏輯框架無關。

最後再看mysqld中的stop函數。

stop(){
    if [ ! -f "$mypidfile" ]; then
        # not running; per LSB standards this is "ok"
        action $"Stopping $prog: " /bin/true      # pid文件都不存在,直接顯示成功
        return 0
    fi
    MYSQLPID=`cat "$mypidfile" 2>/dev/null`       # 讀取pidfile中的pid號
    if [ -n "$MYSQLPID" ]; then                   # 如果pid不爲空,則
        /bin/kill "$MYSQLPID" >/dev/null 2>&1     # 先發送默認的TERM信號殺一次
        ret=$?
        if [ $ret -eq 0 ]; then         # 如果殺成功了,則執行下面一段。
                                        # 否則直接失敗,但這不可能。爲了邏輯完整,後面仍寫了else
            TIMEOUT="$STOPTIMEOUT"
            while [ $TIMEOUT -gt 0 ]; do   # 在延遲時間內,每隔1秒殺一次
                /bin/kill -0 "$MYSQLPID" >/dev/null 2>&1 || break
                sleep 1
                let TIMEOUT=${TIMEOUT}-1
            done
            if [ $TIMEOUT -eq 0 ]; then    # 如果達到延遲時間邊界,則返回殺死進程超時信息
                echo "Timeout error occurred trying to stop MySQL Daemon."
                ret=1
                action $"Stopping $prog: " /bin/false
            else                           # 否則進程殺死成功,刪除pidfile和lockfile
                rm -f $lockfile
                rm -f "$socketfile"
                action $"Stopping $prog: " /bin/true
            fi
        else
            action $"Stopping $prog: " /bin/false
        fi
    else                                   # 如果pid爲空,則表示未成功讀取pidfile。
        # failed to read pidfile, probably insufficient permissions
        action $"Stopping $prog: " /bin/false
        ret=4
    fi
    return $ret
}

雖然有點長,但有了前面SysV腳本要具備的能力的概念,stop函數的邏輯都一樣好簡單。

1.5 reload函數分析

關於reload函數,主要有兩點:(1).語法檢查;(2).發送HUP信號給"master"進程。其中語法檢查要程序自身能支持,例如httpd -tnginx -t

以下是/etc/init.d/{httpd,nginx}兩個腳本中的reload函數。

## reload() in /etc/rc.d/init.d/httpd
reload() {
    echo -n $"Reloading $prog: "
    if ! LANG=$HTTPD_LANG $httpd $OPTIONS -t >&/dev/null; then  # 語法檢查
        RETVAL=6
        echo $"not reloading due to configuration syntax error"
        failure $"not reloading $httpd due to configuration syntax error"
    else
        # Force LSB behaviour from killproc        # 語法檢查通過,發送HUP信號
        LSB=1 killproc -p ${pidfile} $httpd -HUP
        RETVAL=$?
        if [ $RETVAL -eq 7 ]; then             # 注意reload失敗時退出狀態碼爲7
            failure $"httpd shutdown"
        fi
    fi
    echo
}

## reload() in /etc/rc.d/init.d/nginx
reload() {
    configtest_q || return 6           # 語法檢查
    echo -n $"Reloading $prog: "
    killproc -p $pidfile $prog -HUP    # 發送HUP信號
    echo
}

configtest_q() {
    $nginx -t -q -c $NGINX_CONF_FILE
}


case "$1" in
    reload)
        rh_status_q || exit 7        # reload失敗時,退出狀態碼7
        $1
        ;;

唯一需要注意的是,reload失敗時,退出狀態碼爲7。這大概已經約定俗成了吧。

再看/etc/init.d/sshd中的reload。

reload()
{
    echo -n $"Reloading $prog: "
    killproc -p $PID_FILE $SSHD -HUP
    RETVAL=$?
    echo
}

case "$1" in
    reload)
        rh_status_q || exit 7
        reload
        ;;

有意思的是mysqld的reload。它直接退出不做任何動作。

case "$1" in
  reload)
    exit 3
    ;;

如果不使用killproc函數,而是使用kill命令,那麼應該找出"master" pid。可以使用functions中的pidofproc函數。例如:

pid=$(pidofprco -p pidfile $processname)
action "Reloading $prog: " kill -HUP $pid

1.6 status、restart、force-reload等

  • status:就是爲了獲取進程狀態的,一般直接調用functions中的status函數status -p "$pidfile" $prog
  • restart:一般直接stop再start即可。
  • force-reload:其實就是restart。
  • condrestart:稱爲條件式重啓。所謂的條件一般是判斷鎖文件是否存在,存在則重啓,否則忽略該動作。"try-restart"也是一樣的行爲。

1.7 結束語

其實SysV服務啓動腳本大多都很簡單,至少它們的邏輯幾乎都一樣。在瞭解了functions中的幾個函數後,再把腳本的各參數(如start、stop)應該要具備的能力搞搞清楚,這類腳本完全是小菜一兩碟。

 

回到系列文章大綱:http://www.cnblogs.com/f-ck-need-u/p/7048359.html

轉載請註明出處:http://www.cnblogs.com/f-ck-need-u/p/7524401.html

注:若您覺得這篇文章還不錯請點擊下右下角的推薦,有了您的支持才能激發作者更大的寫作熱情,非常感謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章