Serverless Express + AWS Lambda から Axios による外部システムへのアクセスを X-Ray で可視化する
こんにちは、製造ビジネステクノロジー部の若槻です。
AWS X-Ray で可視化できる通信は AWS サービス間の通信だけではありません。下記で紹介されているように Axios などによる外部の API へリクエストを送信する場合も、X-Ray を使用してトレースをすることができます。
今回は、下記のような Serverless Express + AWS Lambda + API Gateway の構成で、Axios による外部 API へのリクエストの X-Ray によるトレースを試してみました。
やってみた
Axios の通信を X-Ray でトレースする方法はいくつかあるのですが、下記にある captureHTTPsGlobal
を使用すれば、HTTP および HTTPS 通信をグローバルにキャプチャすることができるので実装が最もシンプルになります。
ハンドラーコードの実装です。Axios は内部的に http および https モジュールを使用して通信しているため、captureHTTPsGlobal でそれらをキャプチャ対象としています。
import serverlessExpress from "@codegenie/serverless-express";
import express, { Request, Response } from "express";
import { captureHTTPsGlobal } from "aws-xray-sdk-core";
import axios from "axios";
import http from "http";
import https from "https";
const app = express();
app.use(express.json());
/**
* Axios 通信を X-Ray でトレースするための設定
* captureHTTPsGlobal を使用して、HTTP および HTTPS 通信をグローバルにキャプチャしている
*/
captureHTTPsGlobal(http);
captureHTTPsGlobal(https);
const EXTERNAL_SYSTEM_1 = "https://ip-ranges.amazonaws.com/ip-ranges.json";
const EXTERNAL_SYSTEM_2 = "https://status.aws.amazon.com/rss/all.rss";
app.get("/hello", async (_: Request, res: Response): Promise<void> => {
await axios.get(EXTERNAL_SYSTEM_1);
await axios.get(EXTERNAL_SYSTEM_2);
res.status(200).send({ message: "Hello, world!" });
});
app.use((_: Request, res: Response): void => {
res.status(404).send({ error: "Not Found!" });
});
export const handler = serverlessExpress({ app });
AWS CDK によるリソースの実装です。Lambda 関数自体のトレース有効化を忘れずに行うようにしましょう。
import * as cdk from "aws-cdk-lib";
import * as apigateway from "aws-cdk-lib/aws-apigateway";
import * as logs from "aws-cdk-lib/aws-logs";
import * as lambda_nodejs from "aws-cdk-lib/aws-lambda-nodejs";
import * as lambda from "aws-cdk-lib/aws-lambda";
import { Construct } from "constructs";
export class MainStack extends cdk.Stack {
constructor(scope: Construct, id: string) {
super(scope, id);
/**
* Lambda Function
*/
const handler = new lambda_nodejs.NodejsFunction(this, "Handler", {
entry: "src/rest-api-router.ts",
logGroup: new logs.LogGroup(this, "LogGroup"),
tracing: lambda.Tracing.ACTIVE, // AWS X-Ray によるトレースを有効化
});
/**
* API Gateway REST API
*/
new apigateway.LambdaRestApi(this, "RestApi", {
handler,
deployOptions: {
tracingEnabled: true, // AWS X-Ray によるトレースを有効化
},
});
}
}
API Gateway から Lambda 関数を呼び出します。
$ curl https://09y1s838c3.execute-api.ap-northeast-1.amazonaws.com/prod/hello
{"message":"Hello, world!"}
すると Axios による外部 API へのリクエストが X-Ray によってトレースされました。2つの外部システムへのリクエストがそれぞれ別のセグメントとして表示されています。
AWS SDK によるリクエストは可視化されるのか?
captureHTTPsGlobal により Axios のリクエストが可視化されることは確認できましたが、AWS SDK v3 を使用したリクエストも captureHTTPsGlobal のみで一括で可視化できるのでしょうか?
次のように DynamoDBClient
を使用して DynamoDB からデータを取得するコードを追加してみます。
import serverlessExpress from "@codegenie/serverless-express";
import express, { Request, Response } from "express";
import { captureHTTPsGlobal } from "aws-xray-sdk-core";
import axios from "axios";
import http from "http";
import https from "https";
import { DynamoDBClient, ScanCommand } from "@aws-sdk/client-dynamodb";
const app = express();
app.use(express.json());
/**
* Axios 通信を X-Ray でトレースするための設定
* captureHTTPsGlobal を使用して、HTTP および HTTPS 通信をグローバルにキャプチャしている
*/
captureHTTPsGlobal(http);
captureHTTPsGlobal(https);
const EXTERNAL_SYSTEM_1 = "https://ip-ranges.amazonaws.com/ip-ranges.json";
const EXTERNAL_SYSTEM_2 = "https://status.aws.amazon.com/rss/all.rss";
const dynamoDBClient = new DynamoDBClient({}); // X-Ray によるトレース有効化なし
const TABLE_NAME = process.env.TABLE_NAME || "";
app.get("/hello", async (_: Request, res: Response): Promise<void> => {
await axios.get(EXTERNAL_SYSTEM_1);
await axios.get(EXTERNAL_SYSTEM_2);
// DynamoDB からデータを取得
await dynamoDBClient.send(new ScanCommand({ TableName: TABLE_NAME }));
res.status(200).send({ message: "Hello, world!" });
});
app.use((_: Request, res: Response): void => {
res.status(404).send({ error: "Not Found!" });
});
export const handler = serverlessExpress({ app });
CDK コード
import * as cdk from "aws-cdk-lib";
import * as apigateway from "aws-cdk-lib/aws-apigateway";
import * as logs from "aws-cdk-lib/aws-logs";
import * as lambda_nodejs from "aws-cdk-lib/aws-lambda-nodejs";
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as dynamodb from "aws-cdk-lib/aws-dynamodb";
import { Construct } from "constructs";
export class MainStack extends cdk.Stack {
constructor(scope: Construct, id: string) {
super(scope, id);
/**
* DynamoDB Table
*/
const table = new dynamodb.Table(this, "Table", {
partitionKey: { name: "id", type: dynamodb.AttributeType.STRING },
removalPolicy: cdk.RemovalPolicy.DESTROY, // 開発用のため
billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, // オンデマンド課金モード
});
/**
* Lambda Function
*/
const handler = new lambda_nodejs.NodejsFunction(this, "Handler", {
entry: "src/rest-api-router.ts",
logGroup: new logs.LogGroup(this, "LogGroup"),
tracing: lambda.Tracing.ACTIVE, // AWS X-Ray によるトレースを有効化
environment: {
TABLE_NAME: table.tableName, // DynamoDB テーブル名を環境変数として設定
},
});
// DynamoDB テーブルへのアクセス権限を Lambda 関数に付与
table.grantReadWriteData(handler);
/**
* API Gateway REST API
*/
new apigateway.LambdaRestApi(this, "RestApi", {
handler,
deployOptions: {
tracingEnabled: true, // AWS X-Ray によるトレースを有効化
},
});
}
}
先ほどと同様に API Gateway から Lambda 関数を呼び出してみると、DynamoDB へのリクエストはトレースされませんでした。引き続き Axios の通信のみが可視化されています。
captureAWSv3Client と captureHTTPsGlobal を併用してみる
以前の下記ブログで captureAWSv3Client
による AWS SDK v3 のトレース方法を紹介しました。
それでは captureAWSv3Client
と captureHTTPsGlobal
を併用して、AWS SDK v3 と Axios の通信をトレースが同時に行えるでしょうか?試してみましょう。
import serverlessExpress from "@codegenie/serverless-express";
import express, { Request, Response } from "express";
import { captureHTTPsGlobal } from "aws-xray-sdk-core";
import axios from "axios";
import http from "http";
import https from "https";
import { DynamoDBClient, ScanCommand } from "@aws-sdk/client-dynamodb";
import { captureAWSv3Client } from "aws-xray-sdk";
const app = express();
app.use(express.json());
/**
* Axios 通信を X-Ray でトレースするための設定
* captureHTTPsGlobal を使用して、HTTP および HTTPS 通信をグローバルにキャプチャしている
*/
captureHTTPsGlobal(http);
captureHTTPsGlobal(https);
const EXTERNAL_SYSTEM_1 = "https://ip-ranges.amazonaws.com/ip-ranges.json";
const EXTERNAL_SYSTEM_2 = "https://status.aws.amazon.com/rss/all.rss";
const dynamoDBClient = captureAWSv3Client(new DynamoDBClient({})); // X-Ray によるトレース有効化
const TABLE_NAME = process.env.TABLE_NAME || "";
app.get("/hello", async (_: Request, res: Response): Promise<void> => {
await axios.get(API_ENDPOINT_1);
await axios.get(API_ENDPOINT_2);
// DynamoDB からデータを取得
await dynamoDBClient.send(new ScanCommand({ TableName: TABLE_NAME }));
res.status(200).send({ message: "Hello, world!" });
});
app.use((_: Request, res: Response): void => {
res.status(404).send({ error: "Not Found!" });
});
export const handler = serverlessExpress({ app });
先ほどと同様に API Gateway から Lambda 関数を呼び出してみると、Axios による外部 API へのリクエストと DynamoDB へのリクエストの両方を重複なくトレースすることができました。両者の併用は問題なく動作するようです。
おわりに
Serverless Express + AWS Lambda から Axios による外部システムへのアクセスを X-Ray で可視化する方法を紹介しました。
この記事がどなたかの参考になれば幸いです。
以上