Goji のアプリケーションサーバーを Circus で管理する

[Goji](https://github.com/zenazn/goji) には graceful shutdown は標準で用意されているのですが、 graceful restart がありません。
調べていたところ Circus というものを見つけたので、以下の記事を参考にやってみました。

[Go で書いたサーバーを管理するには circus が便利](http://methane.hatenablog.jp/entry/2013/11/11/Go_で書いたサーバーを管理するには_circus_が便利)


## Goji でアプリケーションサーバーを書く

まず、 Goji でシンプルなアプリケーションサーバーを書きます。

```go
package main

import (
	"fmt"
	"net/http"

	"github.com/zenazn/goji"
	"github.com/zenazn/goji/web"
)

func helloWorld(c web.C, w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello, world!")
}

func main() {
	goji.Get("/", helloWorld)
	goji.Serve()
}
```

Goji はデフォルトだと 8000 を使うので [HTTPie](https://github.com/jkbrzt/httpie) だと以下のようにリクエストを送れます。
(curl など他の HTTP クライアントでも構いません)

```bash
$ http GET :8000
HTTP/1.1 200 OK
Content-Length: 13
Content-Type: text/plain; charset=utf-8
Date: Mon, 14 Jul 2015 09:04:21 GMT

Hello, world!
```

Hello, world! が返ってきました。

## Circus 化する

Circus で管理するためにはコードに手を加える必要があります。 Socket の listen は Circus に任せて、アプリケーションは Circus から受け取ったファイルディスクリプタ (FD) を使うからです。

修正後の `main` は以下のようになります。

```go
func main() {
	goji.Get("/", hello)

	fd := flag.Uint("fd", 0, "File descriptor to listen and serve.")
	flag.Parse()

	if *fd != 0 {
		listener, err := net.FileListener(os.NewFile(uintptr(*fd), ""))
		if err != nil {
			panic(err)
		}
		goji.ServeListener(listener)
	} else {
		goji.Serve()
	}
}
```

`fd` フラグで Circus から FD を受け取ります。 Listener を作成したら `ServeListener()` に渡して listen を開始します。
`fd` を受け取らなかった場合は `Serve()` でデフォルト Listener を使用します。 (ポート 8000)

## Circus でサーバーを管理する

Circus の設定ファイルを書きます。

```ini
[circus]
statsd = 1

[watcher:webapp]
cmd = goji-with-circus --fd $(circus.sockets.web)
stop_signal = SIGINT
numprocesses = 1
use_sockets = True
copy_env = True

[socket:web]
host = 0.0.0.0
port = 8000
```

`cmd` が実行されるコマンドです。`fd` フラグに渡している `circus.sockets.web` が下の方で定義している `socket:web` になります。注意点として、 `stop_signal` に　`SIGINT` を設定しないと Goji は graceful shutdown しません。デフォルトでは `SIGTERM` が使われるためです。
では実行してみましょう。

```bash
$ circusd circus.ini 
2015-07-14 09:13:01 circus[3617] [INFO] Starting master on pid 3617
2015-07-14 09:13:01 circus[3617] [INFO] sockets started
2015-07-14 09:13:01 circus[3617] [INFO] Arbiter now waiting for commands
2015-07-14 09:13:01 circus[3617] [INFO] webapp started
2015/07/14 09:13:01.716297 Starting Goji on 0.0.0.0:8000
2015-07-14 09:13:01 circus[3617] [INFO] circusd-stats started
2015-07-14 09:13:01 circus[3621] [INFO] Starting the stats streamer
```

これで Circus を使う前と同じようにリクエストを送ることができます。

```bash
$ http GET :8000
HTTP/1.1 200 OK
Content-Length: 13
Content-Type: text/plain; charset=utf-8
Date: Tue, 14 Jul 2015 09:14:18 GMT

Hello, world! 
```

## Circus をモニターする

Circus には 2 つのコマンドラインツールが用意されています。そのひとつが `circus-top` です。

```bash
$ circus-top

Circus Top
------------------------------------------------------------------------------------
circus
   PID                      CPU (%)             MEMORY (%)          AGE (s)
  4245 (circusd-stats)       2.60                0.10                77.16
  3617 (circusd)             0.00                0.10                954.12
                             1.30 (avg)          0.20 (sum)          954.12 (older)

sockets
   ADDRESS                  HITS
  0.0.0.0:8000                 0
                               0 (sum)

webapp
   PID                      CPU (%)             MEMORY (%)          AGE (s)
  4244                       0.00                0.00                77.15
                             0.00 (avg)          0.00 (sum)          77.15 (older)

------------------------------------------------------------------------------------
```

名前のとおり `top` の Circus 版という位置付けです。プロセスの CPU 使用率などを見ることができます。

## Circus を操作する

もうひとつは `circusctl` です。こちらは Circus に対して様々な操作を行うことができます。

* watcher の一覧

    ```bash
    $ circusctl list
    circusd-stats,webapp
    ```

* watcher に属するプロセスの一覧

    ```bash
    $ circusctl list webapp
    4244
    ```

* watcher に属するプロセスの増減

    ```bash
    $ circusctl incr webapp
    ok
    $ circusctl list webapp
    4244,4735
    $ circusctl decr webapp
    ok
    $ circusctl list webapp
    4735
    ```

* お待ちかねの graceful restart

    ```bash
    $ circusctl reload
    ok
    ```

また、　`circusctl` には REPL も用意されています。

```bash
$ circusctl
circusctl 0.12.0
circusd-stats: active
webapp: active
(circusctl) list
circusd-stats,webapp
(circusctl) reload
ok
```

## 参考

今回のコードは GitHub にあります。

[tchssk/goji-with-circus](https://github.com/tchssk/goji-with-circus)