Datadog へのトレースデータの送信に OpenTelemetry Collector を使ってみる
Japan Datadog User Group #12 で発表した内容についての技術的な補足記事です。
当日の発表資料はこちら。
はじめに
Datadog へのトレースデータの送信に Datadog が提供している公式 SDK ではなく OpenTelemetry ベースの計装で OpenTelemetry Collector を使って送信してみます。
Datadog 公式の SDK ではなく、あえて OpenTelemetry ベースの計装にすることで、将来的に他のオブザーバビリティツールに切り替えたり、ローカル開発環境だけ別のツールを利用したい場合でもアプリケーション側の変更を最小限に抑えられるメリットがあります。
OpenTelemetry Collector としては Datadog が公式に提供している OpenTelemetry Collector のディストリビューション Datadog Distribution of OpenTelemetry Collector ( 以下、 DDOT ) を利用します。
DDOT の説明や選定基準については上記の発表スライドをご覧ください。
ゴール
OpenTelemetry Collector を使って Datadog にトレースデータを送信することをゴールとします。
前提
アプリケーションは Go で実装され、 Kubernetes で動作していることを前提とします。
Datadog Container Agent は Datadog Operator を使って Kubernetes 上にデプロイされていることを前提とします。
設定手順
Datadog Operator の設定
まずは Datadog Operator を DDOT に対応しているバージョン ( v1.15.0 以上 ) にアップデートします。
以下の Helm レポジトリを利用します。
https://helm.datadoghq.com
image:
repository: gcr.io/datadoghq/operator
tag: 1.15.0
Datadog Container Agent で DDOT を有効化
次に Datadog Container Agent の設定を行います。
Custom Resource の設定は以下の URL で確認できます。
DDOT を有効化するには以下の 2 点を設定します。
-
spec.override.nodeAgent.image.tagを7.65.0以上の-fullイメージに設定する -
spec.features.otelCollector.enabledをtrueに設定する
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 を用いて計装します。
計装方法の詳細に関しての詳細は省略しますが、以下のような実装例になります。
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
最後に
今回はあえて Datadog が公式で提供している SDK ではなく OpenTelemetry ベースの計装で OpenTelemetry Collector を使って Datadog にトレースデータを送信する方法を紹介しました。
また、今回は検証していませんが、トレース以外にもメトリクス・ログ・プロファイルも OpenTelemetry Collector 経由で送信することで OpenTelemetry ベースの計装に統一できるメリットもあります。
Discussion