@ito_tetsushi

東京・神奈川のサーバーレス / Web / モバイルアプリエンジニア

AWS CDKでAWS Lambda関数を構築する方法(TypeScript版)

概要

AWS CDK(Cloud Development Kit)は、AWS上のリソースをPython / Java / .NET / TypeScriptを使って構築することができるフレームワークです。 Amazon LambdaはAWS上でコードを実行できるFaaS(Function as a Service)です。

動作確認

  • aws-cdk@1.6.1

AWS CDKのCLIをインストール

$ npm i -g aws-cdk

AWS CDKのプロジェクトを作成

# ディレクトリを作成
$ mkdir lambda

# ディレクトリに移動
$ cd lambda

# AWS CDKプロジェクトを作成
$ cdk init app --language=typescript

この時点でのディレクトリは以下のようになっています。

$ tree . -I node_modules
.
├── README.md
├── bin
│   └── lambda.ts
├── cdk.json
├── lib
│   └── lambda-stack.ts
├── package-lock.json
├── package.json
└── tsconfig.json

AWS Lambdaを構築するためのパッケージを追加する

AWS CDKでは、AWSの各リソースを構築するためのクラスはそれぞれ別パッケージとなっています。 次のコマンドでAWS Lambdaを構築するためのパッケージをインストールします。

$ npm i @aws-cdk/aws-lambda @aws-cdk/aws-iam

AWS Lambdaの関数本体のコードを作成する

今回は入力をログに出力するだけのシンプルなLambda関数をNode.jsを使って書きます。 resources/sample-function/index.js として、以下の内容を作成します。

exports.handler = async events => {
  console.log(event);
  return {};
};

AWS Lambdaを構築するコードを追加する

lib/lambda-stack.ts に以下のコードを追加します。

import cdk = require("@aws-cdk/core");
// 以下のコードを追加
import iam = require("@aws-cdk/aws-iam");
import lambda = require("@aws-cdk/aws-lambda");

export class LambdaStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // 以下のコードを追加(IAMロール作成)
    const role = new iam.Role(this, "SampleFunctionRole", {
      assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com"),
      path: "/service-role/",
      inlinePolicies: {
        CloudWatchWritePolicy: new iam.PolicyDocument({
          statements: [
            new iam.PolicyStatement({
              actions: [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
              ],
              resources: ["*"]
            })
          ]
        })
      }
    });

    // 以下のコードを追加(Lambda関数作成)
    new lambda.Function(this, "SampleFunction", {
      code: lambda.Code.asset("resources/sample-function"), // パスはプロジェクトのルートからのパス
      handler: "index.handler",
      runtime: lambda.Runtime.NODEJS_10_X,
      role
    });
  }
}

Lambda関数を生成する前に、Lambdaに設定するIAMロールを生成しています。 付与する権限はCloudWatch Logsへの書き込みです。

Lambda関数本体をアップロードするためのS3バケットを作成する

AWS Lambdaの関数をAWS CDKでデプロイするにはいくつか方法がありますが、Amazon S3を経由する方法を使います。 AWSアカウントで初めてS3にファイルを配置するようなデプロイを行う際は、まず以下のコマンドでS3バケットを作成する必要があります。

$ npx cdk bootstrap

 ⏳  Bootstrapping environment aws://XXXXXXXXX/ap-northeast-1...
CDKToolkit: creating CloudFormation changeset...
 0/2 | 21:15:08 | CREATE_IN_PROGRESS   | AWS::S3::Bucket | StagingBucket
 0/2 | 21:15:10 | CREATE_IN_PROGRESS   | AWS::S3::Bucket | StagingBucket Resource creation Initiated
 1/2 | 21:15:32 | CREATE_COMPLETE      | AWS::S3::Bucket | StagingBucket
 2/2 | 21:15:33 | CREATE_COMPLETE      | AWS::CloudFormation::Stack | CDKToolkit
 ✅  Environment aws://XXXXXXXXX/ap-northeast-1 bootstrapped.

これで準備完了です。

AWS CDKで構築したリソースをデプロイする

IAMロールの変更を伴う場合、変更点が表示されて確認が求められます。

# TypeScriptをコンパイル
$ npx tsc

# リソースをデプロイ
$ cdk deploy

This deployment will make potentially sensitive changes according to your current security approval level (--require-approval broadening).
Please confirm you intend to make the following modifications:

IAM Statement Changes
┌───┬───────────────────────────────────┬────────┬───────────────────────────────────┬───────────────────────────────────┬───────────┐
│   │ Resource                          │ Effect │ Action                            │ Principal                         │ Condition │
├───┼───────────────────────────────────┼────────┼───────────────────────────────────┼───────────────────────────────────┼───────────┤
│ + │ ${SampleFunctionRole.Arn}         │ Allow  │ sts:AssumeRole                    │ Service:lambda.amazonaws.com      │           │
├───┼───────────────────────────────────┼────────┼───────────────────────────────────┼───────────────────────────────────┼───────────┤
│ + │ *                                 │ Allow  │ logs:CreateLogGroup               │ AWS:${SampleFunctionRole}         │           │
│   │                                   │        │ logs:CreateLogStream              │                                   │           │
│   │                                   │        │ logs:PutLogEvents                 │                                   │           │
└───┴───────────────────────────────────┴────────┴───────────────────────────────────┴───────────────────────────────────┴───────────┘
(NOTE: There may be security-related changes not in this list. See http://bit.ly/cdk-2EhF7Np)

Do you wish to deploy these changes (y/n)? y
LambdaStack: deploying...
Updated: asset.cdc666b01549182a49a7205d6dccaa8f295349e048a070ed4c3c2cfba0f724a9 (zip)
LambdaStack: creating CloudFormation changeset...
 0/4 | 21:18:21 | CREATE_IN_PROGRESS   | AWS::CDK::Metadata    | CDKMetadata
 0/4 | 21:18:22 | CREATE_IN_PROGRESS   | AWS::IAM::Role        | SampleFunctionRole (SampleFunctionRoleE8514E0F)
 0/4 | 21:18:22 | CREATE_IN_PROGRESS   | AWS::IAM::Role        | SampleFunctionRole (SampleFunctionRoleE8514E0F) Resource creation Initiated
 0/4 | 21:18:23 | CREATE_IN_PROGRESS   | AWS::CDK::Metadata    | CDKMetadata Resource creation Initiated
 1/4 | 21:18:24 | CREATE_COMPLETE      | AWS::CDK::Metadata    | CDKMetadata
 2/4 | 21:18:35 | CREATE_COMPLETE      | AWS::IAM::Role        | SampleFunctionRole (SampleFunctionRoleE8514E0F)
 2/4 | 21:18:38 | CREATE_IN_PROGRESS   | AWS::Lambda::Function | SampleFunction (SampleFunction7DB1D36A)
 2/4 | 21:18:39 | CREATE_IN_PROGRESS   | AWS::Lambda::Function | SampleFunction (SampleFunction7DB1D36A) Resource creation Initiated
 3/4 | 21:18:39 | CREATE_COMPLETE      | AWS::Lambda::Function | SampleFunction (SampleFunction7DB1D36A)
 4/4 | 21:18:41 | CREATE_COMPLETE      | AWS::CloudFormation::Stack | LambdaStack

 ✅  LambdaStack

Stack ARN:
arn:aws:cloudformation:ap-northeast-1:854529464916:stack/LambdaStack/e5755240-ce44-11e9-8c4f-0efdab6a43a0

AWS Lambdaの関数をAWS CDK経由でデプロイすることができました。

f:id:ito_tetsushi:20190903212104p:plain

リソースを削除する

# リソースを削除する
$ npx cdk destroy

DynamoDBテーブルへのアクセス権限をスマートに付与する

AWS CDKには、Cfn で始まるコンストラクタ(ex:CfnTable , CfnFunction)とそれ以外のコンストラクタの2種類があります。 Cfn付きコンストラクタはAWS CloudFormationで用意されているフィールドがそのまま反映された形の仕様となっています。

一方、 Cfnなしコンストラクタの方は、CloudFormationの仕様からさらに抽象化されて、よりシンプルに、より便利にリソースを構築できるようになっています。

DynamoDBテーブルとLambda関数にはCfnなしコンストラクタが用意されているので、相互に連携して便利に権限付与を行うことができます。

必要な部分だけ抜粋したものですが、Lambdaの関数からDynamoDBテーブルへアクセスできるような権限を付与するには、以下のように書きます。

// Lambda関数を生成
const lambdaFunction = new lambda.Function(this, "SampleFunction", {
  code: lambda.Code.asset("resources/sample-function"), // パスはプロジェクトのルートからのパス
  handler: "index.handler",
  runtime: lambda.Runtime.NODEJS_10_X,
  role
});

// DynamoDBテーブルを作成
const dynamodbTable = new dynamodb.Table(this, "PostTable", {
  partitionKey: {
    name: "id",
    type: dynamodb.AttributeType.STRING
  }
});

// 読み取り権限を追加
dynamodbTable.grantReadData(lambdaFunction);

// 書き込み権限を追加
dynamodbTable.grantWriteData(lambdaFunction);

// 読み書き権限を追加
dynamodbTable.grantReadWriteData(lambdaFunction);

Cfnなしコンストラクタが提供されていないリソースについては、この書き方ができないので、iam.Role クラスを使って地道にリソース間の権限付与を行なっています。 Cfnなしコンストラクタも徐々に増えていくかと思うので、キャッチアップすることでどんどんCDKのプロジェクトをシンプルに、より安全にすることができそうです。