本連載では、以下のイメージの構成にあるAWSリソース基盤自動化環境の構築を実践しています。
前回は、Frontend、Backendサブネットに配置するアプリケーションロードバランサー(ALB)を構築するCloudFormationテンレプートを実装しました。続く今回は、バックエンドサブネットからのアクセスを想定したRDS(RelationalDatabaseService)を構築するCLoudFormationテンプレートを作成します。
なお、実際のソースコードはGitHub上にコミットしています。以降のソースコードでは本質的でない記述を一部省略しているので、実行コードを作成する場合は、必要に応じて適宜GitHub上のソースコードも参照してください。
RDSスタック構築テンプレート
RDSは、連載「AWSで作るクラウドネイティブアプリケーションの基本」の第11回で構築したものと同等のものを構築します。
CloudFormationで構築する場合、リソースタイプが「AWS::RDS::DBInstance」となるDBインスタンスの定義と、DBを配置するサブネットをグループ化したサブネットグループ定義「AWS::RDS::DBSubnetGroup」、そして監視のためのモニター権限をIAMロール「AWS::IAM::Role」として作成しておかなければなりません。プロパティとして設定可能な属性は、各リンク先の通りです。また、Conditions要素を使って、RDSを商用環境/ステージング環境/開発環境という3つのパターンに分けて作成するようにします。
ここで、開発環境はVPC外の任意のアドレスからもアクセスが可能ように設定するものとします。また、RDSにはユーザーとパスワードの設定が必要になりますが、秘匿性の高いパスワードは、AWS SystemsManager ParameterStoreから取得可能なDynamicReferences機能を使ってテンプレートを構成します。テンプレートのサンプルは以下の通りです。
AWSTemplateFormatVersion: '2010-09-09'
// omit
Parameters:
// omit
RdsUser: #(A)
Description: Database Master User Name
Type: String
Default: postgresql
EnvType: #(B)
Description: Which environments to deploy your service.
Type: String
AllowedValues: ["Dev", "Staging", "Production"]
Default: Staging
SecurityGroupDev: #(C)
Description: The local machine permitted SecurityGroup is needed as paramater for Dev.
Type: String
Default: ""
Conditions: #(D)
ProductionResources: {"Fn::Equals" : [{"Ref":"EnvType"}, "Production"]}
StagingResources: !Equals [ !Ref EnvType, "Staging"]
DevResources: {"Fn::Equals" : [{"Ref":"EnvType"}, "Dev"]}
Resources:
RDSProductionInstance: #(E)
Type: AWS::RDS::DBInstance
DeletionPolicy: Snapshot
Condition: "ProductionResources" #(F)
Properties:
DBInstanceIdentifier: mynavi-sample-cloudformation-production-postgresql
DBName: sample_database
Engine: postgres
MultiAZ: false
MasterUsername: !Ref RdsUser
MasterUserPassword: '{{resolve:ssm-secure:mynavi-sample-cloudformation-rds-password:1}}' #(G)
DBInstanceClass: db.t2.micro
AllocatedStorage: '20'
DBSubnetGroupName: !Ref DBSubnetGroup
MonitoringInterval: 10
MonitoringRoleArn: !GetAtt DBMonitorRole.Arn
VPCSecurityGroups:
- Fn::ImportValue: !Sub ${VPCName}-SecurityGroupRdsPostgres
RDSStagingInstance: #(H)
Type: AWS::RDS::DBInstance
DeletionPolicy: Snapshot
Condition: "StagingResources"
Properties:
DBInstanceIdentifier: mynavi-sample-cloudformation-staging-postgresql
DBName: sample_database
Engine: postgres
MultiAZ: false
MasterUsername: !Ref RdsUser
MasterUserPassword: '{{resolve:ssm-secure:mynavi-sample-cloudformation-rds-password:1}}'
DBInstanceClass: db.t2.micro
AllocatedStorage: '20'
DBSubnetGroupName: !Ref DBSubnetGroup
MonitoringInterval: 10
MonitoringRoleArn: !GetAtt DBMonitorRole.Arn
VPCSecurityGroups:
- Fn::ImportValue: !Sub ${VPCName}-SecurityGroupRdsPostgres
RDSDevInstance: #(I)
Type: AWS::RDS::DBInstance
DeletionPolicy: Snapshot
Condition: "DevResources"
Properties:
DBInstanceIdentifier: mynavi-sample-cloudformation-dev-postgresql
DBName: sample_database
Engine: postgres
MultiAZ: false
MasterUsername: !Ref RdsUser
MasterUserPassword: '{{resolve:ssm-secure:mynavi-sample-cloudformation-rds-password:1}}'
DBInstanceClass: db.t2.micro
AllocatedStorage: '20'
DBSubnetGroupName: !Ref DBSubnetGroup
MonitoringInterval: 10
MonitoringRoleArn: !GetAtt DBMonitorRole.Arn
PubliclyAccessible: true #(J)
VPCSecurityGroups:
- !Ref SecurityGroupDev
DBSubnetGroup: #(K)
Type: AWS::RDS::DBSubnetGroup
Properties:
DBSubnetGroupDescription: DB Subnet Group for Private Subnet
SubnetIds:
- Fn::ImportValue: !Sub ${VPCName}-PrivateSubnet1
- Fn::ImportValue: !Sub ${VPCName}-PrivateSubnet2
DBSubnetGroupForDev: #(L)
Type: AWS::RDS::DBSubnetGroup
Properties:
DBSubnetGroupDescription: DB Subnet Group for Private Subnet
SubnetIds:
- Fn::ImportValue: !Sub ${VPCName}-PublicSubnet1
- Fn::ImportValue: !Sub ${VPCName}-PublicSubnet2
DBMonitorRole: #(M)
Type: AWS::IAM::Role
Properties:
Path: "/"
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AmazonRDSEnhancedMonitoringRole
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- monitoring.rds.amazonaws.com
Action:
- sts:AssumeRole
Outputs:
//omit
RDSProductionInstanceEndPoint: #(N)
Condition: "ProductionResources" #(O)
Description: RDS
Value: !GetAtt RDSProductionInstance.Endpoint.Address
Export:
Name: !Sub ${VPCName}-RDSEndpoint-Production
// omit
RDSのテンプレートの記述の基本となるポイントは下表の通りです。
記述 | 説明 |
---|---|
A | パラメータ要素として、RDSのユーザーを作成します |
B | RDSを構築する環境をEnvTypeパラメータとして指定可能にします。このパラメータに応じて、Conditionsを設定し、作成するリソースを切り替えます |
C | 開発環境としてのRDSにアクセス可能な端末をターゲットとしたセキュリティグループをパラメータとして受け取ります。本連載の第27回で解説したのと同様に、事前にセキュリティグループを定義し、IDをパラメータとして渡す前提で実装しておきます |
D | Conditionsとして、EnvTypeパラメータの値に応じて、3つの論理名を定義します(詳細は表下の囲み記事「Conditionsについて」を参照してください) |
E | 商用環境向けのDBインスタンスのリソース定義を行います。詳細は「AWS::RDS::DBInstance」を参照してください。なお、サブネットグループやモニタリングロールはI、Jの定義を、適用するセキュリティグループはセキュリティグループスタック構築テンプレートで作成したものを、クロススタックリファレンスを使って参照します |
F | Cで定義したConditionsの論理名が”ProductionResources”だった場合に、リソース定義が有効化するよう、Condition要素を定義します |
G | DynamicReferences機能(詳細は表下の囲み記事「DynamicReferencesについて」を参照してください)を使って、AWS SystemsManager ParameterStoreからRDSユーザーのパスワードを取得します。ここでは”mynavi-sample-cloudformation-rds-password”という定義名のセキュアパラメータを受け取る設定とします |
H | ステージング環境向けのDBインスタンスのリソース定義を行います。詳細は「AWS::RDS::DBInstance」を参照してください。なお、サブネットグループやモニタリングロールはK、Mの定義を、適用するセキュリティグループはセキュリティグループスタック構築テンプレートで作成したものを、クロススタックリファレンスを使って参照します |
I | 開発環境向けのDBインスタンスのリソース定義を行います。詳細は「AWS::RDS::DBInstance」を参照してください。なお、サブネットグループやモニタリングロールはL、Mの定義を、適用するセキュリティグループはセキュリティグループスタック構築テンプレートで作成したものを、クロススタックリファレンスを使って参照します |
J | 開発環境向けのDBインスタンスのみパブリックアクセスを許可する設定としておきます |
K | DBサブネットグループを定義します。詳細は、「AWS::RDS::DBSubnetGroup」を参照してください。なお、サブネットはVPC/Subnet/RouteTable/InternetGatewayスタック構築テンプレートで作成したものを、クロススタックリファレンスを使って参照します |
L | 開発環境のDBインスタンスはパブリックアクセスする場合もあるため、パブリックサブネットに配置するよう、サブネットグループをKと同様に設定します(プライベートサブネットに配置してあるとパブリックアクセスはできません) |
M | DBを監視するためのIAMロールを作成します。ポリシーはAmazonRDSEnhancedMonitoringRoleをアタッチします。詳細は、「AWS::IAM::Role」を参照してください |
N | RDSのエンドポイントをOutputs出力します |
O | Eと同様、Conditionsの論理名が”ProductionResources”だった場合に、リソース定義が有効化するよう、Condition要素を定義します |
【Conditionsについて】
Conditions要素は条件とする論理名を定義し、ResourcesやOutputs要素内で、条件に一致する論理名が適用された際に定義が有効化されます。以下では、EnvTypeのデフォルトパラメータ”Dev”の場合、Conditionsに定義した論理名”DevResources”が有効化されます。Resources要素で各リソース定義にCondition要素を定義すると、”DevResources”の定義のみリソースが作成されます。
Parameters:
EnvType:
Description: Which environments to deploy your service.
Type: String
AllowedValues: ["Dev", "Staging", "Production"]
Default: Dev
Conditions:
ProductionResources: {"Fn::Equals" : [{"Ref":"EnvType"}, "Production"]} #完全修飾形表記
StagingResources: !Equals [ !Ref EnvType, "Staging"] #簡略化表記
DevResources: {"Fn::Equals" : [{"Ref":"EnvType"}, "Dev"]}
Resources:
RDSDevInstance:
Type: AWS::RDS::DBInstance
DeletionPolicy: Snapshot
Condition: "DevResources"
Properties:
//omit
Condtionsでの条件の書き方は完全修飾形と簡略化表記の2種類から選択できますが、可読性の観点から簡略化表記の記載をお薦めします。
【Dynamic Referencesについて】
CLoudFormationからAWS Systems Manager ParametersStoreやSecretsManagerに保存されたデータへのアクセスができるようになっており、「Dynamic References」と呼ばれます。以下のような書式でデータアクセスが可能です。'{{resolve:service-name:reference-key}}'
詳細はAWS公式の「Dynamic Referencesを参照してテンプレートを指定する」も参考にしてください。
続いて、AWSコンソール画面から、「AWS SystemsManager」を選択し、セキュアパラメータを使って以下のようにパスワードを設定します。
作成したテンプレートに対して、以下のようにスタック名とテンプレートパスを変更し、ヘルパースクリプトを実行します。IAMロールを作成するので、”—capabilities CAPABILITY_IAM”オプションを忘れずに設定した上でコマンド実行してください。
#!/usr/bin/env bash
stack_name="mynavi-sample-rds"
template_path="sample-alb-rds.yml"
parameters="EnvType=Staging"
aws cloudformation deploy --stack-name ${stack_name} --template-file ${template_path} --parameter-overrides ${parameters} --capabilities CAPABILITY_IAM
実行が正常に終了すると、RDSが作成されます。
以上、今回はConditions要素やDynamicReferences機能を使いながら、RDSを構築するCloudFormationテンプレートを実装しました。次回は、DynamoDBを構築するテンプレートを作成します。