この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
はじめに
テントの中から失礼します、CX事業本部のてんとタカハシです!
ALB のターゲットに VPC Lambda を指定する構成を CDK でやってみたので、記事にしようと思います。
この構成は、Lambda から RDS に繋げたり、固定IP を持った Nat Gateway 経由で外部サービスにアクセスするなどの用途で使えそうです。
ソースコードは下記のリポジトリにも置いてあります。
GitHub - iam326/alb-vpc-lambda-sample-cdk
環境
環境は下記の通りです。
$ sw_vers
ProductName: Mac OS X
ProductVersion: 10.15.7
BuildVersion: 19H2
$ cdk --version
1.85.0 (build 5f44668)
$ yarn --version
1.22.10
$ node --version
v12.19.0
単純な構成
構成図
構成は下記の通りです。ALB と VPC Lambda を単純に紐づけているだけの構成です。
実装
スタックの実装は下記の通りです。ターゲットグループのターゲットとして VPC Lambda を指定します。
import * as cdk from '@aws-cdk/core';
import { Vpc, SubnetType, SecurityGroup, Peer, Port } from '@aws-cdk/aws-ec2';
import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2';
import { LambdaTarget } from '@aws-cdk/aws-elasticloadbalancingv2-targets';
import * as lambda from '@aws-cdk/aws-lambda';
export class AlbVpcLambdaSampleCdkStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const prefix: string = this.node.tryGetContext('projectName');
const vpc = new Vpc(this, 'Vpc', {
cidr: '10.0.0.0/16',
enableDnsHostnames: true,
enableDnsSupport: true,
subnetConfiguration: [
{
cidrMask: 24,
name: 'PublicSubnet',
subnetType: SubnetType.PUBLIC,
},
{
cidrMask: 24,
name: 'PrivateSubnet',
subnetType: SubnetType.PRIVATE,
},
],
natGateways: 2,
maxAzs: 2,
});
const securityGroup = new SecurityGroup(this, 'SecurityGroup', {
securityGroupName: `${prefix}-sg`,
vpc,
});
securityGroup.addIngressRule(Peer.anyIpv4(), Port.tcp(80));
const alb = new elbv2.ApplicationLoadBalancer(this, 'Alb', {
vpc,
vpcSubnets: vpc.selectSubnets({ subnetGroupName: 'PublicSubnet' }),
loadBalancerName: `${prefix}-alb`,
internetFacing: true,
securityGroup,
});
const helloWorldFunction = new lambda.Function(this, 'HelloWorldFunction', {
code: lambda.Code.fromAsset('dist/hello-world'),
functionName: `${prefix}-hello-world`,
handler: 'index.handler',
runtime: lambda.Runtime.NODEJS_12_X,
timeout: cdk.Duration.seconds(10),
memorySize: 128,
vpc,
});
const albTargetGroup = new elbv2.ApplicationTargetGroup(
this,
'AlbTargetGroup',
{
vpc,
targetGroupName: `${prefix}-tg`,
targetType: elbv2.TargetType.LAMBDA,
targets: [new LambdaTarget(helloWorldFunction)],
}
);
alb.addListener('AlbListener', {
protocol: elbv2.ApplicationProtocol.HTTP,
port: 80,
defaultTargetGroups: [albTargetGroup],
});
}
}
Lambda の実装は下記の通りです。Content-Type
を指定しないとapplication/octet-stream
で返してしまうので、application/json
を指定しています。
export async function handler(): Promise<any> {
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ hello: 'world' }),
};
}
動作確認
curl で ALB にアクセスすると、期待通りのレスポンスが返ってきました。
$ curl xxxxx.ap-northeast-1.elb.amazonaws.com
{"hello":"world"}
複数の VPC Lambda でパスルーティングする構成
構成図
構成は下記の通りです。アクセスされた HTTP メソッドとパスによって、振り分け先を変えています。
実装
先ほどの実装をベースに変更点を記載します。
export class AlbVpcLambdaSampleCdkStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
...
// 新しい Lambda を作成。返す値以外は同じコード。
const HogeFugaPiyoFunction = new lambda.Function(
this,
'HogeFugaPiyoFunction',
{
code: lambda.Code.fromAsset('dist/hoge'),
functionName: `${prefix}-hoge-fuga-piyo`,
handler: 'index.handler',
runtime: lambda.Runtime.NODEJS_12_X,
timeout: cdk.Duration.seconds(10),
memorySize: 128,
vpc,
}
);
// これは不要なのでコメントアウト
// const albTargetGroup = new elbv2.ApplicationTargetGroup(
// this,
// 'AlbTargetGroup',
// {
// vpc,
// targetGroupName: `${prefix}-tg`,
// targetType: elbv2.TargetType.LAMBDA,
// targets: [new LambdaTarget(helloWorldFunction)],
// }
// );
// defaultTargetGroups ではなく、defaultAction として、404 NotFound を返す設定を入れる
const listener = alb.addListener('AlbListener', {
protocol: elbv2.ApplicationProtocol.HTTP,
port: 80,
// defaultTargetGroups: [albTargetGroup],
defaultAction: elbv2.ListenerAction.fixedResponse(404, {
contentType: elbv2.ContentType.TEXT_PLAIN,
messageBody: 'NotFound',
}),
});
// GET /hello であれば、helloWorldFunction に繋ぐ
listener.addTargets('AlbListenerTargetHello', {
priority: 1,
conditions: [
elbv2.ListenerCondition.httpRequestMethods(['GET']),
elbv2.ListenerCondition.pathPatterns(['/hello']),
],
targets: [new LambdaTarget(helloWorldFunction)],
});
// POST /hoge であれば、HogeFugaPiyoFunction に繋ぐ
listener.addTargets('AlbListenerTargetHoge', {
priority: 2,
conditions: [
elbv2.ListenerCondition.httpRequestMethods(['POST']),
elbv2.ListenerCondition.pathPatterns(['/hoge']),
],
targets: [new LambdaTarget(HogeFugaPiyoFunction)],
});
}
}
デプロイすると、ALB のリスナールールは下記の通りになります。
動作確認
指定する HTTP メソッドとパスによって、レスポンスが変わるようになりました。存在しないパスにアクセスした場合はNotFound
を返すようになっています。
$ curl xxxxx.ap-northeast-1.elb.amazonaws.com/hello
{"hello":"world"}
$ curl -X POST xxxxx.ap-northeast-1.elb.amazonaws.com/hoge
{"fuga":"piyo"}
$ curl xxxxx.ap-northeast-1.elb.amazonaws.com/foo
NotFound
おわりに
新年1本目の記事になりました。今月中に残り3本は書こうと思っています、がんばるぞお〜。
今回は以上になります。最後まで読んで頂きありがとうございました!