ども、「ブログはショートコント」だが持論の初老丸です。

ショートコント…

Python スクリプトを Windows Server でサーヴィスとして動かしたい。

まずは

コントは起承転結で。

ひとまず、自分で作った Python スクリプトを Windows Server でサーヴィスとして登録する方法としては以下の二つを見つけた。

  • Windows でサーヴィスとして登録する
  • honcho を使ってみる(Windows 上で動くか不明確)

Python 製でプロセス監視ツールとして名高い Supervisord は Windows では動作しないようだ。

Supervisor works on just about everything except for Windows. It is tested and supported on Linux, Mac OS X, Solaris, and FreeBSD. It is written entirely in Python, so installation does not require a C compiler.

うむ。

Supervisord だと指定したプロセスが落ちていたら自動で再起動してくれるのが非常に有り難いのだけど、この機能も実装されていると嬉しいなあと思いつつ…

環境

ということで、各種参考記事に掲載されているサンプルを写経しながら以下の環境で試していく。

20160206202300

承(pywin32 を利用する Windows サービスとして登録する)

Python スクリプトをサービスとして登録したい場合には pywin32 を利用すると良いらしい。

参考

ジャパニーズだと以下の記事が参考になった。

初めに Pythonのスクリプトを書いていて、常駐化させたい時があります。*nix系だとpython-daemon 1.5.5 : Python Package Indexを使って実現できるようですが、 Windowsのサービスは探してもあまり情報が見当たらないので腹を据えて調べてみました。なんだかんだでw...

masahito.hatenablog.com

有難うございます。

pywin32 のインストール

pywin32 のインストールは pywin32 からダウンロードする。

20160207170803

Python のバージョンを合わせる必要があるので注意する。32bit 版と 64bit 版もあるので注意する。自分はここでスベった。

サービスとして利用するスクリプトサンプル

以下のように写経して動かしてみる

# -*- coding:utf-8 -*-

import win32serviceutil
import win32service
import win32event
import servicemanager
import socket
import time
import logging

logging.basicConfig(
    filename = 'c:\oreno-service.log',
    level = logging.DEBUG,
    format="%(asctime)s %(levelname)s %(message)s"
)

class OrenoSvc (win32serviceutil.ServiceFramework):
    _svc_name_ = "Oreno-Service01"
    _svc_display_name_ = "Oreno Service01"

    # Class の初期化
    def __init__(self,args):
        win32serviceutil.ServiceFramework.__init__(self,args)
        self.stop_event = win32event.CreateEvent(None,0,0,None)
        socket.setdefaulttimeout(60)
        self.stop_requested = False

    '''
     - サービス停止時に呼ばれるメソッド
      - win32event.SetEvent で win32event.CreateEvent(None,0,0,None) で作成したイベントハンドル(非シグナル)がセットされる
    '''
    def SvcStop(self):
        self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
        win32event.SetEvent(self.stop_event)
        logging.info('サービスを停止します ...')
        self.stop_requested = True

    '''
     - サービス開始時に呼ばれるメソッド
      - servicemanager.LogMsg でログを出力している
      - main() を呼ぶ
    '''
    def SvcDoRun(self):
        servicemanager.LogMsg(
            servicemanager.EVENTLOG_INFORMATION_TYPE,
            servicemanager.PYS_SERVICE_STARTED,
            (self._svc_name_,'')
        )
        self.main()

    '''
     - 以下に実際の処理を書いていく
    '''
    def main(self):
        logging.info('Oreno Service を開始します...')
        while True:
            if self.stop_requested:
                logging.info('停止シグナルをうけました: ループを停止します ...')
                break
            time.sleep(5)
            logging.info("コマネチ!! at %s" % time.ctime())
        return

if __name__ == '__main__':
    win32serviceutil.HandleCommandLine(OrenoSvc)

引数なしで実行すると以下のように出力される。

PS C:UsersAdministratorDocumentspython> python.exe .o-re-no-service.py
Usage: 'o-re-no-service.py [options] install|update|remove|start [...]|stop|restart [...]|debug [...]'
Options for 'install' and 'update' commands only:
 --username domainusername : The Username the service is to run under
 --password password : The password for the username
 --startup [manual|auto|disabled|delayed] : How the service starts, default = manual
 --interactive : Allow the service to interact with the desktop.
 --perfmonini file: .ini file to use for registering performance monitor data
 --perfmondll file: .dll file to use when querying the service for
   performance data, default = perfmondata.dll
Options for 'start' and 'stop' commands only:
 --wait seconds: Wait for the service to actually start or stop.
                 If you specify --wait with the 'stop' option, the service
                 and all dependent services will be stopped, each waiting
                 the specified period.

ふむふむ。

サービスの登録

サービスの登録は install オプションを利用する。

PS C:UsersAdministratorDocumentspython> python.exe .o-re-no-service.py install
Installing service Oreno-Service01
Service installed

以下のようにサービスが登録されたくさ。

20160207180407

サービスの開始

サービスの開始は start オプションを利用する。

PS C:UsersAdministratorDocumentspython> python.exe .o-re-no-service.py start
Starting service Oreno-Service01

以下のようにサービスが開始されたばい。

20160207180630

今回のサービスは 5 秒ごとにコマネチ!!をログに記録するので、ちゃんとコマネチ!!が記録されているかを確認する。

20160207181023

キタでコマネチ。

サービスの停止

サービスの停止は stop オプションを利用する。

PS C:UsersAdministratorDocumentspython> python.exe .o-re-no-service.py stop
Stopping service Oreno-Service01

以下の通りサービスは停止している。

20160207180407

画像は install 時の使い回しだが、ちゃんとサービスが停止している。

サービスの削除

サービスの削除は remove オプションを利用する。

PS C:UsersAdministratorDocumentspython> python.exe .o-re-no-service.py remove
Removing service Oreno-Service01
Service removed

転(honcho を使ってみる)

honcho とは

Honcho: a python clone of Foreman. For managing Procfile-based applications.

pypi.python.org

ドキュメントに記載されているように foreman の Clone として開発されているツール。foreman 同様に複数のプロセスを管理することが出来るライブラリ。

Windows サポートについては以下の通りマージされているようだ。

@nickstenning : Hi Nick! I am trying to revive this: #6You wrote then "If you can fix this (possibly by using signal and ignoring interrupts in the timer thread) I'll happily pull this in."==> H...

github.com

honcho インストール

pip で一発。

PS C:UsersAdministratorDocumentspython> pip install honcho
Collecting honcho
  Downloading honcho-0.6.6-py2.py3-none-any.whl
Collecting colorama (from honcho)
  Downloading colorama-0.3.6-py2.py3-none-any.whl
Installing collected packages: colorama, honcho
Successfully installed colorama-0.3.6 honcho-0.6.6
You are using pip version 7.1.2, however version 8.0.2 is available.
You should consider upgrading via the 'python -m pip install --upgrade pip' command.

サービスとして利用するサンプルスクリプト

Windows サービスに登録した際のスクリプトを流用。

# -*- coding:utf-8 -*-
import time
import logging

# Referred http://symfoware.blog68.fc2.com/blog-entry-883.html
stdout_log = logging.StreamHandler()
stdout_log.setLevel(logging.DEBUG)
stdout_log.setFormatter(logging.Formatter('%(asctime)s %(levelname)s %(message)s'))
file_log = logging.FileHandler(filename = 'c:\oreno-service.log')
file_log.setLevel(logging.DEBUG)
file_log.setFormatter(logging.Formatter('%(asctime)s %(levelname)s %(message)s'))
logging.getLogger().addHandler(stdout_log)
logging.getLogger().addHandler(file_log)
logging.getLogger().setLevel(logging.DEBUG)

class OrenoSvc:
    '''
     - 以下に実際の処理を書いていく
    '''
    def main(self):
        logging.info('Starting Oreno Service ...')
        try:
            while True:
                time.sleep(5)
                logging.info("Komanechi!! at %s" % time.ctime())
        except KeyboardInterrupt:
            logging.info('stop signal was received :  Stopping loop ...')

if __name__ == '__main__':
    ore = OrenoSvc()
    ore.main()

Procfile を用意する

foreman 同様に Procfile を作成する。

ore: python o-re-no-service02.py

これだけ。

サービスの開始

honcho.exe start でスタート。

PS C:UsersAdministratorDocumentspython> honcho.exe start
2016-02-07 09:50:09 [2304] [WARNING] Your terminal is not configured to receive UTF-8 encoded text. Please adjust your locale settings or force UTF-8 output by setting PYTHONIOENCODING="utf-8".
09:50:09 system | ore.1 started (pid=2796)

開始すると以下のようなコマンドプロンプトのウィンドウが起動する。

20160207185642

今回はログを stdout にも出力するようにしていたのでログが出力される。

また、run オプションもあるので start オプションとの違いについて確認する必要がある。

サービスの停止

サービスの停止は下図のようにコマンドプロンプトのウィンドウを閉じる。

20160207185818

ウィンドウを閉じると以下のようにコマンドプロンプト(今回は PowerShell 上)に出力される。

10:00:04 ore.1  | ^C
10:00:04 system | ore.1 stopped (rc=-1073741510)

ホントにこれしか方法が無いかは引き続き要調査。

動画デモ

こんな感じで動いている。

ということで…

Python スクリプトを Windows 上でサービスとして動かす方法を以下の二つの方法でコントしてみた。

  • pywin32 を利用してスクリプト自体は Windows サービスに登録する
  • 独自のプロセス管理ツール(honcho)を利用する

どっちが良いか

Windows なら特に理由がなければ Windows サービスへの登録の方が良さそうな気がする。理由は単純で Windows サービス管理下にある為、何か問題があった場合等は Event Viewer で状態を確認することが出来る(スクリプト内から Event ログを吐くことも当然出来る)為。但し、お手軽にサービスとして動かしたいとか、pywin32 の処理をスクリプトに含めるのが面倒等の場合には honcho でも良さそうな気がする。

あと、Windows サービスも honcho も Supervisord のようにプロセスが停止していたら叩き起こしてくれるような処理は独自に実装が必要なのは注意が必要。

以上。

元記事はこちら

自分で作った Python スクリプトを Windows Server にサーヴィスとして登録するショートコント(1)