AWS CDK v2 でVPC上にAPI Gateway + Lambda + RDS + RDS Proxyを構築
2022-02-2825 min read
目次
概要
AWS CDK v2 でVPC上にAPI Gateway + Lambda + RDS + RDS Proxyを構築し、 Lmabdaでmysqlまでの接続を確認した際のメモです
CDKもアプリケーションも全てtypescriptで実装しています。
Stack
以下のソースがAWS CDKのスタックです。
lib/LambdaWithVpcStack.ts
import {
Stack,
RemovalPolicy,
Duration,
aws_iam as Iam,
aws_ec2 as ec2,
aws_rds as rds,
aws_lambda as lambda,
aws_secretsmanager as secrets,
aws_apigateway as apigw,
} from "aws-cdk-lib";
import { NodejsFunction } from "aws-cdk-lib/aws-lambda-nodejs";
import { Construct } from "constructs";
export interface CustomizedProps extends StackProps {
projectName: string;
}
export class LambdaWithVpcStack extends Stack {
constructor(scope: Construct, id: string, props: CustomizedProps) {
super(scope, id, props);
const PROJECT_NAME = props.projectName;
let vpc: ec2.Vpc;
vpc = new ec2.Vpc(this, "VPC", {
cidr: "10.0.0.0/16",
vpcName: `${PROJECT_NAME}-vpc`,
enableDnsHostnames: true,
enableDnsSupport: true,
subnetConfiguration: [
{
cidrMask: 24,
name: "PublicSubnet",
subnetType: ec2.SubnetType.PUBLIC,
},
{
cidrMask: 24,
name: "PrivateSubnet",
subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
},
],
// natGateways: 2,
maxAzs: 2,
});
const bastionGroup = new ec2.SecurityGroup(this, 'Bastion to DB', {vpc});
const lambdaToRDSProxyGroup = new ec2.SecurityGroup(this, "Lambda to RDSProxy", {vpc});
const dbConnectionGroup = new ec2.SecurityGroup(this, "RDSProxy to DB",{vpc});
dbConnectionGroup.addIngressRule(
dbConnectionGroup,
ec2.Port.tcp(3306),
"allow db connection"
);
dbConnectionGroup.addIngressRule(
lambdaToRDSProxyGroup,
ec2.Port.tcp(3306),
"allow lambda connection"
);
dbConnectionGroup.addIngressRule(
bastionGroup,
ec2.Port.tcp(3306),
'allow bastion connection'
);
// パブリックサブネットに踏み台サーバを配置する
const host = new ec2.BastionHostLinux(this, "BastionHost", {
vpc,
instanceType: ec2.InstanceType.of(
ec2.InstanceClass.T4G,
ec2.InstanceSize.NANO
),
securityGroup: bastionGroup,
subnetSelection: {
subnetType: ec2.SubnetType.PUBLIC,
},
});
host.instance.addUserData("yum -y update", "yum install -y mysql jq");
// RDSの認証情報
const databaseCredentialsSecret = new secrets.Secret(
this,
"DBCredentialsSecret",
{
secretName: id + "-rds-credentials",
generateSecretString: {
secretStringTemplate: JSON.stringify({
username: "syscdk",
}),
excludePunctuation: true,
includeSpace: false,
generateStringKey: "password",
},
}
);
// Lambda関数からSecret ManagerにアクセスするためのVPCエンドポイント
new ec2.InterfaceVpcEndpoint(this, "SecretManagerVpcEndpoint", {
vpc: vpc,
service: ec2.InterfaceVpcEndpointAwsService.SECRETS_MANAGER,
});
const rdsInstance = new rds.DatabaseInstance(this, "DBInstance", {
engine: rds.DatabaseInstanceEngine.mysql({
version: rds.MysqlEngineVersion.VER_8_0_26,
}),
credentials: rds.Credentials.fromSecret(databaseCredentialsSecret),
instanceType: ec2.InstanceType.of(
ec2.InstanceClass.T3,
ec2.InstanceSize.MICRO
),
vpc,
vpcSubnets: {
// subnetType: ec2.SubnetType.ISOLATED,
subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
},
securityGroups: [dbConnectionGroup],
removalPolicy: RemovalPolicy.DESTROY,
deletionProtection: false,
parameterGroup: new rds.ParameterGroup(this, "ParameterGroup", {
engine: rds.DatabaseInstanceEngine.mysql({
version: rds.MysqlEngineVersion.VER_8_0_26,
}),
parameters: {
character_set_client: "utf8mb4",
character_set_server: "utf8mb4",
},
}),
});
const proxy = rdsInstance.addProxy(id + "-proxy", {
secrets: [databaseCredentialsSecret],
debugLogging: true,
vpc,
securityGroups: [dbConnectionGroup],
});
const iamRoleForLambda = new Iam.Role(this, "iamRoleForLambda", {
roleName: `${PROJECT_NAME}-lambda-role`,
assumedBy: new Iam.ServicePrincipal("lambda.amazonaws.com"),
// VPCに設置するためのポリシー定義
managedPolicies: [
Iam.ManagedPolicy.fromAwsManagedPolicyName(
"service-role/AWSLambdaVPCAccessExecutionRole"
),
],
});
// lambda
const appLambda1 = new NodejsFunction(this, "getSample1", {
entry: "src/lambda/index.ts", // どのコードを使用するか
runtime: lambda.Runtime.NODEJS_14_X, // どのバージョンか
timeout: Duration.seconds(30), // 何秒でタイムアウトするか
role: iamRoleForLambda, // どのIAMロールを使用するか
vpc: vpc, // VPCに設置する場合に必要
securityGroups: [lambdaToRDSProxyGroup],
environment: {
PROXY_ENDPOINT: proxy.endpoint,
RDS_SECRET_NAME: id + "-rds-credentials",
AWS_NODEJS_CONNECTION_REUSE_ENABLED: "1", // keepaliveを有効にする
},
memorySize: 128, // default=128
});
// 認証情報へのアクセス許可
databaseCredentialsSecret.grantRead(appLambda1);
const restApi = new apigw.RestApi(this, "RestApi", {
restApiName: "rds-proxy-go",
deployOptions: {
stageName: "dev",
},
// CORS設定
defaultCorsPreflightOptions: {
allowOrigins: apigw.Cors.ALL_ORIGINS,
allowMethods: apigw.Cors.ALL_METHODS,
allowHeaders: apigw.Cors.DEFAULT_HEADERS,
statusCode: 200,
},
});
restApi.root.addProxy({
defaultIntegration: new apigw.LambdaIntegration(appLambda1),
anyMethod: true
});
}
}
Lambdaの実装
src/lambda/index.ts
import { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda";
import * as AWS from "aws-sdk";
import * as mysql from "mysql2/promise"
import * as fs from "fs"
const cert = `
-----BEGIN CERTIFICATE-----
ここにクライアント証明書の内容を記載する
https://www.amazontrust.com/repository/AmazonRootCA1.pem
-----END CERTIFICATE-----
`.trim()
export const handler = async (
event: APIGatewayProxyEvent
): Promise<APIGatewayProxyResult> => {
const params = event.queryStringParameters ? event.queryStringParameters : {};
const RESPONSE_HEADERS = {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "Content-Type,Authorization,access-token",
};
const secretsManager = new AWS.SecretsManager({
region: "ap-northeast-1",
})
const response = await secretsManager.getSecretValue({
SecretId: "LambdaWithVpcStack-rds-credentials",
}).promise()
const {host, username, password} = JSON.parse(response.SecretString ?? '')
const connection = await mysql.createConnection({
host: process.env.PROXY_ENDPOINT,
user: username,
password,
database: 'app',
// ssl: {
// cert: fs.readFileSync(__dirname + '/cert/AmazonRootCA1.pem', 'utf-8')
// },
ssl: {
cert: cert
}
});
let result = await connection.query("select * from user")
return {
statusCode: 200,
headers: RESPONSE_HEADERS,
body: JSON.stringify(result[0])
};
// [{"id":1,"name":"思い出の本","price":100},{"id":2,"name":"AWS Database Book","price":200000},{"id":3,"name":"日記帳12345","price":3000000},{"id":4,"name":"あいうえお","price":12345}]
};
mysqlの初期化
以下のコマンドで踏み台にログインします。
aws ssm start-session --target ${踏み台EC2のID}
mysqlに接続しデータベースの中身を作成。
CREATE DATABASE app;
USE app;
CREATE TABLE user(id int primary key auto_increment, name text, value int);
INSERT INTO user (name, price) VALUES ('田中', 100);
INSERT INTO user (name, price) VALUES ('山田', 200000);
INSERT INTO user (name, price) VALUES ('佐藤', 3000000);
INSERT INTO user (name, price) VALUES ('鈴木', 12345);
※ 証明書
https://www.amazontrust.com/repository/AmazonRootCA1.pem
結果
次の様なレスポンスがAPI Gatewayのエンドポイントから帰ってこればOK
[
{
"id": 1,
"name": "田中",
"value": 100
},
{
"id": 2,
"name": "山田",
"value": 200000
},
{
"id": 3,
"name": "鈴木",
"value": 3000000
},
{
"id": 4,
"name": "佐藤",
"value": 12345
}
]
参考にしたサイト
Recommends
New Posts
Hot posts!
Date
Tags
(110)
(54)
(54)
(47)
(45)
(36)
(30)
(29)
(24)
(24)
(22)
(21)
(21)
(20)
(19)
(17)
(16)
(16)
(15)
(14)
(12)
(12)
(12)
(12)
(12)
(12)
(11)
(10)
(10)
(10)
(10)
(10)
(9)
(9)
(8)
(8)
(8)
(8)
(7)
(7)
(6)
(6)
(6)
(6)
(6)
(5)
(5)
(5)
(5)
(4)
Author