Slide 1

Slide 1 text

作ってよかったgraceful shutdownライブラリ ne-sachirou/go-graceful

Slide 2

Slide 2 text

.。oO(さっちゃんですよヾ(〃l _ l)ノ゙☆)

Slide 3

Slide 3 text

.。oO(さっちゃんですよヾ(〃l _ l)ノ゙☆) id:ne-sachirou はてなで、監視SaaSのMackerelを作ってゐます

Slide 4

Slide 4 text

Goサブ会標準化分科會 (※先程stefafafanから紹介が在ったので、そちらを御覽ください) はてなでは(Mackerelでも)Goを(も)使ってapplicationを開發・運用してゐる 開發・運用チームでのGoの利用をsupportする仕組みに、Goサブ會・Goサブ會標準化 分科會が在る

Slide 5

Slide 5 text

Goサブ会標準化分科會 ● Goサブ會 ○ 社内のエンジニアの有志が參加する ○ Goに關して、動向を追ったり、相談する ○ 毎週30分 ● Goサブ會標準化分科會 ○ Goサブ會の中から更に有志が參加する ○ Goサブ會の實働部隊 ■ libraryを調査・開發したり ● 最近はboilerplateを減らそうと頑張ってゐる ■ guidelineを考へたり

Slide 6

Slide 6 text

Go サブ會 標準化 分科會 開發・運用 チーム 開發・運用 チーム 開發・運用 チーム 知識共有 相談 實働部隊 參加

Slide 7

Slide 7 text

graceful shutdown processが、處理中の狀態をうまい具合に扱ってから終了するやり方

Slide 8

Slide 8 text

用語 ● process ○ OSのprocess ● server ○ 目的とする處理を行ふもの ■ mainから直接起動したい go routineが だいたい相當する ○ 複數在りうる ● server manager ○ 起動や終了等・serverの狀態を管理する gracefulな起動・終了 server manager server server process

Slide 9

Slide 9 text

gracefulな起動・終了 gracefulな起動 ● 全てのserverがreadyになるのを待つ ● serverが一つでも起動に失敗すれば、processを終了させる ○ (※もっと柔軟にやりたい時はある …) gracefulな終了 ● 全てのserverが終了前處理を終へるのを待つ ○ HTTP serverであれば、新しくrequestを受け附けないやうにし、處理中の requestが終了する迄待 つ ○ 常駐processであれば、狀態を保存する ● 但し、一定時閒でtimeoutし強制終了する

Slide 10

Slide 10 text

gracefulな起動・終了 gracefulな起動 gracefulな終了

Slide 11

Slide 11 text

Goでのgracefulな起動・終了 gracefulな起動 ctx := context.Background() ctx, stop := signal.NotifyContext(ctx, os.Interrupt) // SIGINTをtrapする defer stop() ctx, cancel := context.WithCancelCause(ctx) // google.golang.org/grpc の例 srv2 := grpc.NewServer() go func(ctx context.Context) { // 複數のserverを起動する爲にgo routineを分離する listener, err := net.Listen("tcp", ":0") if err != nil { cancel(err) // 起動に失敗したらprocessを終了する return } if err := srv2.Serve(listener); err != nil { cancel(err) // 起動に失敗したらprocessを終了する } }(ctx)

Slide 12

Slide 12 text

Goでのgracefulな起動・終了 gracefulな終了 <-ctx.Done() // 終了signal、或いは起動失敗を待つ if err := context.Cause(ctx); err != nil && !errors.Is(err, context.Canceled) { // 起動に失敗したら異常終了する log.Fatalln(err.Error()) } ctx, cancelT := context.WithTimeout(context.Background(), 10 * time.Second) // timeout時刻を過ぎたら強制終了する。 Contextは作り直さねばならない defer cancelT() var wg sync.WaitGroup wg.Add(1) go func(ctx context.Context) { // 複數のserverをparallelに終了させる defer wg.Done() stopped := make(chan struct{}) go func() { srv2.GracefulStop() close(stopped) }() select { case <-stopped: case <-ctx.Done(): // timeoutしたら強制終了する } }(ctx) wg.Wait() // 全てのserverが終了するのを待つ if err := context.Cause(ctx); err != nil && !errors.Is(err, context.Canceled) { // graceful shutdownに成功したら正常終了、失敗したら異常終了する log.Fatalln(err.Error()) }

Slide 13

Slide 13 text

Goでのgracefulな起動・終了 gracefulな終了 <-ctx.Done() // 終了signal、或いは起動失敗を待つ if err := context.Cause(ctx); err != nil && !errors.Is(err, context.Canceled) { // 起動に失敗したら異常終了する log.Fatalln(err.Error()) } ctx, cancelT := context.WithTimeout(context.Background(), 10 * time.Second) // timeout時刻を過ぎたら強制終了する。 Contextは作り直さねばならない defer cancelT() var wg sync.WaitGroup wg.Add(1) go func(ctx context.Context) { // 複數のserverをparallelに終了させる defer wg.Done() stopped := make(chan struct{}) go func() { srv2.GracefulStop() close(stopped) }() select { case <-stopped: case <-ctx.Done(): // timeoutしたら強制終了する } }(ctx) wg.Wait() // 全てのserverが終了するのを待つ if err := context.Cause(ctx); err != nil && !errors.Is(err, context.Canceled) { // graceful shutdownに成功したら正常終了、失敗したら異常終了する log.Fatalln(err.Error()) } やればできるが、code記述量が多い

Slide 14

Slide 14 text

Goでのgracefulな起動・終了 ● 決まり切ったcode(boilerplate)を每囘長々と書かねばならない ○ ○ codeをコピペするが、微妙な差異が生まれてゆく ■ コピペ元によって品質が變はりうる ■ コピペしたcodeがよいものなのか、每囘調査せねばならない … ○ 改善が要る時も、コピペした codeを全部改善して囘るのは、多分無理 ● 「同じ問題」が發生してゐる

Slide 15

Slide 15 text

「同じ問題」が發生してゐる ● 踏みやすいbugを避けたい ○ http.ErrServerClosed等、特定のerrorを無視しないといけない ○ DoneしたContextを再利用してはいけない ○ 終了signalをtrapし忘れる ○ shutdownのtimeoutを設定し忘れる ○ 常に正常終了する | 常に異常終了する ● 複數のserverを起動したい ○ 一部のserverが起動に失敗したまま processが動き續けて欲しくない ○ timeout期限內でparallelに終了させたい ● 任意のserverを扱ひたい ● net/httpやgRPC等、よく使ふserverは簡單に使ひたい

Slide 16

Slide 16 text

「同じ問題」が發生してゐる ● libraryを入れればよい ○ cf. Libraryを作ると云ふ開発手法 https://speakerdeck.com/ne_sachirou/librarywozuo-rutoyun-hukai-fa-shou-fa ● 既存のsolutionは、 ○ net/http專用だったり、複數の serverを扱へなかったり… ○ 作ろう ■ Goサブ會標準化分科會の出番

Slide 17

Slide 17 text

コピペcodeを纏めたものを作った ne-sachirou/go-graceful: graceful shutdown for Go https://github.com/ne-sachirou/go-graceful

Slide 18

Slide 18 text

任意のserverを扱ふ場合 type ExampleBlockingServer struct{} // Serve methodとShutdown methodを實裝する func (s *ExampleBlockingServer) Serve(ctx context.Context) error { for { select { case <-ctx.Done(): return nil case <-time.After(time.Second): } } } func (s *ExampleBlockingServer) Shutdown(ctx context.Context) error { return nil } func main() { ctx := context.Background() srv := graceful.Servers{Servers: []graceful.Server{&ExampleBlockingServer{}}} // 複數のserverを扱へる if err := srv.Graceful(ctx, graceful.GracefulShutdownTimeout(time.Second)); err != nil { // 起動もしくは終了に失敗したら異常終了する panic(err) } }

Slide 19

Slide 19 text

單一のHTTP serverを扱ふ場合 ctx := context.Background() mux := http.NewServeMux() if err := gracefulhttp.ListenAndServe( // ListenAndServe! ctx, ":8000", mux, graceful.GracefulShutdownTimeout(time.Second), ); err != nil { panic(err) }

Slide 20

Slide 20 text

單一のgRPC serverを扱ふ場合 ctx := context.Background() opts := []grpc.ServerOption{} srv := grpc.NewServer(opts...) reflection.Register(srv) if err := gracefulgrpc.ListenAndServe( // ListenAndServe! ctx, ":4317", srv, graceful.GracefulShutdownTimeout(time.Second), ); err != nil { panic(err) }

Slide 21

Slide 21 text

實codeに導入してみた MackerelのOpenTelemetryを扱ふmicroserviceに導入してみた ※ベータ版テスト受け附け中です Mackerel で OpenTelemetry のラベル付きメトリックを使ってみよう - Mackerel お知らせ #mackerelio https://mackerel.io/ja/blog/entry/using-open-telemetry-labeled-metrics-with-mackerel

Slide 22

Slide 22 text

實codeに導入してみた HTTP serverと、別portでhealth check用HTTP serverを起動してゐるprocess gRPC serverと、health check用HTTP serverを起動してゐるprocess

Slide 23

Slide 23 text

實codeに導入してみた ● boilerplateが減った ● 「graceful shutdownをどう書くべきか」を取り出して議論する場が生まれた ○ 改善できるようになった

Slide 24

Slide 24 text

試してみてください ne-sachirou/go-graceful: graceful shutdown for Go https://github.com/ne-sachirou/go-graceful