nginxでレスポンスをgzipで圧縮する
nginxでレスポンスをgzipで圧縮するための設定を調べた。
nginxでgzipモジュールを提供しているのでこれを利用する。設定例は以下の通り。
http { gzip on; gzip_comp_level 2; gzip_types text/plain text/css text/xml application/xml text/javascript application/javascript application/json; gzip_buffers 4 8k; }
クライアント側はリクエストヘッダーにAccept-Encoding
を含める必要がある。
なお、curlで--compressed
オプションを指定すると、-Hで以下のヘッダーを付与してくれる。
Accept-Encoding: deflate, gzip
レスポンスヘッダーにはContent-Encoding: gzip
が含まれる。
参考
Emacs × Go でタグジャンプ
helm-gtagsを使ってて、RubyとかPHP書いてたときにはうまくタグを作れてたんだけどGolangでうまく作れてなかった。以下の記事を見つけて試しにやってみたらうまくいった。
/usr/local/etc/gtags.conf
を上記の通り修正して以下のコマンドを実行する。
$ gtags -v --gtagslabel=pygments --debug --explain
logrotateのタイミングでsupervisordのプロセスが落ちる
結論から言うと、ドキュメントちゃんと読んでなかったおれが悪い。
例えば、nginxのログに対してlogrotateの設定ファイルを書くと、
/var/log/nginx/*log { create 0644 nginx nginx daily rotate 10 missingok notifempty compress postrotate [ -f /var/run/nginx.pid ] && kill -USR1 `cat /var/run/nginx.pid` endscript }
みたいな感じになる。nginxのプロセスに対してUSR1シグナルを送ることでログファイルを開き直していることがわかる。Rails書いていた時に使用していたUnicornでもたしか同じような設定だったと思う。
なので、supervisordのログに対して同じようなlogrotateの設定ファイルを書いていたら、ログローテーションは正しく行われずにsupervisordのプロセスだけ落ちて、supervisordが管理していた子プロセスはそのまま生きている(親はinit)という状態になってしまい、再起動するのにいちいちkillコマンドを実行しなくてはならなくなった。。。
原因だが、supervisordでログを開き直すのにはUSR2シグナルを送る必要があるのに、特に確認もせずにUSR1シグナルを送る記述をしていたためだった。ドキュメントにもちゃんと書いてある!
http://supervisord.org/running.html#signals
おそらくsupervisordでUSR1シグナルを受け取った場合の処理は何もしておらずデフォルトの動作となるためsueprvisordのプロセスだけ落ちてしまうという結果になったと思う。
まとめ
ちゃんとドキュメント読もう
Convert markdown to pdf
自分用メモ。 markdown-pdfを使用してmarkdownをpdfに変換する
node.jsの公式サイトからnpmをインストールする
markdown-pdfをインストールする
$ npm install markdown-pdf
適用したいCSSをgithubからcloneする
自分はmixu/markdown-stylesを使用した
$ git clone https://github.com/mixu/markdown-styles.git
markdown-pdfを実行する
$ markdown-pdf -o xxx.pdf -s markdown-styles/layouts/github/assets/css/github-markdown.css xxx.md
golangでpanicをrecoverしたときにスタックトレースを表示する
golangではそもそもpanicしないようにコードを書くべきだしpanicが発生するならここで発生するのだと把握した上でコードを書くべきだ、という認識はある。とはいえ、golang書き始めてまもないので「念のために」recoverをコードに書いておきたかった。
具体的なコードは以下の通り。
https://play.golang.org/p/CxiR4j5Q6F_a
package main import ( "fmt" "log" "runtime" ) func main() { log.SetFlags(log.LstdFlags | log.Lshortfile) defer func() { if err := recover(); err != nil { log.Printf("[ERROR] %s\n", err) for depth := 0; ; depth++ { _, file, line, ok := runtime.Caller(depth) if !ok { break } log.Printf("======> %d: %v:%d", depth, file, line) } } }() a := []int{1, 2} fmt.Println(a[2]) }
結果は以下の通り。
2009/11/10 23:00:00 main.go:13: [ERROR] runtime error: index out of range 2009/11/10 23:00:00 main.go:19: ======> 0: /tmp/sandbox464635953/main.go:15 2009/11/10 23:00:00 main.go:19: ======> 1: /usr/local/go/src/runtime/asm_amd64p32.s:472 2009/11/10 23:00:00 main.go:19: ======> 2: /usr/local/go/src/runtime/panic.go:491 2009/11/10 23:00:00 main.go:19: ======> 3: /usr/local/go/src/runtime/panic.go:28 2009/11/10 23:00:00 main.go:19: ======> 4: /tmp/sandbox464635953/main.go:25 2009/11/10 23:00:00 main.go:19: ======> 5: /usr/local/go/src/runtime/proc.go:195 2009/11/10 23:00:00 main.go:19: ======> 6: /usr/local/go/src/runtime/asm_amd64p32.s:1040
2017年を振り返る
2017年はWebエンジニア1年目の年だった。つらつらとやったことを振り返る。
最初の半年はRailsを書いていた。初めてのWeb開発だったので最初はさすがに覚えることが多く大変だったが、ビジネスロジックが中心だったので1ヶ月もするとだいたい慣れてきた。ビジネスロジックを単に実装しているだけだとフレームワークの昨日の暗記ゲームみたいな気がしてきた。半年くらいたって、プログラマーとしてもっと本質的なことを勉強しないといけないと思うようになった。ただこの時点では、本質的なことを学ぶといっても何が本質的なことかわからなかった。
7月くらいから数ヶ月、PHPを書いていた。既存サービスの改修。PHP書いたことは正直あまり印象に残っていないが、アプリケーション以外の部分も担当するようになった。開発環境が用意されてないからVagrantでスクリプト書いたり、Apache JMeterで簡単な負荷検証やったりした。この辺りから本やブログを片っ端から読むようになって、プログラマーとして本質的なことがぼんやり見えてきた気がした。とりあえずコンピュータサイエンスかなと。
9月。仕事ではまだPHPを書いていた。コンピュータサイエンスわからんことばっかりだったからとりあえずOSの教科書読もうと思って、Operating System Conceptsを読もうとした。英語で1000ページくらい、気合いで読んだ。OSがやってることがなんとなくわかるようになった。プロセスやらスケジューリングやら。HTTP以下のレイヤーもよくわからんと思って、ハイパフォーマンスブラウザネットワーキングを読んだ。こういった本を読むと、不思議なことに今まで書いたコードとの繋がりが少し見えてきて楽しくなってきた。一方で、本を読んでばかりだったのでなかなか上手いコードを書けるようにならないのが気がかりだった。
10月。GoでAPIを書くことになった。フレームワークを使用しないことになってログまわりやらミドルウェアやらを自分で書くことになった。これが楽しくて、今までフレームワークに任せていた部分を理解できている気がした。CIまわりを整備したりもした。開発のチームリーダー的なポジションとなったが、他に議論できる知り合いもいなかったので悩んだ時はOSSのコードをよく参考にするようになった。ちょっとコード書けるようになってきたかもと浮かれた。コンピュータサイエンスの勉強では、引き続きOSまわりを勉強していたがやはりコードに落とし込めないことが気がかりだった。アルゴリズムの勉強も始めた。
2018年は以下のことを軸に自分のリソースを割り当てたい。
- Goを書いていく
- コンピュータサイエンスを学ぶ
Goを書いていく
自分はまだ何も武器がないプログラマなので、Goの人、と言われるくらいになる。
コンピュータサイエンスを学ぶ
ここを勉強しておかないとプログラマとして死ぬ気がするのでやる。2018年前半はアルゴリズムを継続的にやる。後半はまた考える。
negroniの実装をちゃんと理解できていなかったので自分で書き直してみる
以前、ミドルウェアをうまく扱うパッケージとしてnegroniを紹介したが、改めてコードを読むとちゃんと理解できていなかったので自分で書き直してみる。車輪の再発明。GitHubに上げるまでもないのでここに残す。
GitHub - urfave/negroni: Idiomatic HTTP Middleware for Golang
package chain import ( "net/http" ) // Handler is an interface. type Handler interface { ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) } // HandlerFunc is an adapter to allow the use of ordinary functions as HTTP handlers. type HandlerFunc func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) // ServeHTTP simply calls f(w, r, next). func (f HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { f(w, r, next) } // Middleware is a stack of middlwares. type Middleware struct { handler Handler next *Middleware } // ServeHTTP calls ServeHTTP of next middleware. func (m Middleware) ServeHTTP(w http.ResponseWriter, r *http.Request) { m.handler.ServeHTTP(w, r, m.next.ServeHTTP) } // Wrap converts http.Handler into chain.Handler func Wrap(h http.Handler) Handler { return HandlerFunc(func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { h.ServeHTTP(w, r) next(w, r) }) } // New is the constructor of Middlewre func New(handlers ...Handler) Middleware { return *build(handlers) } func build(handlers []Handler) *Middleware { var next *Middleware if len(handlers) == 0 { return voidMiddleware() } else if len(handlers) > 1 { next = build(handlers[1:]) } else { next = voidMiddleware() } return &Middleware{handlers[0], next} } func voidMiddleware() *Middleware { return &Middleware{ handler: HandlerFunc(func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {}), next: &Middleware{}, } }
メモ。
使い方は以下の通り。
package main import ( "./chain" "net/http" ) func hello1(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { w.Write([]byte("Hello1\n")) next(w, r) } func hello2(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { w.Write([]byte("Hello2\n")) next(w, r) } func hello3(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { w.Write([]byte("Hello3\n")) next(w, r) } // MyHandler is a sample handler. type MyHandler struct{} // ServeHTTP greet to world. func (mh MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Hello, world!\n")) } func main() { n := chain.New( chain.HandlerFunc(hello1), chain.HandlerFunc(hello2), chain.HandlerFunc(hello3), chain.Wrap(MyHandler{}), ) http.ListenAndServe(":8888", n) }
参考
supervisorの起動スクリプト
supervisorの最新版をeasy_installでインストールしたら起動スクリプトがなかったので簡単なやつを書いた。centos6.7で /etc/init.d/supervisord として配置する。
#!/bin/sh # # supervisord - this script starts and stops supervisord # # chkconfig: 35 80 20 # description: Autostart supervisord. # Source function library. . /etc/rc.d/init.d/functions supervisorctl="/usr/bin/supervisorctl" supervisord="/usr/bin/supervisord" name="supervisor" [ -f $supervisord ] || exit 1 [ -f $supervisorctl ] || exit 1 RETVAL=0 start() { echo -n "Starting $name: " $supervisord -c /etc/supervisord.conf RETVAL=$? if [ $RETVAL -eq 0 ] then echo -e "SUCCESS" else echo -e "FAILED" fi return $RETVAL } stop() { echo -n "Stopping $name: " $supervisorctl shutdown RETVAL=$? if [ $RETVAL -eq 0 ] then echo -e "SUCCESS" else echo -e "FAILED" fi return $RETVAL } case "$1" in start) start ;; stop) stop ;; restart) stop start ;; status) $supervisorctl status ;; esac exit $RETVAL
参考
deferでのrecoverのよくわからない挙動がよくわからない
deferで呼び出した関数でrecoverした場合にはpanicは発生しない。
https://play.golang.org/p/Z0oJ2I26wq8
package main import "fmt" func main() { defer recoverFunc() panic("Panic happens!") } func recoverFunc() { if err := recover(); err != nil { fmt.Printf("Recover: %v¥n", err) } }
しかし、deferで呼び出した関数からさらに呼び出した関数でrecoverした場合にはpanicが発生する(recoverが働かない)。
https://play.golang.org/p/FTqz736dHD9
package main import "fmt" func main() { defer deferredFunc() panic("Panic happens!") } func deferredFunc() { recoverFunc() } func recoverFunc() { if err := recover(); err != nil { fmt.Printf("Recover: %v¥n", err) } }
直感的に後者の場合でもpanicが発生しないかと思ったんだけどな。
Goでgraceful restartをミニマムに書く
前回の記事でgraceful shutdownをミニマムに実装した。その続きでgraceful restartをミニマムに標準パッケージのみで実装してみる。
参考にしたのは以下のパッケージ。
実装
前回と同様、理解のために最低限の実装を行なっている。エラー処理もところどころ真面目にやっていない。
package main import ( "fmt" "log" "net" "net/http" "os" "os/exec" "os/signal" "strconv" "sync" "syscall" "time" ) // indexHandler ... func indexHandler(w http.ResponseWriter, r *http.Request) { time.Sleep(10 * time.Second) w.Write([]byte("Hello, middleware!\n")) } func main() { http.HandleFunc("/", indexHandler) srv := &http.Server{Addr: ":8888", Handler: http.DefaultServeMux} var wg sync.WaitGroup srv.ConnState = func(conn net.Conn, state http.ConnState) { switch state { case http.StateActive: log.Println("StateActive!!!") wg.Add(1) case http.StateIdle: log.Println("StateIdle!!!") wg.Done() } } if isMaster() { log.Printf("master pid: %d\n", os.Getpid()) laddr, _ := net.ResolveTCPAddr("tcp", "localhost:8888") l, _ := net.ListenTCP("tcp", laddr) log.Println(l) supervise(l) } log.Printf("worker pid: %d\n", os.Getpid()) fdStr := os.Getenv("__MASTER__") fd, _ := strconv.Atoi(fdStr) file := os.NewFile(uintptr(fd), "listen socket") defer file.Close() l, _ := net.FileListener(file) waitSignal(l) err := srv.Serve(l) wg.Wait() if err != nil { log.Fatal(err) } } func waitSignal(l net.Listener) { c := make(chan os.Signal, 1) signal.Notify(c, syscall.SIGINT) go func() { sig := <-c switch sig { case syscall.SIGINT: signal.Stop(c) l.Close() } }() } func isMaster() bool { return os.Getenv("__MASTER__") == "" } func supervise(l *net.TCPListener) { p, _ := forkExec(l) c := make(chan os.Signal, 1) signal.Notify(c, syscall.SIGUSR2) for { switch sig := <-c; sig { case syscall.SIGUSR2: new, _ := forkExec(l) p.Signal(syscall.SIGINT) p.Wait() p = new } } } func forkExec(l *net.TCPListener) (*os.Process, error) { progName, err := exec.LookPath(os.Args[0]) if err != nil { return nil, err } pwd, err := os.Getwd() if err != nil { return nil, err } f, err := l.File() if err != nil { return nil, err } defer f.Close() files := []*os.File{os.Stdin, os.Stdout, os.Stderr, f} fdEnv := fmt.Sprintf("%s=%d", "__MASTER__", len(files)-1) return os.StartProcess(progName, os.Args, &os.ProcAttr{ Dir: pwd, Env: append(os.Environ(), fdEnv), Files: files, }) }
動作確認すると、
// server $ go run test.go // server起動 // handlerを修正: "Hello, middleware!\n" => "Hello, middleware!!!!!!\n" // client1 $ curl localhost:8888 // server master process にUSR2シグナルを送る $ kill -USR2 xxxxx // client2 $ curl localhost:8888 // client1 $ curl localhost:8888 Hello, middleware! // 修正前のレスポンスが返る // client2 $ curl localhost:8888 Hello, middleware!!!!!! // 修正後のレスポンスが返る // server // 停止することなく動いたまま
感想
ポイントは
- 再起動のシグナルを受け取るmaster processと実際のサーバ処理を行うworker processを用意すること
- master processは最初に作成したリスナーをworker process作成時に渡し、masterとworkerでリスナーを共有すること
- master processは再起動のシグナルを受け取ったら新しいworker processを作成してから古いworker processにシグナルを送りgraceful shutdownをさせること
だろうか。ソケットまわりを勉強して「ソケットもファイルも同じインターフェースで処理できる」ということを知っていたが理解はしていなかった。しかし、実際に標準netパッケージのfunc (*TCPListener) Fileやfunc FileListenerを使用してリスナーとファイルの相互変換を実装したことで少しは理解できたかなと感じた。あ、リスナーとソケットは同じ意味くらいの認識でいます。 また、masterとworkerでソケットを共有するために、func StartProcessをうまく利用する実装方法を知ることができて勉強になった。
なお、masterとworkerでソケットを共有するというのは以下のブログにある図が非常にわかりやすかった。