nginx+circus+go(net/http利用）でwebアプリケーションの動作環境構築

# 概要
goのWebappをOSがUbuntu 16.04 LTSのProduction環境で動かすための環境作りについてまとめています。フロントの処理をnginx,プロセス管理をcircusで行いそれぞれのメリットを享受できるようにしています。既にGOJIで対応されているケース等サンプルがありますが、net/httpのみのケースだと情報が点在していたのと、circusを入れるときにpythonの依存関係の問題ですんなり入らなかったのでそこら辺を参考にしていただければと思います。

# Webアプリケーションのサンプル
ファイルディスクリプタをパラメータで渡している他、ない場合はTCPポート8080で起動できるようにもしています。

```main.go
package main

import (
    "encoding/json"
    "flag"
    "fmt"
    "log"
    "net"
    "net/http"
    "os"
    "os/signal"
    "syscall"
)

var port = flag.Uint("port", 8080, "port to listen")
var fd = flag.Uint("fd", 0, "File descriptor to listen and serve.")

//初期化処理
func init() {
    flag.Parse()
}

//メイン
func main() {

    sigchan := make(chan os.Signal)
    signal.Notify(sigchan, syscall.SIGTERM)
    signal.Notify(sigchan, syscall.SIGINT)

    var l net.Listener
    var err error

    if *fd == 0 {
        log.Println(fmt.Sprintf("listening on port %d", *port))
        l, err = net.ListenTCP("tcp", &net.TCPAddr{Port: int(*port)})
    } else {
        log.Println("listening on socket")
        l, err = net.FileListener(os.NewFile(uintptr(*fd), ""))
    }

    if err != nil {
        log.Fatal(err)
        panic(err)
    }

    go func() {
        mux := routes()
        log.Println(http.Serve(l, mux))
    }()

    <-sigchan
}

//ルーティング
func routes() (mux *http.ServeMux) {
    mux = http.NewServeMux()

    //実際のアプリでは複数ハンドラーを記載
    mux.HandleFunc("/somepass", SomeHandler)


    return
}

//サンプルJsonを出すための構造体（実際では意味のある構造体を作る方が良いです）
type V map[string]interface{}

//ハンドラーサンプル
func SomeHandler(w http.ResponseWriter, r *http.Request) {

    values := V{"testKey": "testValue"}

    content, err := json.Marshal(values)
    if err != nil {
        w.WriteHeader(http.StatusInternalServerError)
        return
    }

    w.Header().Add("Content-type", "application/json")

    w.WriteHeader(http.StatusOK)
    w.Write(content)
}

```

# nginxの設定
通常の手順通りにnginxをインストール後、UNIXドメインソケットに繋ぐように設定を変更します。

```
server {

  （中略）

  location / {
    proxy_pass http://backend_goweb;
  }

  （中略）
}

upstream backend_goweb {
    server unix:/var/run/goweb.sock;
}
```

# circusの設定

## インストール
```
apt-get install -y python-pip libzmq-dev libevent-dev python-dev python-virtualenv
pip install circus
pip install circus-web
```

## 設定ファイル作成
以下のような設定をcircus.iniとして作成。コマンド名、ログのパスは適宜変更してください。これでUNIXドメインソケットで起動することができる他、標準入力と標準出力がそのままログとして出せるようになります。

```circus.ini
[circus]
statsd = 1

[watcher:goweb]
cmd = /pass_to_go_application/application_name -fd $(circus.sockets.web)
stop_signal = SIGINT
numprocesses = 1
use_sockets = True
copy_env = True

stdout_stream.class = FileStream
stdout_stream.filename = /var/log/any_log_pass/stdout.log

stderr_stream.class = FileStream
stderr_stream.filename = /var/log/any_log_pass/stderr.log

[socket:web]
path = /var/run/goweb.sock
family = AF_UNIX
```

## 実行

```
# circusd circus.ini 
2018-03-22 15:46:58 circus[4092] [INFO] Starting master on pid 4092
2018-03-22 15:46:58 circus[4092] [INFO] sockets started
2018-03-22 15:46:58 circus[4092] [ERROR] exception __init__() takes exactly 3 arguments (4 given) caught
  File "/usr/local/lib/python2.7/dist-packages/tornado/gen.py", line 315, in wrapper
    yielded = next(result)
  File "/usr/local/lib/python2.7/dist-packages/circus/arbiter.py", line 529, in start
    self.ctrl.start()
  File "/usr/local/lib/python2.7/dist-packages/circus/controller.py", line 116, in start
    self.check_delay, self.loop)
```
上記のエラーが出て動きません…。

https://github.com/circus-tent/circus/issues/1059
ライブラリの依存関係で引っかかっているようなので、以下で回避。

```
# pip install tornado==4.5.3 circus
```

ソケットを削除して、再度トライ

```
# rm /var/run/goweb.sock
# circusd circus.ini 
2018-03-22 15:54:23 circus[10462] [INFO] Starting master on pid 10462
2018-03-22 15:54:23 circus[10462] [INFO] sockets started
2018-03-22 15:54:23 circus[10462] [INFO] Arbiter now waiting for commands
2018-03-22 15:54:23 circus[10462] [INFO] goweb started
2018-03-22 15:54:23 circus[10462] [INFO] circusd-stats started
2018-03-22 15:54:23 circus[10467] [INFO] Starting the stats streamer
```

無事起動しましたので、デーモンとして実行します。

```
# circusd --daemon circus.ini
```

再起動出来ることも確認します。

```
# circusctl restart 
ok
```

http://(your domain)/somepassで{"testKey":"testValue"}というJSONが返って来れば成功です。

#参考

- [Circus: A Process & Socket Manager](http://circus.readthedocs.io/en/latest/)
 + 本家。
- [[github]circus-tent/circus](https://github.com/circus-tent/circus)
 + 本家Github。
- [nginx+circus+gojiによるgolang webアプリケーションの動作環境構築](https://qiita.com/reiki4040/items/795a008d1b12ee657d9a)
 + GOJIで対応されているケース。参考にさせていただきました。
- [Goji のアプリケーションサーバーを Circus で管理する](https://qiita.com/tchssk/items/e9222facefb1eb049596)
 + 同様GOJIで対応されているケース。Circusの使い方等参考になりました。
- [ピクシブ社内広告サーバーでのGoの開発・運用 #gocon /p_ads_server_gocon2015](https://speakerdeck.com/catatsuy/p-ads-server-gocon2015)
- [Golang_ads_deliver](https://speakerdeck.com/catatsuy/golang-ads-deliver)
- [Requirements for Go server in production env](https://go-talks.appspot.com/github.com/yyoshiki41/go-sever-requirements/main.slide#1)
 + 本番環境を作るという点や、その中でcircusをどう使うかという点で非常に参考になりました。