13
9

More than 3 years have passed since last update.

AWS CDKを使ってDynamoDBとLambda環境を構築する

Posted at

前回 の続きでLambdaとDynamoDBの環境を構築していきます。

作成する環境

以下のような環境を構築するコードとなっています。
IsolatedサブネットにLambda関数をデプロイし、VPC Gatewayを経由してDynamoDBテーブルのストリームをフェッチできるようにします。

Untitled Diagram.png

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を確認してみてください。

13
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
9