yuchiの日記

キーボード以外の話を書くぞの場所

OpenTelemetryに入門する

この記事は🎄GMOペパボエンジニア Advent Calendar 2023 17日目の記事です

adventar.org

概要

最近社内やSRE関係のイベントなどでOpenTelemetryの話題を聞くようになったので「雰囲気で話を聞いている」を脱すべく色々調べた記録です。自分用のメモを公開用に編集したので文章がおかしいところがあるかもしれないですがお許しください。

OpenTelemetryは日々開発が進んでいるプロダクトです。最新の情報は各種公式ドキュメントを参考にすることをおすすめします。(この記事は2023年12月の情報を元に記事を書いています)

OpenTelemetryについて知る

OpenTelemetryはログやメトリクス、トレースのテレメトリデータを作成、管理するために設計されたObservability(可観測性)フレームワークおよびツールキットでベンダー依存ではなく共通の規格となっている。

https://opentelemetry.io/docs/what-is-opentelemetry/

Observability(可観測性)とは

ざっくり以下2点 - システムを詳細に分かってなくても何の問題が起こっているかが分かりやすくなる。 - 新たな問題のトラブルシュートが分かりやすくなる。

この状態にするにはアプリケーションコードがトレース、メトリクス、ログを出す必要がある。

https://opentelemetry.io/docs/concepts/observability-primer/#what-is-observability

OpenTelemtryの仕組み

OpenTelemetry対応させたアプリケーション上からOTel Collectorを通してGrafanaやJaegerなどに送るような流れになっている。

今までの各プラットフォームのAgentが担っていた箇所をOTel Collectorで置き換えるようなイメージ。設定次第で併用もできる。

引用: https://opentelemetry.io/docs/

実際に触ってみる

公式ドキュメントにはGoの1~6の数字をブラウザ上に出すサンプルコードが示されているのでそれを参考にブラウザでアクセスすると標準出力でトレース情報が吐き出されるようにします。

opentelemetry.io

吐き出されたトレース情報

{
        "Name": "roll",
        "SpanContext": {
                "TraceID": "1b256e3bcf583dcb2f693aa5b9f135be",
                "SpanID": "3314466bc6b8d377",
                "TraceFlags": "01",
                "TraceState": "",
                "Remote": false
        },
        "Parent": {
                "TraceID": "1b256e3bcf583dcb2f693aa5b9f135be",
                "SpanID": "7655ef934c2b3460",
                "TraceFlags": "01",
                "TraceState": "",
                "Remote": false
        },
        "SpanKind": 1,
        "StartTime": "2023-12-08T03:39:46.663242+09:00",
        "EndTime": "2023-12-08T03:39:46.664132+09:00",
        "Attributes": [
                {
                        "Key": "roll.value",
                        "Value": {
                                "Type": "INT64",
                                "Value": 6
                        }
                }
        ],
        "Events": null,
        "Links": null,
        "Status": {
                "Code": "Unset",
                "Description": ""
        },
        "DroppedAttributes": 0,
        "DroppedEvents": 0,
        "DroppedLinks": 0,
        "ChildSpanCount": 0,
        "Resource": [
                {
                        "Key": "service.name",
                        "Value": {
                                "Type": "STRING",
                                "Value": "dice"
                        }
                },
                {
                        "Key": "service.version",
                        "Value": {
                                "Type": "STRING",
                                "Value": "0.1.0"
                        }
                },
                {
                        "Key": "telemetry.sdk.language",
                        "Value": {
                                "Type": "STRING",
                                "Value": "go"
                        }
                },
                {
                        "Key": "telemetry.sdk.name",
                        "Value": {
                                "Type": "STRING",
                                "Value": "opentelemetry"
                        }
                },
                {
                        "Key": "telemetry.sdk.version",
                        "Value": {
                                "Type": "STRING",
                                "Value": "1.21.0"
                        }
                }
        ],
        "InstrumentationLibrary": {
                "Name": "rolldice",
                "Version": "",
                "SchemaURL": ""
        }
}
{
        "Name": "/",
        "SpanContext": {
                "TraceID": "1b256e3bcf583dcb2f693aa5b9f135be",
                "SpanID": "7655ef934c2b3460",
                "TraceFlags": "01",
                "TraceState": "",
                "Remote": false
        },
        "Parent": {
                "TraceID": "00000000000000000000000000000000",
                "SpanID": "0000000000000000",
                "TraceFlags": "00",
                "TraceState": "",
                "Remote": false
        },
        "SpanKind": 2,
        "StartTime": "2023-12-08T03:39:46.663201+09:00",
        "EndTime": "2023-12-08T03:39:46.664580041+09:00",
        "Attributes": [
                {
                        "Key": "http.method",
                        "Value": {
                                "Type": "STRING",
                                "Value": "GET"
                        }
                },
                {
                        "Key": "http.scheme",
                        "Value": {
                                "Type": "STRING",
                                "Value": "http"
                        }
                },
                {
                        "Key": "http.flavor",
                        "Value": {
                                "Type": "STRING",
                                "Value": "1.1"
                        }
                },
                {
                        "Key": "net.host.name",
                        "Value": {
                                "Type": "STRING",
                                "Value": "localhost"
                        }
                },
                {
                        "Key": "net.host.port",
                        "Value": {
                                "Type": "INT64",
                                "Value": 8080
                        }
                },
                {
                        "Key": "net.sock.peer.addr",
                        "Value": {
                                "Type": "STRING",
                                "Value": "::1"
                        }
                },
                {
                        "Key": "net.sock.peer.port",
                        "Value": {
                                "Type": "INT64",
                                "Value": 50016
                        }
                },
                {
                        "Key": "http.user_agent",
                        "Value": {
                                "Type": "STRING",
                                "Value": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"
                        }
                },
                {
                        "Key": "http.route",
                        "Value": {
                                "Type": "STRING",
                                "Value": "/rolldice"
                        }
                },
                {
                        "Key": "http.wrote_bytes",
                        "Value": {
                                "Type": "INT64",
                                "Value": 2
                        }
                },
                {
                        "Key": "http.status_code",
                        "Value": {
                                "Type": "INT64",
                                "Value": 200
                        }
                }
        ],
        "Events": null,
        "Links": null,
        "Status": {
                "Code": "Unset",
                "Description": ""
        },
        "DroppedAttributes": 0,
        "DroppedEvents": 0,
        "DroppedLinks": 0,
        "ChildSpanCount": 1,
        "Resource": [
                {
                        "Key": "service.name",
                        "Value": {
                                "Type": "STRING",
                                "Value": "dice"
                        }
                },
                {
                        "Key": "service.version",
                        "Value": {
                                "Type": "STRING",
                                "Value": "0.1.0"
                        }
                },
                {
                        "Key": "telemetry.sdk.language",
                        "Value": {
                                "Type": "STRING",
                                "Value": "go"
                        }
                },
                {
                        "Key": "telemetry.sdk.name",
                        "Value": {
                                "Type": "STRING",
                                "Value": "opentelemetry"
                        }
                },
                {
                        "Key": "telemetry.sdk.version",
                        "Value": {
                                "Type": "STRING",
                                "Value": "1.21.0"
                        }
                }
        ],
        "InstrumentationLibrary": {
                "Name": "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp",
                "Version": "0.46.1",
                "SchemaURL": ""
        }
}

標準出力されるだけではトレースするには難しいため、可視化するために外部のツールへ送信します。 今回はGrafana Agentを用いて先程のトレース情報をGrafana Cloudへ送り可視化してみます。

標準出力で出すコードをOTLPを用いてhttp経由で送信するように変更をします

// otel.go

func newTraceProvider(res *resource.Resource) (*trace.TracerProvider, error) {
    ctx := context.Background()
    traceExporter, err := otlptracehttp.New(
        ctx,
        otlptracehttp.WithEndpoint("grafana-agent:4318"),
        otlptracehttp.WithInsecure(),
    )

Grafana Cloudへ送信するため使用するGrafana Agentの設定ファイルを作ります。 まずはGrafana Cloudのページ(https://grafana.com/orgs/[Organization Name])から今回使用するTempoの情報を確認します。

Password:の欄にあるGenerate now.からAPI Tokenを作成するとRead権限しかないAPI Tokenになって書き込みができないので注意が必要です。気づかず2時間近く溶かしました。

https://grafana.com/orgs/[Organization Name]/access-policiesから新たにTempoへのWrite付きのAPI Tokenを作成しましょう

必要な情報が揃ったらconfig.yamlを作成します。必要な箇所は置き換えてください。

# config.yaml
traces:
  configs:
  - name: default
    receivers:
      otlp:
        protocols:
          http:
            endpoint: 0.0.0.0:4318
    remote_write:
      - endpoint: [tempo-url].grafana.net:443
        basic_auth:
          username: [UserID]
          password: [API Key]

Goで作成したアプリケーションとGrafana AgentをDockerComposeで起動できるようにDockerfileとdocker-compose.yamlを用意します。

FROM golang:1.21
WORKDIR /go-rolldice
COPY . .
RUN go mod tidy
RUN go build -o app .
CMD ["/go-rolldice/app"]
# docker-compose.yaml
version: '3'
services:
  go-rolldice:
    container_name: go-rolldice
    image: go-rolldice
    ports:
      - "8080:8080"
    build: .
    volumes:
      - .:/etc/go-rolldice/
    environment:
      - OTEL_SERVICE_NAME = go-rolldice
      - OTEL_METRICS_EXPORTER = otlp
  grafana-agent:
    container_name: grafana-agent
    image: grafana/agent:latest
    ports:
      - "4318:4318"
    volumes:
      - ./grafana-agent/config.yaml:/etc/agent/agent.yaml
    command: -config.file=/etc/agent/agent.yaml

docker-composeで起動してlocalhost:8080/rolldiceへ数回アクセスし、GrafanaのExploreからTempoを選ぶと届いているトレース情報を確認することができます。

シンプルなアプリケーションなので複雑なトレース情報ではないですが、ちゃんとroll.valueが4のトレースデータが届いて見ることができました。

まとめ

公式のドキュメントを参考にOpenTelemetryの初歩的なところを触ってみました。

OpenTelemetryはどのように動いているか、実際のアプリケーションコードへ導入するにはどのように変えていくと良いかのイメージを少しつけることができました。

なにより使用される単語が少々特殊なものが多い印象だったのでそれらの意味を理解できただけで非常に良かったです(実際CNDT2023へ行く前に調べていたのでセッションを聞きながら「この前調べたやつだ!」と思っていました)

この記事がOpenTelemetryを初めて触る方の参考になれば幸いです。