AWS CDK
VPCを作成し、おなじサブネットにElastiCacheとLambdaを配置します。ElastiCacheの設定エンドポイントをLambdaの環境変数に設定します。
cacheNodeTypeは下記のリンク先から適切なノードを選択してください。
VPC LambdaからElastiCacheにアクセスするために、セキュリティグループのインバウンドルールを作成する必要があります。
lib/stack/sample-stack.ts
import { Construct } from 'constructs';
import * as cdk from 'aws-cdk-lib';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as elasticache from 'aws-cdk-lib/aws-elasticache';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as lambdaNodeJs from 'aws-cdk-lib/aws-lambda-nodejs';
export class SampleStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// VPC
const vpc = this.createVpc();
const securityGroup = this.createVpcSecurityGroup(vpc);
// ElastiCache
const memcache = this.createElasticache(vpc, securityGroup);
const memcachedConfigEndpoint = `${memcache.attrConfigurationEndpointAddress}:${memcache.attrConfigurationEndpointPort}`;
// VPC Lambda
this.createFunc(vpc, memcachedConfigEndpoint);
}
/**
* @description VPCを作成する
* {@link Vpc | https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_ec2.Vpc.html}
*/
private createVpc(): ec2.IVpc {
return new ec2.Vpc(this, 'Vpc', {
ipAddresses: ec2.IpAddresses.cidr('192.168.0.0/16'),
subnetConfiguration: [
{
name: `vpc-public`,
cidrMask: 24,
subnetType: ec2.SubnetType.PUBLIC,
},
{
cidrMask: 24,
name: `vpc-private`,
subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
},
],
});
}
/**
* {@link SecurityGroup | https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_ec2.SecurityGroup.html}
*/
private createVpcSecurityGroup(vpc: ec2.IVpc): ec2.SecurityGroup {
return new ec2.SecurityGroup(this, 'VpcSecurityGroup', { vpc });
}
/**
* @description ElastiCache for Memcachedを作成する
* {@link Amazon ElastiCache for Memcached | https://docs.aws.amazon.com/ja_jp/AmazonElastiCache/latest/mem-ug/WhatIs.html}
* {@link CfnSubnetGroup | https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_elasticache.CfnSubnetGroup.html}
* {@link CfnCacheCluster | https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_elasticache.CfnCacheCluster.html}
*/
private createElasticache(vpc: ec2.IVpc, securityGroup: ec2.SecurityGroup): elasticache.CfnCacheCluster {
const subnetGroup = new elasticache.CfnSubnetGroup(this, 'SubnetGroup', {
description: 'private subnet',
subnetIds: vpc.selectSubnets({ subnetType: ec2.SubnetType.PRIVATE_ISOLATED }).subnetIds,
});
const cluster = new elasticache.CfnCacheCluster(this, 'ElastiCache', {
engine: 'memcached',
cacheNodeType: 'cache.r6g.large',
numCacheNodes: 1,
cacheSubnetGroupName: subnetGroup.ref,
vpcSecurityGroupIds: [securityGroup.securityGroupId], // vpcSecurityGroupIds,cacheSecurityGroupNamesのいずれかが必須
});
// インバウンドルールの追加
const port = ec2.Port.tcp(cluster.port ?? 11211);
securityGroup.addIngressRule(ec2.Peer.anyIpv4(), port);
return cluster;
}
/**
* @description Lambdaの作成
* {@link NodejsFunction | https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_lambda_nodejs.NodejsFunction.html}
*/
private createFunc(vpc: ec2.IVpc, memcachedConfigEndpoint: string): lambdaNodeJs.NodejsFunction {
return new lambdaNodeJs.NodejsFunction(this, 'SampleFunc', {
entry: 'src/lambda/sample/index.ts',
handler: 'handler',
runtime: lambda.Runtime.NODEJS_18_X,
timeout: cdk.Duration.minutes(5),
environment: {
MEMCACHED_CONFIG_ENDPOINT: memcachedConfigEndpoint,
},
vpc,
vpcSubnets: vpc.selectSubnets({ subnetType: ec2.SubnetType.PRIVATE_ISOLATED }),
});
}
}
Lambda
ここでは、memcachedのクライアントに「Memcache Plus」というライブラリを使用します。
このライブラリはElastiCacheの設定エンドポイントからエンドポイントを取得するautodiscoverという機能が備わっています。
% npm install memcache-plus aws-lambda
% npm install --save-dev @types/aws-lambda esbuild@0
src/lambda/sample/index.ts
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import MemcachePlus = require('memcache-plus');
export async function handler(event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> {
const configEndpoint: string = process.env.MEMCACHED_CONFIG_ENDPOINT ?? '';
const client = new MemcachePlus({
hosts: [configEndpoint],
autodiscover: true,
});
const ttl = 86400 * 30; // キャッシュ期間。単位秒。
const key = 'key';
const input = Math.floor(Math.random() * 1000);
// データをキャッシュする。keyは250バイトまで。
await client.set(key, input, ttl);
// キャッシュしたデータを取得する。存在しない場合はnullを返却する。
const output = await client.get(key);
if (input === output) {
console.debug(`memcachedから取得した値: ${output}`);
return {
statusCode: 200,
body: output,
};
} else {
console.error(`期待値: ${input}, 実際: ${output}`);
return {
statusCode: 500,
body: `期待値: ${input}, 実際: ${output}`
};
}
}
types/memcache-plus.d.ts
declare module 'memcache-plus' {
interface MemcacheOptions {
autodiscover?: boolean;
backoffLimit?: number;
bufferBeforeError?: number;
disabled?: boolean;
hosts?: string | string[];
maxValueSize?: number;
queue?: boolean;
netTimeout?: number;
reconnect?: boolean;
}
class MemcacheClient {
constructor(param: string | string[] | MemcacheOptions);
get(key: string): Promise<any>;
set(key: string, value: any, lifetime?: number): Promise<any>;
// 必要に応じて他のメソッドの型定義を追加
}
export = MemcacheClient;
}
テスト
test/sample.test.ts
test/sample.test.ts
import * as cdk from 'aws-cdk-lib';
import { Match, Template } from 'aws-cdk-lib/assertions';
import { SampleStack } from '../lib/sample-stack';
test('SecurityGroup', () => {
const app = new cdk.App();
const stack = new SampleStack(app, 'SampleStack');
const template = Template.fromStack(stack);
// Security Group
template.hasResourceProperties('AWS::EC2::SecurityGroup', {
VpcId: Match.anyValue(),
SecurityGroupIngress: [{
"CidrIp": "0.0.0.0/0",
"Description": "from 0.0.0.0/0:11211",
"FromPort": 11211,
"IpProtocol": "tcp",
"ToPort": 11211
}]
});
});
test('ElastiCache', () => {
const app = new cdk.App();
const stack = new SampleStack(app, 'SampleStack');
const template = Template.fromStack(stack);
// Subnet Group
template.resourceCountIs('AWS::ElastiCache::SubnetGroup', 1);
template.hasResourceProperties('AWS::ElastiCache::SubnetGroup', {
SubnetIds: Match.anyValue(),
Description: 'private subnet',
});
// Cache Cluster
template.resourceCountIs('AWS::ElastiCache::CacheCluster', 1);
template.hasResourceProperties('AWS::ElastiCache::CacheCluster', {
Engine: 'memcached',
CacheNodeType: 'cache.r6g.large',
NumCacheNodes: 1,
CacheSubnetGroupName: Match.anyValue(),
VpcSecurityGroupIds: Match.anyValue(),
});
});
test('Lambda', () => {
const app = new cdk.App();
const stack = new SampleStack(app, 'SampleStack');
const template = Template.fromStack(stack);
// Lambda
template.resourceCountIs('AWS::Lambda::Function', 1);
template.hasResourceProperties('AWS::Lambda::Function', {
Runtime: 'nodejs18.x',
Timeout: 300,
VpcConfig: Match.anyValue(),
});
});
エラー
Error: spawnSync docker ENOENT
esbuildをインストールすることで解消します。
$ npm install --save-dev esbuild@0
error TS2688: Cannot find type definition file for 'babel__generator'.
node_modulesを削除してnpm installすると解消すると解消することがあります。
Autodiscovery failed. Error: connect ETIMEDOUT 192.168.3.236:11211
セキュリティグループにインバウンドルールが追加されていない場合にこのエラーが起こることがあります。
TCPのインバウンド・アウトバウンド双方にトラフィックの許可が必要です。アウトバウンドルールはデフォルトで全て許可されるので、インバウンドルールを設定します。
手動で設定する場合は下記のように設定します。