AWS X-rayはリクエストをトレースして、タイムラインやサービスマップを可視化、分析できるサービス。 サービスの数が増えると見えづらくなる、どこにどれくらいのトラフィックがあって、どこで問題が起きているのかといったことを一目で確認できる。
料金はトレースの記録と取得に対してかかり、SamplingRuleの設定によって抑えることができる。
今回はローカルにdocker-composeで立ち上げたWebサーバーからセグメントデータをxray-daemon経由で送り、コンソール上で取得できることを確認する。全体のコードはGitHubにある。
xray-daemonは次のようなフォーマットのデータを受け取りバッチで送るデーモン。
$ cat segment.txt
{"format": "json", "version": 1}
{"trace_id": "1-594aed87-ad72e26896b3f9d3a27054bb", "id": "6226467e3f845502", "start_time": 1498082657.37518, "end_time": 1498082695.4042, "name": "test.elasticbeanstalk.com"}
$ cat segment.txt > /dev/udp/127.0.0.1/2000
ローカルで動かす場合は -o
を付けてインスタンスメタデータを読みに行かないようにする必要がある。ドキュメントでは.aws
を/root
にマウントしているが、そうすると送る際に NoCredentialProviders: no valid providers in chain. Deprecated.
になってしまう。Dockerfileを見たところxrayユーザーで動かしていることが分かったので /home/xray
にしている。
version: "3.9"
services:
xray-daemon:
image: amazon/aws-xray-daemon:3.x
ports:
- "2000:2000/udp"
command:
- "-o" # Don't check for EC2 instance metadata.
volumes:
- ~/.aws:/home/xray/.aws:ro
environment:
AWS_REGION: ap-northeast-1
app:
build: .
ports:
- "8080:8080"
volumes:
- ~/.aws:/root/.aws:ro
depends_on:
- xray-daemon
environment:
AWS_REGION: ap-northeast-1
xray-sdkで http.Client
や http.Handler
をWrapしたり、AWS SDKのconfigを追加することで透過的にセグメントデータが送られる。
package main
import (
"io"
"net/http"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/aws/aws-xray-sdk-go/instrumentation/awsv2"
"github.com/aws/aws-xray-sdk-go/xray"
"github.com/labstack/echo/v4"
)
func main() {
if err := xray.Configure(xray.Config{
DaemonAddr: "xray-daemon:2000",
ServiceVersion: "1.0.0",
}); err != nil {
panic(err)
}
e := echo.New()
e.Use(echo.WrapMiddleware(func(h http.Handler) http.Handler {
return xray.Handler(xray.NewFixedSegmentNamer("test-app"), h)
}))
e.GET("/", hello)
e.Logger.Fatal(e.Start(":8080"))
}
func hello(c echo.Context) error {
ctx := c.Request().Context()
// http request
req, err := http.NewRequest(http.MethodGet, "https://aws.amazon.com/", nil)
if err != nil {
return c.JSON(http.StatusInternalServerError, err.Error())
}
resp, err := xray.Client(http.DefaultClient).Do(req.WithContext(ctx))
if err != nil {
return c.JSON(http.StatusInternalServerError, err.Error())
}
io.Copy(io.Discard, resp.Body)
resp.Body.Close()
// sub segment
subCtx, subSeg := xray.BeginSubsegment(ctx, "waiting-something")
// aws sdk
cfg, err := config.LoadDefaultConfig(subCtx)
if err != nil {
return c.JSON(http.StatusInternalServerError, err.Error())
}
awsv2.AWSV2Instrumentor(&cfg.APIOptions)
svc := s3.NewFromConfig(cfg)
if _, err := svc.ListBuckets(subCtx, &s3.ListBucketsInput{}); err != nil {
c.JSON(http.StatusInternalServerError, err.Error())
return nil
}
subSeg.Close(nil)
return c.JSON(http.StatusOK, "hello")
}
サービス間のTraceID等の受け渡しのために次のようなTracing headerが 付与される。
X-Amzn-Trace-Id: Root=1-5759e988-bd862e3fe1be46a994272793;Sampled=1
リクエストを送るとコンソール上からタイムラインとサービスマップが確認できる。
もしテスト対象のコードにxrayが含まれているなら AWS_XRAY_SDK_DISABLED=true
で実行すると良さそうだ。
contextにsegmentデータが含まれていないと、通常 segment cannot be found
になってしまうが、それも回避できる。
func test() {
os.Setenv("AWS_XRAY_SDK_DISABLED", "true")
xray.BeginSubsegment(context.TODO(), "test")
}
あとはCDKでSamplingRuleとGroupを作成した。追加料金はかかるが、Insightを有効にすることでGroupの異常を検知することができる。
new CfnSamplingRule(this, 'TestAppSamplingRule', {
samplingRule: {
ruleName: "test-app",
resourceArn: "*",
priority: 10,
fixedRate: 0,
reservoirSize: 10,
serviceName: "*",
serviceType: "*",
host: "localhost:8080",
httpMethod: "*",
urlPath: "*",
version: 1,
}
})
new CfnGroup(this, 'TestAppGroup', {
groupName: "test-app-group",
filterExpression: 'http.url CONTAINS "localhost:8080"',
insightsConfiguration: {
insightsEnabled: true,
notificationsEnabled: false
}
})