ginとendressを使ってGraceful Restartを実装してみた
GolangのWeb Frameworkであるgin、"Zero downtime restarts"を行うためのendressを使って
タイトルにあるようにGraceful RestartするWebのサンプルを作ってみました。
https://github.com/gin-gonic/gin
https://github.com/fvbock/endless
サンプルソース
ソースは以下のようになります。
main.go
package main import ( "fmt" "log" "os/exec" "syscall" "time" "github.com/fvbock/endless" "github.com/gin-gonic/gin" ) func main() { router := gin.Default() router.GET("/hello", func(c *gin.Context) { t := time.Now() const layout = "2006-01-02 15:04:05" const version = "V1" message := fmt.Sprintf("Hello! Now is %s. This version is %s.", t.Format(layout), version) c.JSON(200, gin.H{"message": message}) }) router.GET("/restart", func(c *gin.Context) { pid, err := readPid() if err != nil { log.Fatal(err) } err = exec.Command("kill", "-SIGHUP", pid).Start() if err != nil { log.Fatal(err) } message := "accept restart. pid = " + pid c.JSON(200, gin.H{"message": message}) }) server := endless.NewServer("localhost:4430", router) server.BeforeBegin = func(add string) { pid := syscall.Getpid() err := writePid(pid) if err != nil { log.Fatal(err) } log.Printf("Actual pid is %d", pid) } err := server.ListenAndServe() if err != nil { log.Fatal(err) } }
pid.go
package main import ( "io/ioutil" "log" "os" "path/filepath" "strconv" ) func getPidFilePath() (string, error) { exe, err := os.Executable() if err != nil { return "", err } return filepath.Dir(exe) + "/pid", nil } func writePid(pid int) error { path, err := getPidFilePath() if err != nil { log.Fatal(err) } file, err := os.Create(path) if err != nil { log.Fatal(err) } defer file.Close() _, err = file.Write(([]byte)(strconv.Itoa(pid))) if err != nil { return err } return nil } func readPid() (string, error) { path, err := getPidFilePath() if err != nil { log.Fatal(err) } data, err := ioutil.ReadFile(path) if err != nil { return "", err } return string(data), nil }
挨拶と現在日時を返す/hello、(セキュリティ的には微妙ですが)Graceful Restartを行う/restartの2つのGetメソッドを持ちます。
やっていることはソースを見てもらえれば分かりやすいかとは思いますが、ざっと書くと
- ginでrouterを作り、Getメソッドを実装する
- そのrouterをendressに渡してserverとして起動する
ことを行っています。
起動時にはpid.go内のwritePid()にてプロセスIDをファイルに出力しておき、/restartが呼ばれた時にはreadPid()で読み込んで使用します。
動作確認
ビルドしてバイナリを起動すると以下のように出力されます。
[GIN-debug] GET /hello --> main.main.func1 (3 handlers) [GIN-debug] GET /restart --> main.main.func2 (3 handlers) 2019/06/03 22:03:51 Actual pid is 97
同時にpidファイルにプロセスIDが出力されるはずです。
別のターミナルを起動し、動作確認のためにいくつかのコマンドを実行します。
$ curl http://localhost:4430/hello {"message":"Hello! Now is 2019-06-03 22:05:02. This version is V1."} $ curl http://localhost:4430/restart {"message":"accept restart. pid = 97"} $ curl http://localhost:4430/hello {"message":"Hello! Now is 2019-06-03 22:06:15. This version is V1."} $ kill -SIGHUP 202 # 最初にバイナリを起動したターミナルにrestart後のプロセスIDが出力される $ kill -SIGINT 212
「kill -SIGHUP プロセスID」でGraceful Restartができます。ここで指定するプロセスIDは、バイナリの起動時にターミナルに出力された値です。
URLの/restartでもRestartできますが、実際は「kill -SIGHUP」を内部で呼び出しているだけです。
終了は「kill -SIGINT プロセスID」で行います。