概要
goのWebappをOSがUbuntu 16.04 LTSのProduction環境で動かすための環境作りについてまとめています。フロントの処理をnginx,プロセス管理をcircusで行いそれぞれのメリットを享受できるようにしています。既にGOJIで対応されているケース等サンプルがありますが、net/httpのみのケースだと情報が点在していたのと、circusを入れるときにpythonの依存関係の問題ですんなり入らなかったのでそこら辺を参考にしていただければと思います。
Webアプリケーションのサンプル
ファイルディスクリプタをパラメータで渡している他、ない場合はTCPポート8080で起動できるようにもしています。
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]
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が返って来れば成功です。
参考
- 本家。
- 本家Github。
- GOJIで対応されているケース。参考にさせていただきました。
- 同様GOJIで対応されているケース。Circusの使い方等参考になりました。
- ピクシブ社内広告サーバーでのGoの開発・運用 #gocon /p_ads_server_gocon2015
- Golang_ads_deliver
- Requirements for Go server in production env
- 本番環境を作るという点や、その中でcircusをどう使うかという点で非常に参考になりました。