AWS CDK を学習していく過程を記録する。
はじめに
[備忘録]AWS CDKでAPI Gateway, Lambda を構築にて、API GatewayをトリガーにLambdaを実行することで、LambdaがRestAPIとして外部ネットワークから利用可能になることを確認した。
今回は、RDS、RDS Proxy を構築し、Lambda の中の処理を書き換えて、API Gateway から Lambda 経由で mysql に接続できることを確認したい。
RDS とは
Amazon Relational Database Service (Amazon RDS) は、AWS クラウド でリレーショナルデータベースを簡単にセットアップし、運用し、スケーリングすることのできるウェブサービスです。
つまり、サーバの設定やOSのインストールなどすることなくすぐにDB構築が可能なサービスであり、RDSでは mysql、Oracle Database、Microsoft SQL Server、PostgreSQL など一般的なDB管理システム(RDBMS)を使用することができる。
RDS Proxy とは
Amazon RDS Proxy は、Amazon Relational Database Service (RDS) 向けの高可用性フルマネージド型データベースプロキシで、アプリケーションのスケーラビリティやデータベース障害に対する回復力と安全性を高めます。
つまり、アプリケーションと RDS の間でアクセスを中継し、DBへの接続を効率的に管理してくれるサービスと言える。
実施したこと
- RDSの作成
- Lambdaの作成
1. RDSの作成
import { BastionHostLinux, InstanceClass, InstanceSize, InstanceType, InterfaceVpcEndpoint, InterfaceVpcEndpointAwsService, Port, SecurityGroup, SubnetType, Vpc } from 'aws-cdk-lib/aws-ec2';
import { Credentials, DatabaseInstance, DatabaseInstanceEngine, MysqlEngineVersion, ParameterGroup } from 'aws-cdk-lib/aws-rds';
import { Secret } from 'aws-cdk-lib/aws-secretsmanager';
// vpc
const vpc = new Vpc(this, 'AppVpc', {
cidr: '10.0.0.0/16',
vpcName: 'app-vpc',
enableDnsHostnames: true,
enableDnsSupport: true,
subnetConfiguration: [
{
cidrMask: 24,
name: 'PublicSubnet',
subnetType: SubnetType.PUBLIC,
},
{
cidrMask: 24,
name: 'PrivateSubnet',
subnetType: SubnetType.PRIVATE_ISOLATED,
},
],
maxAzs: 2
});
const bastionGroup = new SecurityGroup(this, 'Bastion to DB', {
vpc: vpc
});
const lambdaToRDSProxyGroup = new SecurityGroup(this, 'Lambda to RDSProxy', {
vpc: vpc
});
const dbConnectionGroup = new SecurityGroup(this, 'RDSProxy to DB', {
vpc: vpc
});
dbConnectionGroup.addIngressRule(
dbConnectionGroup,
Port.tcp(3306),
'allow db connection'
);
dbConnectionGroup.addIngressRule(
lambdaToRDSProxyGroup,
Port.tcp(3306),
'allow lambda connection'
);
dbConnectionGroup.addIngressRule(
bastionGroup,
Port.tcp(3306),
'allow bastion connection'
);
// bastion server
const host = new BastionHostLinux(this, 'AppBastionHost', {
vpc: vpc,
instanceType: InstanceType.of(
InstanceClass.T2,
InstanceSize.MICRO
),
securityGroup: bastionGroup,
subnetSelection: {
subnetType: SubnetType.PUBLIC
},
});
host.instance.addUserData("yum -y update", "yum install -y mysql jq");
// credential about RDS
const databaseCredentialsSecret = new Secret(this, 'AppDbCredentialsSecret', {
secretName: id + '-rds-credentials',
generateSecretString: {
secretStringTemplate: JSON.stringify({
username: 'syscdk',
}),
excludePunctuation: true,
includeSpace: false,
generateStringKey: 'password',
},
});
// vpc endpoint to access from lambda to secret manager
new InterfaceVpcEndpoint(this, 'SecretManagerVpcEndpoint', {
vpc: vpc,
service: InterfaceVpcEndpointAwsService.SECRETS_MANAGER,
});
// rds
const rdsInstance = new DatabaseInstance(this, 'DBInstance', {
engine: DatabaseInstanceEngine.mysql({
version: MysqlEngineVersion.VER_8_0_33
}),
credentials: Credentials.fromSecret(databaseCredentialsSecret),
instanceType: InstanceType.of(
InstanceClass.T2,
InstanceSize.MICRO
),
vpc: vpc,
vpcSubnets: {
subnetType: SubnetType.PRIVATE_ISOLATED
},
securityGroups: [dbConnectionGroup],
removalPolicy: RemovalPolicy.DESTROY,
deletionProtection: false,
parameterGroup: new ParameterGroup(this, 'ParameterGroup', {
engine: DatabaseInstanceEngine.mysql({
version: MysqlEngineVersion.VER_8_0_33
}),
parameters: {
character_set_client: "utf8mb4",
character_set_server: "utf8mb4",
},
}),
});
// rds proxy
const proxy = rdsInstance.addProxy(id + '-proxy', {
secrets: [databaseCredentialsSecret],
debugLogging: true,
vpc: vpc,
securityGroups: [dbConnectionGroup],
});
デプロイし、早速AWS System Managerを使用して踏み台サーバー経由でmysqlへの接続を試みた。
すると Session Manager プラグイン がないため踏み台にログインできなかったので、[備忘録] macOS で AWS Session Manager(SSM) を使ってLinuxサーバーに接続できるようになるまでにまとめた手順で Session Manager プラグイン をインストールした。
% aws ssm start-session --target {踏み台サーバーのID}
Starting session with SessionId: *****
sh-4.2$
インストール後は無事に踏み台サーバーにログインでき、さらにSecrets Manager にある接続情報でmysqlへの接続も成功。
動作確認のためのテーブル作成まで行った。
mysql -usyscdk -p -h ${DBのエンドポイント}
MySQL [(none)]> CREATE DATABASE app;
MySQL [(none)]> USE app;
MySQL [app]> CREATE TABLE test(id int primary key auto_increment, value int);
MySQL [app]> INSERT INTO test (value) VALUES (100);
MySQL [app]> INSERT INTO test (value) VALUES (200);
MySQL [app]> INSERT INTO test (value) VALUES (300);
MySQL [app]> INSERT INTO test (value) VALUES (400);
MySQL [app]> SELECT * FROM app.test;
+----+-------+
| id | value |
+----+-------+
| 1 | 100 |
| 2 | 200 |
| 3 | 300 |
| 4 | 400 |
+----+-------+
4 rows in set (0.00 sec)
2. Lambdaの作成
app-lambda.ts を下記のように書き換えた。
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import * as AWS from 'aws-sdk'
import * as mysql from 'mysql2/promise';
const cert =`
-----BEGIN CERTIFICATE-----
クライアント証明書
-----END CERTIFICATE-----
`.trim()
export const handler = async (
event: APIGatewayProxyEvent
): Promise<APIGatewayProxyResult> => {
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: "AppStack-rds-credentials",
}).promise()
const {host, username, password} = JSON.parse(response.SecretString ?? '')
console.log(`username = ${username}`);
const connection = await mysql.createConnection({
host: process.env.PROXY_ENDPOINT,
database: 'app',
user: username,
password: password,
ssl: {
cert: cert
},
});
let result = await connection.query("select * from test")
return {
statusCode: 200,
headers: RESPONSE_HEADERS,
body: JSON.stringify(result[0])
};
};
デプロイしlambdaを実行したところ、期待通り test テーブルに保存した内容が返ってきた。
最後に
AWS CDKでRDS、RDS Proxy を構築し、Lambda から mysql に接続できることを確認できた。
参考にした文献を記す。