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

ABEMAの安定稼働を支えたOpenTelemetryの導入事例

Avatar for Miyazaki Taiga Miyazaki Taiga
March 10, 2023
11

 ABEMAの安定稼働を支えたOpenTelemetryの導入事例

Avatar for Miyazaki Taiga

Miyazaki Taiga

March 10, 2023

Transcript

  1. AbemaTV, Inc. All Rights Reserved AbemaTV, Inc. All Rights Reserved

    1 ABEMAの安定稼働を支えた OpenTelemetryの導入事例 2023 March 10th Taiga Miyazaki
  2. AbemaTV, Inc. All Rights Reserved 宮﨑 大芽 2022 年 中

    途 入 社 。 ABEMAのバックエンドエンジニアとしてサービス開発に従事。 FIFA ワールドカップ カタール 2022 に向けた開発期間中では、サービスの負荷対策や 障害対策に携わり、現在はプロダクト基盤チームで決済システムの開発に携わる。 趣味は、漫画やアニメ。映画ブルージャイアントがおすすめ。 2 Profile
  3. AbemaTV, Inc. All Rights Reserved 3 1. 背景 2. OpenTelemetry

    概要 3. 導入方法 4. 既存システムに導入する上で苦労した点 5. 活用事例 6. 今後の展望 INDEX
  4. AbemaTV, Inc. All Rights Reserved ポイント 背景 5 • ABEMA

    はマイクロサービスアーキテクチャを適用している • W杯に向けた負荷試験では、サービス全体に対する複合的な負荷試験を行った • 負荷試験を開始した当初は、サービス全体に対して分散トレーシングが導入され ていなかった ◦ 複雑なマイクロサービスの分析が難しい
  5. AbemaTV, Inc. All Rights Reserved 特徴 OpenTelemetry 概要 7 •

    ベンダーに依存しない • OpenTracing, OpenCensus の後継 • テレメトリデータを計測・生成・収集・エクスポートに利用
  6. AbemaTV, Inc. All Rights Reserved 導入方法 10 • Application は

    Kubernetes (GKE) で稼働 • Anthos Service Mesh (ASM) 導入済み ◦ Istio ベースの Service Mesh ◦ 基本的に Application の通信は Sidecar として挿入されている istio-proxy (envoy) 経由で送受信される 前提
  7. AbemaTV, Inc. All Rights Reserved 11 概念図 導入方法 LB istio-proxy

    container application container Cloud Trace istio-proxy container application container … Spans gRPC request HTTP microservice A microservice B Instrumentation (OpenTelemetry)
  8. AbemaTV, Inc. All Rights Reserved 導入方法 12 • istio-proxy が

    Sidecar として起動している Pod で tracing が可能 • ASM が対応している Trace Context のフォーマット ◦ W3C TraceContext ◦ Zipkin B3 ◦ Google Cloud Trace ◦ gRPC TraceBin Anthos Service Mesh (ASM) との連携
  9. AbemaTV, Inc. All Rights Reserved 導入方法 13 • context が正しく伝播されていないレガシーコードの修正

    • レガシーコードが古い google.golang.org/grpc に依存している問題の解消 • 初期スコープでは Trace Context の伝播と ASM の連携を目標とした • 最終的に各ミドルウェアのクライアント用の instrumentation library の導入、span exporter の設定を行う方針 既に稼働しているサービスへの段階的導入
  10. AbemaTV, Inc. All Rights Reserved 導入方法 14 • otelhttp •

    otelgrpc • redisotel • otelmongo ABEMA で利用した instrumentation library (一部)
  11. AbemaTV, Inc. All Rights Reserved 導入方法 15 • otelhttp •

    otelgrpc • redisotel • otelmongo ABEMA で利用した instrumentation library (一部)
  12. AbemaTV, Inc. All Rights Reserved 導入方法 16 • otelhttp •

    otelgrpc • redisotel • otelmongo ABEMA で利用した instrumentation library (一部)
  13. AbemaTV, Inc. All Rights Reserved 導入方法 17 otelhttp (server) func

    newHandler(api *ResourceAPI) http.Handler { mux := http.NewServeMux() mux.HandleFunc("/", api.healthCheck) mux.HandleFunc("/readiness", api.readinessHandler) mux.HandleFunc("/path/to/resource", api.saveResource) var h http.Handler = mux h = otelMiddleware(h) return h } func otelMiddleware(next http.Handler) http.Handler { return otelhttp.NewHandler(next, "server", otelhttp.WithMessageEvents(otelhttp.ReadEvents, otelhttp.WriteEvents), otelhttp.WithFilter( filters.All( // ignore health check endpoints filters.Not(filters.Path("/")), filters.Not(filters.Path("/readiness")), ), ), ) } ヘルスチェックのリクエストは無視 する
  14. AbemaTV, Inc. All Rights Reserved 導入方法 18 otelhttp (client) func

    request(ctx context.Context) error { req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://example.com", http.NoBody) if err != nil { return err } cli := &http.Client{ Transport: otelhttp.NewTransport(http.DefaultTransport), Timeout: 5 * time.Second, } resp, err := cli.Do(req) if err != nil { return err } defer resp.Body.Close() return nil } transport を wrap する
  15. AbemaTV, Inc. All Rights Reserved 導入方法 19 • otelhttp •

    otelgrpc • redisotel • otelmongo ABEMA で利用した instrumentation library (一部)
  16. AbemaTV, Inc. All Rights Reserved 導入方法 20 otelgrpc (server) func

    newServer(hs health.HealthServer, service v1.AppServiceServer) *grpc.Server { s := grpc.NewServer( grpc_middleware.WithUnaryServerChain( otelgrpc.UnaryServerInterceptor( otelgrpc.WithInterceptorFilter( // ignore health check request filters.Not(filters.HealthCheck()), ), ), ), grpc_middleware.WithStreamServerChain( otelgrpc.StreamServerInterceptor( // ignore health check request filters.Not(filters.HealthCheck()), ), ), ) health.RegisterHealthServer(s, hs) v1.RegisterAppServiceServer(s, service) reflection.Register(s) return s } interceptor を追加 stream も同様 ヘルスチェックの リクエストは無視する
  17. AbemaTV, Inc. All Rights Reserved 導入方法 21 otelgrpc (client) func

    newClient(ctx context.Context, addr string) (*grpc.ClientConn, error) { otelgrpc.UnaryClientInterceptor() dialOpts := []grpc.DialOption{ grpc.WithUserAgent("service/1.0.0"), grpc_middleware.ChainUnaryClient(otelgrpc.UnaryClientInterceptor()), grpc_middleware.ChainStreamClient(otelgrpc.StreamClientInterceptor()), } return grpc.DialContext(ctx, addr, dialOpts...) } interceptor を追加
  18. AbemaTV, Inc. All Rights Reserved 導入方法 22 • OpenCensus Bridge

    を導入 • Google Cloud のサービス (Pub/Sub、Spanner、Bigtable、Firestore…) へのリクエストを可視化 Google Cloud Client Libraries との連携
  19. AbemaTV, Inc. All Rights Reserved 導入方法 23 initialize tracer provider

    ① func init() { otel.SetTextMapPropagator( b3.New(b3.WithInjectEncoding(b3.B3MultipleHeader))) // OpenCensus Bridge tracer := otel.Tracer("go.opentelemetry.io/otel/bridge/opencensus") octrace.DefaultTracer = opencensus.NewTracer(tracer) } func NewTracerProvider() (trace.TracerProvider, func()) { opts := []sdktrace.TracerProviderOption{ sdktrace.WithBatcher(newCloudTraceExporter()), sdktrace.WithSampler(sdktrace.TraceIDRatioBased(fraction)), } tp := sdktrace.NewTracerProvider(opts...) otel.SetTracerProvider(tp) cleanup := func() { ctx, cancel := context.WithTimeout(context.Background(), shutdownTimeout) defer cancel() if err := tp.Shutdown(ctx); err != nil { log.Printf("[ERROR]: %v", err) } } return tp, cleanup } Trace Context の伝播 Google Cloud Client Library 対応 Tracer Provider 初期化
  20. AbemaTV, Inc. All Rights Reserved 導入方法 24 initialize tracer provider

    ② func newCloudTraceExporter() sdktrace.SpanExporter { exporter, err := texporter.New() if err != nil { log.Printf("[ERROR]: fallback to noop exporter: %v", err) return newNoopExporter() } return exporter } func newNoopExporter() sdktrace.SpanExporter { return &noopExporter{} } type noopExporter struct{} func (*noopExporter) ExportSpans(context.Context, []sdktrace.ReadOnlySpan) error { return nil } func (*noopExporter) Shutdown(context.Context) error { return nil } 失敗時に Application が起動しないと困るので 何 もしない exporter に fallback Cloud Trace Exporter の初期化
  21. AbemaTV, Inc. All Rights Reserved 導入方法 25 • custom sampler

    を実装して、ノイズになる一部の span を無視する • 既に導入されている logging library を go.opentelemetry.io/otel でも利用するために github.com/go-logr/logr との bridge を実装 細かい対応
  22. AbemaTV, Inc. All Rights Reserved 既存システムに導入する上で苦労した点 27 • Trace Context

    が伝播することの検証 ◦ Zipkin B3 Single Header には対応してないことが判明 ◦ 既存の Application で context が伝播されていない箇所が判明 (後述) • ASM 側で有効にするだけでは Tracing は利用できない ◦ Application 側の対応も必要 Anthos Service Mesh (ASM) との連携
  23. AbemaTV, Inc. All Rights Reserved 28 Anthos Service Mesh (ASM)

    との連携 既存システムに導入する上で苦労した点 istio-proxy container application container Microservic B Middleware … Trace Context Microservice A LB
  24. AbemaTV, Inc. All Rights Reserved 29 Anthos Service Mesh (ASM)

    との連携 既存システムに導入する上で苦労した点 istio-proxy container application container Middleware … Trace Context Microservice A Microservic B LB
  25. AbemaTV, Inc. All Rights Reserved 既存システムに導入する上で苦労した点 30 • そもそも context

    が渡されていない関数呼び出しがあった • context.Background() や context.TODO() で渡されている箇所 context が正しく伝播されていないレガシーコードの修正
  26. AbemaTV, Inc. All Rights Reserved 31 context の伝播 - Before

    既存システムに導入する上で苦労した点 func (a *ResourceAPI) saveResource(w http.ResponseWriter, r *http.Request) { dec := json.NewDecoder(r.Body) defer r.Body.Close() p := new(SaveParam) if err := dec.Decode(p); err != nil { w.WriteHeader(http.StatusBadRequest) return } // 同期処理 if err := a.svc.Save(p); err != nil { w.WriteHeader(http.StatusInternalServerError) return } // 非同期処理 go func() { if err := a.svc.Update(context.Background(), &UpdateParam{}); err != nil { log.Printf("[ERROR]: %v", err) } }() } context が渡されていない 上流の context が 渡されていない
  27. AbemaTV, Inc. All Rights Reserved 32 context の伝播 - After

    既存システムに導入する上で苦労した点 func (a *ResourceAPI) saveResource(w http.ResponseWriter, r *http.Request) { dec := json.NewDecoder(r.Body) defer r.Body.Close() p := new(SaveParam) if err := dec.Decode(p); err != nil { w.WriteHeader(http.StatusBadRequest) return } // 同期処理 ctx := r.Context() if err := a.svc.Save(ctx, p); err != nil { w.WriteHeader(http.StatusInternalServerError) return } // 非同期処理 go func() { bg := trace.ContextWithSpan(context.Background(), trace.SpanFromContext(ctx)) if err := a.svc.Update(bg, &UpdateParam{}); err != nil { log.Printf("[ERROR]: %v", err) } }() } context を渡す context に Span をコピー
  28. AbemaTV, Inc. All Rights Reserved 既存システムに導入する上で苦労した点 33 • レガシーコードが google.golang.org/grpc

    v1.29.1 に依存 ◦ google.golang.org/grpc/naming に依存しているため ▪ ASM 導入により k8s Watch API を使用しなくなり不要に • otelgrpc は google.golang.org/grpc/credentials/insecure に依存 ◦ google.golang.org/grpc v1.34.0 で追加された 古い google.golang.org/grpc への依存
  29. AbemaTV, Inc. All Rights Reserved 活用事例 35 • DB が遅くないのに

    API の duration が遅い • istio-proxy container がボ トルネックとなっていた 負荷試験
  30. AbemaTV, Inc. All Rights Reserved 今後の展望 38 • Application が特定のソリューションに依存することを防ぐ

    ◦ 特定のソリューション: Cloud Trace, AWS X-Ray 等 • Infrastructure で共通のメタデータの付与 • パフォーマンスの最適化 OpenTelemetry Collector 導入
  31. AbemaTV, Inc. All Rights Reserved 今後の展望 39 • マルチクラウド (Google

    Cloud, AWS) を想定した構成 ◦ クラウド横断でのトレーシング • コストコントロール ◦ 柔軟な保存期間 Grafana Tempo 導入
  32. AbemaTV, Inc. All Rights Reserved まとめ 41 • マイクロサービスのボトルネックの把握のために、 OpenTelemetry

    を用いた 分散トレーシングを実現 • ASM, OpenCensus Bridge, 各種 library を用いて導入を行った • 負荷試験や監視時に活用し、FIFA ワールドカップ カタール 2022 を乗り切る ことができた