golangでgracefull restartする


# 概要

シグナルとGracefull Shutdownを用いて、ホットデプロイ(Gracefull Restart)を行う仕組みを理解する

Gracefull Restartを実現するための仕組みに、Server::Starterというperlで書かれたソフトがあるらしいが、今回はそのGo版を使ってみる(githubがアーカイブされているけど)

# start_serverの概要

とりあえずstart_serverをインストール

go get github.com/lestrrat/go-server-starter/cmd/start_server

一旦役者を整理する

  • server_starter: マネージサーバー
  • server: APIサーバー

server_starterがserverを起動し、SIGHUPシグナルを受け取ったら,新たなserverプロセスをたちあげ、古いserverプロセスにSIGTERMシグナルを送ることでserverをシャットダウンする。

ただserverプロセスをブチッと切ると処理中のリクエストが死んでしまうのでこれをgracefull shutdownすることで全体としてgracefull restartを可能にしている。

server_starter  <- SIGHUP
\- old server <- SIGTERM
\- new server 
1
2
3

のような関係である

# Gracefull Shutdown

Gracefull Shutdownという概念があるらしく、以下の2点を満たしてプロセスを止めることをいう。

  • 新規のリクエストを受け付けなくする。
  • 処理中のリクエストを完了させる

これらの処理はGo1.8から標準パッケージに入ったようなので簡単に実装できる

# serverプログラム

package main

import (
	"context"
	"fmt"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/lestrrat/go-server-starter/listener"
)

func main() {
	signals := make(chan os.Signal, 1)
	signal.Notify(signals, syscall.SIGTERM)
	listeners, err := listener.ListenAll()
	if err != nil {
		panic(err)
	}
	server := http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			fmt.Fprintf(w, "start request\n")
			time.Sleep(3 * time.Second)
			fmt.Fprintf(w, "server pid: %d %v\n", os.Getpid(), os.Environ())
		}),
	}
	go server.Serve(listeners[0])
	<-signals
	server.Shutdown(context.Background())
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

server.Shutdown()で簡単にできる(https://golang.org/pkg/net/http/#Server.Shutdown)

# server_starterでserverを起動

start_server --port 8888 --pid-file app.pid -- ./server

starting new worker 98703
1

serverが立ち上がりました。

ps al

F   UID   PID  PPID PRI  NI    VSZ   RSS WCHAN  STAT TTY        TIME COMMAND
4     0     1     0  20   0   3868  1320 -      Ss+  pts/0      0:00 bash
4     0   621     0  20   0   3992  3048 -      Ss   pts/1      0:00 bash
0     0 98692   621  20   0 705900  2172 -      Sl+  pts/1      0:00 start_server --port 8888 --pid-file app.pid -- ./server
0     0 98703 98692  20   0 706396  3764 -      Sl+  pts/1      0:00 ./server
1
2
3
4
5

こんな感じでstart_serverがserverを起動しているのがわかる

SIGHUPシグナルを送ってみる

kill -HUP $(cat app.pid)

worker 98703 died unexpectedly with status 1, restarting
starting new worker 99890
1
2

古いserverプロセスが死んで、新たなserverプロセスが立ち上がっているのがわかります。

kill -TERM $(cat app.pid)

received TERM, sending TERM to all workers:99890
worker 99890 died, status:0
exiting
1
2
3

もちろんstart_server自体にSIGTERMを送るとすべて死ぬ