Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

© DMM 新機能の追加先 11 認証認可機能の追加を開発効率よく実現する GKE オンプレ API Gateway 認証認可基盤 認証サービス 認可サービス 新しい 認証・認可機能ニー ズ エコシステムを利 用し 生産性高く開発で きる 古いシステムで 改修が難しい

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

© DMM 辛い例①

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

© DMM an incident occurs …

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

© DMM 辛い例②

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

© DMM an incident occurs …

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

© DMM 辛い例③

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

© DMM an incident occurs …

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

© 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のポインタを生成

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

© 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を ラップする

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

© 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に 時間を付与する

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

© DMM 課題 GoのContextの使い方 ✖ Contextに可変な値(TotalExternalDuration)を付加する方法で解決したこ と middlewareとhttp.ClientがContextの値を介して結合度が高くなってしまっ たのも気になる → 今後負債化するかも 基本的にcontext.WithValue()では、一連のリクエストにおいて不変の値を 伝搬するべきです!!! 48

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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