徒然なるままにDatadog APM

[3-shake Advent Calendar 2022](https://qiita.com/advent-calendar/2022/3-shake) の4日目です。

Datadog APMを使った監視設計をすることがあり、使い勝手が良かったため基本的な部分と設定した方がいいなと思っている事項を書いていきます。

プロファイリング機能は使いませんでしたので、本記事では対象外です。

## APM(Application Performance Management)

APMとは分散トレーシングとパフォーマンスを始めとしたその周辺データの管理と可視化を行うための機能です。例えば以下のようなデータを紐づけることができます。
- エラー率
- ログやメトリクス
- レイテンシー
- CPU・メモリの使用率

**【用語】**
Datadog APMにおける基本用語を簡単に説明します。

- スパン: 1ロジックの開始・終了の実行時間を計測する単位。スパン同士は親子関係を持たせることができます
- トレース: 複数のスパンを1つのリクエストとして取りまとめて計測する単位。トレースは1つ以上のスパンを含みます。

![Untitled.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532842/33eab574-c18a-2c6b-1671-89a4df7039fc.png)
引用: https://qiita.com/muroon/items/ddda1f20c943dcd434c5

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532842/f628ecf6-b0a3-b74d-3691-049f4e863474.png)
引用: https://docs.datadoghq.com/ja/tracing/

## 料金

[https://www.datadoghq.com/ja/pricing/?product=apm--continuous-profiler#apm--continuous-profiler](https://www.datadoghq.com/ja/pricing/?product=apm--continuous-profiler#apm--continuous-profiler)

基本はホスト単位での課金です。

- APM: $31(年間コミット)、$36(オンデマンド)
    - 取り込み無料枠: 1ホスト/150GB
    - Indexed Span無料枠:  1ホスト/100万件
- APM & Continuous Profiler: $40(年間コミット)、$48(オンデマンド)
    - APMに含まれる無料枠
    - プロファイル無料枠: 4コンテナ/1ホスト
- 追加課金
    - APMのスパン取り込み: $0.10/GB
    - APMのスパン保存: $1.70/100万件(保存期間15日)
    - プロファイルするコンテナ: $2/コンテナ
- 料金監視のためのメトリクス
    - `datadog.estimated_usage.apm_hosts`
    - `datadog.estimated_usage.apm.indexed_spans`
    - `datadog.estimated_usage.apm.ingested_bytes`
    - `datadog.estimated_usage.profiling.containers`
    
    スパイクに併せてスケールアウトしたり、アプリ実装が良からぬ方向に改修されたりすることで料金が跳ね上がる可能性があるので、これらメトリクスでパッと状況を見れるようにしておくことを推奨します。
    

## セットアップ

### Datadog agent側
* [https://docs.datadoghq.com/ja/tracing/](https://docs.datadoghq.com/ja/tracing/)
* 実行基盤ごとの設定方法でenable化する

### アプリ実装側
https://docs.datadoghq.com/ja/tracing/#datadog-%E3%81%B8%E3%83%88%E3%83%AC%E3%83%BC%E3%82%B9%E3%82%92%E9%80%81%E4%BF%A1
言語ごとにライブラリを使って設定します。
対応言語: Java/Python/Ruby/Go/NodeJS/PHP/C++/.NET Core/.NET Framework

**今回はGoでのコードを例として取り上げます。**
* ライブラリ: [https://github.com/DataDog/dd-trace-go](https://github.com/DataDog/dd-trace-go)
    * 基本的にサーバに併せてインポートして、インターセプター挿入するとよしなにやってくれます
    * よく使われる標準ライブラリやフレームワークはトレースを送るように[追加実装されたもの](https://github.com/DataDog/dd-trace-go/tree/main/contrib)が提供されているため、まずはそれを活用してみることを推奨します
        * もしくは[openTelemetry使う](https://docs.datadoghq.com/ja/tracing/trace_collection/open_standards/)とか

gRPCサーバへの実装例
```go
package main

import (
	"fmt"
	"net"

(中略)

	grpctrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/google.golang.org/grpc"
	"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"
)

func main() {

(中略)

	tracer.Start(
		tracer.WithEnv("dev"),
	)
	defer tracer.Stop()

	ddUnaryInterceptor := grpctrace.UnaryServerInterceptor(grpctrace.WithServiceName("web-service"))

	grpcServer := grpc.NewServer(
		grpcMiddleware.WithUnaryServerChain(
			ddUnaryInterceptor,
		),
	)
　　　  　grpc_health_v1.RegisterHealthServer(grpcServer, health.NewServer())
 
(中略)

 	err = grpcServer.Serve(lis)
 	if err != nil {
 		log.Fatalf("failed to serve: %v", err)
 	}
}
```
以下はMySQLでクエリをトレースする場合の実装例 

```go
package mysql

import (
(中略)
	"github.com/go-sql-driver/mysql"
	"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext"
	"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"

	sqltrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/database/sql"
)

func newConn() *Conn {
(中略)
	sqltrace.Register("mysql",
		&mysql.MySQLDriver{},
		sqltrace.WithServiceName("mysql-service"),
	)
	dsn := fmt.Sprintf(dsnFormat,
		c.MySQL.User,
		c.MySQL.Password,
		c.MySQL.Host,
		c.MySQL.Port,
		c.MySQL.DB,
	)

	conn, err := sqltrace.Open("mysql", dsn)
(中略)
}

func (conn *Conn) Get(ctx context.Context, query string, args ...interface{}) (row *sql.Row, err error) {
	span, ctx := tracer.StartSpanFromContext(ctx,
		"mysql-get",
		tracer.SpanType(ext.SpanTypeSQL),
		tracer.ServiceName("mysql-service"),
	)
	defer span.Finish(tracer.WithError(err))

	return conn.get(false, ctx, query, args...)
}
```
    

## その他設定
この設定は入れておいた方がいいだろうということをいくつかご紹介します。

### 取り込み量のサンプリング

[https://docs.datadoghq.com/tracing/trace_pipeline/ingestion_mechanisms/](https://docs.datadoghq.com/tracing/trace_pipeline/ingestion_mechanisms/?tab=java)

データをサンプリングすることで無料枠を超過するのを防いだり、データの見通しをよくすることができます。

Datadogに送信・取り込みするかどうかはライブラリとagentで設定します。

設定はagentとライブラリでできますが、**いずれも環境変数で設定することが推奨されています**

- agentで設定する場合
    - 設定ファイル(datadog.yaml)にて
        - `max_traces_per_second - integer - optional - default: 10`
        - [https://github.com/DataDog/datadog-agent/blob/main/pkg/config/config_template.yaml#L1093-L1101](https://github.com/DataDog/datadog-agent/blob/main/pkg/config/config_template.yaml#L1093-L1101)
    - 環境変数にて
        - `DD_APM_MAX_TPS - integer - optional - default: 10`
- ライブラリで設定する場合
    - 各言語ごとの設定に従ってください
    - 大体の言語で以下の環境変数を設定することで実現できます
    
    ```
    @env DD_TRACE_SAMPLE_RATE=0.1
    @env DD_TRACE_SAMPLING_RULES=[{"service": `my-service`, "sample_rate": 0.5}]
    ```
    

### トレースとログの紐付け

[https://docs.datadoghq.com/ja/tracing/connect_logs_and_traces/](https://docs.datadoghq.com/ja/tracing/connect_logs_and_traces/)

トレースとログを紐づけておくことで、トレースをもとに該当のログに素早くアクセスすることができるため調査や分析での効率がアップします。

負荷試験中のボトルネック調査やエラーログの調査の効率が上がるので併せて設定しておくことを推奨します。

言語ごとのライブラリの設定に従って実装することで実現できます。

### Istioの通信可視化

[https://docs.datadoghq.com/ja/tracing/setup_overview/proxy_setup/?tab=istio](https://docs.datadoghq.com/ja/tracing/setup_overview/proxy_setup/?tab=istio)

Istioでサービスメッシュを組んでいるk8sクラスタの場合は、トレーシングを設定することで、スパンの親子関係をよしなにやってくれるのでトレースの構築が楽になります。

またData planeもスパンに含めて可視化することができます。

公式に沿ってIstioインストール時にTracingのフラグを立てるだけでした。

## まとめ
分散システムを扱うことはごく当たり前になった昨今で監視やデバッグに必須の分散トレースですが、Datadogだと導入が比較的楽でかつ必要十分な機能を備えています。
ぜひ検討の候補に入れてみてはいがでしょうか？

明日は[@tayakun](https://qiita.com/tayakun)のターンです。引き続きお楽しみに！