OpenTelemetryは、分散システムにおける観測データを収集するためのツール群とAPIを提供するオープンソースプロジェクトです。その目的は、アプリケーションのパフォーマンスと動作を理解し、問題を特定して解決するためのデータを提供することです。OpenTelemetryは、トレーシング、メトリクス、ログといった観測データの収集を自動化し、一貫性のある方法でこれらのデータを分析するための標準化された手段を提供します。これにより、開発者はアプリケーションのパフォーマンスを監視し、問題を特定して解決するための洞察を得ることができます。
OpenTelemetryプロジェクトの代表的なプロダクトは以下のとおりです。
- OpenTelemetry SDK
- アプリケーションにトレースデータなどを公開するためのライブラリ
- TraceやLogsなどに対応している
- OpenTelemetry Protocol(OTLP)
- メトリクス、ログ、トレースなどのテレメトリを転送するためのプロトコル
- アプリケーションにインストルメンテーションされたSDK、各種エージェント、OpenTelemetry Collectorとの間でテレメトリを転送するために利用
- OpenTelemetry Collector
- メトリクス、ログ、トレースなどの各種テレメトリの送受信やデータ処理を行うコンポーネント
- 様々なプロバイダーに対応するためにプラグイン方式を採用している
このハンズオンでは、OpenTelemetry Collectorでメトリクス・ログ・トレースを扱う方法と、OpenTelemetry SDKを用いて分散トレースを行う方法について実際に体験します。
OpenTelemetry Collectorは、内部的には4つのコンポーネントで構成されています。
- Receiver
- データを受け付けるためのコンポーネント
- プッシュベース、プルベースの両方に対応しており、さまざまなデータソースに対応
- Exporter
- データを外部へ送信するコンポーネント
- プッシュベース、プルベースの両方に対応しており、さまざまなデータソースに対応
- Processor
- データを
Receiver
で受信してから、Exporter
で送信するまでに処理を行うコンポーネント
- データを
- Connector
Exporter
とReceiver
の両方の役割をもち、パイプラインを接続するコンポーネント- あるデータ型からあるデータ型へと変換なども行うことが可能
これらのコンポーネントはプラガブルな仕組みになっています。
例えば、Receiver
ではPrometheusからメトリクスデータを取得するプラグイン・ローカルのJSONファイルからログデータを取得するプラグインなどがあったり、Exporter
ではPrometheusのRemote Write Endpointにメトリクスデータを出力するプラグイン・Lokiにログデータを出力するプラグイン・Datadogにデータを出力するプラグインなどがあります。
多岐にわたるプラグインは、主要なプラグインだけを含んでいるCoreディストリビューションと、さまざまなプラグインを含んでいるContribディストリビューションが用意されています。他にも、各ベンダーが提供しているカスタムディストリビューションも存在します。
- 「Core ディストリビューション(open-telemetry/opentelemetry-collector)」
- 「Contrib ディストリビューション(open-telemetry/opentelemetry-collector-contrib)」
opentelemetry-collector-contrib
はさまざまなプラグインが含まれておりバイナリサイズが大きいため、必要があれば自前でビルドすることが推奨されています。
各種コンポーネントで利用可能なプラグインは下記のGitリポジトリで管理されています。
- Receiver
- Exporter
- Processor
- Connector
helmfileを使用してOpenTelemetry Operatorをインストールします。
OpenTelemetry Operatorをインストールすることで、OpenTelemetry CollectorなどをKubernetesのリソースで管理することができます。
ValidatingWebhookの兼ね合いでhelmfile sync
が失敗することがあるため、失敗した場合は再度実行してください。
helmfile sync -f helm/helmfile.yaml
monitoring NamespaceにデプロイされたOpenTelemetry OperatorのPodが起動していれば成功です。
kubectl -n monitoring get pods
# 実行結果
NAME READY STATUS RESTARTS AGE
opentelemetry-operator-677b4d8655-6q5j5 2/2 Running 0 20h
OpenTelemetry Collectorは、メトリクス・ログ・トレースなどのテレメトリの送受信やデータ処理を行うことができるコンポーネントです。 CollectorはDeploymentやDaemonSetとしてデプロイし、その上でさまざまなデータソースからデータを取得し、任意の処理を行った上で、さまざまなデータソースに出力します。
OpenTelemetry Collectorは、こうしたデータ処理のパイプライン定義を設定ファイルで記述します。 下記は設定ファイルの例です。 設定ファイルは主に、下記の4つから構成されています。
- receivers
- processors
- exporters
- service
receivers
processors
exporters
には、それぞれ利用するプラグインの設定を書いていきます。
今回の例だと hostmetrics
receiver・debug
exporter・prometheusremotewrite
exporterの3種類を利用するために設定を行っています。
設定したreceiverなどは、service.pipelinesのところで利用する設定をします。
receiverやexporterによっては、メトリクス・ログ・トレースなどの複数テレメトリをサポートしている場合もあるため、service.pipelines.metrics
やservice.pipelines.logs
のように、どのテレメトリに対して利用するのかを指定する必要があります。
なお、対応していないテレメトリに対してプラグインを利用する設定を書いた場合、OpenTelemetry Collectorが起動しません。
receivers:
hostmetrics:
collection_interval: 30s
scrapers:
cpu: {}
memory: {}
processors: {}
exporters:
debug: {}
prometheusremotewrite:
endpoint: "https:/kube-prometheus-stack-prometheus.prometheus.svc:8080/prometheus/remote/write"
external_labels:
oteltest: cndt2023
service:
pipelines:
metrics:
receivers: [hostmetrics]
processors: []
exporters: [debug, prometheusremotewrite]
Open Telemetryの設定ファイルに関しては、公式サイトに情報がまとまっています。また、利用可能なプラグインについては、先ほど「CoreプラグインとContribプラグイン」で紹介しているのでそちらを参照してください。
最初にログをOpenTelemetry Collectorで扱う例を紹介します。
ログを取り扱う場合は、ちょうど Fluentd
やFluent Bit
と同じようなものだと想像してもらうとイメージしやすいかもしれません。
今回は、/var/log
ディレクトリにあるcndt-*.json
にマッチするログをfilelog
Receiverで取得し、JSON形式でパースした後、デバッグログとして出力しながら、file
Exporterでまとめてファイルとしても出力する簡単な例で試してみます。
OpenTelemetry Collectorをデプロイするには、OpenTelemetryCollectorリソースをKubernetesに登録します。 これにより、OpenTelemetry Operatorが自動的にDeploymentやDaemonSetを作成します。
今回はログエージェントとして動作させる想定なため、.spec.mode
にはdaemonsetを指定し、DaemonSetとして起動します。また、起動するPodにはホスト上の/var/log
をマウントしておきたいので、volumeMounts
に関する設定もしておきます。
最後に、先ほど紹介したOpenTelemetry Collectorの設定を.spec.config
に行っておきます。
apiVersion: opentelemetry.io/v1alpha1
kind: OpenTelemetryCollector
metadata:
name: log-collector
spec:
mode: "daemonset"
volumeMounts:
- name: host-log-volumes
mountPath: /var/log
volumes:
- name: host-log-volumes
hostPath:
path: /tmp
config: |
receivers:
filelog:
include: [ /var/log/cndt-*.json ]
operators:
- type: json_parser
exporters:
debug:
verbosity: detailed
file:
path: /var/log/all.json
rotation:
max_megabytes: 1
service:
pipelines:
logs:
receivers: [filelog]
processors: []
exporters: [file, debug]
OpenTelemetryCollectorリソースをデプロイすると、自動的にDaemonSetとPodが起動していることがわかります。
kubectl apply -f manifests/log-collector.yaml
# 実行結果
opentelemetrycollector/log-collector created
kubectl get opentelemetrycollector
# 実行結果
NAME MODE VERSION READY AGE IMAGE MANAGEMENT
log-collector daemonset 0.87.0 1/1 18h otel/opentelemetry-collector-contrib:0.87.0 managed
kubectl get daemonsets,pods -l app.kubernetes.io/name=log-collector-collector
# 実行結果
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
daemonset.apps/log-collector-collector 2 2 2 2 2 <none> 18h
NAME READY STATUS RESTARTS AGE
pod/log-collector-collector-pwl6p 1/1 Running 0 18h
pod/log-collector-collector-gsd8a 1/1 Running 0 18h
次に、実際にログをホスト上のディレクトリに書き込んでみましょう。
今回はホスト上の/var/log/cndt-*.json
のログをまとめてくれるようになっているため、試しに下記の通り書き込んでみます。
sudo docker exec -it kind-worker sh -c "echo '{\"key1\": \"value1\", \"key2\": \"value2\"}' >> /tmp/cndt-1.json"
その後、file
Exporterによって出力されたファイルを見てみると、確かにReceiverで受け取ったログがExporterで出力されていることが確認できます。
sudo docker exec -it kind-worker sh -c "cat /tmp/all.json | jq ."
# 実行結果
{
"resourceLogs": [
{
"resource": {},
"scopeLogs": [
{
"scope": {},
"logRecords": [
{
"observedTimeUnixNano": "1698769057617769597",
"body": {
"stringValue": "{\"key1\": \"value1\", \"key2\": \"value2\"}"
},
"attributes": [
{
"key": "key1",
"value": {
"stringValue": "value1"
}
}
{
"key": "key2",
"value": {
"stringValue": "value2"
}
},
{
"key": "log.file.name",
"value": {
"stringValue": "cndt-1.json"
}
},
],
"traceId": "",
"spanId": ""
}
]
}
]
}
]
}
/tmp/cndt-2023.json
など、正規表現にマッチするさまざまなファイル名に対してログを出力して/tmp/all.json
が書き換わることも確認してみてください。
また、余裕がある方は各種Receiver・ExporterのDocsを確認し、さまざまな処理やオプションを試してみてください。
- 利用しているReceiver
- 利用しているExporter
今回は簡単な例でしたが、syslogやFluentdなどさまざまなデータを受け取ってフィルタリングやラベルの追加処理を実施後、Lokiに転送しつつ、S3にgzip圧縮して送るといった複雑なユースケースにも対応することができます。
次にメトリクスをOpenTelemetry Collectorで扱う例を紹介します。
今回は、hostmetrics
Receiverを利用し、ホスト上のCPUやメモリに関するデータを取得し、そのデータをprometheusremotewrite
Exporterを利用してPrometheusに対してRemote Writeで書き込みを行ってみます。
ログの時と同様に、OpenTelemetryCollectorリソースを利用してKubernetesにデプロイします。
今回はホスト上のメトリクスを取得するエージェントとして動作させる想定なため、.spec.mode
にはdaemonsetを指定し、DaemonSetとして起動します。
最後に、OpenTelemetry Collectorの設定を.spec.config
に行っておきます。
apiVersion: opentelemetry.io/v1alpha1
kind: OpenTelemetryCollector
metadata:
name: metrics-collector
spec:
mode: "daemonset"
config: |
receivers:
hostmetrics:
collection_interval: 30s
scrapers:
cpu: {}
memory: {}
processors: {}
exporters:
debug: {}
prometheusremotewrite:
endpoint: http://kube-prometheus-stack-prometheus.prometheus:9090/api/v1/write
external_labels:
oteltest: cndt2023
service:
pipelines:
metrics:
receivers: [hostmetrics]
processors: []
exporters: [debug, prometheusremotewrite]
OpenTelemetryCollectorリソースをデプロイすると、自動的にDaemonSetとPodが起動していることがわかります。
kubectl apply -f manifests/metrics-collector.yaml
# 実行結果
opentelemetrycollector/metrics-collector created
kubectl get opentelemetrycollector metrics-collector
# 実行結果
NAME MODE VERSION READY AGE IMAGE MANAGEMENT
metrics-collector daemonset 0.87.0 1/1 44m otel/opentelemetry-collector-contrib:0.87.0 managed
kubectl get daemonsets,pods -l app.kubernetes.io/name=metrics-collector-collector
# 実行結果
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
daemonset.apps/metrics-collector-collector 2 2 2 2 2 <none> 3m24s
NAME READY STATUS RESTARTS AGE
pod/metrics-collector-collector-8dvss 2/2 Running 0 3m24s
pod/metrics-collector-collector-mzd9j 2/2 Running 0 3m24s
まずはdebug
Exporterが出力しているログを確認してみます。
下記のようにログを確認すると、2つのメトリクスで合計166個のデータポイントが取得されていることが確認できます。
メトリクス名まで確認したい場合には、OpenTelemetryCollectorのspec.config
のexporters.debug.verbosity
をdetailed
に設定することで確認することができます。
kubectl logs -l app.kubernetes.io/name=metrics-collector-collector -f
# 実行結果
2023-11-12T20:08:46.171Z info MetricsExporter {"kind": "exporter", "data_type": "metrics", "name": "debug", "resource metrics": 2, "metrics": 2, "data points": 166}
2023-11-12T20:09:16.171Z info MetricsExporter {"kind": "exporter", "data_type": "metrics", "name": "debug", "resource metrics": 2, "metrics": 2, "data points": 166}
2023-11-12T20:09:46.171Z info MetricsExporter {"kind": "exporter", "data_type": "metrics", "name": "debug", "resource metrics": 2, "metrics": 2, "data points": 166}
...
次に、実際にGrafana上からメトリクスを確認してみましょう。
https://grafana.example.com/explore
に接続し、system_cpu_time_seconds_total
のメトリクスを確認してみます。
今回利用しているhostmetrics
Receiverで取得しているメトリクスにはHost Metrics Receiverのページから確認することができます。
exporters.prometheusremotewrite.external_labels
の設定でoteltest=cndt2023
のラベルを付与しているため、ラベルの指定をすることでOpenTelemetry Colelctorが出力したメトリクスのみに絞ることも可能です。
実際に出力されたメトリクスを確認すると、CPUの使用内訳(state)やユニット(cpu)などのラベルが付与された状態でメトリクスが書き込まれていることが確認できます。
- 利用しているReceiver
- 利用しているExporter
今回は簡単な例でしたが、OpenMetrics互換のエンドポイントからデータを取得し、Prometheus・Datadog・PubSubにデータをそれぞれ転送するといったユースケースにも対応することができます。
また、ScrapeしてRemote Writeするといったユースケースでは、Prometheusの代替にするためにprometheusreceiver
といったプラグインを利用することもできます。
TraceをOpenTelemetryで管理するハンズオンを進める前に、TraceバックエンドであるJaegerをインストールします。
Jaeger Operator自体はすでにインストールしているため、Jaeger
リソースを利用してデプロイします。
今回は簡略化のために、永続化はしない設定を利用します。
apiVersion: jaegertracing.io/v1
kind: Jaeger
metadata:
name: jaeger
namespace: jaeger
spec:
strategy: allInOne
allInOne:
options:
log-level: debug
storage:
type: memory
options:
memory:
max-traces: 100000
ingress:
enabled: true
hosts:
- jaeger.example.com
ingressClassName: nginx
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "false"
実際にJaegerをデプロイします。
kubectl apply -f manifests/jaeger.yaml
デプロイされたJaegerリソースを確認し、ステータスがRunningなことを確認してください。
kubectl -n jaeger get jaeger
# 実行結果
NAME STATUS VERSION STRATEGY STORAGE AGE
jaeger Running 1.49.0 allinone memory 10m
デプロイされたJaegerのUIは、https://jaeger.example.com/search
から確認することができます。
今回は、otlp
Receiverを利用し、OpenTelemetry Protocol(OTLP)を利用してトレースデータを受け取ります。そしてそのデータをotlp
Exporterを利用してJaegerに対して保存、debug
Exporterを利用して標準出力にもデバッグログを出力します。
Jaegerは以前まではjaeger
プロトコルを利用していましたが、最近はOTLPにネイティブ対応しています。
ログの時と同様に、OpenTelemetryCollectorリソースを利用してKubernetesにデプロイします。
今回はトレースデータの取得先ホスト上のメトリクスを取得するエージェントとして動作させる想定なため、.spec.mode
にはdaemonsetを指定し、DaemonSetとして起動します。
最後に、OpenTelemetry Collectorの設定を.spec.config
に行っておきます。
apiVersion: opentelemetry.io/v1alpha1
kind: OpenTelemetryCollector
metadata:
name: trace-collector
spec:
mode: "daemonset"
config: |
receivers:
otlp:
protocols:
grpc:
processors: {}
exporters:
debug: {}
otlp:
endpoint: jaeger-collector.jaeger:4317
tls:
insecure: true
service:
pipelines:
traces:
receivers: [otlp]
processors: []
exporters: [debug, otlp]
OpenTelemetryCollectorリソースをデプロイすると、自動的にDaemonSetとPodが起動していることがわかります。 また、OTLPを受け付ける口として、Serviceも作成されています。
kubectl apply -f manifests/trace-collector.yaml
# 実行結果
opentelemetrycollector/trace-collector created
kubectl get opentelemetrycollector trace-collector
# 実行結果
NAME MODE VERSION READY AGE IMAGE MANAGEMENT
trace-collector daemonset 0.87.0 10m managed
kubectl get daemonset,pods,services -l app.kubernetes.io/name=trace-collector-collector
# 実行結果
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
daemonset.apps/trace-collector-collector 2 2 2 2 2 <none> 28m
NAME READY STATUS RESTARTS AGE
pod/trace-collector-collector-csr9k 1/1 Running 0 10m
pod/trace-collector-collector-rvx7w 1/1 Running 0 10m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/trace-collector-collector ClusterIP 10.96.157.88 <none> 4317/TCP 28m
service/trace-collector-collector-headless ClusterIP None <none> 4317/TCP 28m
OpenTelemetry Operatorには、各種アプリケーションのトレースデータを自動インスツルメンテーションするためのInstrumentation
リソースが用意されています。
トレースデータを計装する場合には、SDKを利用してアプリケーションに手動インスツルメンテーションするか、サイドカーとしてコンテナをデプロイして自動インスツルメンテーションするかの2通りの方法があります。
今回は、アプリケーションに手を加えなくて利用可能な自動インスツルメンテーションを利用します。
自動インスツルメンテーションを行う場合には、Instrumentation
リソースで設定を行います。今回はデータを取得し、OpenTelemetryCollectorを経由してJaegerのOTLP用のgRPCエンドポイント jaeger-collector.jaeger:4317
に対してデータを転送する設定を行います。
apiVersion: opentelemetry.io/v1alpha1
kind: Instrumentation
metadata:
name: go-instrumentation
namespace: handson
spec:
exporter:
endpoint: http://trace-collector-collector.default:4317
propagators:
- tracecontext
- baggage
sampler:
type: parentbased_traceidratio
argument: "1"
kubectl apply -f manifests/go-instrumentation.yaml
自動インスツルメンテーションを利用するには、Podのアノテーションにinstrumentation.opentelemetry.io/inject-go
などの言語ごとの有効化設定を行います。
なお、Goの場合にはeBPFを利用して計装するために、追加で実行しているバイナリをアノテーションで指定する必要があります。
今回の例ではrootディレクトリ直下にある/rollouts-demo
を実行しているため、下記のような指定をする必要があります。
apiVersion: apps/v1
kind: Deployment
metadata:
name: handson-blue
namespace: handson
spec:
template:
metadata:
annotations:
instrumentation.opentelemetry.io/inject-go: go-instrumentation
instrumentation.opentelemetry.io/otel-go-auto-target-exe: "/rollouts-demo"
spec: {...}
今回はすでにデプロイされているhandson-blue
Deploymentに対して設定を行います。kubectl patchコマンドを利用してこれらのアノテーションを付与します。
kubectl -n handson patch deployment handson-blue -p '{"spec":{"template":{"metadata":{"annotations":{"instrumentation.opentelemetry.io/inject-go": "go-instrumentation"}}}}}'
kubectl -n handson patch deployment handson-blue -p '{"spec":{"template":{"metadata":{"annotations":{"instrumentation.opentelemetry.io/otel-go-auto-target-exe": "/rollouts-demo"}}}}}'
作成されたPodを確認すると、Podにサイドカーとしてopentelemetry-auto-instrumentation
コンテナが含まれた形で作成されていることが確認できます。
kubectl -n handson get pods
# 実行結果
NAME READY STATUS RESTARTS AGE
sample-app-blue-5fb8dc75fd-7cvxg 2/2 Ready 0 30s
次に、実際にJaeger上からメトリクスを確認してみましょう。
まず、http://app.example.com/
に接続し、一定量のトレースデータを出力します。
http://jaeger.example.com/explore
に接続し、Service名にsample-app-blue
を指定してみると、トレースデータが確認できます。
今回は複雑なマイクロサービスではないため、シンプルな表示になっていますが、サービス間の通信がある場合はもう少し複雑なトレースデータを確認することができます。
最後にこの章でアプリケーションに加えた変更をクリーンアップしておきます。
kubectl -n handson patch deployment handson-blue -p '{"spec":{"template":{"metadata":{"annotations":{"instrumentation.opentelemetry.io/inject-go": null}}}}}'
kubectl -n handson patch deployment handson-blue -p '{"spec":{"template":{"metadata":{"annotations":{"instrumentation.opentelemetry.io/otel-go-auto-target-exe": null}}}}}'
- 利用しているReceiver
- 利用しているExporter
OpenTelemetryでは、OpenTelemetry Protocol(OTLP)を利用して、OpenTelemetry CollectorからOpenTelemetry Collectorへテレメトリデータを転送することもできます。 複数のノードからデータを取得し、サンプリングするAggregator層を挟むなど、処理を中継させたりすることなども可能です。
今回の例ではProcessorは利用しませんでしたが、Processorも非常に重要なコンポーネントの1つです。 例えば、大規模な環境で多くのテレメトリデータが出力される場合、それらのデータを適切に処理するにはProcessorが必要不可欠です。 Prosessorではメモリ制限・サンプリング・バッチ処理などを行うこともできるため、そうした処理を間に挟むことで負荷を削減することもできます。
推奨されるProcessorについては、Docsを確認してください。
以下の手順でJava向けにInstrumentation
リソースの設定を行います。
テレメトリーの経路はGoの場合と同様です。
apiVersion: opentelemetry.io/v1alpha1
kind: Instrumentation
metadata:
name: java-instrumentation
namespace: handson
spec:
exporter:
endpoint: http://trace-collector-collector.default:4317
propagators:
- tracecontext
- baggage
sampler:
type: parentbased_traceidratio
argument: "1"
kubectl apply -f manifests/java-instrumentation.yaml
サンプルのJavaアプリをデプロイします。 今回はDeploymentにアノテーションが付いているいるのでpatchは不要です。
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend
namespace: handson
spec:
replicas: 1
selector:
matchLabels:
app: backend
template:
metadata:
labels:
app: backend
annotations:
instrumentation.opentelemetry.io/inject-java: "java-instrumentation"
spec: {...}
kubectl apply -f manifests/deployment.yaml
作成されたPodを確認すると、bff-xxx
とbackend-xxx
が立ち上がっています。
kubectl -n handson get pods
# 実行結果
NAME READY STATUS RESTARTS AGE
backend-85cd7fc5c7-dlfjx 1/1 Running 0 46m
bff-fcd8c68dc-qf2cj 1/1 Running 0 29m
サンプルのアプリに対応したService
, Ingress
をデプロイします。
kubectl apply -f manifests/service.yaml
kubectl apply -f manifests/ingress.yaml
kubectl -n handson get svc,ingress
# 実行結果
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/backend ClusterIP 10.96.144.18 <none> 8080/TCP 3h54m
service/bff ClusterIP 10.96.152.92 <none> 8080/TCP 3h54m
service/handson ClusterIP 10.96.31.46 <none> 8080/TCP 29d
NAME CLASS HOSTS ADDRESS PORTS AGE
ingress.networking.k8s.io/app-ingress-by-nginx nginx app.example.com 10.96.225.194 80 163m
ingress.networking.k8s.io/java-ingress-by-nginx nginx app.example.com 10.96.225.194 80 3h54m
次に、実際にJaeger上からメトリクスを確認してみましょう。
まず、http://app.example.com/bff
に接続し、一定量のトレースデータを出力します。
http://jaeger.example.com/explore
に接続し、Service名にbff
を指定してみると、トレースデータが確認できます。