使用init脚本,无非就是实现软件服务的开机启动。但是用Debian或Ubuntu时,都是用apt-get来安装软件,而且服务类的软件都自带init脚本,对init脚本变得生疏了。工作中在CentOS上配置了两个开机启动脚本,并发现一个问题。

开机守护进程启动Tomcat6
用了Tomcat这么久,以前居然一直没做个这个配置。Tomcat官方网站推荐用jsvc守护进程来启动Tomcat。

关于jsvc,Apache官网上说得非常清楚了:
http://commons.apache.org/proper/commons-daemon/jsvc.html

关于Unix(包括Linux)系统上Tomcat6守护进程配置:
https://tomcat.apache.org/tomcat-6.0-doc/setup.html#Unix_daemon

这里的步骤简单为:
1)找到jsvc的源码包:tomcat6/bin/commons-daemon-native.tar.gz
2)解压、配置并编译

cd $CATALINA_HOME/bin
tar xvfz commons-daemon-native.tar.gz
cd commons-daemon-1.0.x-native-src/unix
./configure
make
cp jsvc $CATALINA_HOME/bin/

3)配置init脚本。jsvc源码包里有Tomcat5的init脚本,复制过来,按需要进行修改,并放到 /etc/init.d/ 目录下。例如文件名为 tomcat 。jsvc启动Tomcat后,可能根据Tomcat的启动情况来设置返回值(即通过$?获取的值),导致Tomcat正常启动后,jsvc仍会返回1,而不是0。因此,这里改为Tomcat启动后,通过检测相关端口来判断Tomcat是否启动成功并显示启动后的提示信息。脚本示例如下:

#!/bin/sh 
# 
# Startup Script for Tomcat6 
# 
# chkconfig: 345 98 02 
# description: Tomcat Daemon 
# processname: tomcat 
# pidfile: /var/run/tomcat.pid 
# config: 
# 
# Source function library.
. /etc/rc.d/init.d/functions 
# 
prog=tomcat

JAVA_HOME=/usr/local/jdk1.6.0_45
CATALINA_HOME=/usr/local/apache-tomcat-6.0.35
DAEMON_HOME=$CATALINA_HOME/bin
TOMCAT_USER=tomcat
HTTP_PORT=8080

# for multi instances adapt those lines.
TMP_DIR=$CATALINA_HOME/temp
PID_FILE=/var/run/tomcat.pid
CATALINA_BASE=$CATALINA_HOME

CATALINA_OPTS="-server -Xss512k -Xms64M -Xmx512M"
CLASSPATH=\
$JAVA_HOME/lib/tools.jar:\
$CATALINA_HOME/bin/commons-daemon.jar:\
$CATALINA_HOME/bin/bootstrap.jar

start() {
  echo -n $"Starting $prog: "
  
  #
  # Start Tomcat
  #
  $DAEMON_HOME/jsvc $JSVC_OPTS \
  -user $TOMCAT_USER \
  -home $JAVA_HOME \
  -Dcatalina.home=$CATALINA_HOME \
  -Dcatalina.base=$CATALINA_BASE \
  -Djava.io.tmpdir=$TMP_DIR \
  -wait 10 \
  -pidfile $PID_FILE \
  -outfile $CATALINA_HOME/logs/catalina.out \
  -errfile '&1' \
  $CATALINA_OPTS \
  -cp $CLASSPATH \
  org.apache.catalina.startup.Bootstrap
  #
  # To get a verbose JVM
  #-verbose \
  # To get a debug of jsvc.
  #-debug \

  # Check if Tomcat is running on HTTP_PORT
  lsof -i:$HTTP_PORT > /dev/null
  ret=$?
  if [ $ret -eq 0 ]; then
    echo_success
  else
    echo_failure
  fi
  echo
  return $ret
}

stop() {
  echo -n $"Stopping $prog: "
  
  #
  # Stop Tomcat
  #
  $DAEMON_HOME/jsvc $JSVC_OPTS \
  -stop \
  -pidfile $PID_FILE \
  org.apache.catalina.startup.Bootstrap
  
  ret=$?
  if [ $ret -eq 0 ]; then
    echo_success
  else
    echo_failure
  fi
  echo
  return $ret
}

restart() {
  stop
  start
}

case "$1" in
  start)
    start
    ;;

  stop)
    stop
    ;;

  restart)
    restart
    ;;

  *)
    echo "Usage $prog start/stop/restart"
    exit 1;;
esac

4)加入开机启动。利用chkconfig命令:

chkconfig --add tomcat
chkconfig tomcat on

所有Java程序都可以用jsvc来启动,但是,启动的类需要实现init(String[] args)、start()、stop()、destroy()这4个方法。服务在启动时会先调用init(String[] args)方法,然后调用start()方法,在服务停止是会首先调用stop()方法,然后调用destroy()方法。

直接启动Tomcat6
其实init脚本只是个含有特定格式(例如要求实现带start和stop参数的功能)的shell脚本。就是说,可以直接执行Tomcat6自带的启动和停止脚本($CATALINA_HOME/bin目录下的startup.sh和shutdown.sh)。

具体配置参考这个:
http://www.cnblogs.com/lianche/p/3154096.html

init脚本的模板如下:(以memcache为例)

#!/bin/sh
#
# Startup Script for memcached
#
# chkconfig: 345 65 35
# description: memcached
# processname: memcached
# pidfile: /var/run/memcached.pid
# config:
#
# Source function library.
. /etc/rc.d/init.d/functions
#
prog=memcached

FILE_HOME=/usr/local/memcache
PID_FILE=/var/run/memcached.pid

start() {
  echo -n $"Starting $prog: "
  
  $FILE_HOME/bin/memcached -d -m 128 -u root -l 0.0.0.0 -p 11211 -c 512 -P $PID_FILE

  retval=$?
  if [ $retval -eq 0 ]; then
    echo_success
  else
    echo_failure
  fi
  echo
  return $retval
}

stop() {
  echo -n $"Stopping $prog: "

  cat $PID_FILE | xargs kill -9

  ret=$?
  if [ $ret -eq 0 ]; then
    echo_success
  else
    echo_failure
  fi
  echo
  return $ret
}

restart() {
  stop
  start
}

case "$1" in
  start)
    start
    ;;

  stop)
    stop
    ;;

  *)
    echo "Usage $prog start/stop/restart"
    exit 1;;
esac

关于没有生成PID文件的程序
有些程序不像memcached那样把进程id记录到PID_FILE,于是就不能使用kill命令来关闭该进程了。虽然直接调用startup.sh和shutdown.sh来启动和关闭Tomcat也是没有PID_FILE,但是Tomcat会使用端口来关闭自己进程。遇到既没有PID_FILE,也没有像Tomcat那样有特殊的关闭方法的话,就自能自己用ps命令找到对应的进程id,然后kill掉。

以下以“花生壳(phddns)”为例。其中get_pid函数是获取进程号的。本来ps -C phddns -o pid=就可以获取到进程id了。但是由于启动脚本的文件名也是phddns,ps -C phddns会找到脚本所在的进程id,所以要再加上grep来找到/usr/bin/phddns命令的进程。

#!/bin/sh
#
# Startup Script for phddns
#
# chkconfig: 345 56 44
# description: phddns Daemon
# processname: phddns
# pidfile:
# config:
#
# Source function library.
. /etc/rc.d/init.d/functions
#
prog=phddns

get_pid() {
  #get the process id using ps
  echo $(ps -C phddns -o pid,command | grep '/usr/bin/phddns' | awk '{print $1}')
}

start() {
  echo -n $"Starting $prog: "

  PID_NUM=$(get_pid)

  if [ -n "$PID_NUM" ]; then
    echo_failure
    echo
    echo $prog is already running.
  else
    /usr/bin/phddns -c /etc/phlinux.conf -d > /dev/null

    ret=$?
    if [ $ret -eq 0 ]; then
      echo_success
    else
      echo_failure
    fi
    echo
  fi
  return $ret
}

stop() {
  echo -n $"Stopping $prog: "

  PID_NUM=$(get_pid)

  if [ -n "$PID_NUM" ]; then
    kill -9 $PID_NUM

    ret=$?
    if [ $ret -eq 0 ]; then
      echo_success
    else
      echo_failure
    fi
    echo
  else
    echo_failure
    echo
    echo $prog is not running.
  fi
  return $ret
}

restart() {
  stop
  start
}

case "$1" in
  start)
    start
    ;;

  stop)
    stop
    ;;

  restart)
    restart
    ;;

  *)
    echo "Usage $prog start/stop/restart"
    exit 1;;
esac

关于启动顺序
开机启动时,如果服务A依赖服务B来运行时,必须要先启动服务B,再启动服务A。通常在init脚本头(注释)中,用 chkconfig 及其后面3个参数来标记。例如:
chkconfig 345 65 35
345表示在init状态为3、4、5时启动,65表示启动顺序(只支持两位数字,数字越小,启动越早),35表示中止顺序(也只支持两位数字,数字越小,中止越早)。

那么,服务A的chkconfig第2个参数要大于服务B的。

生成的启动顺序,可以参照 /etc/rc.d 文件夹(号代表0到6)内的文件名称。其中K开头的表示中止,S开头的表示启动。

PS. 由于工作上用到了CentOS,相关的配置也用得越来越多。这是N年前想不到的事情。

标签: CentOS, init.d, tomcat, jsvc

添加新评论