RaspberryPi에서 systemctl를 통한 데몬 서비스 등록

라즈베리파이에서 파이썬으로 작성한 데몬을 만들고 서비스를 등록할 일이 생겨 기록을 남겨본다.

1. 데몬 프로세스

데몬 프로세스는 PPID가 1(init)인 프로세스를 말하는 것으로 백그라운드에서 서비스를 제공하는 프로세스이다.
PPID가 1이라는 것은 서비스를 실행시킨 쉘이 종료되더라도 프로세스가 살아있다는 것을 의미한다.

데몬 프로세스를 만드는 법은 어렵지 않다.
위키에는 다음과 같이 나와있다.

  • 프로세스를 제어하고 있는 터미널로부터 분리한다. (fork호출, 부모 프로세스는 exit호출)
  • 프로세스를 세션 리더로 만든다.
  • 프로세스를 프로세스 그룹의 리더로 만든다. (setsid() 호출)
  • (한 번이나 두 번) 포크한 뒤 프로세스를 종료하여 자식 프로세스가 백그라운드에 남게 한다. 이 방법은 세션 리더를 만드는 데도 쓰이며, 부모 프로세스를 종료하지 않고 일반적인 작업을 수행할 수도 있다. 이 방법을 요약하여 ‘fork off and die’라고 한다.
  • 루트 디렉터리(“/”)를 현재 작업 디렉터리로 만든다. (chdir(“/”) 호출)
  • umask를 0으로 변경해서 호출한 쪽의 umask와 상관 없이 open(), creat() 등의 호출을 수행할 수 있도록 한다. (umask(0)호출)
  • 상속받았으며, 부모 프로세스가 열고 있는 파일들을 자식 프로세스에서 모두 닫는다. 여기에는 0, 1, 2번 파일 서술자(각각 stdin, stdout, stderr)도 포함된다.
  • 로그 파일이나 콘솔, 또는 /dev/null을 stdin, stdout, stderr로 설정한다.

무언가 어려워보이지만 실제 코드로는 그렇게 복잡하지도 않다.

import os, sys 
from datetime import datetime

if __name__ == '__main__':
    # First Fork
    pid = os.fork()
    if pid > 0:
        # Parent Dies
        exit(0)
    else:
        # Change Working Directory
        os.chdir('/')
        os.setsid()
        os.umask(0)

        # Second Fork
        pid = os.fork()
        if pid > 0:
            # Parent Dies
            exit(0)
        else:
            # Daemon Process
            sys.stdout.flush()
            sys.stderr.flush()

            si = open(os.devnull, 'r')
            so = open(os.devnull, 'a+')
            se = open(os.devnull, 'a+')

            os.dup2(si.fileno(), sys.stdin.fileno())
            os.dup2(so.fileno(), sys.stdout.fileno())
            os.dup2(se.fileno(), sys.stderr.fileno())

            # Process Begin
            proc = process()
            exit_code = proc.run()

            # Process End
            exit(exit_code)

물론 이것 말고도 python-daemon이라는 좋은 패키지가 있으니 사용하면 된다.

2. 서비스 등록

크게 어렵지 않다.
/etc/systemd/system에 서비스 파일을 등록하면 된다.
서비스 파일은 다음과 같이 작성한다.

[Unit]
Description=blah blah process
After=multi-user.target

[Service]
Type=forking
ExecStart=/usr/bin/python /home/hooni/daemon.py
Restart=always
WorkingDirectory=/home/hooni

[Install]
WantedBy=multi-user.target

그 이후에는 systemctl을 통해 서비스를 관리하면 된다.
키워드는 다음과 같다.

$ systemctl daemon-reload
$ systemctl enable my_daemon
$ systemctl start my_daemon
$ systemctl status my_daemon

댓글 남기기