$30 off During Our Annual Pro Sale. View Details »

マイクロサービスの効率的な監視〜不安定な依存先との闘い〜

Tao Watanabe
September 20, 2023

 マイクロサービスの効率的な監視〜不安定な依存先との闘い〜

DMM.go #6 の登壇資料です。
https://dmm.connpass.com/event/295065/

Tao Watanabe

September 20, 2023
Tweet

Other Decks in Programming

Transcript

  1. © DMM
    マイクロサービスの効率的な監視
    〜不安定な依存先との闘い〜
    DMM.go #6
    プラットフォーム事業本部  Tao Watanabe
    1

    View Slide

  2. © DMM
    自己紹介
    入社:2022年 新卒入社
    所属:プラットフォーム事業本部
     マイクロサービスアーキテクトグループ
     認証チーム
    最近はオンプレMySQL→TiDB Cloud への
    移行プロジェクトに注力しています
    2
    Tao Watanabe
    X: @tao_wata

    View Slide

  3. © DMM
    ● DMM プラットフォーム (PF)と
    マイクロサービスアーキテクトグループの紹介
    ● 認証認可基盤の紹介
    ● 認証認可基盤の運用・監視における辛み
    ● それを解消した方法
    ● 結果と課題・まとめ
    Agenda
    3

    View Slide

  4. © DMM
    プラットフォーム(PF)事業本部とは
    DMMの各サービスが利用する共通機能を開発する組織
    4
    会員登録 ログイン 不正対策
    ポイント
    購入
    決済
    継続課金 など
    カスタマー
    サポート
    120人規模のエンジニア組織

    View Slide

  5. © DMM
    PF事業本部が長年抱えていた課題
    5
    組織規模が大きいため、マイクロサービスアーキテクチャを
    以前から採用
    → 各サービスの開発チームのテクノロジースタックがサイロ化
    → 開発効率を上げられない、共通の技術課題を解決できない
    これを解決するためマイクロサービスアーキテクトグループが発足

    View Slide

  6. © DMM
    マイクロサービスアーキテクトグループ
    6
    DMMプラットフォームにおける開発組織戦略を策定・実行し、
    開発効率・セキュリティレベル向上を実現する
    以下のエコシステムを提供
    • マイクロサービスプラットフォーム
    (Kubernetesクラスタ)
    • 負荷試験基盤
    • 監視機能(Datadog)
    • 認証認可機能
    • SLO/SLIの導入
    • レビューシステム など

    View Slide

  7. © DMM
    PFのマイクロサービスアーキテクチャの概要
    7
    API
    Gateway
    認可サービス
    認証サービス
    決済サービス
    会員サービス
    認証認可基盤

    View Slide

  8. © DMM
    認証認可基盤
    8
    DMM会員をはじめ、従業員や各アプリケーションが持つリソースに対する認証と認可を
    一元管理する基盤。
    オンプレのレガシーシステムをプロキシしている
    使用技術
    言語: Go
    インフラ: Kubernetes(GKE)
    CI: GitHub Actions
    CD: ArgoCD
    監視: Datadog
    GKE オンプレ
    API
    Gateway
    認証認可基盤
    認証サービス
    認可サービス
    DMMの成長を
    支えてきた
    レガシーシステム

    View Slide

  9. © DMM
    認証認可基盤が果たす役目
    9
    • 段階的なリプレイス先
    • 新機能の追加先

    View Slide

  10. © DMM
    段階的なリプレイス先
    10
    レガシーシステムの段階的なリプレイスを容易にする
    GKE オンプレ
    API
    Gateway
    認証認可基盤
    認証サービス
    認可サービス
    エンドポイントごとの
    リプレイスが容易

    View Slide

  11. © DMM
    新機能の追加先
    11
    認証認可機能の追加を開発効率よく実現する
    GKE オンプレ
    API
    Gateway
    認証認可基盤
    認証サービス
    認可サービス
    新しい
    認証・認可機能ニー

    エコシステムを利
    用し
    生産性高く開発で
    きる
    古いシステムで
    改修が難しい

    View Slide

  12. © DMM
    認証認可基盤が果たす役目
    12
    認証認可機能の負債脱却のための中心的な役割
    一方で、リプレイスが進むまでは単なるProxy
    → 認証認可機能のオーナーシップはProxy先のシステムを管轄するチーム
    にあるという状況

    View Slide

  13. © DMM
    認証認可基盤の運用で
    どんな困難があったか

    View Slide

  14. © DMM
    当初はAPI Gatewayのエンドポイントを監視
    14
    API
    Gateway
    認証認可基盤
    (自サービス)
    レガシー
    システム
    リバース
    プロキシ
     
    Redis
     
    Redis
    API Gatewayのエンドポイントを起点とし
    レイテンシを監視 合計 100ms
    10ms 5ms 35ms
    オンプレ
    50ms

    View Slide

  15. © DMM
    監視・運用における辛さ・難しさ
    • 依存先レガシーシステム起因のアラートで夜中に起こされる
    • 依存元API Gatewayの不調にも影響される
    • 自サービス自体の品質が分からない
    15

    View Slide

  16. © DMM
    辛い例①

    View Slide

  17. © DMM
    レガシーシステムのレイテンシが悪化
    17
    API
    Gateway
    認証認可基盤
    (自サービス)
    レガシー
    システム
    リバース
    プロキシ
     
    Redis
     
    Redis
    レイテンシ悪化でアラート発火
    10ms 5ms 35ms
    レイテンシ悪

    オンプレ

    View Slide

  18. © DMM
    an incident
    occurs …

    View Slide

  19. © DMM
    他チーム管轄のプロダクトが原因
    19
    API
    Gateway
    認証認可基盤
    (自サービス)
    レガシー
    システム
    リバース
    プロキシ
     
    Redis
     
    Redis
    10ms 5ms 35ms
    レイテンシ悪

    自チームの管轄 他チームの管轄

    View Slide

  20. © DMM
    やれることがない...

    View Slide

  21. © DMM
    辛い例②

    View Slide

  22. © DMM
    依存元のAPI Gatewayの不調
    22
    API
    Gateway
    認証認可基盤
    (自サービス)
    レガシー
    システム
    リバース
    プロキシ
     
    Redis
     
    Redis
    5ms 35ms
    オンプレ
    50ms
    レイテンシ悪

    レイテンシ悪化でアラート発火

    View Slide

  23. © DMM
    an incident
    occurs …

    View Slide

  24. © DMM
    API Gatewayも他チーム管轄
    24
    API
    Gateway
    認証認可基盤
    (自サービス)
    レガシー
    システム
    リバース
    プロキシ
     
    Redis
     
    Redis
    5ms 35ms 50ms
    レイテンシ悪

    自チームの管轄
    他チームの管轄

    View Slide

  25. © DMM
    やれることがない...

    View Slide

  26. © DMM
    辛い例③

    View Slide

  27. © DMM
    オンプレのリバースプロキシの不調
    27
    API
    Gateway
    認証認可基盤
    (自サービス)
    レガシー
    システム
    リバース
    プロキシ
     
    Redis
     
    Redis
    5ms
    10ms
    オンプレ
    50ms
    レイテンシ悪

    レイテンシ悪化でアラート発火

    View Slide

  28. © DMM
    an incident
    occurs …

    View Slide

  29. © DMM
    やれることはない...

    View Slide

  30. © DMM
    作業効率・モチベーションの低下
    他チーム管轄サービス起因でアラートが発火
    →結果、不必要な対応を迫られることもある
    30

    View Slide

  31. © DMM
    これを解決するために
    割り切った戦略を採用

    View Slide

  32. © DMM
    割り切った戦略を採用
    自サービスのレイテンシしか見ない
    32

    View Slide

  33. © DMM
    API Gatewayと依存先システムのレイテンシは含めず監視
    33
    API
    Gateway
    認証認可基盤
    (自サービス)
    レガシー
    システム
    リバース
    プロキシ
     
    Redis
     
    Redis
    10ms 5ms 35ms
    オンプレ
    50ms

    View Slide

  34. © DMM
    Goでどのように実装をしたか
    注意: Goのプラクティスに反する方法を利用しています

    View Slide

  35. © DMM
    Goのプラクティスに反した方法
    Contextにtime.Durationへの参照を付加し、
    http.Clientの中で外部APIへのリクエスト待ち時間を都度Contextの参照先
    へ加算する
    基本的にcontext.WithValue()では、一連のリクエストにおいて不変の値を
    伝搬するべきです!!!
    外部APIへ依存する箇所にすでにctxを引き回していたので
    実装が楽だった...
    35

    View Slide

  36. © DMM
    リクエストシーケンス
    36
    自サービス

    View Slide

  37. © DMM
    自サービスのレイテンシを算出
    37
    自サービスのレイテンシを
    全体処理時間 - 依存先システムのレスポンスを待つ時間の総計(外部処理
    時間)
    として算出

    View Slide

  38. © DMM
    38
    全体処理時間 計測開始
    Contextに参照をセット
    全体処理時間計測終了
    Contextの参照先の値を取得
    内部処理時間計算
    外部処理時間 計測開始
    外部処理時間 計測終了
    Contextにセットされた
    参照先を更新
    ミドルウェアとHTTPクライアントで処理時間を計測
    ctx
    ctx

    View Slide

  39. © DMM
    39
    全体処理時間 計測開始
    Contextに参照をセット
    全体処理時間計測終了
    Contextの参照先の値を取得
    内部処理時間計算
    外部処理時間 計測開始
    外部処理時間 計測終了
    Contextにセットされた
    参照先を更新
    ミドルウェアで全体処理時間の計測を開始
    ctx
    ctx

    View Slide

  40. © DMM
    type Store struct {
    UserID string
    TraceID string
    TotalExternalDuration time.Duration
    }
    type storeKeyType struct{}
    var storeKey = storeKeyType{}
    40
    func MiddlewareDuration(next http.Handler) http.Handler {
    return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
    start := time.Now()
       store := &Store{}
    ctx := context.WithValue(ctx, storeKey, store)
    next.ServeHTTP(rw, r.WithContext(ctx))
    wholeDuration := time.Now().Sub(start)
    internalDuration := wholeDuration - store.TotalExternalDuration
    if span, ok := tracer.SpanFromContext(r.Context()); ok {
    span.SetTag("internal.duration", internalDuration.Microseconds())
    }
    })
    }
    ミドルウェアで全体処理時間の計測を開始
    ①Contextに格納して伝搬する値
    をまとめた構造体と
    そのKeyを定義する
    ⚠可変の値として外部処理時間の総
    計が含まれる
    ②全体処理時間の計測開始
    ④ContextにValue(*Store)を
    セット
    ③Storeのポインタを生成

    View Slide

  41. © DMM
    RoundTripperで外部処理時間の計測
    41
    全体処理時間 計測開始
    Contextに*Storeをセット
    全体処理時間計測終了
    内部処理時間計算
    外部処理時間 計測開始
    外部処理時間 計測終了
    *Storeを更新

    View Slide

  42. © DMM
    RoundTripperで外部処理時間の計測
    42
    type durationRoundTripper struct {
    base http.RoundTripper
    }
    func (rt *durationRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
    start := time.Now()
    res, err := rt.base.RoundTrip(req)
    duration := time.Since(start)
    store := GetStoreFromContext(req.Context())
    store.TotalExternalDuration += duration
    return res, err
    }
    func WrapClientWithDurationRoundTripper(c *http.Client) *http.Client {
    if c.Transport == nil {
    c.Transport = http.DefaultTransport
    }
    c.Transport = &durationRoundTripper{base: c.Transport}
    return c
    }
    ①外部処理時間を計測する
    ②ContextからStoreの参照を取り出す
    ③外部処理時間の総計に算出した外部処理時間を加算する
    ④ 以上の処理を追加したRoundTripperでCllientを
    ラップする

    View Slide

  43. © DMM
    43
    全体処理時間 計測開始
    Contextに参照をセット
    全体処理時間計測終了
    内部処理時間計算
    外部処理時間 計測開始
    外部処理時間 計測終了
    Contextにセットされた
    参照先を更新
    ミドルウェア(帰り)で内部処理時間を算出
    ctx
    ctx

    View Slide

  44. © DMM
    type Store struct {
    UserID string
    TraceID string
    TotalExternalDuration time.Duration
    }
    type storeKeyType struct{}
    var storeKey = storeKeyType{}
    44
    func MiddlewareDuration(next http.Handler) http.Handler {
    return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
    start := time.Now()
       store := &Store{}
    ctx := context.WithValue(ctx, storeKey, store)
    next.ServeHTTP(rw, r.WithContext(ctx))
    wholeDuration := time.Now().Sub(start)
    internalDuration := wholeDuration - store.TotalExternalDuration
    if span, ok := tracer.SpanFromContext(r.Context()); ok {
    span.SetTag("internal.duration", internalDuration.Microseconds())
    }
    })
    }
    ミドルウェア(帰り)で内部処理時間を算出
    ①全体処理時間の算出
    ②(全体処理時間 - 外部処理時間の総計) を計算し、
    内部処理時間を算出
    ③トレーシングライブラリのSpanに
    時間を付与する

    View Slide

  45. © DMM
    自サービスの内部処理時間のみを監視
    45
    API
    Gateway
    認証認可基盤
    (自サービス)
    レガシー
    システム
    リバース
    プロキシ
     
    Redis
     
    Redis
    10ms 5ms 35ms
    オンプレ
    50ms

    View Slide

  46. © DMM
    静かな夜を手に入れた
    自分達では対処できない不具合によるアラート対応が減った
    思い切った監視に切り替えた結果...
    46

    View Slide

  47. © DMM
    spanに内部処理時間を付与したことで、シークバーで絞り込めるようになった
    → 自チーム管轄のRedisの遅延などによる内部処理の遅れ, 不具合を
      検知できるようになった
    思い切った監視に切り替えた結果...
    47

    View Slide

  48. © DMM
    課題 GoのContextの使い方
    ✖ Contextに可変な値(TotalExternalDuration)を付加する方法で解決したこ

    middlewareとhttp.ClientがContextの値を介して結合度が高くなってしまっ
    たのも気になる
    → 今後負債化するかも
    基本的にcontext.WithValue()では、一連のリクエストにおいて不変の値を
    伝搬するべきです!!!
    48

    View Slide

  49. © DMM
    課題 マイクロサービスのオーナーシップ
    今回自サービスの責任範囲のみに監視対象を絞る戦略を採用したが...
    依存先も含めたサービスの品質に誰かは責任を持たなければならない
    ユーザーに提供する機能のオーナーシップを持つ人は誰?
    負債脱却中はこの辺りの責任範囲が難しい
    49

    View Slide

  50. © DMM
    まとめ
    何らかのProxyや共通基盤の場合、依存先システムの監視を切り捨て、
    自サービス自体の可用性を監視すれば必要十分なこともある
    Contextの誤用・濫用はやめよう
    Contextの使い方はよく議論されている
    (トレードオフな部分もあるのでは)
    50

    View Slide

  51. © DMM
    ご静聴ありがとうございました

    View Slide