How to create Celery Windows Service?

前端 未结 5 1771
鱼传尺愫
鱼传尺愫 2020-12-24 04:56

I\'m trying to create a Windows Service to launch Celery. I have come across an article that does it using Task Scheduler. However it seems to launch numerous celery instanc

相关标签:
5条回答
  • 2020-12-24 05:40

    A good project here but didn't succeed to use it : Link to the GitHub of the django-windows-tools. It gave me a timeout at the last command line. Doesn't have enough time to search why.

    The package allow settings FastCGI, Celery and Static files of a Django project on IIS.

    0 讨论(0)
  • 2020-12-24 05:49

    I got the answer from another website. Celeryd (daemon service for Celery) runs as a paster application, searching for 'Paster Windows Service' lead me here. It describes how to run a Pylons application as a Windows Service. Being new to paster framework and hosting python web services, it didn't cross my mind to check it at first. But that solution works for Celery with a slight change here and there in the script.

    I've modified the script to make it easier for modifying Celery settings. The essential changes are:

    1. Create an INI file with the settings for Celery service (shown below)
    2. Create a python script to create a Windows service.

    INI file settings (celeryd.ini):

    [celery:service]
    service_name = CeleryService
    service_display_name = Celery Service
    service_description = WSCGI Windows Celery Service
    service_logfile = celeryd.log
    

    Python script to create Windows Service (CeleryService.py):

    """
    The most basic (working) Windows service possible.
    Requires Mark Hammond's pywin32 package.  
    Most of the code was taken from a  CherryPy 2.2 example of how to set up a service
    """
    import pkg_resources
    import win32serviceutil
    from paste.script.serve import ServeCommand as Server
    import os, sys
    import ConfigParser
    
    import win32service
    import win32event
    
    SCRIPT_DIR          = os.path.abspath(os.path.dirname(__file__))
    INI_FILE            = 'celeryd.ini'
    SERV_SECTION        = 'celery:service'
    SERV_NAME           = 'service_name'
    SERV_DISPLAY_NAME   = 'service_display_name'
    SERV_DESC           = 'service_description'
    SERV_LOG_FILE       = 'service_logfile'
    SERV_APPLICATION    = 'celeryd'
    SERV_LOG_FILE_VAR   = 'CELERYD_LOG_FILE'
    
    # Default Values
    SERV_NAME_DEFAULT           = 'CeleryService'
    SERV_DISPLAY_NAME_DEFAULT   = 'Celery Service'
    SERV_DESC_DEFAULT           = 'WSCGI Windows Celery Service'
    SERV_LOG_FILE_DEFAULT       = r'D:\logs\celery.log'
    
    class DefaultSettings(object):
        def __init__(self):
            if SCRIPT_DIR:
                os.chdir(SCRIPT_DIR)
            # find the ini file
            self.ini = os.path.join(SCRIPT_DIR,INI_FILE)
            # create a config parser opject and populate it with the ini file
            c = ConfigParser.SafeConfigParser()
            c.read(self.ini)
            self.c = c
    
        def getDefaults(self):
            '''
            Check for and get the default settings
            '''
            if (
                (not self.c.has_section(SERV_SECTION)) or
                (not self.c.has_option(SERV_SECTION, SERV_NAME)) or
                (not self.c.has_option(SERV_SECTION, SERV_DISPLAY_NAME)) or
                (not self.c.has_option(SERV_SECTION, SERV_DESC)) or
                (not self.c.has_option(SERV_SECTION, SERV_LOG_FILE))
                ):
                print 'setting defaults'
                self.setDefaults()
            service_name = self.c.get(SERV_SECTION, SERV_NAME)
            service_display_name = self.c.get(SERV_SECTION, SERV_DISPLAY_NAME)
            service_description = self.c.get(SERV_SECTION, SERV_DESC)
            iniFile = self.ini
            service_logfile = self.c.get(SERV_SECTION, SERV_LOG_FILE)
            return service_name, service_display_name, service_description, iniFile, service_logfile
    
        def setDefaults(self):
            '''
            set and add the default setting to the ini file
            '''
            if not self.c.has_section(SERV_SECTION):
                self.c.add_section(SERV_SECTION)
            self.c.set(SERV_SECTION, SERV_NAME, SERV_NAME_DEFAULT)
            self.c.set(SERV_SECTION, SERV_DISPLAY_NAME, SERV_DISPLAY_NAME_DEFAULT)
            self.c.set(SERV_SECTION, SERV_DESC, SERV_DESC_DEFAULT)
            self.c.set(SERV_SECTION, SERV_LOG_FILE, SERV_LOG_FILE_DEFAULT)
            cfg = file(self.ini, 'wr')
            self.c.write(cfg)
            cfg.close()
            print '''
    you must set the celery:service section service_name, service_display_name,
    and service_description options to define the service 
    in the %s file
    ''' % self.ini
            sys.exit()
    
    
    class CeleryService(win32serviceutil.ServiceFramework):
        """NT Service."""
    
        d = DefaultSettings()
        service_name, service_display_name, service_description, iniFile, logFile = d.getDefaults()
    
        _svc_name_ = service_name
        _svc_display_name_ = service_display_name
        _svc_description_ = service_description
    
        def __init__(self, args):
            win32serviceutil.ServiceFramework.__init__(self, args)
            # create an event that SvcDoRun can wait on and SvcStop
            # can set.
            self.stop_event = win32event.CreateEvent(None, 0, 0, None)
    
        def SvcDoRun(self):
            os.chdir(SCRIPT_DIR)
            s = Server(SERV_APPLICATION)
            os.environ[SERV_LOG_FILE_VAR] = self.logFile
            s.run([self.iniFile])
            win32event.WaitForSingleObject(self.stop_event, win32event.INFINITE)
    
        def SvcStop(self):
            self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
            #win32event.SetEvent(self.stop_event)
            self.ReportServiceStatus(win32service.SERVICE_STOPPED)
            sys.exit()
    
    if __name__ == '__main__':
        win32serviceutil.HandleCommandLine(CeleryService)
    

    To install the service run python CeleryService.py install and then python CeleryService.py start to start the service. NOTE: These commands should be run in command-line with administrator rights.

    If the service needs to be removed, run python CeleryService.py remove.

    I was trying to host Celery as part of enhancing my RhodeCode installation. This solution seems to work. Hope this will help someone.

    0 讨论(0)
  • 2020-12-24 05:54

    @azalea 's answer helped me a lot, but one thing I like to highlight here is, the service (celery_service.py) needs to be installed with your user/password, otherwise, when you run subprocess.Popen(args) in SvcDoRun() function, nothing will happen as there will be a permission issue. To set the user/password, you can choose one of two methods:

    1. Using command line:

      python33 .\celeryService1.py --username .\USERNAME --password PASSWORD
      
    2. Go to Computer Management(local) > Services and Applications > Services, find your server (in @azalea's example, it is "Celery Distributed Task Queue Service"), and right click to open Properties page, input "This account" in Log On tab

    0 讨论(0)
  • 2020-12-24 05:54

    Thanks to Azalea as this led me to the path in being able to create 2 windows services with Celery 4 on Windows.

    One to be able to start/stop multiple workers T Second to be able to start/stop the beat service and tidy up the pid using Celery 4.

    Only caveat in this that I do not have a solution for is you can not restart the workers as you need to ensure the spawned processes for multiple are stopped before starting back up.

    Workers.py:

    '''Usage : python celery_service.py install (start / stop / remove)
    Run celery as a Windows service
    '''
    import win32service
    import win32serviceutil
    import win32api
    import win32con
    import win32event
    import subprocess
    import sys
    import os
    import shlex
    import logging
    import time
    
    # The directory for celery_worker.log and celery_worker_service.log
    # Default: the directory of this script
    INSTDIR = 'X:\Application\Project'
    LOGDIR = 'X:\Application\LogFiles'
    # The path of python Scripts
    # Usually it is in PYTHON_INSTALL_DIR/Scripts. e.g.
    # r'C:\Python27\Scripts'
    # If it is already in system PATH, then it can be set as ''
    PYTHONSCRIPTPATH = 'C:\Python36\Scripts'
    # The directory name of django project
    # Note: it is the directory at the same level of manage.py
    # not the parent directory
    PROJECTDIR = 'Project'
    
    logging.basicConfig(
        filename = os.path.join(LOGDIR, 'celery_worker_service.log'),
        level = logging.DEBUG, 
        format = '[%(asctime)-15s: %(levelname)-7.7s] %(message)s'
    )
    
    class CeleryService(win32serviceutil.ServiceFramework):
    
        _svc_name_ = "CeleryWorkers"
        _svc_display_name_ = "CeleryWorkers"
    
        def __init__(self, args): 
            win32serviceutil.ServiceFramework.__init__(self, args)
            self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
    
        def SvcStop(self):
            logging.info('Stopping {name} service ...'.format(name=self._svc_name_))        
            self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
            win32event.SetEvent(self.hWaitStop)
            logging.info('Stopped1 {name} service ...'.format(name=self._svc_name_))   
            logging.info('Stopped3 {name} service ...'.format(name=self._svc_name_)) 
            command = '"{celery_path}" -A {proj_dir} --workdir=X:/Application/Project control shutdown --timeout=10'.format(
            celery_path=os.path.join(PYTHONSCRIPTPATH, 'celery.exe'),
            proj_dir=PROJECTDIR,
            log_path=os.path.join(LOGDIR,'celery_worker.log'))
            logging.info('command: ' + command)
            args = shlex.split(command)
            proc = subprocess.Popen(args)
            logging.info('Stopped celery shutdown  ...') 
            self.ReportServiceStatus(win32service.SERVICE_STOPPED)
            logging.info('Stopped2 {name} service ...'.format(name=self._svc_name_))  
            sys.exit()           
    
        def SvcDoRun(self):
            logging.info('Starting {name} service ...'.format(name=self._svc_name_))
            os.chdir(INSTDIR) # so that proj worker can be found
            logging.info('cwd: ' + os.getcwd())
            self.ReportServiceStatus(win32service.SERVICE_RUNNING)
            command = '"{celery_path}" -A {proj_dir} -c 8 worker --workdir=X:/Application/Project --pidfile=celeryservice.pid  -f "{log_path}" -l info'.format(
                celery_path=os.path.join(PYTHONSCRIPTPATH, 'celery.exe'),
                proj_dir=PROJECTDIR,
                log_path=os.path.join(LOGDIR,'celery_worker.log'))
            logging.info('command: ' + command)
            args = shlex.split(command)
            proc = subprocess.Popen(args)
            logging.info('pid: {pid}'.format(pid=proc.pid))
            self.timeout = 3000
            while True:
                rc = win32event.WaitForSingleObject(self.hWaitStop, self.timeout)
                if rc == win32event.WAIT_OBJECT_0:
                    # stop signal encountered
                    # terminate process 'proc'
                    PROCESS_TERMINATE = 1
                    handle = win32api.OpenProcess(PROCESS_TERMINATE, False, proc.pid)
                    win32api.TerminateProcess(handle, -1)
                    win32api.CloseHandle(handle)                
                    break
    
    if __name__ == '__main__':
       win32serviceutil.HandleCommandLine(CeleryService)
    

    Beatservice.py:

    '''Usage : python celery_service.py install (start / stop / remove)
    Run celery as a Windows service
    '''
    import win32service
    import win32serviceutil
    import win32api
    import win32con
    import win32event
    import subprocess
    import sys
    import os
    import shlex
    import logging
    import time
    import signal
    
    # The directory for celery_beat.log and celery_beat_service.log
    # Default: the directory of this script
    INSTDIR = os.path.dirname(os.path.realpath(__file__))
    LOGPATH = 'X:\Application\Logs'
    # The path of python Scripts
    # Usually it is in PYTHON_INSTALL_DIR/Scripts. e.g.
    # r'C:\Python27\Scripts'
    # If it is already in system PATH, then it can be set as ''
    PYTHONSCRIPTPATH = 'C:\Python36\Scripts'
    # The directory name of django project
    # Note: it is the directory at the same level of manage.py
    # not the parent directory
    PROJECTDIR = 'PROJECT'
    
    logging.basicConfig(
        filename = os.path.join(LOGPATH, 'celery_beat_service.log'),
        level = logging.DEBUG, 
        format = '[%(asctime)-15s: %(levelname)-7.7s] %(message)s'
    )
    
    class CeleryService(win32serviceutil.ServiceFramework):
    
        _svc_name_ = "CeleryBeat"
        _svc_display_name_ = "CeleryBeat"
    
        def __init__(self, args):
            win32serviceutil.ServiceFramework.__init__(self, args)
            self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)           
    
        def SvcStop(self):
            logging.info('Stopping 1 {name} service ...'.format(name=self._svc_name_))        
            self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
            win32event.SetEvent(self.hWaitStop)
            pidno = open("X:\Aplication\Project\celerybeat.pid", "r")
            _pid_id_ = pidid=pidno.read()
            pidno.close()
            logging.info(_pid_id_)
            logging.info('taskkill /F /PID {pidid} ..'.format(pidid=_pid_id_))
            cmdcom = 'taskkill /F /PID {pidid}'.format(pidid=_pid_id_)
            logging.info(cmdcom)
            killargs = shlex.split(cmdcom)
            process = subprocess.Popen(killargs)
            output, error = process.communicate()
            logging.info(output)
            logging.info('Stopping 2 {name} service ...'.format(name=self._svc_name_))
            os.remove("X:\Application\PROJECT\celerybeat.pid")
            logging.info('X:\Application\PROJECT\celerybeat.pid  file removed')
            self.ReportServiceStatus(win32service.SERVICE_STOPPED)
            sys.exit()
    
        def SvcDoRun(self):
            logging.info('Starting {name} service ...'.format(name=self._svc_name_))
            os.chdir(INSTDIR) # so that proj worker can be found
            logging.info('cwd: ' + os.getcwd())
            self.ReportServiceStatus(win32service.SERVICE_RUNNING)
            command = '"{celery_path}" -A {proj_dir} beat --workdir=X:/Application/Project -f X:/Application/logs/beat.log -l info'.format(
                celery_path=os.path.join(PYTHONSCRIPTPATH, 'celery.exe'),
                proj_dir=PROJECTDIR,
                log_path=os.path.join(LOGPATH,'celery_beat.log'))
            logging.info('command: ' + command)
            args = shlex.split(command)
            proc = subprocess.Popen(args)
            logging.info('pid: {pid}'.format(pid=proc.pid))
            self.timeout = 3000
            while True:
                rc = win32event.WaitForSingleObject(self.hWaitStop, self.timeout)
                if rc == win32event.WAIT_OBJECT_0:
                    # stop signal encountered
                    # terminate process 'proc'
                    PROCESS_TERMINATE = 1
                    handle = win32api.OpenProcess(PROCESS_TERMINATE, False, proc.pid)
                    win32api.TerminateProcess(handle, -1)
                    win32api.CloseHandle(handle)                
                    break
    
    if __name__ == '__main__':
       win32serviceutil.HandleCommandLine(CeleryService)
    
    0 讨论(0)
  • 2020-12-24 05:58

    The accepted answer does not apply for running celery with a Django application. But it inspired me to come up with a solution for running celery as a Windows service with Django. Note that the following is for Django projects only. It may work with other applications with some modifications.

    The following discussion assumes Python >= 3.6 and RabbitMQ are already installed, and rabbitmq-server is running on localhost.

    Create a file celery_service.py (or whatever you like) inside your Django project's top level folder, same level as manage.py, with the following content:

    '''Usage : python celery_service.py install (start / stop / remove)
    Run celery as a Windows service
    '''
    import win32service
    import win32serviceutil
    import win32api
    import win32con
    import win32event
    import subprocess
    import sys
    import os
    from pathlib import Path
    import shlex
    import logging
    import time
    
    # The directory for celery.log and celery_service.log
    # Default: the directory of this script
    INSTDIR = Path(__file__).parent
    # The path of python Scripts
    # Usually it is in path_to/venv/Scripts.
    # If it is already in system PATH, then it can be set as ''
    PYTHONSCRIPTPATH = INSTDIR / 'venvcelery/Scripts'
    # The directory name of django project
    # Note: it is the directory at the same level of manage.py
    # not the parent directory
    PROJECTDIR = 'proj'
    
    logging.basicConfig(
        filename = INSTDIR / 'celery_service.log',
        level = logging.DEBUG, 
        format = '[%(asctime)-15s: %(levelname)-7.7s] %(message)s'
    )
    
    class CeleryService(win32serviceutil.ServiceFramework):
    
        _svc_name_ = "Celery"
        _svc_display_name_ = "Celery Distributed Task Queue Service"
    
        def __init__(self, args):
            win32serviceutil.ServiceFramework.__init__(self, args)
            self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)           
    
        def SvcStop(self):
            logging.info('Stopping {name} service ...'.format(name=self._svc_name_))        
            self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
            win32event.SetEvent(self.hWaitStop)
            self.ReportServiceStatus(win32service.SERVICE_STOPPED)
            sys.exit()           
    
        def SvcDoRun(self):
            logging.info('Starting {name} service ...'.format(name=self._svc_name_))
            os.chdir(INSTDIR) # so that proj worker can be found
            logging.info('cwd: ' + os.getcwd())
            self.ReportServiceStatus(win32service.SERVICE_RUNNING)
            command = '"{celery_path}" -A {proj_dir} worker -f "{log_path}" -l info -P eventlet'.format(
                celery_path=PYTHONSCRIPTPATH / 'celery.exe',
                proj_dir=PROJECTDIR,
                log_path=INSTDIR / 'celery.log')
            logging.info('command: ' + command)
            args = shlex.split(command)
            proc = subprocess.Popen(args)
            logging.info('pid: {pid}'.format(pid=proc.pid))
            self.timeout = 3000
            while True:
                rc = win32event.WaitForSingleObject(self.hWaitStop, self.timeout)
                if rc == win32event.WAIT_OBJECT_0:
                    # stop signal encountered
                    # terminate process 'proc'
                    PROCESS_TERMINATE = 1
                    handle = win32api.OpenProcess(PROCESS_TERMINATE, False, proc.pid)
                    win32api.TerminateProcess(handle, -1)
                    win32api.CloseHandle(handle)                
                    break
    
    if __name__ == '__main__':
       win32serviceutil.HandleCommandLine(CeleryService)
    

    Before the script can be run, you need to

    1. Optionally create a python virtual environment e.g. 'venvcelery'.

    2. Install the following requirements:

      django>=2.0.0 sqlalchemy>=1.0.14 celery>=4.3.0,<5.0 pywin32>=227 eventlet>=0.25

    3. Fix pywin32 pywintypes36.dll location. ref

    4. Correctly set PYTHONSCRIPTPATH and PROJECTDIR in celery_service.py

    PYTHONSCRIPTPATH is usually the "Scripts" folder under your python's installation path or current virtual environment

    PROJECTDIR is the directory name of the Django project.

    It is the directory at the same level of manage.py, not the parent directory.

    Now you can install / start / stop / remove the service with:

    python celery_service.py install
    python celery_service.py start
    python celery_service.py stop
    python celery_service.py remove
    

    I created a demo Django project with celery running as a Windows service:

    https://github.com/azalea/django_celery_windows_service

    In case you are interested in a running example.


    Note: this is an updated version assuming Python >= 3.6, Django 2.2 and Celery 4.

    An older version with Python 2.7, Django 1.6 and Celery 3 can be viewed in edit history.

    0 讨论(0)
提交回复
热议问题