首頁 > 軟體

如何寫SysV服務管理指令碼

2020-06-16 17:06:53

本文目錄:

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.linuxidc.com/Linux/2017-09/147066.htm


IT145.com E-mail:sddin#qq.com