前回は、AWS Cloud Development Kit(AWS CDK)で関数を作成しました。
【AWS】AWS Cloud Development Kit(AWS CDK)で関数を作成する
今回は、URL の接続回数をカウントするアプリを作成します。
ワークショップに習って、URL のパスに発行されたリクエストの数をカウントするアプリを作成します。
HitCounter アプリを作成する
まずは、lib フォルダに、hitcounter.ts ファイルを作成します。
ts
import { aws_lambda } from "aws-cdk-lib";
import { Construct } from "constructs";
export interface HitCounterProps {
downstream: aws_lambda.IFunction;
}
export class HitCounter extends Construct {
constructor(scope: Construct, id: string, props: HitCounterProps) {
super(scope, id);
}
}
ワークショップによると、
HitCounterという Construct クラスを定義。
scope、id、propsのコンストラクター引数を定義し、Constructにアクセスする。
propsは、aws_lambda.IFunction型のdownstreamというプロパティを含む、HitCounterProps型の引数である。
とのことです。
HitCounter の Lambda ハンドラーを作成します。
lambda フォルダに、hitcounter.js を作成します。
js
import { aws_dynamodb, aws_lambda } from "aws-cdk-lib";
export async function handler(event) {
console.log("request:", JSON.stringify(event, undefined, 2));
const dynamo = new aws_dynamodb();
const lambda = new aws_lambda();
await dynamo
.updateItem({
TableName: process.env.HITS_TABLE_NAME,
Key: { path: { S: event.path } },
UpdateExpression: "ADD hits :incr",
ExpressionAttributeValues: { ":incr": { N: "1" } },
})
.promise();
const resp = await lambda
.invoke({
FunctionName: process.env.DOWNSTREAM_FUNCTION_NAME,
Payload: JSON.stringiry(event),
})
.promise();
console.log("downstream response:", JSON.stringify(resp, undefined, 2));
return JSON.parse(resp.Payload);
}
DynamoDB にデータとして追加する内容と、Lambda 関数を呼び出すコードになっています。
lib フォルダの hitcounter.ts に戻って、hitcounter の handler を呼び出します。
ts
import { aws_lambda, aws_dynamodb } from "aws-cdk-lib";
import { Construct } from "constructs";
export interface HitCounterProps {
downstream: aws_lambda.IFunction;
}
export class HitCounter extends Construct {
public readonly handler: aws_lambda.Function;
constructor(scope: Construct, id: string, props: HitCounterProps) {
super(scope, id);
const table = new aws_dynamodb.Table(this, "Hits", {
partitionKey: { name: "path", type: aws_dynamodb.AttributeType.STRING },
});
this.handler = new aws_lambda.Function(this, "HitCounterHandler", {
runtime: aws_lambda.Runtime.NODEJS_14_X,
handler: "hitcounter.handler",
code: aws_lambda.Code.fromAsset("lambda"),
environment: {
DOWNSTREAM_FUNCTION_NAME: props.downstream.functionName,
HITS_TABLE_NAME: table.tableName,
},
});
}
}
今回は、environmentに、hitcounter.js で設定した、DOWNSTREAM_FUNCTION_NAMEとHITS_TABLE_NAMEを定義しています。
HitCounter の準備ができたので、アプリに実装します。
lib フォルダの test-aws-cdk-stack.ts を開きます。
HitCounter を追加しましょう。
ts
import { Stack, StackProps, aws_lambda, aws_apigateway } from "aws-cdk-lib";
import { Construct } from "constructs";
import { HitCounter } from "./hitcounter";
export class TestAwsCdkStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
const hello = new aws_lambda.Function(this, "HelloHandler", {
runtime: aws_lambda.Runtime.NODEJS_14_X,
code: aws_lambda.Code.fromAsset("lambda"),
handler: "hello.handler",
});
const helloWithCounter = new HitCounter(this, "HelloHitCounter", {
downstream: hello,
});
new aws_apigateway.LambdaRestApi(this, "Endpoint", {
handler: helloWithCounter.handler,
});
}
}
HitCounter が hello 関数に中継するようです。
ターミナルでcdk diff を実行し、差分をチェックしましょう。
Hello が HelloHitCounter に変わっていました。
ターミナルでcdk deploy を実行し、デプロイしてみます。
ターミナルで、URL が表示されたので、アクセスすると、
内部エラーが起こっている様です。
Lambda 関数の CloudWatch ログを表示する
なぜ、この様なエラーが起こっているか、HitCounter のログで確認します。
AWS Lambda コンソール( https://console.aws.amazon.com/lambda/home )を開きます。
HitCounter に関する関数をクリックします。
モニタリングをクリックします。
『ClousWatch のログを表示』をクリックします。
『Search log group』をクリック しましょう。
ログを見てみると、
『Cannot use import statement outside a module』というエラーが発生しています。
lambda フォルダの hitcounter.js のimport { aws_lambda, aws_dynamodb } from "aws-cdk-lib";が間違っている様です。
ターミナルで、npm install --save aws-sdk を実行して、aws-sdk をインストールします。
aws_lambdaとaws_dynamodbのコードを書き換えます。
さらに、exports.handler にしておきましょう。
js
const { DynamoDB, Lambda } = require("aws-sdk");
exports.handler = async function (event) {
console.log("request:", JSON.stringify(event, undefined, 2));
const dynamo = new DynamoDB();
const lambda = new Lambda();
await dynamo
.updateItem({
TableName: process.env.HITS_TABLE_NAME,
Key: { path: { S: event.path } },
UpdateExpression: "ADD hits :incr",
ExpressionAttributeValues: { ":incr": { N: "1" } },
})
.promise();
const resp = await lambda
.invoke({
FunctionName: process.env.DOWNSTREAM_FUNCTION_NAME,
Payload: JSON.stringiry(event),
})
.promise();
console.log("downstream response:", JSON.stringify(resp, undefined, 2));
return JSON.parse(resp.Payload);
};
再びターミナルで cdk deployを実行すると、
エラーが解消されていません。
ログを見てみると、
今度は、『AccessDeniedException』と言われています。
これは、DynamoDB の読み書きの権限が Lambda に与えられていないので、エラーが発生しています。
lib フォルダの hitcounter.ts で、コードを追加修正しましょう。
ts
import { aws_lambda, aws_dynamodb } from "aws-cdk-lib";
import { Construct } from "constructs";
export interface HitCounterProps {
downstream: aws_lambda.IFunction;
}
export class HitCounter extends Construct {
public readonly handler: aws_lambda.Function;
constructor(scope: Construct, id: string, props: HitCounterProps) {
super(scope, id);
const table = new aws_dynamodb.Table(this, "Hits", {
partitionKey: { name: "path", type: aws_dynamodb.AttributeType.STRING },
});
this.handler = new aws_lambda.Function(this, "HitCounterHandler", {
runtime: aws_lambda.Runtime.NODEJS_14_X,
handler: "hitcounter.handler",
code: aws_lambda.Code.fromAsset("lambda"),
environment: {
DOWNSTREAM_FUNCTION_NAME: props.downstream.functionName,
HITS_TABLE_NAME: table.tableName,
},
});
table.grantReadWriteData(this.handler);
}
}
三度、ターミナルでcdk deploy を実行して、デプロイします。
エラーのタイプが変わりました。
しかし、ブラウザを確認しても、エラーが解消されていないみたいです。
DynamoDB コンソール( https://console.aws.amazon.com/dynamodbv2/home )のテーブルで確認しましょう。
テーブルは、作成されていました。
lib フォルダの hitcounter.ts に移動します。
呼び出し許可の権限を与えましょう。
ts
import { aws_lambda, aws_dynamodb } from "aws-cdk-lib";
import { Construct } from "constructs";
export interface HitCounterProps {
downstream: aws_lambda.IFunction;
}
export class HitCounter extends Construct {
public readonly handler: aws_lambda.Function;
constructor(scope: Construct, id: string, props: HitCounterProps) {
super(scope, id);
const table = new aws_dynamodb.Table(this, "Hits", {
partitionKey: { name: "path", type: aws_dynamodb.AttributeType.STRING },
});
this.handler = new aws_lambda.Function(this, "HitCounterHandler", {
runtime: aws_lambda.Runtime.NODEJS_14_X,
handler: "hitcounter.handler",
code: aws_lambda.Code.fromAsset("lambda"),
environment: {
DOWNSTREAM_FUNCTION_NAME: props.downstream.functionName,
HITS_TABLE_NAME: table.tableName,
},
});
table.grantReadWriteData(this.handler);
props.downstream.grantInvoke(this.handler);
}
}
四度、ターミナルで cdk deploy を実行します。
やっと、ブラウザに表示されました。
ワークショップのように、何度か、URL の prod/の後ろを変えて実行してみます。
DynamoDB のテーブルを確認すると、
実行した path の回数分、hits がカウントされました。
次回は、テストで作成しているアプリを修正し、テーブルとしてブラウザに表示させます。
【AWS】AWS Cloud Development Kit(AWS CDK)でカウントした回数をブラウザに表示する