2013年3月17日日曜日

Go言語でWebサーバを立てる 第2回 Webサーバアプリをサービス化(daemon)する

こんにちは。scarvizです。

今回は前回作成したWebサーバアプリをサービス化してみようと思います。

今回もGo開発マシン(Ubuntu12.04)を使います。
Linuxでサービス化をするには、バックグラウンドプロセスで動作するようにdaemonを作成する必要があります。
基本的な流れは、C言語で作成するLinuxのdaemonと同じです。

下記の#4を参考にしました。
https://code.google.com/p/go/issues/detail?id=227




■daemon関数の作成
daemon関数は以下のように作成しました。

/*
daemon関数
*/
func daemon(nochdir, noclose int) int {
 var ret uintptr
 var err syscall.Errno

 // バックグラウンドプロセスにする
 // 子プロセスを生成し,親プロセスを終了する
 ret, _, err = syscall.Syscall(syscall.SYS_FORK, 0, 0, 0)
 if err != 0 {
  return -1
 }
 switch ret {
  case 0:
   // 子プロセスが生成できたらそのまま処理を続ける
   break
  default:
   // 親プロセスだとここで終了する
   os.Exit(0)
 }

 
 // 新しいセッションを生成(子プロセスがセッションリーダになる)
 pid, _ := syscall.Setsid()
 if pid == -1 {
  return -1
 }
 
 if nochdir == 0 {
  // カレントディレクトリの再設定。ここではルートにしておく
  os.Chdir("/")
 }
 
 // ファイルのパーミッションを再設定(必須ではない。オプション)
 syscall.Umask(0)

 if noclose == 0 {
  // 標準入出力先を/dev/nullファイルに変更して、すべて破棄する
  f, e := os.OpenFile("/dev/null", os.O_RDWR, 0)
  if e == nil {
   fd := int(f.Fd())
   syscall.Dup2(fd, int(os.Stdin.Fd()))
   syscall.Dup2(fd, int(os.Stdout.Fd()))
   syscall.Dup2(fd, int(os.Stderr.Fd()))
  }
 }

 return 0
}

親プロセスが端末とつながっているので、Syscall関数で端末とつながっていない子プロセスを生成し、親プロセスは終了させます。
子プロセス上でSetsid関数を呼び、新規にセッションを生成することで、子プロセスがセッションリーダになり、バックグラウンドプロセスとして有効になります。
子プロセスのカレントディレクトリがどこになるのか不明なため、Chdir関数で一度ルートに移動させます。
標準入出力先も不明になっているので、ここではDup2関数で/dev/nullファイルに変更し、すべて破棄するようにしています。


■daemon関数を組み込む
前回のコードにdaemon関数を組み込み、syscallをimportします。
daemonの実行はmain関数の初めにdaemon(0,0)で実行します。
importとmain関数は以下のようになります。
※Top関数やDisplay関数は前回を参照してください。

package main

import (
 "fmt"
 "html"
 "net/http"
 "os"
 "syscall"
)

func main() {
 // daemon実行
 errcd := daemon(0, 0)
 
 if errcd != 0 {
  fmt.Println("daemon err!!")
  os.Exit(1)
 }

 // ここでカレントディレクトリを実行ファイルのディレクトリにしておくと後々困らない
 // hogehogeを実行ファイルのディレクトリに置き換えてください
 // os.Chdir("/hogehoge")

 // topページ
 http.HandleFunc("/", Top)
 // displayページ
 http.HandleFunc("/disp", Display)

 // Webサーバを8080ポートで開始する
 err := http.ListenAndServe(":8080", nil)

 // エラーが発生した場合にここに到達する
 if err != nil {
  // deamon関数で端末と切り離しているので出力されない
  // エラー内容を確認するにはlogファイルを出力するなどの対応が必要
  // fmt.Println(err)
  os.Exit(2)
 }
}

deamon実行後に、Chdir関数をコメントアウトしている部分ですが、カレントディレクトリがルートになっているので、必要があれば変更してください。
htmlファイルを読み込むなどを後々すると思うので、実行ファイルのディレクトリに変更しておくと良いと思います。
コマンドラインパラメータからパスを取得できるようにしておくと便利ですね。

では、端末から実行してみましょう。
go runで実行するとキャンセル待ちをせず、すぐ端末を開放してくれたと思います。
下記にアクセスすると、ちゃんと「Welcome!!」と表示されていると思います。
http://localhost:8080/


■サービスを停止する
さて、実行が確認できたのはいいのですが、今度はCtrl+Cでキャンセルが出来ないので、停止が出来ません。
停止するにはマシン自体をシャットダウンするか、killコマンドを実行することで停止できます。

killコマンドでは指定したPIDを停止するコマンドなので、まずはPIDを調べます。
下記を実行してください。

ps aux

プロセス一覧が表示されたと思います。
ただ、どれが該当するものなのかが分かりにくいと思います。
下記コマンドで対象を絞りましょう。

ps aux | grep -w "go"

go runはコンパイルと実行を続けて実施するコマンドなので、実行ファイルを一時ファイルとして出力し、実行しているようです。
なので、下記のようなプロセス名(go-buildxxxxxxxxxのxは数字)になっているものが、現在実行しているGoのサービスになります。

/tmp/go-buildxxxxxxxxx/command-line-arguments/_obj/a.out

このプロセスのPIDを確認し、下記コマンドを実行してください。
xxxxxは対象サービスのPIDになります。

kill xxxxx

もう一度ブラウザから下記にアクセスするとエラーになると思います。
http://localhost:8080/

これで停止が確認できました。

■実行ファイルを作成する
実行ファイルを作成するにはgo buildを実行します。
今回のサービスのgoファイル名を「gowebserver.go」とします。
下記コマンドを実行してください。

go build gowebserver.go

カレントにgowebserverが作成されていると思います。
この実行ファイルを実行してみます。

./gowebserver

ブラウザから下記にアクセスすると「Welcome!!」が表示されるのを確認してください。
http://localhost:8080/

停止はさっきと同じように下記コマンドを実行しPIDを確認します。

ps aux

今度は一覧のプロセス名に「./gowebserver」と表示されているものがあると思います。
go buildで作成した実行ファイルから実行したので、ファイル名が表示されるようになりました。
このPIDを確認したら下記コマンドで停止します。
xxxxxは「./gowebserver」のPIDになります。

kill xxxxx

ブラウザから下記にアクセスするとエラーになると思います。
http://localhost:8080/


これでWebサーバアプリがサービス化できましたね。
次回はこの実行ファイル(gowebserver)をサーバマシンにサービス登録して、クライアントマシンから確認してみようと思います。

1 件のコメント:

  1. go言語初心者です。こちらの記事を参考に、ウェブサーバのデーモン化
    が出来ました。ありがとうございました。分かりやすかった。

    返信削除