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

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

Avatar for Miyazaki Taiga Miyazaki Taiga
March 10, 2023
0

 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 を乗り切る ことができた