前回 の続きでLambdaとDynamoDBの環境を構築していきます。
作成する環境
以下のような環境を構築するコードとなっています。
IsolatedサブネットにLambda関数をデプロイし、VPC Gatewayを経由してDynamoDBテーブルのストリームをフェッチできるようにします。
AWS CDKのインストール
sudo npm install -g aws-cdk
cdk ---version
1.18.0 (build bc924bc)
mkdir mini-cdk
cd mini-cdk
cdk init app --language=typescript
npm install @aws-cdk/aws-lambda @aws-cdk/aws-dynamodb @aws-cdk/aws-ec2 @aws-cdk/aws-iam
# 作成するスタッックのファイル
touch lib/mini-vpc-stack.ts
touch lib/mini-lambda-stack.ts
touch lib/mini-dynamo-stack.ts
# 都合上削除
rm test/mini-cdk.test.ts
Lambda関数の作成
mini-cdk
プロジェクトの中にlambda
ディレクトリを作成してそこにLambda関数をコーディングしていきます。
mkdir -p lambda/src
touch lambda/src/fetchStream.ts
cd lambda
tsc --init
npm init -y
npm install aws-sdk aws-lambda
npm install -D @types/aws-lambda
DynamoDBストリームをコンソールに出力するだけの簡単なLambda関数を作成します。
lambda/src/fetchStream.ts
import { DynamoDBStreamHandler } from "aws-lambda";
export const handler: DynamoDBStreamHandler = (event, context) => {
event.Records.forEach(record => {
// TODO Complex Processing
console.log(record.eventName);
console.log(JSON.stringify(record, null, 2));
});
};
VPCスタックの作成
前回 のものを丸々同じです。再掲しておきます。
lib/mini-vpc-stack.ts
import cdk = require("@aws-cdk/core");
import ec2 = require("@aws-cdk/aws-ec2");
export class MiniVpcStack extends cdk.Stack {
public readonly vpc: ec2.Vpc;
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// VPCを作成する
this.vpc = this.createVpc();
// GatewayEndpointをセットアップする
this.setupGatewayEndpoint();
// InterfaceEndpointをセットアップする
this.setupInterfaceEndpoint();
}
private createVpc() {
return new ec2.Vpc(this, "MiniVpc", {
cidr: "172.17.0.0/16",
natGateways: 0, // NatGatewayはお金がかかるので使わない
maxAzs: 1, // Availability Zoneの数
subnetConfiguration: [
{ name: "Public", cidrMask: 20, subnetType: ec2.SubnetType.PUBLIC },
{ name: "Isolated", cidrMask: 20, subnetType: ec2.SubnetType.ISOLATED }
]
});
}
private setupGatewayEndpoint() {
// IsolatedのサブネットからS3に直接アクセスできるようにVPCエンドポイントを利用する
this.vpc.addGatewayEndpoint("S3EndpointForIsolatedSubnet", {
service: ec2.GatewayVpcEndpointAwsService.S3,
subnets: [{ subnetType: ec2.SubnetType.ISOLATED }]
});
// IsolatedのサブネットからDynamoDBに直接アクセスできるようにVPCエンドポイントを利用する
this.vpc.addGatewayEndpoint("DynamoDbEndpointForIsolatedSubnet", {
service: ec2.GatewayVpcEndpointAwsService.DYNAMODB,
subnets: [{ subnetType: ec2.SubnetType.ISOLATED }]
});
}
private setupInterfaceEndpoint() {
// InterafceEndpoint用のセキュリティグループ
const interfaceEndpointSecurityGroup = new ec2.SecurityGroup(
this,
"InterfaceEndpointSecurityGroup",
{
securityGroupName: "InterfaceEndpointSecurityGroup",
vpc: this.vpc
}
);
interfaceEndpointSecurityGroup.addIngressRule(
ec2.Peer.anyIpv4(),
ec2.Port.tcp(443)
);
// Isolated SubnetからECRに接続するためのエンドポイント
this.vpc.addInterfaceEndpoint("ECRInterfaceEndpoint", {
securityGroups: [interfaceEndpointSecurityGroup],
service: ec2.InterfaceVpcEndpointAwsService.ECR,
subnets: {
subnetType: ec2.SubnetType.ISOLATED
}
});
this.vpc.addInterfaceEndpoint("ECR_DockerInterfaceEndpoint", {
securityGroups: [interfaceEndpointSecurityGroup],
service: ec2.InterfaceVpcEndpointAwsService.ECR_DOCKER
});
}
}
DynamoDBスタックの作成
lib/mini-dynamo-stack.ts
import cdk = require("@aws-cdk/core");
import dynamodb = require("@aws-cdk/aws-dynamodb");
export class MiniDynamoDbStack extends cdk.Stack {
public readonly todoTable: dynamodb.Table;
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// Setup Todo Table
this.todoTable = this.setupTodoTable();
}
private setupTodoTable() {
return new dynamodb.Table(this, "TodoTable", {
tableName: "todos",
removalPolicy: cdk.RemovalPolicy.DESTROY,
billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
partitionKey: {
name: "id",
type: dynamodb.AttributeType.STRING
},
stream: dynamodb.StreamViewType.NEW_AND_OLD_IMAGES
});
}
}
Lambdaスタックの作成
lib/mini-lambda-stack.ts
import cdk = require("@aws-cdk/core");
import lambda = require("@aws-cdk/aws-lambda");
import ec2 = require("@aws-cdk/aws-ec2");
import dynamodb = require("@aws-cdk/aws-dynamodb");
import iam = require("@aws-cdk/aws-iam");
interface MiniLambdaStackProps extends cdk.StackProps {
vpc: ec2.Vpc;
todoTable: dynamodb.Table;
}
export class MiniLambdaStack extends cdk.Stack {
private readonly vpc: ec2.Vpc;
private readonly todoTable: dynamodb.Table;
constructor(scope: cdk.Construct, id: string, props: MiniLambdaStackProps) {
super(scope, id, props);
this.vpc = props.vpc;
this.todoTable = props.todoTable;
// DynamoDbテーブルのストリームをフェッチするLambda関数のセットアップ
this.setupFetchTodoTableStreamFunction();
}
// DynamoのTodo TableストリームをフェッチするためLambdaの関数を定義する
private setupFetchTodoTableStreamFunction() {
const role = new iam.Role(this, "FetchTodoTableStreamRole", {
roleName: "FetchTodoTableStreamRole",
assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com")
});
// DynanmoDB Streamをフェッチするために必要なポリシー
if (this.todoTable.tableStreamArn) {
role.addToPolicy(
new iam.PolicyStatement({
actions: [
"dynamodb:GetRecords",
"dynamodb:GetShardIterator",
"dynamodb:ListStream",
"dynamodb:DescribeStream"
],
resources: [this.todoTable.tableStreamArn]
})
);
}
// Isolated SubnetからInterface Endpointを経由してDynamoDBにアクセスするため、
// ENIを作成するポリシーも必要になる
// AWSのManagedPolicyを利用することによって、ENI作成ポリシーとロググループ関連のポリシーをアタッチすることができる
role.addManagedPolicy(
iam.ManagedPolicy.fromAwsManagedPolicyName(
"service-role/AWSLambdaVPCAccessExecutionRole"
)
);
const handler = new lambda.Function(this, "FetchTodoTableStreamFunction", {
functionName: "FetchTodoTableStreamFunction",
handler: "src/fetchStream.handler",
code: lambda.Code.fromAsset("lambda"),
runtime: lambda.Runtime.NODEJS_12_X,
vpc: this.vpc,
vpcSubnets: {
subnetType: ec2.SubnetType.ISOLATED
},
role
});
// DynamoDBのStreamをSourceとするためのマッピング設定
if (this.todoTable.tableStreamArn) {
handler.addEventSourceMapping("FetchTodoTableStreamSourceMapping", {
eventSourceArn: this.todoTable.tableStreamArn,
batchSize: 10,
startingPosition: lambda.StartingPosition.LATEST
});
}
}
}
エンドポイントの作成
ここまで作成したスタックのエンドポイントとなるファイルを作成します。
bin/mini-cdk.ts
# !/usr/bin/env node
import "source-map-support/register";
import cdk = require("@aws-cdk/core");
import { MiniVpcStack } from "../lib/mini-vpc-stack";
import { MiniDynamoDbStack } from "../lib/mini-dynamo-stack";
import { MiniLambdaStack } from "../lib/mini-lambda-stack";
import util = require("util");
import childProcess = require("child_process");
main();
async function main() {
// Lambdaソース(TypeScript)をビルドする
await buildTypeScript();
const app = new cdk.App();
// VPCスタック
const miniVpcStack = new MiniVpcStack(app, "MiniVpcStack");
// DynamoDbスタック
const miniDynamoDbStack = new MiniDynamoDbStack(app, "MiniDynamoDbStack");
// Lambdaスタック
new MiniLambdaStack(app, "MiniLambdaStack", {
vpc: miniVpcStack.vpc,
todoTable: miniDynamoDbStack.todoTable
});
app.synth();
}
async function buildTypeScript() {
const exec = util.promisify(childProcess.exec);
exec("cd lambda && tsc");
}
ビルドとデプロイ
ビルドしてjs
ファイルを生成します。
npm run build
ここまでで準備が整ったので、スタックをまとめてデプロイして環境を構築します。
export AWS_DEFAULT_REGION=ap-northeast-1
cdk -c keyName=mini-cdk deploy *Stack --require-approval never --profile dev
デプロイが完了したら、DynamoDBのtodos
テーブルを更新してCloudWatch Logsを確認してみてください。