🐶

Datadog へのトレースデータの送信に OpenTelemetry Collector を使ってみる

に公開

Japan Datadog User Group #12 で発表した内容についての技術的な補足記事です。

https://datadog-jp.connpass.com/event/360923/

当日の発表資料はこちら。

https://speakerdeck.com/tetsuya28/datadog-distribution-of-opentelemetry-collector-intro

はじめに

Datadog へのトレースデータの送信に Datadog が提供している公式 SDK ではなく OpenTelemetry ベースの計装で OpenTelemetry Collector を使って送信してみます。

Datadog 公式の SDK ではなく、あえて OpenTelemetry ベースの計装にすることで、将来的に他のオブザーバビリティツールに切り替えたり、ローカル開発環境だけ別のツールを利用したい場合でもアプリケーション側の変更を最小限に抑えられるメリットがあります。

OpenTelemetry Collector としては Datadog が公式に提供している OpenTelemetry Collector のディストリビューション Datadog Distribution of OpenTelemetry Collector ( 以下、 DDOT ) を利用します。

https://docs.datadoghq.com/ja/opentelemetry/setup/ddot_collector/

DDOT の説明や選定基準については上記の発表スライドをご覧ください。

ゴール

OpenTelemetry Collector を使って Datadog にトレースデータを送信することをゴールとします。

前提

アプリケーションは Go で実装され、 Kubernetes で動作していることを前提とします。

Datadog Container Agent は Datadog Operator を使って Kubernetes 上にデプロイされていることを前提とします。

設定手順

Datadog Operator の設定

まずは Datadog Operator を DDOT に対応しているバージョン ( v1.15.0 以上 ) にアップデートします。

https://github.com/DataDog/datadog-operator/releases/tag/v1.15.0

以下の Helm レポジトリを利用します。

https://helm.datadoghq.com

values.yaml
image:
  repository: gcr.io/datadoghq/operator
  tag: 1.15.0

Datadog Container Agent で DDOT を有効化

次に Datadog Container Agent の設定を行います。

Custom Resource の設定は以下の URL で確認できます。

https://github.com/DataDog/datadog-operator/blob/main/docs/configuration.v2alpha1.md

DDOT を有効化するには以下の 2 点を設定します。

  1. spec.override.nodeAgent.image.tag7.65.0 以上の -full イメージに設定する
  2. spec.features.otelCollector.enabledtrue に設定する
datadog-container-agent.yaml
apiVersion: datadoghq.com/v2alpha1
kind: DatadogAgent
metadata:
  name: datadog
spec:
  global:
    clusterName: xxx
    tags:
      - env:prd
    credentials:
      apiSecret:
        secretName: datadog-secret
        keyName: api-key
    logLevel: warn
  override:
    clusterAgent:
      createPodDisruptionBudget: true
      replicas: 2
    nodeAgent:
      env:
        - name: DD_LOG_FORMAT_JSON
          value: "true"
      image:
        tag: 7.66.0-full
  features:
    otelCollector:
      enabled: true
      ports:
        - containerPort: 4317
          hostPort: 4317
          name: otel-grpc
      conf:
        configData: ""

この状態で Kubernetes 上にデプロイすると、 Datadog Container Agent の Pod 上に otel-agent コンテナが追加されます。

DDOT での Datadog 向けの設定

上記の Datadog の spec.features.otelCollector.conf.configData に DDOT の設定を YAML 形式で記載します。

receivers:
  otlp:
    protocols:
      grpc:
         endpoint: 0.0.0.0:4317
exporters:
  datadog:
    api:
      key: ${env:DD_API_KEY}
      site: ${env:DD_SITE}
processors:
  # 詳細は後述
  infraattributes:
    allow_hostname_override: true
  resource:
    attributes:
      - key: deployment.environment
        value: prd
        action: upsert
  batch:
    timeout: 10s
  k8sattributes:
    auth_type: serviceAccount
    passthrough: false
    pod_association:
    - sources:
        - from: connection
connectors:
  datadog/connector:
service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [resource, k8sattributes, infraattributes, batch]
      exporters: [datadog/connector, datadog]
    metrics:
      receivers: [datadog/connector]
      processors: [resource, k8sattributes, infraattributes, batch]
      exporters: [datadog]

アプリケーションへの計装

アプリケーション側に OpenTelemetry を用いて計装します。

計装方法の詳細に関しての詳細は省略しますが、以下のような実装例になります。

main.go
import (
	"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
	otelresource "go.opentelemetry.io/otel/sdk/resource"
	otelsdktrace "go.opentelemetry.io/otel/sdk/trace"
	otelsemconv "go.opentelemetry.io/otel/semconv/v1.12.0"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	"google.golang.org/grpc/keepalive"
)

func main() {
	resource := otelresource.NewWithAttributes(
		otelsemconv.SchemaURL,
	)

	sampler := newOtelCustomSampler(
		otelsdktrace.ParentBased(),
	)

	tracerOptions := []otelsdktrace.TracerProviderOption{
		otelsdktrace.WithResource(resource),
		otelsdktrace.WithSampler(sampler),
	}

	dialOptions := []grpc.DialOption{
		grpc.WithTransportCredentials(insecure.NewCredentials()),
		grpc.WithDefaultCallOptions(
			grpc.MaxCallRecvMsgSize(math.MaxInt32),
			grpc.MaxCallSendMsgSize(math.MaxInt32),
		),
		grpc.WithKeepaliveParams(keepalive.ClientParameters{
			Time:                defaultKeepaliveTime,
			Timeout:             defaultKeepaliveTimeout,
			PermitWithoutStream: false,
		}),
	}

	addr := fmt.Sprintf("%s:%s", os.Getenv("DD_OTEL_HOST"), os.Getenv("DD_OTEL_PORT"))

	conn, err := grpc.NewClient(addr, dialOptions...)
	if err != nil {
		return nil, err
	}

	exporter, err := otlptracegrpc.New(ctx, otlptracegrpc.WithGRPCConn(conn))
	if err != nil {
		return nil, err
	}
}

アプリケーションマニフェストの設定

Daemonset で稼働している Datadog Container Agent の otel-agent コンテナにトレースデータを送信するために、アプリケーションのマニフェストの環境変数に hostIP と上記 DDOT の設定に記載した OTLP のポートを設定します。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: xxx
spec:
  selector:
    matchLabels:
      name: xxx
  template:
    metadata:
      labels:
        name: xxx
    spec:
      containers:
        - name: xxx
          image: xxx
          env:
            - name: DD_OTEL_HOST
              valueFrom:
                fieldRef:
                  fieldPath: status.hostIP
            - name: DD_OTEL_PORT
              value: 4317

その他

利用上の注意点

OpenTelemetry の semconv との互換性

OpenTelemetry 側の semconv と Datadog 側の semconv を一致させるために一部追加で設定が必要な箇所があります。
本記事でも随時情報を追記していきます。

二重課金の可能性

Datadog Container Agent で DDOT を有効化すると、 Datadog Container Agent が収集するホスト名と DDOT が収集するホスト名が異なるので同一の Kubernetes ノードが別のホストとしてカウントされるので Infrastructure host が二重に課金される可能性があります。

この問題に対する対応として、 DDOT の processor に以下の設定を追加することで同一のホスト名に揃えることができます。

processors:
  infraattributes:
    allow_hostname_override: true

https://github.com/DataDog/datadog-agent/pull/35621

最後に

今回はあえて Datadog が公式で提供している SDK ではなく OpenTelemetry ベースの計装で OpenTelemetry Collector を使って Datadog にトレースデータを送信する方法を紹介しました。

また、今回は検証していませんが、トレース以外にもメトリクス・ログ・プロファイルも OpenTelemetry Collector 経由で送信することで OpenTelemetry ベースの計装に統一できるメリットもあります。

Discussion