대용량 플랫폼2011.02.25 15:39
Daemon 이란 백그라운드로 실행되면서, 사용자의 인터페이스(tty)가 없는 프로그램을 말한다. 우리가 흔히 사용하는 리눅스 서비스들은 대부분 데몬으로 동작하며, -d로 끝나는 프로그램(예: sshd, syslogd)이 모두 해당된다. 리눅스 명령인 nohup으로 백그라운드 구동은 가능하지만, kill 명령으로 종료해야 한다.
따라서 비정상 종료시의 처리를 하려면, OS로부터 signal을 받아야 하므로 데몬으로 구동시켜야 한다. 
Apache Commons Daemon에서 제공하는 Jsvc를 이용하여, 간단한 데몬 프로그램을 작성해 보자.
(우리가 잘 알고 있는 Tomcat도 Jsvc를 이용하여, 데몬으로 프로세스를 실행한다고 한다.)

JsvcDaemon.zip

아래 내용대로 작성한 쉘스크립트와 소스로 만든 이클립트 프로젝트. (Ubuntu 12.04 에서 테스트함.)

java와 jsvc는 설치후에, 이클립스에서 구동해 보자.
TestDaemon.sh에서 java, jsvc, commons-daemon.jar 파일의 위치는 자신에 맞게 수정해야 한다.


0. Java 설치

1-1. Jsvc 설치 (Ubuntu)
apt-get install jsvc

1-2. Jsvc 설치 (CentOS)
이미 컴파일된 Jsvc를 다운로드할 수는 있지만, 실제로 받아서 실행해 보면 에러를 출력하므로, 직접 컴파일하여야 한다.
Linux(CentOS 5.5)에 /opt/jdk1.6.0_24/에 JAVA를 설치했다고 가정한다. 

mkdir /root/commons-daemon
cd /root/commons-daemon

wget http://www.apache.org/dist/commons/daemon/binaries/1.0.5/commons-daemon-1.0.5.jar
wget http://mirror.apache-kr.org//commons/daemon/source/commons-daemon-1.0.5-src.tar.gz
tar zxvf commons-daemon-1.0.5-src.tar.gz
cd commons-daemon-1.0.5-src/src/native/unix/
support/buildconf.sh
./configure --with-java=/opt/jdk1.6.0_24/
make
mv jsvc /root/commons-daemon
위와 같이 실행하면, 작업할 경로(/root/commons-daemon)에 jar와 jsvc파일이 생성되어 있을 것이다.

2. 실행할 JAVA 클래스 작성
jsvc에 의해서 실행되는 JAVA 클래스는 Daemon 인터페이스를 구현해야 한다. 또한 스레드로 실행되어야 하므로, Runnable도 구현해야 한다. 만약 스레드로 구동하지 않을 경우, 종료처리가 제대로 되지 않으며, "Service exit with a return value of 143"라는 오류를 표시할 것이다.

테스트를 위해, 1초마다 숫자를 하나씩 증가시키면서 찍는 프로그램을 JAVA로 아래와 같이 만들었다.
TestDaemon.java

package com.bagesoft.test.daemon;

import org.apache.commons.daemon.Daemon;
import org.apache.commons.daemon.DaemonContext;
import org.apache.commons.daemon.DaemonInitException;

public class TestDaemon implements Daemon, Runnable {
	private String status = "";
	private int no = 0;
	private Thread thread = null;

	@Override
	public void init(DaemonContext context) throws DaemonInitException,
			Exception {
		System.out.println("init...");
		String[] args = context.getArguments();
		if (args != null) {
			for (String arg : args) {
				System.out.println(arg);
			}
		}
		status = "INITED";
		this.thread = new Thread(this);
		System.out.println("init OK.");
		System.out.println();
	}

	@Override
	public void start() {
		System.out.println("status: " + status);
		System.out.println("start...");
		status = "STARTED";
		this.thread.start();
		System.out.println("start OK.");
		System.out.println();
	}

	@Override
	public void stop() throws Exception {
		System.out.println("status: " + status);
		System.out.println("stop...");
		status = "STOPED";
		this.thread.join(10);
		System.out.println("stop OK.");
		System.out.println();
	}

	@Override
	public void destroy() {
		System.out.println("status: " + status);
		System.out.println("destroy...");
		status = "DESTROIED";
		System.out.println("destroy OK.");
		System.out.println();
	}

	@Override
	public void run() {
		while (true) {
			System.out.println(no);
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			if (no > 1000) {
				break;
			}
			no++;
		}
	}
}
위의 클래스를 jar로 묶어서 BageSoft.jar 라는 파일을 만들고 작업경로(/root/commons-daemon/)에 저장했다.

3. JAVA를 실행할 쉘스크립트 작성
쉘스크립트를 작성할 때, 위에서 다운 받아서 압축 풀었던 위치(commons-daemon-1.0.5-src/src/samples)에서 ServiceDaemon.sh를 참고하면 된다.
아래에서 $DAEMON_HOME은 작업 경로를 의미한다.
나중에 디버깅의 편의성 때문에 에러 출력을 표준출력과 같은 파일에 저장하도록 했다.

TestDaemon.sh
#!/bin/sh
JAVA_HOME=/opt/jdk1.6.0_24
JSVC=/root/commons-daemon/jsvc
USER=root

DAEMON_HOME=/root/commons-daemon
PID_FILE=$DAEMON_HOME/daemon.pid
OUT_FILE=$DAEMON_HOME/daemon.out
#ERR_FILE=$DAEMON_HOME/daemon.err

CLASSPATH=\
$DAEMON_HOME/commons-daemon-1.0.5.jar:\
$DAEMON_HOME/BageSoft.jar

MAIN_CLASS=com.bagesoft.test.daemon.TestDaemon
case "$1" in
  start)
    #
    # Start Daemon
    #
	rm -f $OUT_FILE
    $JSVC \
    -user $USER \
    -java-home $JAVA_HOME \
    -pidfile $PID_FILE \
    -outfile $OUT_FILE \
    -errfile $OUT_FILE \
    -cp $CLASSPATH \
    $MAIN_CLASS
    #
    # To get a verbose JVM
    #-verbose \
    # To get a debug of jsvc.
    #-debug \
    exit $?
    ;;

  stop)
    #
    # Stop Daemon
    #
    $JSVC \
    -stop \
    -nodetach \
    -java-home $JAVA_HOME \
    -pidfile $PID_FILE \
    -outfile $OUT_FILE \
    -errfile $OUT_FILE \
    -cp $CLASSPATH \
    $MAIN_CLASS
    exit $?
    ;;

  *)
    echo "[Usage] TestDaemon.sh start | stop"
    exit 1;;
esac
4. 실행/종료
실행 
/root/commons-daemon/TestDaemon.sh start

로그를 남기도록 몇초 기다리고.... 
종료
/root/commons-daemon/TestDaemon.sh stop

제대로 처리 되는지는 daemon.out의 내용을 확인하면 된다.
init(), start(), stop(), destroy()가 모두 수행되면 정상이다.
아래는 start 후 10초 후에 stop 했을 때의 daemon.out의 내용이다.
init... init OK. status: INITED start... start OK. 0 1 2 3 4 5 6 7 8 9
status: STARTED stop... stop OK. status: STOPED destroy... destroy OK.


Posted by 바게 BAGE

댓글을 달아 주세요

  1. 감사합니다.
    정말 많은 도움이 되었습니다.

    관련 자료를 찾고 있었는데.. 당장 실전에 적용하기 가장 좋은 글인 것 같습니다. ^^

    2012.04.16 19:19 [ ADDR : EDIT/ DEL : REPLY ]
    • 도움이 되셨다니 다행입니다. 저도 전에 dev용식님 블로그에서 hadoop, lucene 관련 자료를 보고 많은 도움 받았습니다. 좋은 블로그 운영해 주셔서 너무 감사드립니다. ^^

      2012.04.18 00:53 신고 [ ADDR : EDIT/ DEL ]
  2. 오홋! 찾고있던건데 잘 정리해 주셨군요!!
    위 링크에 스크랩해뒀는데 원치않으시면 페이지 닫을께요 ^^;

    2012.04.20 18:05 [ ADDR : EDIT/ DEL : REPLY ]
  3. 잘 보고 갑니다.
    그런데 정말 쉘 스크립트를 만들어
    ./TestDaemon.sh
    로 실행했더니

    : No such file or directoryroot/commons-daemon
    : No such file or directoryroot/commons-daemon
    '/TestDaemon.sh: line 15: syntax error near unexpected token `in
    '/TestDaemon.sh: line 15: `case "$1" in

    오류들이 뜨네요...

    혹시 글 보시면 좀 도와주세요~

    2013.02.07 01:08 [ ADDR : EDIT/ DEL : REPLY ]
    • 이 글 내용대로 모든 경로를 똑같이 하신 후에 실행해보세요. 혹시 스크립트에 /root/commons-daemon 대신 root/commons-daemon 로 입력하셨는지도 확인해 보시기 바랍니다.
      오류의 내용으로는 경로를 못찾고, 쉘스크립트 실행시 인자(start 또는 stop)도 빠진듯 합니다.

      2013.02.14 00:01 신고 [ ADDR : EDIT/ DEL ]
  4. 해결했어요. 고맙습니다.

    2013.02.14 22:03 [ ADDR : EDIT/ DEL : REPLY ]
  5. 박지훈

    쉘 파일을 실행 후 10초 후에 종료 했는데
    daemon.out 파일에 값이 저장되지 않네요?
    왜 그런지 궁급해요 도와주세요~ ㅜㅜ

    2013.06.28 12:10 [ ADDR : EDIT/ DEL : REPLY ]
  6. 실행하신 소스와 스크립트를 보내주시면, 문제점을 확인해 보겠습니다. bage 7 9 @ g m a i l . c o m
    프로그램이 제대로 동작하지 않았거나, 파일 쓰기 권한이 없을 수도 있습니다.

    2013.06.29 02:00 신고 [ ADDR : EDIT/ DEL : REPLY ]
  7. 박지훈

    지금 소스 보내드렸어요 ㅜㅜ 해결 되었으면 좋겠네욤 ㅜㅜ

    2013.07.01 09:44 [ ADDR : EDIT/ DEL : REPLY ]
  8. 소스와 스크립트에 모두 문제점이 있었네요. (오타와 jar 생성 문제).
    수정해서 메일로 보내드렸습니다. ^^

    2013.07.05 11:34 신고 [ ADDR : EDIT/ DEL : REPLY ]
  9. 글 잘보고 따라 해보고 있는중인데 코드 컴파일할때 import에서 계속 에러가 뜹니다. 어떻게 해결할 수 있을 까요?
    초짜입니다 ㅜㅜ

    2014.07.04 13:24 [ ADDR : EDIT/ DEL : REPLY ]
    • 임포트 오류나는 내용을 이메일로 보내주시면 도와 드리겠습니다.
      아마 구글에서 에러메시지로 검색하셔도 import 오류는 해결하실 수 있을 것 같습니다.

      2014.07.17 02:49 신고 [ ADDR : EDIT/ DEL ]
  10. 안녕하세요 위에 소스대로 그대로 따라해보는 도중
    쉘스크립트 start 실행시키면
    21번째줄에서 Permission denied가 뜨면서 진행이 안되네요
    USER는 제 계정아이디와 root둘다 해봤고, su 계정으로 접근해도 동일한 오류가 나네요
    어떻게 해결해야될까요?

    2014.08.10 14:15 [ ADDR : EDIT/ DEL : REPLY ]
  11. chmod 777 /root/commons-daemon/jsvc 으로 실행권한을 준 후, 다시 해보세요.

    2014.08.22 09:09 신고 [ ADDR : EDIT/ DEL : REPLY ]