CloudFormationカスタムリソースを使って、RDS DBの初期セットアップを実行する
CloudFormationでRDSリソース作成時に、DBの初期化(DBやテーブルの作成、テストレコードの追加等)も併せて実行することを考えます。
今回はCloudFormationカスタムリソースを使用して、DBを初期化します。
構築する環境
主に4つのリソースを作成します。
1つ目はRDS DBインスタンスです。
今回はMySQLタイプのDBインスタンスを作成します。
2つ目はEC2インスタンスです。
DBインスタンスに接続するクライアントとして使用します。
インスタンスは最新版のAmazon Linux 2です。
3つ目はLambda関数です。
この関数がCloudFormationスタック作成時に自動的に実行されるように、CloudFormationカスタムリソースに関数を関連づけます。
この関数の働きは、DBインスタンスを初期化することです。
関数のランタイム環境はPython3.8です。
4つ目はSSMパラメータストアです。
Lambda関数で実行するSQL文を、パラメータストアに文字列として保存します。
なお初期化処理で実行する内容は、以下のAWS公式ページを参考にしました。
CloudFormationテンプレートファイル
上記の構成をCloudFormationで構築します。
以下のURLにCloudFormationテンプレートを配置しています。
https://github.com/awstut-an-r/awstut-fa/tree/main/062
テンプレートファイルのポイント解説
SSMパラメータストア
Resources:
SQLParameter:
Type: AWS::SSM::Parameter
Properties:
Name: !Ref Prefix
Type: String
Value: |
CREATE database tutorial;
USE tutorial;
CREATE TABLE planet (id INT UNSIGNED AUTO_INCREMENT, name VARCHAR(30), PRIMARY KEY(id));
INSERT INTO planet (name) VALUES ("Mercury");
INSERT INTO planet (name) VALUES ("Venus");
INSERT INTO planet (name) VALUES ("Earth");
INSERT INTO planet (name) VALUES ("Mars");
INSERT INTO planet (name) VALUES ("Jupiter");
INSERT INTO planet (name) VALUES ("Saturn");
INSERT INTO planet (name) VALUES ("Uranus");
INSERT INTO planet (name) VALUES ("Neptune");
Code language: YAML (yaml)
Lambda関数で実行するSQL文を、SSMパラメータストアに登録します。
DBおよびテーブルを作成後、テストレコードを保存します。
CloudFormationカスタムリソース
まずカスタムリソースで実行するLambda関数を確認します。
Resources:
Function:
Type: AWS::Lambda::Function
Properties:
Environment:
Variables:
DB_ENDPOINT_ADDRESS: !Ref DBInstanceEndpointAddress
DB_ENDPOINT_PORT: !Ref MySQLPort
DB_PASSWORD: !Ref DBMasterUserPassword
DB_USER: !Ref DBMasterUsername
REGION: !Ref AWS::Region
SQL_PARAMETER: !Ref SQLParameter
Code:
ZipFile: |
import boto3
import cfnresponse
import mysql.connector
import os
db_endpoint_port = os.environ['DB_ENDPOINT_PORT']
db_endpoint_address = os.environ['DB_ENDPOINT_ADDRESS']
db_password = os.environ['DB_PASSWORD']
db_user = os.environ['DB_USER']
region = os.environ['REGION']
sql_parameter = os.environ['SQL_PARAMETER']
CREATE = 'Create'
response_data = {}
def lambda_handler(event, context):
try:
if event['RequestType'] == CREATE:
client = boto3.client('ssm', region_name=region)
response = client.get_parameter(Name=sql_parameter)
sql_statements = response['Parameter']['Value']
conn = mysql.connector.connect(
host=db_endpoint_address,
port=db_endpoint_port,
user=db_user,
password=db_password
)
cur = conn.cursor()
for sql in sql_statements.splitlines():
print(sql)
cur.execute(sql)
cur.close()
conn.commit()
cfnresponse.send(event, context, cfnresponse.SUCCESS, response_data)
except Exception as e:
print(e)
cfnresponse.send(event, context, cfnresponse.FAILED, response_data)
FunctionName: !Sub "${Prefix}-function"
Handler: index.lambda_handler
Layers:
- !Ref LambdaLayer
Runtime: !Ref Runtime
Role: !GetAtt FunctionRole.Arn
Timeout: !Ref Timeout
VpcConfig:
SecurityGroupIds:
- !Ref FunctionSecurityGroup
SubnetIds:
- !Ref PrivateSubnet
Code language: YAML (yaml)
Lambda関数で実行するコードをインライン表記で定義します。
詳細につきましては、以下のページをご確認ください。
cfnresponseモジュールを使用して、関数をLambda-backedカスタムリソースとして実装します。
詳細につきましては、以下のページをご確認ください。
今回の構成では、Lambda関数をVPC内に配置します。
VpcConfigプロパティで、関数を配置するサブネットや、適用するセキュリティグループを指定します。
実行するコードの内容ですが、以下の通りです。
- CloudFormationテンプレートで定義した環境変数を、os.environにアクセスして取得する。
- Boto3でSSM用クライアントオブジェクトを作成する。
- クライアントオブジェクトを使用して、SSMパラメータストアにアクセスし、先述のSQL文を取得する。
- MySQLクライアントを使用してDBインスタンスに接続し、SQLを1文ずつ実行する。
DBインスタンス接続用のMySQLクライアントですが、MySQL Connectorを使用します。
https://dev.mysql.com/doc/connector-python/en/
今回はLambdaレイヤーとしてMySQL Connectorパッケージを用意します。
Lambdaレイヤーの詳細につきましては、以下のページをご確認ください。
ちなみに関数用のIAMロールは以下の通りです。
Resources:
FunctionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action: sts:AssumeRole
Principal:
Service:
- lambda.amazonaws.com
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole
Policies:
- PolicyName: GetSSMParameterPolicy
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- ssm:GetParameter
Resource:
- !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${SQLParameter}"
Code language: YAML (yaml)
AWS管理ポリシーであるAWSLambdaVPCAccessExecutionRoleに加えて、SSMパラメータストアからパラメータを取得する権限を与えます。
続いてCloudFormationカスタムリソース本体を確認します。
Resources:
CustomResource:
Type: Custom::CustomResource
Properties:
ServiceToken: !Ref FunctionArn
Code language: YAML (yaml)
先述のLambda関数を指定します。
VPCエンドポイント
今回の構成では、複数のVPCエンドポイントを作成します。
ここではSSM用VPCエンドポイントに注目します。
Resources:
SSMEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
PrivateDnsEnabled: true
SecurityGroupIds:
- !Ref EndpointSecurityGroup
ServiceName: !Sub "com.amazonaws.${AWS::Region}.ssm"
SubnetIds:
- !Ref PrivateSubnet
VpcEndpointType: Interface
VpcId: !Ref VPC
Code language: YAML (yaml)
VPC内に配置したLambda関数からSSMパラメータストアに接続するために、このVPCエンドポイントを使用します。
VPCエンドポイントを作成することで、インターネットを経由することなく、VPC外のAWSリソースに接続することができます。
(参考)RDS DBインスタンス
Resources:
DBInstance:
Type: AWS::RDS::DBInstance
Properties:
AllocatedStorage: !Ref DBAllocatedStorage
AvailabilityZone: !Sub "${AWS::Region}${AvailabilityZone}"
DBInstanceClass: !Ref DBInstanceClass
DBInstanceIdentifier: dbinstance
DBSubnetGroupName: !Ref DBSubnetGroup
Engine: !Ref DBEngine
EngineVersion: !Ref DBEngineVersion
MasterUsername: !Ref DBMasterUsername
MasterUserPassword: !Ref DBMasterUserPassword
VPCSecurityGroups:
- !Ref DBSecurityGroup
Code language: YAML (yaml)
DBインスタンスです。
CloudFormationカスタムリソースを使用して、DBを初期化するためには、特別な設定は不要です。
(参考)EC2インスタンス
Resources:
Instance:
Type: AWS::EC2::Instance
Properties:
IamInstanceProfile: !Ref InstanceProfile
ImageId: !Ref ImageId
InstanceType: !Ref InstanceType
NetworkInterfaces:
- DeviceIndex: 0
SubnetId: !Ref PrivateSubnet
GroupSet:
- !Ref InstanceSecurityGroup
UserData: !Base64 |
#!/bin/bash -xe
yum update -y
yum install -y mariadb
Code language: YAML (yaml)
ユーザーデータでインスタンスの初期化処理を定義します。
EC2インスタンスからMySQLタイプのDBインスタンスに接続するために、MySQLクライアントパッケージをインストールします。
詳細につきましては、以下のページをご確認ください。
環境構築
CloudFormationを使用して、本環境を構築し、実際の挙動を確認します。
事前準備
事前準備として、Lambdaレイヤー用のデプロイパッケージを用意します。
具体的には、以下のコマンドを実行します。
$ mkdir python
$ pip3 install mysql-connector-python -t ./python
$ zip -r layer.zip python
Code language: Bash (bash)
作成したデプロイパッケージをS3バケットにアップロードします。
CloudFormationスタックを作成し、スタック内のリソースを確認する
CloudFormationスタックを作成します。
スタックの作成および各スタックの確認方法については、以下のページをご確認ください。
各スタックのリソースを確認した結果、今回作成された主要リソースの情報は以下の通りです。
- RDS DBインスタンス:dbinstance
- RDS DBインスタンスのエンドポイント:dbinstance.cl50iikpthxs.ap-northeast-1.rds.amazonaws.com
- EC2インスタンス:i-0b3d18375ca48f06b
- Lambda関数:fa-062-function
- SSMパラメータストアのパラメータ:fa-062
AWS Management Consoleから各リソースを確認します。
まずCloudFormationカスタムリソースを確認します。
Lambda関数とカスタムリソース本体が正常に作成されていることがわかります。
次にSSMパラメータストアに保存されている値を確認します。
DB初期化用のSQL文が保存されていることがわかります。
動作確認
準備が整いましたので、実際の動作を確認します。
Lambda関数の実行結果
まずLambda関数の実行結果を、CloudWatch Logsのロググループで確認します。
ログから、SSMパラメータストアに保存されていたSQLが、1文ずつ実行されていることがわかります。
つまりDB初期化処理が正常に実行されたということです。
そしてCloudFormationカスタムリソースとして、関数が「SUCCESS」を返していることもわかります。
つまり関数はカスタムリソースとして正常に動作したということです。
EC2インスタンス
次にEC2インスタンスに接続し、DBの初期化状況を確認します。
インスタンスへのアクセスはSSM Session Managerを使用します。
% aws ssm start-session --target i-0b3d18375ca48f06b
Starting session with SessionId: root-0b2e1f71f0d2aea98
sh-4.2$
Code language: Bash (bash)
SSM Session Managerの詳細につきましては、以下のページをご確認ください。
まずユーザーデータによるEC2インスタンスの初期化処理の実行状況を確認します。
sh-4.2$ yum list installed | grep mariadb
mariadb.aarch64 1:5.5.68-1.amzn2 @amzn2-core
mariadb-libs.aarch64 1:5.5.68-1.amzn2 installed
sh-4.2$ mysql -V
mysql Ver 15.1 Distrib 5.5.68-MariaDB, for Linux (aarch64) using readline 5.1
Code language: Bash (bash)
正常にMySQLクライアントパッケージがインストールされていることがわかります。
このクライアントパッケージを使用して、DBインスタンスに接続します。
sh-4.2$ mysql -h dbinstance.cl50iikpthxs.ap-northeast-1.rds.amazonaws.com -P 3306 -u testuser -p
Enter password:
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MySQL connection id is 20
Server version: 8.0.28 Source distribution
Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
MySQL [(none)]>
Code language: Bash (bash)
正常に接続することができました。
DBを選択します。
MySQL [(none)]> USE tutorial;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
MySQL [tutorial]>
Code language: Bash (bash)
DBを変更することができました。
CloudFormationカスタムリソースによる初期化処理が正常に実行されているということです。
最後に全レコードを取得します。
MySQL [tutorial]> select * from planet;
+----+---------+
| id | name |
+----+---------+
| 1 | Mercury |
| 2 | Venus |
| 3 | Earth |
| 4 | Mars |
| 5 | Jupiter |
| 6 | Saturn |
| 7 | Uranus |
| 8 | Neptune |
+----+---------+
8 rows in set (0.01 sec)
Code language: Bash (bash)
テストレコードが返ってきました。
正常にCREATE TABLE文やINSERT文が実行されたということです。
以上の通り、CloudFormationカスタムリソースによって、DBインスタンスを初期化することができました。
まとめ
CloudFormationカスタムリソースを使用して、DBを初期化する方法をご紹介しました。