本記事の内容
- AWSが提供するCloudFormationサービスを利用し、Lambda(Python)、RDS Proxy、Auroraを建てる方法を記載した記事です。
- これまでに以下の記事でAurora関連をCloudFormationで実施していました。その続編です。
- 検証目的のためにCloudFormationでシングルインスタンスのAuroraを建てようとしたら手こずった話
- CloudFormationでAuroraとLambdaを構築。LambdaでLOAD DATA FROM S3コマンドを実行しS3のCSVファイルをAuroraにインポートする。
本記事の手順で以下のような構成のクラウド環境を作成します。
※AWSの操作画面はサービスリリースと共に更新されていくため、本記事をご覧いただくタイミングによっては、現在の画面と異なる場合があります。ご了承ください。本記事内の画面キャプチャーは2022年9月時点,
RDS Proxyの詳細は他の記事を参照ください。
こちらが私的に面白かったです。
- Amazon RDS Proxy が BASE にもたらした期待以上の導入メリット
- なぜAWS LambdaとRDBMSの相性が悪いかを簡単に説明する ※RDS Proxyが必要な理由が良く分かります
ここから実際のテンプレートを説明していきます。
実施環境
- Windows 10
- Chrome
- AWS
説明の流れ
- S3にLambdaデプロイ用のzipファイルとデータ投入用のCSVファイルの配置
- CloudFormationテンプレートの準備
- CloudFormationスタックの作成
- Lambadaによるテストデータの投入
1. S3にLambdaデプロイ用のzipファイルとデータ投入用のCSVファイルの配置
CloudFormationでのLambdaのデプロイは、事前にデプロイ用ファイルをS3に配置しておき、それをデプロイする方式をとる。
データ投入用のCSVファイルも事前に配置が必要です。
デプロイ用ファイルとCSVファイルの配置の仕方は、以下の記事を参照してご確認ください。
今回はDB接続のエンドポイントとしてRDS Proxyを利用しているため、
前回のプログラムから接続部分の変更を行う必要があります。
変更点は以下の箇所です。環境変数とコネクト時のホストの指定が変更になります。
(変更前)
DB_WRITER_ENDPOINT_ADDRESS = os.environ["DB_WRITER_ENDPOINT_ADDRESS"]
conn = pymysql.connect(host=DB_WRITER_ENDPOINT_ADDRESS, user=DB_USER, passwd=DB_PASSWORD, db=DB_NAME, connect_timeout=5)
(変更後)
DB_PROXY_ENDPOINT_ADDRESS = os.environ["DB_PROXY_ENDPOINT_ADDRESS"]
conn = pymysql.connect(host=DB_PROXY_ENDPOINT_ADDRESS, user=DB_USER, passwd=DB_PASSWORD, db=DB_NAME, connect_timeout=5)
プログラム全体は以下の通り。
import boto3
import json
import pymysql.cursors
import os
def lambda_handler(event, context):
#Lambdaの環境変数は、CloudFormationのデプロイ時に自動で設定する
DB_NAME = os.environ["DB_NAME"]
DB_USER = os.environ["DB_USER"]
DB_PASSWORD = os.environ["DB_PASSWORD"]
DB_PROXY_ENDPOINT_ADDRESS = os.environ["DB_PROXY_ENDPOINT_ADDRESS"]
#pymysqlを利用してAuroraに接続するため、PyMySQLライブラリーを合わせてデプロイする必要がある
conn = pymysql.connect(host=DB_PROXY_ENDPOINT_ADDRESS, user=DB_USER, passwd=DB_PASSWORD, db=DB_NAME, connect_timeout=5)
with conn.cursor() as cur:
#CreateTableコマンドは、mysqlの通常のコマンドと同じ。
cur.execute("create table service (system_id varchar(255),name varchar(255),url varchar(2048),PRIMARY KEY (system_id))")
#S3に配置したS3ファイルをインポートするためには"LOAD DATA FROM S3 FILE"コマンドを利用する。
# 's3://bucket-name/testData.csv'でインポートするファイルのARNを指定しています。
# FIELDS TERMINATED BY ',' LINES TERMINATED BY '\n' IGNORE 1 LINES の指定で、カンマ区切り・改行が\n・先頭行は無視を指定しています。
# (system_id,name)でインポートするカラムを指定しています。
#cur.execute("LOAD DATA FROM S3 FILE 's3://bucket-name/testData.csv' INTO TABLE service FIELDS TERMINATED BY ',' LINES TERMINATED BY '\n' IGNORE 1 LINES (system_id,name);")
#こちらはSQLの従来通りのinsert intoコマンド。一つずつデータを入れる場合はこちらのSQLで良い。
#cur.execute('insert into service (system_id, name) values("1", "test")')
conn.commit()
#インポートした結果はこちらのselect文で確認する。
cur.execute("select * from service")
for row in cur:
print(row)
return {
'statusCode': 200
}
2. CloudFormationスタックの準備
以下が実際に投入したファイルです。
AWSTemplateFormatVersion: 2010-09-09
#####################################################
# Parameters
#####################################################
Parameters:
DBMasterUserName:
Description: Please enter the name of the master user on an RDS.
Type: String
Default: master
DBMasterUserPassword:
Description: Please enter the password of the master user on an RDS. That should be greater or equal to 8 characters.
Type: String
Default: testdbpass
NoEcho: true
TestDataS3BucketArn:
Description: Please enter the ARN of the S3 bucket where the files to be imported into the Aurora database are stored.
Type: String
Default: arn:aws:s3:::bucket-name
S3BucketName:
Description: Please enter the S3 bucket name.
Type: String
VpcSubnet:
Description: AP VPC subnet
Type: String
Default: 10.0.0.0/16
#####################################################
# Mappings
#####################################################
Mappings:
Constant:
RDS:
ClusterName: test-aurora-cluster
DatabaseName: auroradb
InstanceName: test-aurora-instance1
InstanceType: db.t3.small
RDSProxyName: Auroa-DBProxy
#####################################################
# Resource
#####################################################
#####################################################
# VPC section
#####################################################
Description: Create VPC
Resources:
TestVPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VpcSubnet
Tags:
- Key: Name
Value: TestVPC
#####################################################
# Subnet section
#####################################################
TestPrivateSub1a:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: !Select [0, !Cidr [!GetAtt TestVPC.CidrBlock, 2, 8]] #10.0.0.0/24
MapPublicIpOnLaunch: false
VpcId: !Ref TestVPC
AvailabilityZone: ap-northeast-1a
Tags:
- Key: Name
Value: TestPrivateSub1a
TestPrivateSub1c:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: !Select [1, !Cidr [!GetAtt TestVPC.CidrBlock, 2, 8]] #10.0.1.0/24
MapPublicIpOnLaunch: false
VpcId: !Ref TestVPC
AvailabilityZone: ap-northeast-1c
Tags:
- Key: Name
Value: TestPrivateSub1c
#####################################################
# SecurityGroup section
#####################################################
TestSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
VpcId: !Ref "TestVPC"
GroupDescription: Allow RDS Connection From TCP 3306 Port
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 3306
ToPort: 3306
CidrIp: !GetAtt "TestVPC.CidrBlock"
Tags:
- Key: Name
Value: TestSecurityGroup
TestSecurityGroupForLambda:
Type: AWS::EC2::SecurityGroup
Properties:
VpcId: !Ref "TestVPC"
GroupDescription: Allow MySQL (TCP3306)
Tags:
- Key: Name
Value: TestSecurityGroupForLambda
#####################################################
# RouteTable section
#####################################################
TestRTPriSub:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref TestVPC
Tags:
- Key: Name
Value: TestRTPriSub
CfAssocciateRouteTableForPrivateSubnet:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref TestRTPriSub
SubnetId: !Ref TestPrivateSub1a
#####################################################
# VPC Endpoint section
#####################################################
#PrivateSubnetのLambdaがS3にアクセスするためにはVPC S3エンドポイントが必要。Private Subnetのルーティング設定にエンドポイントを追加する。
VPCS3Endpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
RouteTableIds:
- !Ref TestRTPriSub
ServiceName: !Sub com.amazonaws.${AWS::Region}.s3
VpcId: !Ref TestVPC
#####################################################
# DBSubnetGroup section
#####################################################
DBSubnetGroup:
Type: AWS::RDS::DBSubnetGroup
Properties:
DBSubnetGroupDescription: RDS subnet group.
SubnetIds:
- !Ref "TestPrivateSub1a"
- !Ref "TestPrivateSub1c"
#####################################################
# Aurora section
#####################################################
#AuroraがS3にアクセスするために必要なIAMロールの設定
RDSS3AccessRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service: rds.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: AllowS3Access
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- s3:GetObject
- s3:GetObjectVersion
Resource:
- !Sub '${TestDataS3BucketArn}'
- !Sub '${TestDataS3BucketArn}/*'
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-rds-dbparametergroup.html
DBParameterGroup:
Type: "AWS::RDS::DBParameterGroup"
Properties:
Description: "RDS DB parameter group the Aurora Cluster's instance(s)."
Family: "aurora-mysql5.7"
Parameters:
max_connections: "10"
Tags:
- Key: Name
Value: TestClusterParamaterGroup
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rds-dbclusterparametergroup.html
DBClusterParameterGroup:
Type: "AWS::RDS::DBClusterParameterGroup"
Properties:
Description: "RDS DB cluster parameter group for Hub-Amber"
Family: "aurora-mysql5.7"
Parameters:
aws_default_s3_role: !GetAtt RDSS3AccessRole.Arn #LOAD DATA FROM S3コマンドを実行し、S3のファイルを読み込むためのIAM role。超重要。
character_set_client: "utf8mb4"
character_set_connection: "utf8mb4"
character_set_database: "utf8mb4"
character_set_filesystem: "utf8mb4"
character_set_results: "utf8mb4"
character_set_server: "utf8mb4"
collation_connection: "utf8mb4_bin"
collation_server: "utf8mb4_bin"
time_zone: "Asia/Tokyo"
Tags:
- Key: Name
Value: TestClusterParamaterGroup
DBCluster:
Type: AWS::RDS::DBCluster
Properties:
AssociatedRoles:
- RoleArn: !GetAtt RDSS3AccessRole.Arn #LOAD DATA FROM S3コマンドを実行し、S3のファイルを読み込むためのIAM role。超重要。
Engine: aurora-mysql
EngineVersion: 5.7.mysql_aurora.2.10.2
DatabaseName: !FindInMap [Constant, RDS, DatabaseName]
DBClusterIdentifier: !FindInMap [Constant, RDS, ClusterName]
MasterUsername: !Ref "DBMasterUserName"
MasterUserPassword: !Ref "DBMasterUserPassword"
DBSubnetGroupName: !Ref "DBSubnetGroup"
DBClusterParameterGroupName: !Ref "DBClusterParameterGroup"
VpcSecurityGroupIds:
- !Ref "TestSecurityGroup"
DBInstance:
Type: AWS::RDS::DBInstance
Properties:
Engine: aurora-mysql
EngineVersion: 5.7.mysql_aurora.2.10.2
DBInstanceClass: !FindInMap [Constant, RDS, InstanceType]
DBSubnetGroupName: !Ref "DBSubnetGroup"
DBParameterGroupName: !Ref "DBParameterGroup"
DBClusterIdentifier: !Ref "DBCluster"
AvailabilityZone: ap-northeast-1a
#####################################################
# Secret Manager section
#####################################################
RDSProxySecrets:
Type: AWS::SecretsManager::Secret
Properties:
Name: "SystemManager-db-secret"
Description: "This is a Secrets Manager secret for an RDS DB instance"
SecretString: !Sub
- '{
"username": "${DBMasterUserName}" ,
"password": "${DBMasterUserPassword}" ,
"engine":"mysql",
"host": "${DBCluster.Endpoint.Address}" ,
"port":"3306" ,
"dbname": "${data_base_name}",
"dbInstanceIdentifier": "${DBCluster}"
}'
- data_base_name: !FindInMap [Constant, RDS, DatabaseName]
Tags:
- Key: Name
Value: RDSProxySecrets
DependsOn: DBCluster
#####################################################
# RDS Proxy section
#####################################################
RDSProxyRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service: rds.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: AllowGetSecretValue
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- secretsmanager:GetSecretValue
- secretsmanager:DescribeSecret
Resource:
#- !Ref RDSSecretAttachment
- !Ref RDSProxySecrets
DBProxy:
Type: AWS::RDS::DBProxy
Properties:
Auth:
- IAMAuth: DISABLED
AuthScheme: SECRETS
SecretArn: !Ref RDSProxySecrets #DBの認証情報をあらかじめシークレットARNを利用する
DBProxyName: !FindInMap [Constant, RDS, RDSProxyName]
EngineFamily: MYSQL
IdleClientTimeout: 120
RequireTLS: false
RoleArn: !GetAtt RDSProxyRole.Arn #?
VpcSecurityGroupIds:
- !Ref TestSecurityGroup
VpcSubnetIds:
- !Ref TestPrivateSub1a
- !Ref TestPrivateSub1c
DependsOn: DBCluster
DBProxyTargetGroup:
Type: AWS::RDS::DBProxyTargetGroup
Properties:
DBProxyName: !FindInMap [Constant, RDS, RDSProxyName]
DBClusterIdentifiers:
- !FindInMap [Constant, RDS, ClusterName]
TargetGroupName: default
ConnectionPoolConfigurationInfo:
MaxConnectionsPercent: 100
MaxIdleConnectionsPercent: 50
ConnectionBorrowTimeout: 120
DependsOn:
- DBProxy
- DBCluster
- DBInstance
#####################################################
# Lambda section (データ投入用Lambda)
#####################################################
#Lambda実行権限とAuroraの操作権限を付与したIAMロールの設定
IAMRoleForLambdaFunction:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
-
Effect: "Allow"
Principal:
Service:
- "lambda.amazonaws.com"
Action:
- "sts:AssumeRole"
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole
- arn:aws:iam::aws:policy/AmazonRDSFullAccess
MaxSessionDuration: 3600
Path: "/"
RoleName: "RDSFullAccessLambdaExecRole"
LambdaFunction:
Type: AWS::Lambda::Function
Properties:
Environment:
Variables:
DB_PORT: '3306'
DB_NAME: !FindInMap [Constant, RDS, DatabaseName]
DB_PASSWORD: !Ref "DBMasterUserPassword"
#DB_WRITER_ENDPOINT_ADDRESS: !GetAtt DBCluster.Endpoint.Address
DB_PROXY_ENDPOINT_ADDRESS: !GetAtt DBProxy.Endpoint
DB_USER: !Ref "DBMasterUserName"
Code:
S3Bucket: !Ref "S3BucketName"
S3Key: index.zip
FunctionName: "TestDataInsertLambda"
Handler: index.lambda_handler
Runtime: python3.8
Role: !GetAtt IAMRoleForLambdaFunction.Arn #Lambda実行権限とAuroraの操作権限を付与したIAM
Timeout: 10
VpcConfig:
SecurityGroupIds:
- !Ref TestSecurityGroupForLambda
SubnetIds:
- !Ref TestPrivateSub1a
前回の記事との差異部分のみ説明します。
RDS proxyをCloudFormationで作成するためには以下が必要になります。
- 必要な設定
- RDS ProxyがDBに接続するときに利用する認証情報が格納されたシークレット(AWS::SecretsManager::Secret)
- RDS Proxyがシークレットを参照するためのIAMロール(AWS::IAM::Role)
- RDS Proxyの本体。シークレット情報、IAMロール、サブネット、セキュリティグループ等を設定(AWS::RDS::DBProxy)
- RDS Proxyの接続先情報(AWS::RDS::DBProxyTargetGroup)
以下が今回の差分でメインとなる部分。
#####################################################
# Secret Manager section
#####################################################
RDSProxySecrets:
Type: AWS::SecretsManager::Secret
Properties:
Name: "SystemManager-db-secret"
Description: "This is a Secrets Manager secret for an RDS DB instance"
SecretString: !Sub
- '{
"username": "${DBMasterUserName}" ,
"password": "${DBMasterUserPassword}" ,
"engine":"mysql",
"host": "${DBCluster.Endpoint.Address}" ,
"port":"3306" ,
"dbname": "${data_base_name}",
"dbInstanceIdentifier": "${DBCluster}"
}'
- data_base_name: !FindInMap [Constant, RDS, DatabaseName]
Tags:
- Key: Name
Value: RDSProxySecrets
DependsOn: DBCluster
#####################################################
# RDS Proxy section
#####################################################
RDSProxyRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service: rds.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: AllowGetSecretValue
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- secretsmanager:GetSecretValue
- secretsmanager:DescribeSecret
Resource:
- !Ref RDSProxySecrets
DBProxy:
Type: AWS::RDS::DBProxy
Properties:
Auth:
- IAMAuth: DISABLED
AuthScheme: SECRETS
SecretArn: !Ref RDSProxySecrets #DBの認証情報をあらかじめシークレットARNを利用する
DBProxyName: !FindInMap [Constant, RDS, RDSProxyName]
EngineFamily: MYSQL
IdleClientTimeout: 120
RequireTLS: false
RoleArn: !GetAtt RDSProxyRole.Arn #?
VpcSecurityGroupIds:
- !Ref TestSecurityGroup
VpcSubnetIds:
- !Ref TestPrivateSub1a
- !Ref TestPrivateSub1c
DependsOn: DBCluster
DBProxyTargetGroup:
Type: AWS::RDS::DBProxyTargetGroup
Properties:
DBProxyName: !FindInMap [Constant, RDS, RDSProxyName]
DBClusterIdentifiers:
- !FindInMap [Constant, RDS, ClusterName]
TargetGroupName: default
ConnectionPoolConfigurationInfo:
MaxConnectionsPercent: 100
MaxIdleConnectionsPercent: 50
ConnectionBorrowTimeout: 120
DependsOn:
- DBProxy
- DBCluster
- DBInstance
3. CloudFormationスタックの作成
AWSコンソールでのCloudFormationテンプレートの投入方法は、前回の記事通りです。
こちらを参考に進めてください。
4. Lambadaによるテストデータの投入
Lambdaによるテストデータについても前回の記事通りです。
こちらを参考に進めてください。
テストデータの投入は以上ですので、必要に応じてPythonプログラムを書き換えて利用できます。
本記事は以上です。
クラウド環境の作成の手助けになれば嬉しいです。
参考文献
本記事の作成に当たり、以下の情報も参考にさせて頂きました。ありがとうございました。
- 【AWS】実例で学ぶCloudFormation~RDS Aurora編~
- CloudFormation の Fn::Cidr 組み込み関数の使い方のメモ。
- 【AWS初学者向け・図解】CloudFormationの組み込み関数を現役エンジニアがわかりやすく解説②
- 【CloudFormation】Lambdaの Role を定義する際は !GetAtt を使って明示的に指定する
- AWS LambdaをVPC設定したときに「The provided execution role does not have permissions to call CreateNetworkInterface on EC2」
- CloudFormationでLambdaを作成する3パータン(S3/インライン/コンテナ)
- Amazon RDS Proxy が BASE にもたらした期待以上の導入メリット
- VPC内のLambdaからRDS Proxy経由でRDSに接続する
- 【SAM】Lambda(Python)でRDS Proxyを使ってみる【user/pass, IAM認証】
- なぜAWS LambdaとRDBMSの相性が悪いかを簡単に説明する ※RDS Proxyが必要な理由が良く分かります
- 【CloudFormation】AWS Secrets Managerを使いRDS Proxy経由でRDSに接続する方法
- !FindInMapを!Subの中で使う | CloudFormation
- RDS(postgres)のデータをS3にexportする〜private network編〜
- VPCエンドポイントを使ってプライベートサブネットからS3へアクセスする。
- AssociatedRolesを使ってCFnでS3とデータをロード&アンロードできるAurora MySQL5.7を構築する
- Amazon S3 バケットのテキストファイルから Amazon Aurora MySQL DB クラスターへのデータのロード
- Amazon Aurora MySQLとS3間でデータをロード&アンロードする