この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
こんにちは、クラスメソッドの岡です。
7月にRDS ProxyがGAとなったことで、サーバーレスでのRDS実用化待ったなし!!ということで今回はServerless FrameworkでRDS Aurora Postgres、VPC Lambda、RDS Proxyをまとめてデプロイしてみたので、テンプレートの中身を紹介していこうと思います。
動作確認環境
- Python3.8.5
- Serverless Framework
- Framework Core: 2.1.1
- Plugin: 4.0.4
- SDK: 2.3.2
- Components: 3.1.3
構成
まず、LambdaとRDSを同一のVPC内に所属させます。マルチAZ構成にするためにサブネット(プライベート)を2つ作成します。
今回はLambdaとRDSを同じサブネット内に配置しています。
セキュリティグループはLambda用とRDS用の2つ作成して、RDS用のインバウンドのルールは同一VPC内からのアクセスのみを許可します。
テンプレート
リソースを追加する前にテンプレートのベースを用意しておきます。
service:
name: sample-aurora-postgres
plugins:
- serverless-pseudo-parameters
provider:
name: aws
region: ap-northeast-1
stage: ${opt:stage, self:custom.defaultStage}
profile: ${self:custom.profiles.${opt:stage, self:custom.defaultStage}}
runtime: python3.8
stackName: sample-aurora-postgres
apiName: sample-aurora-postgres
logRetentionInDays: 7
versionFunctions: false
iamRoleStatements:
- Effect: Allow
Action:
- secretsmanager:GetSecretValue
- rds-data:*
- ec2:CreateNetworkInterface
- ec2:DescribeNetworkInterfaces
- ec2:DeleteNetworkInterface
Resource: "*"
environment:
ENV: ${self:custom.environments.ENV}
DB_PORT: 3306
DB_HOST: !GetAtt RDSProxy.Endpoint
custom:
defaultStage: itg
profiles:
itg: sls-itg
stg: sls-stg
prd: sls-prd
environments: ${file(./config/config.${opt:stage, self:custom.defaultStage}.yml)}
secret: ${file(./config/secret/.secret.${opt:stage, self:custom.defaultStage}.yml)}
プラグイン
serverless-pseudo-parameters
はテンプレート内でアカウントIDを参照するために追加しています。
このプラグインを使うと、#{AWS::AccountId}
という記述で参照できます。
ステージとプロファイル
AWSアカウントは環境毎に分かれるケースが多いかと思います。
デプロイコマンド実行時のstageオプションで下記のように切り替えるためにテンプレートに ${opt:stage, self:custom.defaultStage}
と変数を仕込んでおきます。
$ sls deploy →開発アカウント
$ sls deploy --stage=stg →検証アカウント
$ sls deploy --stage=prd →本番アカウント
stageと一緒にprofileも切り替えられるよう、custom.profiles
にそれぞれのステージに相対するAWS CLIのプロファイル名を設定しておきます。
認証情報の読み込み
RDSのクラスター作成時とSecrets Managerのシークレット作成時に同一の認証情報を指定するために ./config/secret/.secret.${env}.yml
という隠しファイルを作っておきます。
USER_NAME: postgres
PASSWORD: postgres
Serverless Frameworkではテンプレート内で ${file(file_name)}
と指定することで、外部ファイルのプロパティを読み込めます。
今回はローカルからデプロイするため、この様な構成にしてますが作成したシークレット情報はGitに含めないよう注意してください。
DBのポートとエンドポイント
ちなみにこちらのドキュメントにある通り、Aurora Postgres(EngineMode: provisioned)のDBクラスターのポートを指定しなかった場合はPostgresでもデフォルトで3306ポートとなります。
今回はポート指定せずに作成するので、3306ポートを共通の環境変数として設定しておきます。
DB_HOSTには後述するRDSProxyのエンドポイントを参照できるようにしておきます。
VPC関連のリソース
Serverless Frameworkと言いつつ、作成するリソースは実質ほぼcfnの記述になります。
resources:
Resources:
## VPC Resource
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/24
Tags:
- { Key: Name, Value: Sample VPC }
PrivateSubnetA:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.0.0/25
AvailabilityZone: ap-northeast-1a
Tags:
- { Key: Name, Value: Sample Private A }
PrivateSubnetC:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.0.128/25
AvailabilityZone: ap-northeast-1c
Tags:
- { Key: Name, Value: Sample Private C }
LambdaSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: SecurityGroup for Lambda Functions
VpcId: !Ref VPC
Tags:
- Key: "Name"
Value: "LambdaSecurityGroup"
AuroraSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: SecurityGroup for Aurora
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 3306
ToPort: 3306
CidrIp: 10.0.0.0/24
Tags:
- Key: "Name"
Value: "AuroraSecurityGroup"
DependsOn: VPC
まずはVPC関連のリソース群です。
セキュリティグループはVPC内のIPアドレス範囲の3306ポートをインバウンドで開けておきます。
RDS関連のリソース
## RDS Resource
DBSubnetGroup:
Type: AWS::RDS::DBSubnetGroup
Properties:
DBSubnetGroupDescription: "SampleDB subnet group"
DBSubnetGroupName: sampledb-subnet-group
SubnetIds:
- !Ref PrivateSubnetA
- !Ref PrivateSubnetC
DBCluster:
Type: AWS::RDS::DBCluster
Properties:
DatabaseName: SampleDB
Engine: aurora-postgresql
# EngineMode: serverless
EngineVersion: "11.6"
MasterUsername: ${self:custom.secret.USER_NAME}
MasterUserPassword: ${self:custom.secret.PASSWORD}
DBClusterParameterGroupName: !Ref DBClusterParameterGroup
DBSubnetGroupName: !Ref DBSubnetGroup
VpcSecurityGroupIds:
- !Ref AuroraSecurityGroup
DependsOn: DBSubnetGroup
DBClusterParameterGroup:
Type: AWS::RDS::DBClusterParameterGroup
Properties:
Description: A parameter group for aurora
Family: aurora-postgresql11
Parameters:
client_encoding: UTF8
DBInstance1:
Type: AWS::RDS::DBInstance
Properties:
DBClusterIdentifier: !Ref DBCluster
DBSubnetGroupName: !Ref DBSubnetGroup
Engine: aurora-postgresql
EngineVersion: "11.6"
DBInstanceClass: db.t3.medium
DependsOn: DBCluster
今回はプライマリインスタンス1台(DBInstance1)のみ作成します。ちなみにDBClusterのEngineModeをserverless
にすると、Aurora Serverlessでの起動となります。
サブネットを2つ以上指定する場合は、DBSubnetGroupを作成する必要があります。
Secrets ManagerとRDS Proxy周りのリソース
AuroraSecret:
Type: AWS::SecretsManager::Secret
Properties:
Name: Sample/aurora
SecretString: '{"username":"${self:custom.secret.USER_NAME}", "password":"${self:custom.secret.PASSWORD}"}'
SecretTargetAttachment:
Type: AWS::SecretsManager::SecretTargetAttachment
Properties:
SecretId: !Ref AuroraSecret
TargetId: !Ref DBCluster
TargetType: "AWS::RDS::DBCluster"
DependsOn: DBCluster
ProxyRole:
Type: AWS::IAM::Role
Properties:
RoleName: sample-proxy-role
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service:
- "rds.amazonaws.com"
Action:
- "sts:AssumeRole"
Path: /
Policies:
- PolicyName: RdsProxyPolicy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- "secretsmanager:GetResourcePolicy"
- "secretsmanager:GetSecretValue"
- "secretsmanager:DescribeSecret"
- "secretsmanager:ListSecretVersionIds"
Resource:
- !Ref AuroraSecret
- Effect: Allow
Action:
- "kms:Decrypt"
Resource: "arn:aws:kms:${self:provider.region}:#{AWS::AccountId}:key/*"
Condition:
StringEquals:
kms:ViaService: "secretsmanager.${self:provider.region}.amazonaws.com"
DependsOn: AuroraSecret
RDSProxy:
Type: AWS::RDS::DBProxy
Properties:
DBProxyName: SampleAuroraProxy
Auth:
- SecretArn: !Ref AuroraSecret
VpcSecurityGroupIds:
- !Ref AuroraSecurityGroup
VpcSubnetIds:
- !Ref PrivateSubnetA
- !Ref PrivateSubnetC
EngineFamily: POSTGRESQL
RoleArn: !GetAtt ProxyRole.Arn
DependsOn: AuroraSecret
DBProxyTargetGroup:
Type: AWS::RDS::DBProxyTargetGroup
Properties:
TargetGroupName: default
DBProxyName: !Ref RDSProxy
DBClusterIdentifiers:
- !Ref DBCluster
DependsOn: RDSProxy
ProxyRole
はRDS Proxyに設定するIAMロールです。
今回はSecrets ManagerにRDS用の認証情報を格納しているので、Secrets Managerの権限と取得後にKMSでの復号化権限をアタッチしておきます。
RDS Proxyの認証情報をSecrets Managerに保存する場合、 SecretTargetAttachment
を作成する必要があります。
また、注意点として公式ドキュメントにもある通り、DBProxyTargetGroup
のTargetGroupNameはdefault
の固定値に設定する必要があります。
VPC Lambdaの定義
functions:
testFunc:
name: test_func
handler: src/handlers/test_func.handler
description: "接続確認用"
vpc:
securityGroupIds:
- !Ref LambdaSecurityGroup
subnetIds:
- !Ref PrivateSubnetA
- !Ref PrivateSubnetC
events:
- http:
path: /test
method: post
cors: true
VPCをLambdaに設定します。上で作成したセキュリティグループとサブネットを指定します。
デプロイ
下記コマンドで開発環境にデプロイします。
$ sls deploy
RDSのクラスターとインスタンスの生成に結構時間がかかります。