はじめに
今回は AWS CDKを使い以下を行ったため、そのソースコードのご紹介です。
- API GatewayのアクセスログをS3へ保存するFirehoseストリームの作成
- 上記Firehoseストリームを送信先とするAPI Gateway APIの作成
言語はTypeScirptになります。
前回投稿内容と照らし合わせながらご紹介していきます。
目次
必要となるAWSリソース🔨
今回のゴールである、API Gatewayのアクセスログの送信先にFirehoseストリームを設定を行う上で、必須となるAWSリソースは以下です。
- IAMロール:Firehoseストリームが必要とする許可を設定
- S3バケット:Firehoseストリームが配信するログの記録先
- Firehoseストリーム:API Gateway アクセスログの送信先
- API Gateway API:アクセスログの送信先としてFirehoseストリームを設定
IAMロールの作成
まずは、IAMロールの作成です。
Firehoseストリームのサービスアクセスで設定するロールになります。
// Create Role
const firehoseDeliveryRole = new Role(scope, `AmazonApigatewayMyApiFirehoseDeliveryRole`, {
roleName: `AmazonApigatewayMyApiFirehoseDeliveryRole`,
assumedBy: new ServicePrincipal('firehose.amazonaws.com'),
inlinePolicies: {
ecsServicePolicy: new PolicyDocument({
statements: [
new PolicyStatement({
actions: [
's3:AbortMultipartUpload',
's3:GetBucketLocation',
's3:GetObject',
's3:ListBucket',
's3:ListBucketMultipartUploads',
's3:PutObject',
],
resources: [`arn:aws:s3:::amazon-apigateway-my-api*`],
}),
new PolicyStatement({
actions: ['lambda:InvokeFunction'],
resources: [`arn:aws:lambda:${scope.region}:${scope.account}:function:FirehoseTransformFunction`],
}),
],
}),
},
});
S3バケットの作成
続いて、S3バケットの作成です。
// Create S3
const logS3 = new Bucket(scope, `AmazonApigatewayMyApiLogS3`, {
accessControl: BucketAccessControl.PRIVATE,
blockPublicAccess: BlockPublicAccess.BLOCK_ALL,
bucketName: `amazon-apigateway-my-api-log-ap-northeast-1`,
encryption: BucketEncryption.S3_MANAGED,
removalPolicy: RemovalPolicy.DESTROY,
});
cdk.Tags.of(logS3).add('Name', `amazon-apigateway-my-api-log-ap-northeast-1`);
cdk.Tags.of(logS3).add('Role', 'S3');
Firehoseストリームの作成
続いて、Firehoseストリームの作成です。
Firehoseストリームの設定では、以下3つの内容を指します。
※レコードを編集、変換、および転換
はCloudWatchLogsへのログ記録で必要な設定で、CDKだとprocessingConfigurationを指しますが、今回は直接Firehoseストリーム > S3となるため定義なしとしています。
// Create KinesisDeliveryStream
const logDeliveryStream = new CfnDeliveryStream(scope, `AmazonApigatewayMyApiLogStream`, {
deliveryStreamName: `amazon-apigateway-my-api-log-stream`,
deliveryStreamType: 'DirectPut',
extendedS3DestinationConfiguration: {
bucketArn: logS3.bucketArn,
bufferingHints: {
intervalInSeconds: 900,
sizeInMBs: 50,
},
compressionFormat: 'UNCOMPRESSED',
prefix: 'apigateway/',
roleArn: firehoseDeliveryRole.roleArn,
},
});
API Gateway APIの作成
最後に、API Gateway APIの作成です。
MyApi
はAPI名です。
accessLogDestination: new FirehoseLogDestination()
で、アクセスログの送信先として上記で作成したFirehoseストリーム(logDeliveryStream
)を設定しています。
// Create ApiGateway Api
const myApi = new RestApi(this, 'MyApi', {
restApiName: 'myApi',
defaultCorsPreflightOptions: {
allowOrigins: Cors.ALL_METHODS,
allowMethods: ['GET', 'OPTIONS'],
statusCode: 204,
},
deployOptions: {
stageName: this.apiGatewayStage,
accessLogDestination: new FirehoseLogDestination(logDeliveryStream),
accessLogFormat: AccessLogFormat.jsonWithStandardFields(),
metricsEnabled: true,
loggingLevel: MethodLoggingLevel.INFO,
dataTraceEnabled: true,
tracingEnabled: true,
},
});
まとめ📝
これまでの処理をまとめると以下のような感じです。
可読性向上のために以下を行いましたが、ファイル分割しても良いかもです( ^ω^ )
- Firehoseストリームの作成処理をメソッド化
- ログ名(logName)・ゾーン名(zoneName)は引数渡しで共通化
import { RemovalPolicy } from 'aws-cdk-lib';
import * as cdk from 'aws-cdk-lib';
import {
AccessLogFormat,
FirehoseLogDestination,
MethodLoggingLevel,
RestApi,
} from 'aws-cdk-lib/aws-apigateway';
import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose';
import { PolicyDocument, PolicyStatement, Role, ServicePrincipal } from 'aws-cdk-lib/aws-iam';
import { BlockPublicAccess, Bucket, BucketAccessControl, BucketEncryption } from 'aws-cdk-lib/aws-s3';
// ApiGateway アクセスログ配信用Firehoseストリーム作成
const ApiGWFirehoseStream: CfnDeliveryStream = createApiGWAccessLogResource(this, 'amazon-apigateway-my-api', zoneName);
// Create ApiGateway Api
const myApi = new RestApi(this, 'MyApi', {
restApiName: 'myApi',
defaultCorsPreflightOptions: {
allowOrigins: Cors.ALL_METHODS,
allowMethods: ['GET', 'OPTIONS'],
statusCode: 204,
},
deployOptions: {
stageName: this.apiGatewayStage,
accessLogDestination: new FirehoseLogDestination(ApiGWFirehoseStream),
accessLogFormat: AccessLogFormat.jsonWithStandardFields(),
metricsEnabled: true,
loggingLevel: MethodLoggingLevel.INFO,
dataTraceEnabled: true,
tracingEnabled: true,
},
});
/**
* ApiGatewayのアクセスログをS3へ保存するためのFirehoseストリームを作成
*
* @remarks
*
* @param scope - スコープ
* @param logName - ログ名
* @param zoneName - DNSZoneName
* @returns Firehoseストリーム
*/
export function createApiGWAccessLogResource(scope: cdk.Stack, logName: string, zoneName: string): CfnDeliveryStream {
// Snake形からCamel形に変換
const logNameCamel = snakeToCamel(logName);
// Create Role
const firehoseDeliveryRole = new Role(scope, `${logNameCamel}FirehoseDeliveryRole`, {
roleName: `${logNameCamel}FirehoseDeliveryRole`,
assumedBy: new ServicePrincipal('firehose.amazonaws.com'),
inlinePolicies: {
ecsServicePolicy: new PolicyDocument({
statements: [
new PolicyStatement({
actions: [
's3:AbortMultipartUpload',
's3:GetBucketLocation',
's3:GetObject',
's3:ListBucket',
's3:ListBucketMultipartUploads',
's3:PutObject',
],
resources: [`arn:aws:s3:::${logName}*`],
}),
new PolicyStatement({
actions: ['lambda:InvokeFunction'],
resources: [`arn:aws:lambda:${scope.region}:${scope.account}:function:FirehoseTransformFunction`],
}),
],
}),
},
});
// Create S3
const logS3 = new Bucket(scope, `${logNameCamel}LogS3`, {
accessControl: BucketAccessControl.PRIVATE,
blockPublicAccess: BlockPublicAccess.BLOCK_ALL,
bucketName: `${logName}-log-${zoneName}`,
encryption: BucketEncryption.S3_MANAGED,
removalPolicy: RemovalPolicy.DESTROY,
});
cdk.Tags.of(logS3).add('Name', `${logName}-log-${zoneName}`);
cdk.Tags.of(logS3).add('Role', 'S3');
// Create KinesisDeliveryStream
const logDeliveryStream = new CfnDeliveryStream(scope, `${logNameCamel}LogStream`, {
deliveryStreamName: `${logName}-log-stream`,
deliveryStreamType: 'DirectPut',
extendedS3DestinationConfiguration: {
bucketArn: logS3.bucketArn,
bufferingHints: {
intervalInSeconds: 900,
sizeInMBs: 50,
},
compressionFormat: 'UNCOMPRESSED',
prefix: 'apigateway/',
roleArn: firehoseDeliveryRole.roleArn,
},
});
return logDeliveryStream;
}
/**
* Snake > Camel形に変換する
* @remarks
* 例:`amazon-apigateway-my-api`は`AmazonApigatewayMyApi`に変換される
* @param str - 変換したい文字列
*
* @returns 変換後結果
*/
export function snakeToCamel(str: string): string {
str = str.replace(/([-_]\w)/g, (g) => g[1].toUpperCase());
return str.charAt(0).toUpperCase() + str.slice(1);
}