Upgrade to Pro — share decks privately, control downloads, hide ads and more …

作ってよかったgraceful shutdownライブラリ #kyotogo

作ってよかったgraceful shutdownライブラリ #kyotogo

さっちゃん

December 02, 2023
Tweet

More Decks by さっちゃん

Other Decks in Programming

Transcript

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    ○ 常駐processであれば、狀態を保存する
    ● 但し、一定時閒でtimeoutし強制終了する

    View full-size slide

  10. gracefulな起動・終了
    gracefulな起動 gracefulな終了

    View full-size slide

  11. 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)

    View full-size slide

  12. 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())
    }

    View full-size slide

  13. 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記述量が多い

    View full-size slide

  14. Goでのgracefulな起動・終了
    ● 決まり切ったcode(boilerplate)を每囘長々と書かねばならない

    ○ codeをコピペするが、微妙な差異が生まれてゆく
    ■ コピペ元によって品質が變はりうる
    ■ コピペしたcodeがよいものなのか、每囘調査せねばならない …
    ○ 改善が要る時も、コピペした codeを全部改善して囘るのは、多分無理
    ● 「同じ問題」が發生してゐる

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  18. 任意の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)
    }
    }

    View full-size slide

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

    View full-size slide

  20. 單一の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)
    }

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide