【第29回】AWS CloudFormationを用いた基盤自動化(9)RDSの構築


本連載では、以下のイメージの構成にあるAWSリソース基盤自動化環境の構築を実践しています。


../_images/cloudformation-scope.png


前回は、Frontend、Backendサブネットに配置するアプリケーションロードバランサー(ALB)を構築するCloudFormationテンレプートを実装しました。 続く今回はバックエンドサブネットからのアクセスを想定したRDS(RelationalDatabaseService)を構築するCLoudFormationテンプレートを作成します。 実際のソースコードは GitHub 上にコミットしています。 ソースコード中で本質的でない記述を一部省略しているので、実行コードを作成する場合は、必要に応じて適宜GitHub上のソースコードも参照してください。


RDSスタック構築テンプレート


RDSは クラウドネイティブ基本第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)〜(O)の通りです。


RDSのCloudFormationテンプレート記述のポイント
記述 説明
パラメータ要素として、RDSのユーザを作成します。
RDSを構築する環境をEnvTypeパラメータとして指定可能にします。このパラメータに応じて、Conditionsを設定し、作成するリソースを切り替えます。
開発環境としてのRDSにアクセス可能な端末をターゲットとしたセキュリティグループをパラメータとして受け取ります。 第27回 と同様、事前にセキュリティグループを定義し、IDをパラメータとして渡す前提で実装しておきます。
Conditionsとして、EnvTypeパラメータの値に応じて、3つの論理名を定義します※。
商用環境向けのDBインスタンスのリソース定義を行います。詳細は AWS::RDS::DBInstance を参照してください。なお、サブネットグループやモニタリングロールは(I)、(J)の定義を、適用するセキュリティグループは セキュリティグループスタック構築テンプレート で作成したものを、クロススタックリファレンスを使って参照します。
(C)で定義したConditionsの論理名が"ProductionResources"だった場合に、リソース定義が有効化するよう、Condition要素を定義します。
DynamicReferences機能※※を使って、AWS SystemsManager ParameterStoreからRDSユーザのパスワードを取得します。ここでは"mynavi-sample-cloudformation-rds-password"という定義名のセキュアパラメータを受け取る設定とします。
ステージング環境向けのDBインスタンスのリソース定義を行います。詳細は AWS::RDS::DBInstance を参照してください。なお、サブネットグループやモニタリングロールは(I)、(J)の定義を、適用するセキュリティグループは セキュリティグループスタック構築テンプレート で作成したものを、クロススタックリファレンスを使って参照します。
開発環境向けのDBインスタンスのリソース定義を行います。詳細は AWS::RDS::DBInstance を参照してください。なお、サブネットグループやモニタリングロールは(I)、(J)の定義を、適用するセキュリティグループは セキュリティグループスタック構築テンプレート で作成したものを、クロススタックリファレンスを使って参照します。
開発環境向けのDBインスタンスのみパブリックアクセスを許可する設定としておきます。
DBサブネットグループを定義します。詳細は、 AWS::RDS::DBSubnetGroup を参照してください。なお、サブネットは VPC/Subnet/RouteTable/InternetGatewayスタック構築テンプレート で作成したものを、クロススタックリファレンスを使って参照します。
開発環境のDBインスタンスはパブリックアクセスする場合もあるため、パブリックサブネットに配置するよう、サブネットグループを(K)と同様に設定します(プライベートサブネットに配置してあるとパブリックアクセスはできません)。
DBを監視するためのIAMロールを作成します。ポリシーはAmazonRDSEnhancedMonitoringRoleをアタッチします。詳細は、 AWS::IAM::Role を参照してください。
RDSのエンドポイントをOutputs出力します。
(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」を選択し、パスワードをセキュアパラメータを使って以下のイメージ通り設定します。


../_images/management_console_ssm_create_secure_parameter.png


作成したテンプレートに対して、ヘルパースクリプトを以下のように、スタック名とテンプレートパスを変更して実行します。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が作成されます。


../_images/management_console_cloudformation_stack_rds.png


今回はConditions要素やDynamicReferences機能を使いながら、RDSを構築するCloudFormationテンプレートを実装しました。次回は、DynamoDBを構築するテンプレートを作成します。


著者紹介

川畑 光平(KAWABATA Kohei) - NTTデータ 課長代理

../_images/pic_image01.jpg

金融機関システム業務アプリケーション開発・システム基盤担当を経て、現在はソフトウェア開発自動化関連の研究開発・推進に従事。

Red Hat Certified Engineer、Pivotal Certified Spring Professional、AWS Certified Solutions Architect Professional等の資格を持ち、アプリケーション基盤・クラウドなど様々な開発プロジェクト支援にも携わる。

2019 APN AWS Top Engineers & Ambassadors 選出。