понедельник, 4 февраля 2013 г.

Запуск скрипта Python как Daemon с добавлением в автозагрузку

ОС: Ubuntu.
Для примера "поднимем" web-сервер на Pyhton.
Определим директорию нашего проекта: /home/webserver/

Скрипт для запуска web-сервера /home/webserver/webserver.py
#!/usr/bin/python
#! _*_ coding: UTF-8 _*_
from BaseHTTPServer import BaseHTTPRequestHandler
from BaseHTTPServer import HTTPServer

class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.end_headers()
        self.wfile.write('Hello, word')
 
     
def start():
    server = HTTPServer(('',8082), Handler)
    server.serve_forever()

if __name__ == '__main__':
    start()

Сделаем данные файлы исполняемые командой:
sudo chmod 777 -R /home/webserver

Запустим скрипт
/home/webserver/webserver.py

Набрав в браузере: http://127.0.0.1:8082/ увидим: "Hello, word"
Однако стоит закрыть терминал и скрипт перестанет работать.
Создаем файл-заготовку для старта демона /home/webserver/daemon.py с содержимым:
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys, os, time, atexit
from signal import SIGTERM

class Daemon:
    """
    Родительский класс дeмона
    Использование: создайте подкласс и переопределите метод run()
    """
    def __init__(self, pidfile, stdin='/dev/null',  stdout='/dev/null', stderr='/dev/null'):
        self.stdin = stdin
        self.stdout = stdout
        self.stderr = stderr
        self.pidfile = pidfile
 
    def daemonize(self):
        try:
            pid = os.fork()
            if pid > 0:
                sys.exit(0)
        except OSError, e:
            sys.stderr.write("fork #1 failed: %d (%s)\n" % (e.errno, e.strerror))
            sys.exit(1)
 
        os.chdir(".")
        os.setsid()
        os.umask(0)
 
        # делаем второй fork
        try:
            pid = os.fork()
            if pid > 0:
                sys.exit(0)
        except OSError, e:
            sys.stderr.write("fork #2 failed: %d (%s)\n" % (e.errno, e.strerror))
            sys.exit(1)
 
        # перенаправление стандартного ввода/вывода
        sys.stdout.flush()
        sys.stderr.flush()
        si = file(self.stdin, 'r')
        so = file(self.stdout, 'a+')
        se = file(self.stderr, 'a+', 0)
        os.dup2(si.fileno(), sys.stdin.fileno())
        os.dup2(so.fileno(), sys.stdout.fileno())
        os.dup2(se.fileno(), sys.stderr.fileno())
 
        # записываем pidfile
        atexit.register(self.delpid)
        pid = str(os.getpid())
        file(self.pidfile,'w+').write("%s\n" % pid)
 
    def delpid(self):
        os.remove(self.pidfile)

    def start(self):
        """
        Запуск дeмона
        """
        # Проверяем pidfile, чтоб узнать не запущен ли уже процесс
        try:
            pf = file(self.pidfile,'r')
            pid = int(pf.read().strip())
            pf.close()
        except IOError:
            pid = None
 
        if pid:
            message = "pidfile %s already exist. Daemon already running?\n"
            sys.stderr.write(message % self.pidfile)
            sys.exit(1)
     
        # Запуск дeмона
        self.daemonize()
        self.run()

    def stop(self):
        """
        Остановка дeмона
        """
        # Берем pid из pidfile
        try:
            pf = file(self.pidfile,'r')
            pid = int(pf.read().strip())
            pf.close()
        except IOError:
            pid = None
 
        if not pid:
            message = "pidfile %s does not exist. Daemon not running?\n"
            sys.stderr.write(message % self.pidfile)
            return # не считается ошибкой при перезапуске

        # Убиваем процесс дeмона
        try:
            while 1:
                os.kill(pid, SIGTERM)
                time.sleep(0.1)
        except OSError, err:
            err = str(err)
            if err.find("No such process") > 0:
                if os.path.exists(self.pidfile):
                    os.remove(self.pidfile)
            else:
                print str(err)
                sys.exit(1)

    def restart(self):
        """
        Перезапуск дeмона
        """
        self.stop()
        self.start()

    def run(self):
        """
        Вы должны переопределить данный метод в подклассе. Он должен быть вызван
        после вызова метода daemonize() в методе start().
        """

Скрипт для запуска в качестве дeмона /home/webserver/webserverdaemon.py:
#!/usr/bin/env python
# -*- coding: utf8 -*-
import sys
import webserver
from daemon import Daemon

class MyDaemon(Daemon):
    def run(self):
        webserver.start()

if __name__ == "__main__":
    my_daemon = MyDaemon('/var/run/webserver.pid')

    if len(sys.argv) >= 2:
        if 'start' == sys.argv[1]:
            print 'starting webserver'
            my_daemon.start()
        elif 'stop' == sys.argv[1]:
            print 'stoping webserver'
            my_daemon.stop()
        elif 'restart' == sys.argv[1]:
            print 'restarting webserver'
            my_daemon.restart()
    else:
        print "Unknown command"
        sys.exit(2)
    sys.exit(0)


Стартовые скрипты при запуске Системы находятся в /etc/init.d
Создаем файл /etc/init.d/webserver со следующим содержимым:
#!/bin/sh
# Description: Starts Python scripts
### BEGIN INIT INFO
# Provides: Scripts
# Required-Start: $network $local_fs $syslog
# Required-Stop: $local_fs $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Description: Start Python scripts
### END INIT INFO

case $1 in
  start)
/home/webserver/webserverdaemon.py start
;;
  stop)
/home/webserver/webserverdaemon.py stop
;;
  restart)
/home/webserver/webserverdaemon.py restart
;;
  *)
 echo "Usage: scripts {start|stop|restart}"
exit 1
esac

Не забываем сделать файлы исполняемыми:
sudo chmod 777 -R /home/webserver
sudo chmod 777 /etc/init.d/webserver

Теперь управлять скриптом можно как дeмон командами:
sudo /etc/init.d/webserver start
sudo /etc/init.d/webserver restart
sudo /etc/init.d/webserver stop
Для того чтобы скрипт запускался с загрузкой системы введите команду:
sudo update-rc.d webserver defaults
Для того чтобы удалить скрипт из автозагрузки :
sudo update-rc.d webserver remove

3 комментария:

  1. Слово "Демон" пишется через "е".
    А классы сейчас попробую ))

    ОтветитьУдалить
  2. Исправил. Буду рад другим замечаниям.

    ОтветитьУдалить
  3. # Убиваем процесс дeмона
    try:
    while 1:
    os.kill(pid, SIGTERM)
    time.sleep(0.1)
    здесь висит. Добавил break

    try:
    while 1:
    print("try_kill")
    os.kill(pid, SIGTERM)
    time.sleep(0.1)
    break

    ОтветитьУдалить