はじめまして。Gaudiyでエンジニアをしているあんどう(@Andoobomber)です。
以前、「OpenTelemetry Collector導入のPoCと今後に向けて」という記事を弊エンジニアの sato(@yusukesatoo06)より公開しました。簡単に記事を要約すると、
- OpenTelemetry及びOpenTelemetry Collectorの説明
- 実際にPoCを作ってみる
- 実導入を試みたがOpenTelemetry Collectorのホスティングに悩み、今後の課題として保留となった
といった内容でした。
あれから1年経ち、GaudiyではOpenTelemetry Collectorを本番環境に組み込み、OpenTelemetryの仕様に準拠して計装し、データの分析や監視を行っています。この記事では、前回からの進捗を紹介すると共にOpenTelemetryの導入方法を書きたいと思います。
- 1. 前回のPoCから変わったこと
- 2. OpenTelemetry Collectorの導入
- 3. (おまけ)opentelemetry-go-instrumentationを試す
- 4. 今後の方針
- 5. まとめ
1. 前回のPoCから変わったこと
1-1. インフラ: Cloud Run → Google Kubernetes Engine(GKE)
前回上がった導入の課題として、「CollectorのホスティングにCloud Runを使用する案が上がったが、Cloud Runの特性上、常時立ち上げておく必要があるCollectorとの相性が悪く断念した」といった課題がありました。(当時はCloud Run SidecarがPreviewでした)
これをどう解決したかというと、Gaudiyのアプリケーション(Gaudiy Fanlink)のインフラ環境をKubernetes(k8s)環境に乗せ替えることで解決しました。
※ただし、OpenTelemetry Collectorの問題を解消するためにk8sに移行した訳ではなく、複合的な要因でk8sに移行しました。詳細については「Kubernetes初学者が担当したGKE移行プロセスの全貌」に書かれているので是非みてください!
1-2. 監視: GCP → Datadog
また、MonitoringツールもGCPの監視サービス(Operations Suite, Cloud Trace/Metrics/Logging)を使っていたのですが、Datadogに乗り換えました。Datadogはとても高機能でGCPやGithubなど多種多様なアプリケーションへのIntegrationがあることや、直感的に操作できるUXが使いやすく乗り換えを決意しました。
元々、全てのアプリケーションはOpenTelemetryの仕様に準拠していたので、Datadogへの移行は、GCP OpenTelemetry Exporterを使ってGCPへExportしていたのを、Collector経由でDatadogにExportするだけで済みました。これがOpenTelemetryの強みであり、予め準拠しておいて本当によかったなと思います。
2. OpenTelemetry Collectorの導入
2-1. Kubernetesリソースの定義
GaudiyではKubernetesのリソース設定にKustomize × Helmを使っています。以下のKustomize YAMLをArgoCDがGKEに適用しています。
apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization metadata: name: opentelemetry-collector namespace: opentelemetry-system helmCharts: - name: opentelemetry-collector repo: https://open-telemetry.github.io/opentelemetry-helm-charts releaseName: opentelemetry-collector ... patches: - patch: |- - op: replace path: /spec/template/spec/containers/0/image value: ${CUSTOM_OTEL_COLLECTOR_PATH} # カスタムしたOTel Collectorを使用 - path: ./configmap.yaml # ConfigMapにPatchを充てる target: version: v1 kind: ConfigMap ~省略~
2-2. Custom OTel Collectorの用意
OpenTelemetry CollectorではDistributionとしてCoreとContrib が用意されており、Coreはextensions/connectors/receivers/processors/exporters
のベース部分が備わっており、Contribは加えてOTel開発者以外のサードパーティ等が開発した機能(Component)が備わっています。
しかし、OTel Collectorアンチパターンの記事にあるようにCoreもContribも本番環境で使うのは推奨されておらず、アプリケーションに必要なComponentを適時選んで独自でビルドして使うことが勧められています。Gaudiyも独自にビルドしたImageをCUSTOM_OTEL_COLLECTOR_PATH
にPushして使っています。
以下はCollectorで使用するComponentのManifestになります。
dist: module: github.com/gaudiy/** name: otelcol description: "Gaudiy OpenTelemetry Collector distribution" otelcol_version: "v0.95.0" output_path: out version: "v0.95.0" receivers: - gomod: go.opentelemetry.io/collector/receiver/otlpreceiver v0.95.0 - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/receiver/hostmetricsreceiver v0.95.0 - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/receiver/prometheusreceiver v0.95.0 connector: - gomod: go.opentelemetry.io/collector/connector/forwardconnector v0.95.0 - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/connector/datadogconnector v0.95.0 - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/connector/routingconnector v0.95.0 - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/connector/servicegraphconnector v0.95.0 - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/connector/spanmetricsconnector v0.95.0 processors: - gomod: go.opentelemetry.io/collector/processor/batchprocessor v0.95.0 - gomod: go.opentelemetry.io/collector/processor/memorylimiterprocessor v0.95.0 - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/processor/attributesprocessor v0.95.0 - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/processor/k8sattributesprocessor v0.95.0 - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/processor/probabilisticsamplerprocessor v0.95.0 - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourcedetectionprocessor v0.95.0 - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/processor/transformprocessor v0.95.0 exporters: - gomod: go.opentelemetry.io/collector/exporter/debugexporter v0.95.0 - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter v0.95.0 extensions: - gomod: go.opentelemetry.io/collector/extension/zpagesextension v0.95.0 - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextension v0.95.0
↑のmanifestをOpenTelemetry Collector Builder(ocb)を使ってビルドしています。
ビルドの手順については公式ドキュメントに書いてあるので、是非そちらを見てみてください。
2-3. CollectorのConfigを設定
ConfigMapにCollectorの設定値を書いています。以前の記事で紹介したreceiverやprocessorなどのフィールドを設定しています。
(一部のパラメータのみ載せています)
apiVersion: v1 kind: ConfigMap metadata: name: opentelemetry-collector-** data: relay: | receivers: otlp: protocols: grpc: endpoint: 0.0.0.0:4317 http: endpoint: 0.0.0.0:4318 ~省略~ processors: transform: error_mode: ignore log_statements: - context: scope statements: # DataDog cannot pick up the span name as resource on # Service Entry Spans. We append it here. This might have a # side effect to overwrite an application that is instrumented with # DataDog SDK. However, as long as OpenTelemetry SDK is used to # instrument, this transformation will help. - set(attributes["resource.name"], name) # - context: log # statements: # - set(attributes["env"], attributes["deployment.environment"]) ~省略~ exporters: datadog: api: key: ${env:DD_API_KEY} fail_on_invalid_key: true host_metadata: enabled: true traces: span_name_as_resource_name: true compute_stats_by_span_kind: true peer_service_aggregation: true metrics: resource_attributes_as_tags: true histograms: mode: counters send_aggregation_metrics: true logs: dump_payloads: true extensions: health_check: ~ memory_ballast: ~ zpages: ~ service: telemetry: metrics: address: 0.0.0.0:8888 extensions: - health_check - memory_ballast - zpages pipelines: traces: receivers: - otlp processors: - resourcedetection - k8sattributes - attributes/trace - transform - batch exporters: - datadog metrics: ~省略~ logs: ~省略~
変わった設定は特にしていないのですが、特筆すべきところは以下の3つです。
2-3-1. exportersにdatadogを設定
DatadogのAPI KeyをSecretで定義しておいて、ENVに渡して設定しています。
そして、.service.pipelines.traces.exporters
にもdatadogを使うことを指定します。tracesだけでなくmetrics/logsでも同様に指定しています。
exporters: datadog: api: key: ${env:DD_API_KEY} fail_on_invalid_key: true service: pipelines: traces: exporters: - datadog
2-3-2. processorsでDatadog用にAttributeを設定する
DatadogではSpanにresource.name
の属性を設定する必要があるのですが、OTelの仕様ではname
という属性で扱われています。解決方法として、processorsでDatadogのspecに合わせて加工しています。
先ほどのCustom OTel Collectorの所でtransformprocessorというcomponentを導入しており、ConfigMapにてtransformの処理を書いています。
processors: transform: error_mode: ignore log_statements: - context: scope statements: - set(attributes["resource.name"], name)
2-3-3. Loggingは社内独自ライブラリを使用
GoのLogging SDKは公式でもまだIn development
のステータスですが、Javaなどは既にSDKが提供されている関係でProtocol Buffersの定義は存在したので、それをベースに社内でzapのadaptorを実装し先行して使っています。
この記事の公開時点では公式にもGoのSDK実装が徐々に入りつつあり、APIが安定した段階で移行する予定です。
以上の設定で、OTel Collectorを立てています。
2-4. アプリケーションにてExport先を設定する
実際のアプリケーションコードは複雑なので割愛しますが、OpenTelemetryのデモ用のコードサンプルを例にすると以下のようなコードになります。
// Collectorのアドレスを設定 // http://opentelemetry-collector.opentelemetry-system.svc.cluster.local:4317 otelAgentAddr, ok := os.LookupEnv("OTEL_EXPORTER_OTLP_ENDPOINT") if !ok { otelAgentAddr = "0.0.0.0:4317" } // Metricsの設定 metricExp, err := otlpmetricgrpc.New( ctx, otlpmetricgrpc.WithInsecure(), otlpmetricgrpc.WithEndpoint(otelAgentAddr)) handleErr(err, "failed to create the collector metric exporter") meterProvider := sdkmetric.NewMeterProvider( sdkmetric.WithResource(res), sdkmetric.WithReader( sdkmetric.NewPeriodicReader( metricExp, sdkmetric.WithInterval(2*time.Second), ), ), ) otel.SetMeterProvider(meterProvider) // Traceの設定 traceClient := otlptracegrpc.NewClient( otlptracegrpc.WithInsecure(), otlptracegrpc.WithEndpoint(otelAgentAddr), otlptracegrpc.WithDialOption(grpc.WithBlock())) traceExp, err := otlptrace.New(ctx, traceClient) handleErr(err, "failed to create the collector trace exporter") bsp := sdktrace.NewBatchSpanProcessor(traceExp) tracerProvider := sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithResource(res), sdktrace.WithSpanProcessor(bsp), ) // set global propagator to tracecontext (the default is no-op). otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) otel.SetTracerProvider(tracerProvider)
OTel Trace/MetricsのGo SDKにExport先としてOTel Collectorのアドレスを渡すだけで設定は完了です。
TraceやMetricsはサンプルコードのようにして、好きな箇所で計装することができます。
// Metrics meter := otel.Meter("demo-server-meter") serverAttribute := attribute.String("server-attribute", "foo") commonLabels := []attribute.KeyValue{serverAttribute} requestCount, _ := meter.Int64Counter( "demo_server/request_counts", metric.WithDescription("The number of requests received"), ) requestCount.Add(ctx, 1, metric.WithAttributes(commonLabels...)) // Trace span := trace.SpanFromContext(ctx) bag := baggage.FromContext(ctx) var baggageAttributes []attribute.KeyValue baggageAttributes = append(baggageAttributes, serverAttribute) for _, member := range bag.Members() { baggageAttributes = append(baggageAttributes, attribute.String("baggage key:"+member.Key(), member.Value())) } span.SetAttributes(baggageAttributes...)
3. (おまけ)opentelemetry-go-instrumentationを試す
OTelではAuto Instrumentation(自動計装)という、組み込むとテレメトリーデータを自動計装してくれるライブラリを出しています。自動計装は言語毎にメカニズムが異なり、各言語毎にライブラリが作られています。
Go言語はまだ開発段階ではありますが opentelemetry-go-instrumentation が出ており、今回は開発環境で試験的に導入してみたのでその詳細について書こうと思います。
※ただし、まだ開発段階なのでLinuxディストリビューションやGoのバージョンによっては動作しない場合があります。
3-1. OpenTelemetry Operatorの利用
OpenTelemetry Operatorは、k8sクラスター内でOpenTelemetryコンポーネントを管理するoperatorです。今回は opentelemetry-go-instrumentation をSidecarで立てるためにoperatorを使います。
まずはhelmを使いリソースの定義をします。
apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization metadata: name: opentelemetry-operator namespace: opentelemetry-operator-system helmCharts: - name: opentelemetry-operator repo: https://open-telemetry.github.io/opentelemetry-helm-charts releaseName: opentelemetry-operator version: 0.x namespace: opentelemetry-operator-system valuesInline: pdb: create: true manager: featureGates: "+operator.autoinstrumentation.go" kubeRBACProxy: image: tag: v0.14.3 includeCRDs: true
featureGatesとして"operator.autoinstrumentation.go"
を有効にしておきます。
3-2. アプリケーション側の設定
アプリケーション側にはSidecarをinjectするアノテーションを付けます。
apiVersion: apps/v1 kind: Deployment metadata: name: my-app labels: app: my-app spec: selector: matchLabels: app: my-app replicas: 1 template: metadata: labels: app: my-app annotations: instrumentation.opentelemetry.io/inject-go: "true" instrumentation.opentelemetry.io/otel-go-auto-target-exe: "/path/to/container/executable"
また、OTel OperatorがアプリケーションにSidecarをInjectできるようInstrumentationのリソースを作ります。
apiVersion: opentelemetry.io/v1alpha1 kind: Instrumentation metadata: name: post-service namespace: post-service spec: exporter: endpoint: http://opentelemetry-collector.opentelemetry-system.svc.cluster.local:4317 go: image: ${AUTO_INSTRUMENTATION_IMAGE_PATH} resourceRequirements: limits: cpu: 500m memory: 2Gi requests: cpu: 500m memory: 1Gi env: - name: OTEL_EXPORTER_OTLP_ENDPOINT value: http://opentelemetry-collector.opentelemetry-system.svc.cluster.local:4317
これによりGoにてAuto Instrumentが可能になりました。
4. 今後の方針
以上により本番環境でOTel Collector経由でDatadogにExportすることができました。ただ、まだまだ課題もあるので今後の方針としていくつか挙げたいと思います。
- 可用性: Collectorの構成をAgent modeとGateway mode両方使って可用性高い構成にしたいと思っているのですが、費用面と現状のアプリケーションの規模を見て意図的に対応していません。将来的には変えていきたいと思っています。(OTel Collectorアンチパターンの記事にも記載されています)
- 機能面: 今の所、最低限のSpanしか登録していない & Attributeも上手く使いこなせていないので、目的に合わせた計装の最適化を行っていきたいと思っています。
5. まとめ
今回はGCPからDatadogへの移行、OpenTelemetry Collectorの導入、そしてopentelemetry-go-instrumentationのテスト及びOpenTelemetry Operatorの利用といった一連のステップを通じて、効率的かつ効果的なテレメトリーデータの収集と分析方法を探求しました。この記事を通して、これからOpenTelemetryを導入しようとしている人の参考になると嬉しいです。
またGaudiyとしては、前回の記事が出てから1年経ち、紆余曲折ありましたがOpenTelemetryを実運用に取り込むことができてObservabilityの向上にむけてかなり前進したのではないかと思っています。
始めた当初、DevメンバーがTrace/Metricsを見て能動的にボトルネックの改善やアプリケーション監視に役立ててくれると良いなと思っていたのですが、その兆候が社内でも見られ始めているので、今回の取り組みは本当にやって良かったと思っています。
Observablityの向上に取り組んでいる人や、OpenTelemetryに関心がある人がいたら、ぜひカジュアルにお話ししましょう。
Gaudiyではエンジニアを積極採用中なので、興味ある人はぜひこちらもご覧ください。