Alloyを使ったオブザーバビリティを試してみた
はじめに
こんにちは!SREエンジニアのKoseです!
Grafanaに最近ハマっている私ですが、Grafana Alloyってご存知ですか?
私は最近知ったのですが、Promtailの後継となる製品のようです。
そこで、今回はAlloyを使ったオブザーバビリティの基礎をまとめていきたいと思います。
(ちょっと長くなりそうだったので、ログとトレース情報に関してこの記事では説明しようと思います。)
前提知識と環境
- 基本的なLinuxコマンドの知識
- Kubernetesの基本的な操作
- オブザーバビリティの基本的な概念
- Kubernetesクラスタが構築されていること(この記事ではEKSで構築してます。)
- Prometheus, Grafanaが環境に実装されていること
利用する技術
| 技術 | 説明 | バージョン |
|---|---|---|
| Grafana Loki | ログ管理システム | 3.4.2 |
| Grafana tempo | 分散トレーシングバックエンド | 2.7.1 |
| Grafana Alloy | テレメトリコレクターエージェント | v4.0.1 |
| Grafana | 可視化ツール | 11.6.1 |
| Prometheus | メトリック収集 | v3.3.1 |
| EKS | コンテナオーケストレーションプラットフォーム | v1.31 |
| Helm | Kubernetesパッケージマネージャー | v3.17.3 |
本文
Grafana Alloyについて
Grafana Alloyは、Grafana社が開発したOSSのデータ収集ツールで、さまざまなシステムからログやトレースを効率的に集めることができます。OpenTelemetry技術をベースにしているため、特定のベンダーに依存せず、多様なシステムと連携できる柔軟性が特徴です。
Alloyの主な機能
Alloyには以下の3つの主要機能があります:
Collect
様々なコンポーネントを使用して、アプリケーション、データベース、Opentelemetryコレクターからテレメトリデータを収集します。
テレメトリデータは AlloyにPushすることも、AlloyがデータソースからPullすることもできるので、様々なテレメトリデータを取得することが可能です。
また、トレースデータに関しては、計装(アプリケーションコードにトレース収集機能を組み込むこと)が必要ですので、Opentelemetry SDKなどで計装を実装してください。
Transform
データを処理し、ラベル、メタデータを挿入したり、不要なデータを除外したりできます。
Write
OpenTelemetry 互換のデータベースまたはコレクター、Grafana スタック、または Grafana Cloud にデータを送信します。
Grafana Tempoについて
Grafana Tempoは、Grafana Labsが開発した高スケーラブルな分散トレーシングバックエンドです。Tempoは、Grafana AlloyやOpentelemetry Collectorなどから送られてくるトレースデータをコスト効率の高いオブジェクトストレージ(S3、GCS、Azure Blobなど)に保存し、トレースIDにより検索することに特化しています。
アーキテクチャはLokiと似たところがあり、比較的にわかりやすいマイクロサービスとなっているかと思います。
興味のある方は公式Documentをご確認ください
今回の検証
さぁ、ここからは手を動かしていきましょう!今回の検証ではログデータと、トレースデータを収集するところを実施したいと思います。
以下の図は今回構築する環境の全体像です。

検証環境構成図
| 主要機能 | 今回の検証で試すこと |
|---|---|
| Collect | Kubernetes Podからログ、トレースデータを取得 |
| Transform | Kubernetes用のラベルを付与 |
| Write | Loki, Tempoにデータを送信 |
インストール手順
Alloyのインストール手順
ここでは、Alloyの基本的なインストール方法を説明します。インストールが完了するとAlloyはKubernetesのDaemonSetとしてデプロイされます。
それでは、Grafana Alloyをインストールしてみましょう。
1. リポジトリ追加
helm repo add grafana https://grafana.github.io/helm-charts
2. Values.yamlの取得
helm show values grafana/alloy > values.yaml
3. Values.yamlを変更
Alloyの設定には関しては後ほど説明しますのでこちらでは割愛します。values.yamlを設定しなくても、起動自体はできます。
4. インストール
helm upgrade --install alloy grafana/alloy --create-namespace --namespace alloy -f values.yaml
インストールの確認
以下のコマンドで、AlloyのPodが正常に起動していることを確認できます:
kubectl get pods -n alloy
正常にインストールできている場合、以下のような出力が表示されます:
NAMESPACE NAME READY STATUS RESTARTS AGE
alloy alloy-2xx77 2/2 Running 0 3h13m
alloy alloy-b6qhq 2/2 Running 0 3h13m
alloy alloy-r2tbp 2/2 Running 0 3h13m
alloy alloy-z4rq5 2/2 Running 0 3h13m
Tempoのインストール手順
Grafana Tempoには2種類のHelmインストールがありますが、今回はtempo-distributedを使用してます。
tempo-distributed Helm chart : microservices modeでデプロイするときに利用
tempo Helm chart : monolithic (single binary) modeでデプロイするときに利用
続いて、Tempoをインストールしてみます。
1. リポジトリ登録
helm repo add grafana https://grafana.github.io/helm-charts
2. valuesの取得
helm show values grafana/tempo-distributed > values.yaml
3. Values.yamlを変更
values.yamlは以下のように設定することで, S3にTraceデータを格納できるようなtempoをインストールすることができます。
当然、S3を利用する場合は、事前にS3バケットを用意しておき、irsaなどによりAWSのロールをKubernetesのServiceAccountに紐づける必要があります。
(Lokiの時と同じ設定なのでAWS側の設定に関してはこちらを参照ください)
serviceAccount:
create: true
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::<AccoutID>:role/tempo-irsa-role
ingester:
persistence:
enabled: true
storageClass: gp2
size: 10Gi
metricsGenerator:
enabled: true
## トレースデータを受け付ける窓口
distributor:
config:
log_received_spans:
enabled: true
log_discarded_spans:
enabled: true
## otlpを受け付けることにより,デフォルト4318/TCP,4317/TCPが有効になります
traces:
otlp:
http:
enabled: true
grpc:
enabled: true
storage:
trace:
backend: s3
s3:
bucket: test-tempo-traces-bucket
endpoint: s3.ap-northeast-1.amazonaws.com
region: ap-northeast-1
4. インストール
helm upgrade --install tempo grafana/tempo-distributed --create-namespace -n tempo -f values.yaml
以下のコマンドで、tempoのPodが正常に起動していることを確認できます:
kubectl get pods -n tempo
正常にインストールできている場合、以下のような出力が表示されます:
NAMESPACE NAME READY STATUS RESTARTS AGE
tempo tempo-compactor-6567d999-5gk5b 1/1 Running 0 22h
tempo tempo-distributor-75cc847cfd-mhnb2 1/1 Running 0 22h
tempo tempo-ingester-0 1/1 Running 0 21h
tempo tempo-ingester-1 1/1 Running 0 21h
tempo tempo-ingester-2 1/1 Running 0 22h
tempo tempo-memcached-0 1/1 Running 0 22h
tempo tempo-metrics-generator-6d94b7fdf6-fmc8x 1/1 Running 0 22h
tempo tempo-querier-64b754d5d7-l44wt 1/1 Running 0 22h
tempo tempo-query-frontend-784d9986f9-lmbbl 1/1 Running 0 22h
Lokiのインストール手順
Lokiのインストール手順については、以前の記事で説明しているのでそちらを参考にしてください
Alloyの設定
Alloyは、様々なコンポーネントを使ってテレメトリデータを収集します。ここでは、K8sログの取得とトレースデータの取得の例を記載します。
他にも多くデータを取得できますので、Reference探してみることをお勧めします。
K8sログの取得
Kubernetesのログ収集設定です。
# Kubernetesから全てのPod情報を検出
discovery.kubernetes "pod" {
role = "pod"
}
# 検出したPod情報にラベルを付与する設定
discovery.relabel "pod_logs" {
targets = discovery.kubernetes.pod.targets
# Kubernetes名前空間をnamespaceラベルとして追加
rule {
source_labels = ["__meta_kubernetes_namespace"]
action = "replace"
target_label = "namespace"
}
# Pod名をpodラベルとして追加
rule {
source_labels = ["__meta_kubernetes_pod_name"]
action = "replace"
target_label = "pod"
}
# コンテナ名をcontainerラベルとして追加
rule {
source_labels = ["__meta_kubernetes_pod_container_name"]
action = "replace"
target_label = "container"
}
}
# Kubernetesのログを取得してLokiに送信するための設定
loki.source.kubernetes "pod" {
targets = discovery.relabel.pod_logs.output
forward_to = [loki.process.pod_logs.receiver]
}
# loki.processは他のLokiコンポーネントからログエントリを受け取り、1つ以上の処理ステージを適用します
loki.process "pod_logs" {
stage.static_labels {
values = {
cluster = "kose-eks-welcomestudy",
}
}
forward_to = [loki.write.loki.receiver]
}
# Lokiへの送信設定
loki.write "loki" {
endpoint {
url = "http://loki-distributor.loki.svc.cluster.local:3100/loki/api/v1/push"
}
}
トレースデータの取得
OpenTelemetryなどで計装したアプリケーションからトレースデータを取得します。
## OTLPレシーバーの設定
## このレシーバーは、アプリケーションからのトレースデータを受信します
otelcol.receiver.otlp "default" {
grpc {} ## gRPCプロトコルでの受信を有効化
http {} ## HTTPプロトコルでの受信も有効化
output {
traces = [otelcol.processor.k8sattributes.default.input] ## 受信したトレースをK8s属性プロセッサに転送
}
}
## Kubernetes属性プロセッサの設定
## このプロセッサは、トレースデータにKubernetes関連の属性を追加します
otelcol.processor.k8sattributes "default" {
extract {
metadata = [
"k8s.namespace.name", ## Kubernetes名前空間
"k8s.pod.name", ## Pod名
"k8s.container.name", ## コンテナ名
]
}
output {
traces = [otelcol.exporter.otlp.default.input] ## 属性が追加されたトレースをOTLPエクスポーターに転送
}
}
## OTLPエクスポーターの設定
## このエクスポーターは、処理されたトレースデータをTempoに転送します
otelcol.exporter.otlp "default" {
client {
endpoint = "tempo-distributor.tempo.svc.cluster.local:4317" ## Tempoのエンドポイント
tls {
insecure = true ## 本番環境ではTLSを設定することをお勧めします
}
}
}
正常性の確認
Grafanaなどの可視化ツールでトレースデータを確認します:

オブザーバビリティの実装
オブザーバビリティの実装はメトリック、ログ、トレースを収集することだけでは終わりません。システムの状態を理解する上では、この3つのデータを組み合わせて扱う必要があります。
ここでは、ログとトレースを組み合わせることにより、よりシステム内部を理解できるようにします。
Trace To Logs
Trace To Logsは、トレースからログへのリンクを作成する機能です。これにより、トレースを調査しているときに関連するログをワンクリックで確認できます。
設定方法
Grafanaでトレースデータソース(Tempo)の設定に以下を追加します:
apiVersion: v1
kind: ConfigMap
metadata:
name: tempo-datasource
namespace: grafana
labels:
grafana_datasource: "true"
data:
tempo-datasource.yaml: |
apiVersion: 1
datasources:
- name: Tempo
type: tempo
url: http://tempo-query-frontend.tempo.svc.cluster.local:3100/
access: proxy
editable: true
jsonData:
lokiSearch:
datasourceUid: Loki
# いきなりV2ですが、こちらを参考にしてます。 https://grafana.com/docs/grafana/latest/datasources/tempo/configure-tempo-data-source/#example-file
tracesToLogsV2:
datasourceUid: Loki # Lokiデータソースを指定(GrafanaのLokiデータソースUID)
tags:
- key: "k8s.container.name" # Tempo trace field
value: "container" # Loki field
spanStartTimeShift: "-1m"
spanEndTimeShift: "1m"
filterByTraceID: true
filterBySpanID: false
# 深く説明はしませんがtraceとMetricsを紐づけることもできます
tracesToMetrics:
datasourceUid: 'Prometheus'
tags:
- key: "k8s.container.name"
value: "container"
spanStartTimeShift: "-10m"
spanEndTimeShift: "10m"
queries:
- name: 'Memory Usage'
query: avg(container_memory_usage_bytes{__ignore_usage__="",$$__tags} )
結果
トレースビューでログへのリンクが表示されます:

log to Traces
Log to Tracesは、ログからトレースへのリンクを作成する機能です。これにより、ログを調査しているときに関連するトレースをワンクリックで確認できます。
設定方法
Grafanaでログデータソース(Loki)の設定に以下を追加します:
apiVersion: v1
kind: ConfigMap
metadata:
name: loki-datasource
namespace: grafana
labels:
grafana_datasource: "true"
data:
loki-datasource.yaml: |
apiVersion: 1
datasources:
- name: Loki
type: loki
url: http://loki-query-frontend.loki.svc.cluster.local:3100/
access: proxy
editable: false
jsonData:
# Log to Tracesの設定
derivedFields:
- datasourceUid: 'Tempo' # Tempoデータソースの識別子
matcherRegex: 'trace_id=([a-f0-9]{32})' # ログメッセージからトレースIDを抽出する正規表現
name: 'TraceID' # UIに表示されるリンク名
url: '$${__value.raw}' # トレースビューへのリンク
今回ログには以下のようにTraceidが出るようにアプリケーションを実装しているので、matcherRegexなどをそれに合うような値で設定してます。
2025/05/21 02:48:07 Request received: path=/hello, method=GET, trace_id=4228e430a1f94dc866fb862c68189da7
結果
ログビューでトレースIDがクリック可能なリンクとして表示されます:

まとめ
このチュートリアルでは、Alloyを使用してテレメトリデータの活用方法を実装する方法を学びました。
Alloyには、これ以外にも様々なコンポーネントがあり、多くのテレメトリデータの取得が可能です。バックエンドとして、Grafana Labsの製品を利用する場合は、利用選択肢として入ってくると思いますが、独自の書き方が多いのでちょっとだけ戸惑うことがあるかもしれません。
次のステップとしてはこのようなことに挑戦していきたいと思います(今後記事にしていきます)
- 様々なコンポーネントを試してみる
- OpenTelemetry Collectorとの違いの調査
お疲れ様でした〜🎉
参考資料
備考:サンプルアプリケーション
アプリケーションを計装するには様々な方法がありますが、より実践的なものとして、OpenTelemetryを利用します。Alloy自体がOpenTelemetryのディストリビューションであるため、相性が良く、シームレスな連携が可能です。
本記事ではGoアプリケーションを例にしていますが、JavaやPythonなど他の言語でも同様のアプローチが可能です。
環境準備
まず、新しいGoプロジェクトを作成します:
mkdir my-go-app
cd my-go-app
go mod init my-go-app
必要なライブラリのインストール
OpenTelemetryとその関連コンポーネントをインストールします:
go get go.opentelemetry.io/otel
go get go.opentelemetry.io/otel/sdk/trace
go get go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc
go get go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp
以下はトレースを生成する簡単なHTTPサーバーの例です。各部分の役割を理解することが重要です:
package main
import (
"context"
"fmt"
"log"
"net/http"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
)
// トレーサーの初期化
// この関数は、OpenTelemetryトレーサーを設定し、Alloyにトレースデータを送信します
func initTracer(ctx context.Context) func() {
// OTLP gRPCエクスポーターの作成
// AlloyのエンドポイントURLを指定します。実際の環境に合わせて変更してください
exporter, err := otlptracegrpc.New(ctx,
otlptracegrpc.WithEndpoint("alloy.alloy.svc.cluster.local:4317"), // AlloyやCollectorのエンドポイント
otlptracegrpc.WithInsecure(), // 本番環境ではTLSを使用することをお勧めします
)
if err != nil {
log.Printf("failed to create exporter: %v", err)
}
// サービス名などのリソース情報を設定
// これにより、トレースデータがどのサービスから発生したかが識別できます
res, err := resource.New(ctx,
resource.WithAttributes(
semconv.ServiceName("hello-api"), // サービス名を適切に設定してください
),
)
if err != nil {
log.Printf("failed to create resource: %v", err)
}
// トレースプロバイダーの作成と設定
tp := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exporter), // トレースデータのバッチ処理
sdktrace.WithResource(res), // リソース情報の追加
)
otel.SetTracerProvider(tp)
// シャットダウン関数を返す(リソースリークを防ぐため)
return func() {
if err := tp.Shutdown(ctx); err != nil {
log.Printf("failed to shutdown TracerProvider: %v", err)
}
}
}
// Hello APIのハンドラー
// このハンドラーは、リクエストを処理し、トレースデータを生成します
func helloHandler(w http.ResponseWriter, req *http.Request) {
// 手動でspanを作成(特定の処理を詳細に計測するため)
_, span := otel.Tracer("hello-handler").Start(req.Context(), "handle-hello")
defer span.End() // 必ずspanを終了させることが重要です
// トレースIDを取得(ログとトレースの関連付けに使用)
traceID := span.SpanContext().TraceID().String()
// リクエスト情報とトレースIDをログ出力
// このログはあとでトレースと関連付けられます
log.Printf("Request received: path=%s, method=%s, trace_id=%s", req.URL.Path, req.Method, traceID)
// スパンに属性を追加(フィルタリングや詳細な分析に役立つ)
span.SetAttributes(attribute.String("custom.attribute", "hello-world"))
// 実際の処理
fmt.Fprintln(w, "Hello, World")
// レスポンス送信をログ
log.Printf("Response sent: trace_id=%s", traceID)
}
func main() {
ctx := context.Background()
shutdown := initTracer(ctx)
defer shutdown() // アプリケーション終了時にトレーサーをクリーンアップ
// 標準のHTTPハンドラーにOpenTelemetry Middlewareを追加
// これにより、すべてのHTTPリクエストが自動的にトレースされます
handler := otelhttp.NewHandler(http.HandlerFunc(helloHandler), "hello-endpoint")
http.Handle("/hello", handler)
fmt.Println("Starting server at :8080")
if err := http.ListenAndServe(":8080", nil); err != nil {
log.Printf("server error: %v", err)
}
}
アプリケーションの起動
以下のコマンドでアプリケーションを起動します:
go run ./
動作確認
アプリケーションが正常に動作しているか確認します:
curl http://localhost:8080/hello # localhostは各環境で読み替えてください
# 正常に動作していれば "Hello, World" と表示されます
Discussion