CloudFormation で Aurora + RDS Proxy を作成してみた

CloudFormation で Aurora + RDS Proxy を作成する手順についてご紹介します。
2023.01.19

はじめに

題名通り、CloudFormation で Aurora (MySQL) と RDS Proxy を作成します。

ただし、Aurora と RDS Proxy を作成する上で、以下のawsリソースも必要になりますので、CloudFormation のテンプレートに記載します。

  • Secrets Manager (AuroraのDB情報を保存)
  • IAM ロールとポリシー (RDS Proxy 用)
  • セキュリティグループ (Aurora,RDS Proxy )
  • インターフェイス型VPCエンドポイント (RDS Proxy が Secrets Manager にアクセスするため)

注意点として、RDS Proxy を作成すると、VPCエンドポイントは、自動作成されるようです。私は、ここでつまづきました。。

そのため、CloudFormation のテンプレートファイルには、VPCエンドポイントは、記載しません。

VPCエンドポイントの自動作成の挙動

ドキュメントでは該当の記述が見つけられませんでしたが、今回の CloudFormation での作成や、コンソール画面から RDS Proxy を作成し確認した限りでは、VPCエンドポイントは自動生成されておりました。

前提条件

  • VPC と 2つの Private Subnet (アベイラビリティゾーン2つ) を作成済み
  • RDS Proxy にアクセスする EC2 や Lambda の作成方法は説明しません。

構成図

前提条件にも記載しましたが、Lambda や EC2は、今回の CloudFormation では、作成しません。

テンプレートの作成

テンプレートは、ymlファイルを採用し、下記のymlファイルをそのままコピペして使用します。

RDS Proxy のセキュリティグループのインバウンドには、例として Lambda のセキュリティグループを作成し、許可するようにしています。

そのため、下記テンプレート内の Resources の LambdaSecurityGroup は、個々で作成したいセキュリティグループに変えても大丈夫です。

CloudFormationテンプレート (クリックすると展開します)
AWSTemplateFormatVersion: '2010-09-09'
Description: Create Aurora and RDS Proxy

Parameters:
  PJPrefix:
    Type: String
  VpcId:
    Type: AWS::EC2::VPC::Id
    Description: vpc id
  EngineType:
    Type: String
    Default: aurora-mysql
  MySQLMajorVersion:
    Type: String
    Default: 5.7
  MySQLMinorVersion:
    Type: String
    Default: 2.11.0
  DBInstanceClass:
    Type: String
    Default: db.t3.small
  PrivateSubnetIdA:
    Type: AWS::EC2::Subnet::Id
  PrivateSubnetIdC:
    Type: AWS::EC2::Subnet::Id
  DatabaseName:
    Type: String
  MasterUsername:
    NoEcho: true
    Type: String
    MinLength: 1
    MaxLength: 16
    AllowedPattern: '[a-zA-Z][a-zA-Z0-9]*'
    ConstraintDescription: Must begin with a letter and contain only alphanumeric characters.
  MasterUserPassword:
    NoEcho: true
    Type: String
    MinLength: 8
    MaxLength: 41
    AllowedPattern: '[a-zA-Z0-9]*'
    ConstraintDescription: Must contain only alphanumeric characters.
  SecretsManagerKMSKeyId:
    Type: String
    Description: AWS managed key in  aws/secretsmanager

Resources:
  RDSProxySecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: !Sub ${PJPrefix}-rds-proxy
      GroupName: !Sub ${PJPrefix}-rds-proxy
      Tags:
        - Key: Name
          Value: !Sub ${PJPrefix}-rds-proxy
      SecurityGroupIngress:
        - SourceSecurityGroupId: !Ref LambdaSecurityGroup
          SourceSecurityGroupOwnerId: !Ref AWS::AccountId
          Description: !Sub ${PJPrefix}-lambda
          FromPort: 3306
          IpProtocol: tcp
          ToPort: 3306
      SecurityGroupEgress:
        - CidrIp: 0.0.0.0/0
          IpProtocol: -1
      VpcId: !Ref VpcId

  RDSSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: !Sub ${PJPrefix}-rds
      GroupName: !Sub ${PJPrefix}-rds
      SecurityGroupIngress:
        - SourceSecurityGroupId: !Ref RDSProxySecurityGroup
          SourceSecurityGroupOwnerId: !Ref AWS::AccountId
          Description: !Sub ${PJPrefix}-rds-proxy
          FromPort: 3306
          IpProtocol: tcp
          ToPort: 3306
      SecurityGroupEgress:
        - CidrIp: 0.0.0.0/0
          IpProtocol: '-1'
      Tags:
        - Key: Name
          Value: !Sub ${PJPrefix}-rds
      VpcId: !Ref VpcId

  LambdaSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: !Sub ${PJPrefix}-lambda
      GroupName: !Sub ${PJPrefix}-lambda
      Tags:
        - Key: Name
          Value: !Sub ${PJPrefix}-lambda
      VpcId: !Ref VpcId
      SecurityGroupEgress:
        - CidrIp: 0.0.0.0/0
          IpProtocol: '-1'

  DBCluster:
    Type: AWS::RDS::DBCluster
    DeletionPolicy: Delete
    Properties:
      BackupRetentionPeriod: 7
      DatabaseName: !Ref DatabaseName
      DBSubnetGroupName: !Ref DBSubnetGroup
      DBClusterParameterGroupName: !Ref DBClusterParameterGroup
      DBClusterIdentifier: !Ref PJPrefix
      DeletionProtection: false
      Engine: !Ref EngineType
      EngineMode: provisioned
      EngineVersion: !Sub ${MySQLMajorVersion}.mysql_aurora.${MySQLMinorVersion}
      MasterUsername: !Ref MasterUsername
      MasterUserPassword: !Ref MasterUserPassword
      PreferredBackupWindow: 16:00-17:00
      PreferredMaintenanceWindow: tue:18:00-tue:19:00
      StorageEncrypted: true
      VpcSecurityGroupIds:
        - !Ref RDSSecurityGroup

  DBClusterParameterGroup:
    Type: AWS::RDS::DBClusterParameterGroup
    Properties:
      Description: !Sub ${PJPrefix}-DBClusterParameterGroup
      Family: aurora-mysql5.7
      Parameters:
        time_zone: Asia/Tokyo

  DBInstance:
    Type: AWS::RDS::DBInstance
    DeletionPolicy: Delete
    Properties:
      AutoMinorVersionUpgrade: false
      AvailabilityZone: !Sub ${AWS::Region}a
      DBClusterIdentifier: !Ref DBCluster
      DBParameterGroupName: !Ref DBParameterGroup
      DBInstanceClass: !Ref DBInstanceClass
      DBSubnetGroupName: !Ref DBSubnetGroup
      DBInstanceIdentifier: !Sub ${PJPrefix}-instance
      Engine: !Ref EngineType
      EngineVersion: !Sub ${MySQLMajorVersion}.mysql_aurora.${MySQLMinorVersion}
      EnablePerformanceInsights: false
      MonitoringInterval: 0
      PubliclyAccessible: false
      PreferredMaintenanceWindow: tue:18:00-tue:19:00
      PromotionTier: 1

  DBParameterGroup:
    Type: AWS::RDS::DBParameterGroup
    Properties:
      Description: !Sub ${PJPrefix}-DBParameterGroup
      Family: !Sub ${EngineType}${MySQLMajorVersion}

  DBSubnetGroup:
    Type: AWS::RDS::DBSubnetGroup
    Properties:
      DBSubnetGroupName: !Sub ${PJPrefix}-subnet
      DBSubnetGroupDescription: '-'
      SubnetIds:
        - !Ref PrivateSubnetIdA
        - !Ref PrivateSubnetIdC

  DBProxy:
    Type: AWS::RDS::DBProxy
    Properties:
      Auth:
        - AuthScheme: SECRETS
          SecretArn: !Ref SecretsManager
          IAMAuth: DISABLED
      DBProxyName: !Ref PJPrefix
      DebugLogging: false
      EngineFamily: MYSQL
      IdleClientTimeout: 1800
      RequireTLS: false
      RoleArn: !GetAtt RDSProxyIAMRole.Arn
      VpcSecurityGroupIds: 
        - !Ref RDSProxySecurityGroup
      VpcSubnetIds:
        - !Ref PrivateSubnetIdA
        - !Ref PrivateSubnetIdC

  DBProxyTargetGroup:
    Type: AWS::RDS::DBProxyTargetGroup
    Properties:
      ConnectionPoolConfigurationInfo:
        MaxConnectionsPercent: 100
        MaxIdleConnectionsPercent: 50
        ConnectionBorrowTimeout: 120
      DBProxyName: !Ref DBProxy
      DBClusterIdentifiers:
        - !Ref DBCluster
      TargetGroupName: default

  SecretsManager:
    Type: AWS::SecretsManager::Secret
    Properties:
      Name: !Sub ${PJPrefix}-proxy
      Description: !Sub ${PJPrefix}-proxy
      SecretString: !Sub '{
        "username": "${MasterUsername}",
        "password": "${MasterUserPassword}",
        "engine":"mysql",
        "host": "${DBCluster.Endpoint.Address}",
        "port":"3306",
        "dbClusterIdentifier": "${DBCluster}"
        }'
      Tags:
        - Key: Name
          Value: !Sub ${PJPrefix}-rds-proxy

  RDSProxyIAMRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - rds.amazonaws.com
            Action:
              - sts:AssumeRole
      MaxSessionDuration: 3600
      ManagedPolicyArns:
        - !Ref SecretsManagerManagedPolicy
      Path: /service-role/
      RoleName: !Sub ${PJPrefix}-rds-proxy

  SecretsManagerManagedPolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      Description: Get values from Secrets Manager
      ManagedPolicyName: !Sub ${PJPrefix}-rds-proxy
      Path: /
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action:
              - secretsmanager:GetSecretValue
            Resource: !Ref SecretsManager
          - Effect: Allow
            Action:
              - kms:Decrypt
            Resource: !Sub arn:aws:kms:${AWS::Region}:${AWS::AccountId}:key/${SecretsManagerKMSKeyId}
            Condition:
              StringEquals:
                kms:ViaService: !Sub secretsmanager.${AWS::Region}.amazonaws.com

パラメータ SecretsManagerKMSKeyId の説明

Secrets Manager では、KMS キーで暗号化を行うため、KMS キーが必要になります。

KMS キーには、AWS マネージド型キーカスタマー管理型のキーの2種類ありますが、今回は、AWS マネージド型キーを利用します。

スタックのパラメータのうち SecretsManagerKMSKeyId は、 AWS マネージド型キーのエイリアスaws/secretsmanagerのキーIDの値になります。

KMS キー aws/secretsmanager が存在しない場合、Secrets Manager を利用されたことがない可能性があります。 適当にシークレットを作成すると、表示されるようになります。

Secrets Managerのコンソールに移動し、適当にシークレットを作成すると、キーが作成されます。

このキーIDをSecretsManagerKMSKeyIdに指定しましょう。

スタック作成

スタックを作成しましょう。

スタックが作成されるまで20分弱かかります。

成功を確認後、以下のリソースが作成されているか確認しましょう。

  • Aurora (MySQL)
  • RDS Proxy
  • Secrets Manager (AuroraのDB情報を保存)
  • IAM ロールとポリシー(RDS Proxy用)
  • セキュリティグループ (Aurora, RDS Proxy, Lambda用)
  • VPCエンドポイント

無事、作成することができました。

コンソールではなく、CloudFormation を利用することで、必要なときにパッと作成することができるので、便利ですね!

参考

AWS CloudFormation の Template reference