12.3 アプリケーションのデプロイ

プログラムの開発が完了したら、Webアプリケーションをデプロイする必要があります。しかし、これらのプログラムはどのようにしてデプロイするのでしょうか?Goプログラムがコンパイルされた後は実行可能なファイルになりますので、Cプログラムを書いたことのある読者であればおそらくdaemonを採用することで完璧にプログラムをバックグラウンドで継続して実行できることを知っておられると思います。しかし、現在Goは完全にdaemonを実現することはできません。そのため、Goのアプリケーションプログラムをデプロイするにあたって、サードパーティのツールを使って管理することができます。サードパーティのツールにはいくつかあります。たとえば、Supervisord、upstart、daemontools等です。この節では現在自分のシステムにおいて採用しているツール、Supervisordをご紹介したいと思います。

daemon

現在Goプログラムではdaemonを実装することはまだできません。このGo言語のbugについての詳細は: `http://code.google.com/p/go/issues/detail?id=227` をご参照ください。かいつまんで言うと現在使用しているプロセスにおいてforkすることはとても難しいということです。簡単にすでに使用されているすべてのプロセスの状態を一致させる方法がないためです。

しかし、多くのウェブサイトでdaemonを実装する方法について見ることができます。例えば以下の2つの方法です:

  • MarGoの実装思想の一つで、Commandを使用して自身のアプリケーションを実行します。もし本当に実装したい場合、このソリューションをおすすめします。

      d := flag.Bool("d", false, "Whether or not to launch in the background(like a daemon)")
      if *d {
          cmd := exec.Command(os.Args[0],
              "-close-fds",
              "-addr", *addr,
              "-call", *call,
          )
          serr, err := cmd.StderrPipe()
          if err != nil {
              log.Fatalln(err)
          }
          err = cmd.Start()
          if err != nil {
              log.Fatalln(err)
          }
          s, err := ioutil.ReadAll(serr)
          s = bytes.TrimSpace(s)
          if bytes.HasPrefix(s, []byte("addr: ")) {
              fmt.Println(string(s))
              cmd.Process.Release()
          } else {
              log.Printf("unexpected response from MarGo: `%s` error: `%v`\n", s, err)
              cmd.Process.Kill()
          }
      }
    
  • もう一つはsyscallを利用したソリューションです。しかしこのソリューションは完全ではありません:

      package main
    
      import (
          "log"
          "os"
          "syscall"
      )
    
      func daemon(nochdir, noclose int) int {
          var ret, ret2 uintptr
          var err uintptr
    
          darwin := syscall.OS == "darwin"
    
          // already a daemon
          if syscall.Getppid() == 1 {
              return 0
          }
    
          // fork off the parent process
          ret, ret2, err = syscall.RawSyscall(syscall.SYS_FORK, 0, 0, 0)
          if err != 0 {
              return -1
          }
    
          // failure
          if ret2 < 0 {
              os.Exit(-1)
          }
    
          // handle exception for darwin
          if darwin && ret2 == 1 {
              ret = 0
          }
    
          // if we got a good PID, then we call exit the parent process.
          if ret > 0 {
              os.Exit(0)
          }
    
          /* Change the file mode mask */
          _ = syscall.Umask(0)
    
          // create a new SID for the child process
          s_ret, s_errno := syscall.Setsid()
          if s_errno != 0 {
              log.Printf("Error: syscall.Setsid errno: %d", s_errno)
          }
          if s_ret < 0 {
              return -1
          }
    
          if nochdir == 0 {
              os.Chdir("/")
          }
    
          if noclose == 0 {
              f, e := os.OpenFile("/dev/null", os.O_RDWR, 0)
              if e == nil {
                  fd := f.Fd()
                  syscall.Dup2(fd, os.Stdin.Fd())
                  syscall.Dup2(fd, os.Stdout.Fd())
                  syscall.Dup2(fd, os.Stderr.Fd())
              }
          }
    
          return 0
      }    
    

上ではGoで実装する二種類のdaemonのソリューションをご紹介しました。しかし、このように皆さんが実装するのはやはりおすすめしません。なぜなら、オフィシャルではまだ正式にdaemonのサポートが宣言されていないからです。当然、一つめのソリューションは今のところまだマシに見えますし、実際現在オープンソースリポジトリskynetではこの方法によってdaemonを採用しています。

Supervisord

上ではGoが現在二種類のソリューションによってdaemonを実装していることをご紹介しました。しかしオフィシャルではまだサポートしていませんので、みなさんにおかれましてはサードパーティの成熟したツールを使って我々のアプリケーション・プログラムを管理することを提案します。supervisordはあなたが管理するアプリケーションプログラムをdaemonプログラムにする事を助け、コマンドを通して簡単にスタート、ストップ、リスタートといった操作を行うことができます。また、管理されているプロセスが一旦崩壊すると自動的に再起動を行うので、プログラムが実行中に中断した場合の自己修復機能を保証することができます。

私は前にアプリケーションで地雷を踏んだことがあります。全てのアプリケーション・プログラムがSupervisordの親プロセスから生成されているため、オペレーティングシステムのファイルディスクリプタを修正した時には忘れずにSupervisordを再起動してください。その下のアプリケーションを再起動しただけではダメです。当初私はOSをインストールしたらまずSupervisordをインストールし、プログラムのデプロイを行い、ファイルディスクリプタを修正して、プログラムを再起動していました。ファイルディスクリプタなんか100000個もあるだろうと思い込んでいたのです。実はSupervisordにはこの時デフォルトの1024個しか用意されていませんでした。結果管理されていたプログラムを含むファイルディスクリプタも全部で1024個しか無く、開放した途端圧力が一気に膨れ上がりOSがファイルディスクリプタを使い切った事でエラーを吐き始めたのです。長い時間をかけてやっとこの地雷を見つけました。

Supervisordのインストール

Supervisordはsudo easy_install supervisorでインストールすることができます。当然Supervisordのオフィシャルサイトでダウンロードし、解凍してソースコードのあるディレクトリでsetup.py installを実行してインストールすることもできます。

  • easy_installを使用する場合は必ずsetuptoolsをインストールする必要があります

    http://pypi.python.org/pypi/setuptools#filesを開きます。あなたのシステムのpythonのバージョンに従って対応するファイルをダウンロードし、sh setuptoolsxxxx.eggを実行します。これによりeasy_installコマンドでSupervisordをインストールすることができます。

Supervisordの設定

Supervisordのデフォルトの設定ファイルのパスは/etc/supervisord.confです。テキストエディタを使ってこのファイルを修正します。以下は設定ファイルの例です:

;/etc/supervisord.conf
[unix_http_server]
file = /var/run/supervisord.sock
chmod = 0777
chown= root:root

[inet_http_server]
# Web管理インターフェース設定
port=9001
username = admin
password = yourpassword

[supervisorctl]
; 必ず'unix_http_server'の設定と合わせる必要があります。
serverurl = unix:///var/run/supervisord.sock

[supervisord]
logfile=/var/log/supervisord/supervisord.log ; (main log file;default $CWD/supervisord.log)
logfile_maxbytes=50MB       ; (max main logfile bytes b4 rotation;default 50MB)
logfile_backups=10          ; (num of main logfile rotation backups;default 10)
loglevel=info               ; (log level;default info; others: debug,warn,trace)
pidfile=/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid)
nodaemon=true              ; (start in foreground if true;default false)
minfds=1024                 ; (min. avail startup file descriptors;default 1024)
minprocs=200                ; (min. avail process descriptors;default 200)
user=root                 ; (default is current user, required if root)
childlogdir=/var/log/supervisord/            ; ('AUTO' child log dir, default $TEMP)

[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

; 管理する単一のプロセスの設定。いくつもprogramを追加することができます。
[program:blogdemon]
command=/data/blog/blogdemon
autostart = true
startsecs = 5
user = root
redirect_stderr = true
stdout_logfile = /var/log/supervisord/blogdemon.log

Supervisordの管理

Supervisordをインストールするとsupervisorとsupervisorctlという2つのコマンドが使えるようになります。以下ではコマンドの説明を行います:

  • supervisord、Supervisordを初期化し起動します。コンフィグの中で設定されたプロセスを起動、管理します。
  • supervisorctl stop programxxx、プロセス(programxxx)を停止します。programxxxは[program:blogdemon]の中で設定された値です。この例ではblogdemonになります。
  • supervisorctl start programxxx、プロセスを起動します。
  • supervisorctl restart programxxx、プロセスを再起動します。
  • supervisorctl stop all、すべてのプロセスを停止します。注:start、restart、stopは最新の設定ファイルを読み込みません。
  • supervisorctl reload、最新の設定ファイルを読み込み、新しい設定に沿ってすべてのプロセスを起動、管理します。

まとめ

この節ではGoがどのようにdaemon化を実現しているのかについてご紹介しました。ただ現在Goのdaemon実装は不足しており、サードパーティのツールによるアプリケーションプログラムのdaemon管理を行う方法に頼る必要があります。そのためここではpythonで書かれたプロセス管理ツールSupervisordをご紹介しました。Supervisordを使って簡単にGoアプリケーションプログラムを管理することができます。

results matching ""

    No results matching ""