OpenTelemetryでEC2上のLaravelアプリケーションをトレースしてみた
リテールアプリ共創部@大阪の岩田です。私事ですが最近チームの異動があり7~8年ぶりにPHPと戯れています。
その中でLaravelにOpenTelemetryのトレースを仕込む機会があったので、検証がてらEC2(Cloud9)上にLaravel Breezeの環境を作ってX-Rayでトレースできるように色々調整してみました。
環境
今回利用した環境は以下の通りです。
- PHP: 8.3.10
- 各種ライブラリ
- laravel/framework: v11.42.1
- open-telemetry/api: 1.2.2
- open-telemetry/contrib-aws: 1.0.0beta12
- open-telemetry/exporter-otlp: 1.2.0
- open-telemetry/opentelemetry-auto-laravel: 1.0.1
- open-telemetry/sdk: 1.2.2
- OpenTelemetry auto-instrumentation extension: 1.1.2
- ADOT CollectorのDockerイメージ: public.ecr.aws/aws-observability/aws-otel-collector:v0.41.2
やってみる
まずはLaravel公式ドキュメントの手順に従ってコマンドを叩いてLaravel Breezeの環境を作ります。
composer global require laravel/installer
laravel new example-app
cd example-app
npm install && npm run build
composer run dev
詳細は割愛しますが別途ALBとRDSも用意し、ALB → EC2(Cloud9) → RDSと通信できる環境を用意します。
ALB経由でLaravel Breezeの画面が表示できたら一旦OKです。
まずは自動計装を試してみる
それではさっそくトレースを仕込んでいきましょう!まずはミニマムに自動計装から試してみます。
まずはPHPのExtensionをインストールします。
pecl install opentelemetry
続いてOTEL関連のライブラリをインストールします。基本的なライブラリ類に加えて open-telemetry/opentelemetry-auto-laravel
をインストールしています。これでLaravel製のアプリが簡単に計装できるようになります。
composer require open-telemetry/api \
open-telemetry/exporter-otlp \
open-telemetry/opentelemetry-auto-laravel \
open-telemetry/sdk
続いてLaravelからトレースデータをエクスポートする先としてADOT Collectorを用意します。以下のファイルを用意し、docker compose up -d
でADOT Collectorを起動しましょう。
services:
otel-collector:
image: public.ecr.aws/aws-observability/aws-otel-collector:v0.41.2
command: ["--config", "/etc/otel-collector-config.yaml"]
volumes:
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
ports:
- '4318:4318'
バインドマウントしている設定ファイルは以下の通りです。
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
exporters:
otlphttp:
traces_endpoint: https://xray.ap-northeast-1.amazonaws.com/v1/traces
compression: gzip
auth:
authenticator: sigv4auth
extensions:
sigv4auth:
region: ap-northeast-1
service: xray
service:
extensions: [sigv4auth]
pipelines:
traces:
receivers: [otlp]
exporters: [otlphttp]
今回はX-Rayの OTLPエンドポイントにエクスポートする構成としています。X-RayのOTLPエンドポイントはTransaction Searchの有効化が必須なので、この辺りは要件に応じてotlphttp
の代わりにawsxray
を指定するなど適宜調整してください。
Laravelの.env
にOTEL関係の環境変数を設定します。
...略
OTEL_PHP_AUTOLOAD_ENABLED=true
OTEL_SERVICE_NAME=laravel
OTEL_TRACES_EXPORTER=otlp
OTEL_EXPORTER_OTLP_PROTOCOL=http/json
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://localhost:4318/v1/traces
OTEL_PROPAGATORS=baggage,tracecontext
OTEL_LOGS_EXPORTER=none
ここまでで準備完了です。Laravel Breezeを再起動して適当にアクセスしてみましょう。しばらくすると無事にX-Rayのトレース結果が確認できました。
コードの修正無しで簡単にトレースできて楽チンですね!
PHPコードを修正してメタデータを調整してみる
とりあえず自動計装はできるようになりましたが、実案件のことを考えるともう少し色々と情報が欲しくなると思います。今後の拡張を考慮し、PHPのコードでもう少し細かく設定していきます。
まずinstrument.php
というファイルを作成し、以下の内容を記述します。
<?php
require_once __DIR__.'/vendor/autoload.php';
use OpenTelemetry\API\Trace\Propagation\TraceContextPropagator;
use OpenTelemetry\Contrib\Otlp\SpanExporter;
use OpenTelemetry\SDK\Common\Attribute\Attributes;
use OpenTelemetry\SDK\Resource\ResourceInfo;
use OpenTelemetry\SDK\Resource\ResourceInfoFactory;
use OpenTelemetry\SDK\Sdk;
use OpenTelemetry\SDK\Trace\SpanProcessor\BatchSpanProcessor;
use OpenTelemetry\SDK\Trace\TracerProvider;
use OpenTelemetry\SemConv\ResourceAttributes;
use OpenTelemetry\SDK\Common\Export\Http\PsrTransportFactory;
$resource = ResourceInfoFactory::emptyResource()->merge(ResourceInfo::create(Attributes::create([
ResourceAttributes::SERVICE_NAMESPACE => 'cm-iwata',
ResourceAttributes::SERVICE_NAME => 'laravel',
ResourceAttributes::SERVICE_VERSION => 'manual-instrument',
ResourceAttributes::DEPLOYMENT_ENVIRONMENT_NAME => 'development',
])));
$spanExporter = new SpanExporter(
(new PsrTransportFactory())->create('http://localhost:4318/v1/traces', 'application/json')
);
$tracerProvider = TracerProvider::builder()
->addSpanProcessor(
BatchSpanProcessor::builder($spanExporter)->build()
)
->setResource($resource)
->build();
Sdk::builder()
->setTracerProvider($tracerProvider)
->setPropagator(TraceContextPropagator::getInstance())
->setAutoShutdown(true)
->buildAndRegisterGlobal();
追加したinstrument.php
をrequre_once
するようapp/bootstrap.php
を修正します。
...略
use Illuminate\Foundation\Configuration\Middleware;
require_once __DIR__ . '/../instrument.php';
return Application::configure(basePath: dirname(__DIR__))
...略
PHPのコードで色々していするようになったので、.env
からはOTEL関連の環境変数を削除します。
#OTEL_PHP_AUTOLOAD_ENABLED=true
#OTEL_SERVICE_NAME=laravel
#OTEL_TRACES_EXPORTER=otlp
#OTEL_EXPORTER_OTLP_PROTOCOL=http/json
#OTEL_EXPORTER_OTLP_ENDPOINT=http://172.31.17.106:4318
#OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://172.31.17.106:4318/v1/traces
#OTEL_PROPAGATORS=baggage,tracecontext
#OTEL_LOGS_EXPORTER=none
ここまでできたらLaravel Breezeを再起動しトレース結果を確認してみましょう。
微妙な違いなので分かりづらいですが、メタデータにPHPのコードで指定した値が反映されているのが分かります。
Detectorを使ってEC2の情報を追加してみる
今回Laravelの実行環境としてEC2(Cloud9)を利用しているので、今度はDetectorを使ってEC2の情報もトレースデータに埋め込んでみます。
まずはADOTのSDKをインストールします。
composer require open-telemetry/contrib-aws:1.0.0beta12
※まだベータ版なので実案件で利用する際はご注意ください。
続いてinstrument.php
を修正しEC2環境固有の情報を付与するようにします。
-use OpenTelemetry\SDK\Resource\ResourceInfoFactory;
use OpenTelemetry\SDK\Sdk;
use OpenTelemetry\SDK\Trace\SpanProcessor\BatchSpanProcessor;
use OpenTelemetry\SDK\Trace\TracerProvider;
use OpenTelemetry\SemConv\ResourceAttributes;
use OpenTelemetry\SDK\Common\Export\Http\PsrTransportFactory;
+use GuzzleHttp\Client;
+use GuzzleHttp\Psr7\HttpFactory;
+use OpenTelemetry\Aws\Ec2\Detector as Ec2Detector;
-$resource = ResourceInfoFactory::emptyResource()->merge(ResourceInfo::create(Attributes::create([
+$client = new Client();
+$requestFactory = new HttpFactory();
+$detector = new Ec2Detector($client, $requestFactory);
+
+$resource = $detector->getResource()->merge(ResourceInfo::create(Attributes::create([
ResourceAttributes::SERVICE_NAMESPACE => 'cm-iwata',
ResourceAttributes::SERVICE_NAME => 'laravel',
ResourceAttributes::SERVICE_VERSION => 'manual-instrument',
修正後したinstrument.php
の全体像はこちらです。
<?php
require_once __DIR__.'/vendor/autoload.php';
use OpenTelemetry\API\Trace\Propagation\TraceContextPropagator;
use OpenTelemetry\Contrib\Otlp\SpanExporter;
use OpenTelemetry\SDK\Common\Attribute\Attributes;
use OpenTelemetry\SDK\Resource\ResourceInfo;
use OpenTelemetry\SDK\Sdk;
use OpenTelemetry\SDK\Trace\SpanProcessor\BatchSpanProcessor;
use OpenTelemetry\SDK\Trace\TracerProvider;
use OpenTelemetry\SemConv\ResourceAttributes;
use OpenTelemetry\SDK\Common\Export\Http\PsrTransportFactory;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\HttpFactory;
use OpenTelemetry\Aws\Ec2\Detector as Ec2Detector;
$client = new Client();
$requestFactory = new HttpFactory();
$detector = new Ec2Detector($client, $requestFactory);
$resource = $detector->getResource()->merge(ResourceInfo::create(Attributes::create([
ResourceAttributes::SERVICE_NAMESPACE => 'cm-iwata',
ResourceAttributes::SERVICE_NAME => 'laravel',
ResourceAttributes::SERVICE_VERSION => 'manual-instrument',
ResourceAttributes::DEPLOYMENT_ENVIRONMENT_NAME => 'development',
])));
$spanExporter = new SpanExporter(
(new PsrTransportFactory())->create('http://localhost:4318/v1/traces', 'application/json')
);
$tracerProvider = TracerProvider::builder()
->addSpanProcessor(
BatchSpanProcessor::builder($spanExporter)->build()
)
->setResource($resource)
->build();
Sdk::builder()
->setTracerProvider($tracerProvider)
->setPropagator(TraceContextPropagator::getInstance())
->setAutoShutdown(true)
->buildAndRegisterGlobal();
これで準備完了です。Laravel Breezeを再起動しトレース結果を確認してみましょう。
リソースの情報としてインスタンスIDやAMI IDが付与されていることが分かります。これで有事の調査も捗りそうですね。
X-RayのトレースIDを伝搬させてみる
ここまでで大分トレースデータが取得できるようになりました。今度はちょっと趣向を変えてX-Rayのトレースを有効化したLambdaからLaravel Breezeにアクセスしてみましょう。以下のコードを実行してみます。
from urllib import request
import json
import os
def lambda_handler(event, context):
headers={
"X-Amzn-Trace-Id": os.environ['_X_AMZN_TRACE_ID']
}
req = request.Request('http://<ALB名>.ap-northeast-1.elb.amazonaws.com/login',headers=headers)
with request.urlopen(req) as res:
status = res.getcode()
print(status)
return {
'statusCode': 200,
'body': json.dumps('Hello from Lambda!')
}
しばらくしてからサービスマップを見ると以下の通りでした。ALBにリクエストする際X-Amzn-Trace-Id
を設定しているものの、サービスマップ上LamdbaとLaravelがつながっておらずトレースが伝搬できていないことが分かります。
ということでPHPのコードを修正し、X-Ray向けのPropagatorを導入します。
-use OpenTelemetry\API\Trace\Propagation\TraceContextPropagator;
use OpenTelemetry\Contrib\Otlp\SpanExporter;
use OpenTelemetry\SDK\Common\Attribute\Attributes;
use OpenTelemetry\SDK\Resource\ResourceInfo;
@@ -15,10 +14,12 @@
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\HttpFactory;
use OpenTelemetry\Aws\Ec2\Detector as Ec2Detector;
+use OpenTelemetry\Aws\Xray\Propagator as XrayPropagator;
$client = new Client();
$requestFactory = new HttpFactory();
$detector = new Ec2Detector($client, $requestFactory);
+$propagator = new XrayPropagator();
$resource = $detector->getResource()->merge(ResourceInfo::create(Attributes::create([
ResourceAttributes::SERVICE_NAMESPACE => 'cm-iwata',
@@ -39,6 +40,6 @@
Sdk::builder()
->setTracerProvider($tracerProvider)
- ->setPropagator(TraceContextPropagator::getInstance())
+ ->setPropagator($propagator)
->setAutoShutdown(true)
->buildAndRegisterGlobal();
修正後のinstrument.php
の全体像です。
<?php
require_once __DIR__.'/vendor/autoload.php';
use OpenTelemetry\Contrib\Otlp\SpanExporter;
use OpenTelemetry\SDK\Common\Attribute\Attributes;
use OpenTelemetry\SDK\Resource\ResourceInfo;
use OpenTelemetry\SDK\Sdk;
use OpenTelemetry\SDK\Trace\SpanProcessor\BatchSpanProcessor;
use OpenTelemetry\SDK\Trace\TracerProvider;
use OpenTelemetry\SemConv\ResourceAttributes;
use OpenTelemetry\SDK\Common\Export\Http\PsrTransportFactory;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\HttpFactory;
use OpenTelemetry\Aws\Ec2\Detector as Ec2Detector;
use OpenTelemetry\Aws\Xray\Propagator as XrayPropagator;
$client = new Client();
$requestFactory = new HttpFactory();
$detector = new Ec2Detector($client, $requestFactory);
$propagator = new XrayPropagator();
$resource = $detector->getResource()->merge(ResourceInfo::create(Attributes::create([
ResourceAttributes::SERVICE_NAMESPACE => 'cm-iwata',
ResourceAttributes::SERVICE_NAME => 'laravel',
ResourceAttributes::SERVICE_VERSION => 'manual-instrument',
ResourceAttributes::DEPLOYMENT_ENVIRONMENT_NAME => 'development',
])));
$spanExporter = new SpanExporter(
(new PsrTransportFactory())->create('http://localhost:4318/v1/traces', 'application/json')
);
$tracerProvider = TracerProvider::builder()
->addSpanProcessor(
BatchSpanProcessor::builder($spanExporter)->build()
)
->setResource($resource)
->build();
Sdk::builder()
->setTracerProvider($tracerProvider)
->setPropagator($propagator)
->setAutoShutdown(true)
->buildAndRegisterGlobal();
この状態で再度Lambdaを実行し、トレースマップを確認してみます。
今度は無事にLambdaとLaravelのトレースがつながりました!やったぜ!
まとめ
EC2 × Laravel × OpenTelemetryによるトレースの実装でした。
PHPでOpenTelemetryを使うのは初めてでしたが、OpenTelemetryは各言語共通の仕様が定められていることもあり割とすんなりと導入できた印象です。早くopen-telemetry/contrib-aws
のbetaが外れることに期待したいです。