🌱

CloudFormationを使ってAuroraMySQLクラスターに結構無理やりな方法でカスタムエンドポイントを作った話

2023/03/10に公開

今回のテーマについて

こんにちわ!

DevelopersIO BASECAMP参加者の加藤です!

AuroraClusterには2023年3月現在

・ライター(クラスター)エンドポイント
・リーダーエンドポイント
・インスタンスエンドポイント
・カスタムエンドポイント

4種類のエンドポイントが存在します!

その中でも今回は「カスタムエンドポイント」の作成がテーマです。

早速ですが、まずはカスタムエンドポイントについての概要や勘所をまとめてみました!


早速カスタムエンドポイントの要点

Auroraクラスターには、

  • ライター
  • リーダー
  • インスタンス
  • カスタム

の4つのエンドポイントタイプがある。(2023年3月現在)


  • カスタムエンドポイントは他のワークロードとの負荷の分離をするのにとても有用。

  • カスタムエンドポイントには更に「WRITER」・「READER」・「ANY」の3タイプが存在する。

  • 「WRITER」はマルチマスタークラスターでのみ選択可能。

  • ライターであるインスタンスもエンドポイントメンバーとして含めたい場合、WRITER/READERという意味の「ANY」を選択する必要がある。
    ・逆にライターインスタンスに影響を与える可能性を排除したい場合は「READER」を選択する必要がある。

  • マネコン(AWS Management Console)でやると簡単、CloudFormationでやるにはCustomResourceとLambda関数を使うなど工夫が必要。

  • 一方でマネコンで作成されるカスタムエンドポイントは全てANYになる(2023年3月現在)

  • カスタムエンドポイントは本来はAWS CLIやRDS APIからの操作が推奨されている。


※カスタムエンドポイントを説明した記事の中でも特にわかりやすいのがこちら

そしてこちらがしっかりと概要を説明した本家のドキュメントになります。

大枠の理解が出来た所で早速挑戦していきたいと思います。


カスタムエンドポイントの作成

まずマネコン(AWS Management Console)でやってみた。

クラスターの作成が完成し、更に立ち上がったインスタンスにライター・リーダーの役割が与えられます。

クラスターのDB識別子をクリックします。


既に
・ライターエンドポイント
・リーダーエンドポイント
が作成されています。

先ほどのインスタンスはそれぞれの役割と一致するエンドポイントのメンバーとなっています。

[接続とセキュリティ]タブが開かれている事を確認し、
「カスタムエンドポイントの作成」をクリックします。


①エンドポイント名を入力
②作成したカスタムエンドポイントに追加したいインスタンスを選択
③必要な場合は「今後作成されるインスタンスをこのクラスターにアタッチする」にチェックを入れます。(主に負荷増をきっかけにオートスケーリングされたリードレプリカなどが想定されます。)
「④エンドポイントの作成」をクリックします。


カスタムエンドポイントが完成しました。


中を覗くと、先ほどチェックをつけたインスタンスがメンバーになっている事が確認出来ます。

非常に直感的です。


CloudFormationでやってみた。

私が今回カスタムエンドポイントと組み合わせて実現させたかった事が以下になります。

①初期にライター・リーダーとなるインスタンスは意図的に選択したい。
②リーダーインスタンスは負荷でスケールさせたい。
③ライターとリーダーのインスタンスとカスタムエンドポイントのメンバーになるインスタンスのインスタンスクラスは別で指定したい。
④カスタムエンドポイントのメンバーになるインスタンスは単一障害点を避ける意味で、初期に作成台数を選択可能にしたい(一旦二台)。
⑤カスタムエンドポイントメンバーのインスタンスは出来る限り昇格して欲しくない。

とこのような内容の為、最小構成を紹介したテンプレートではない点はお許しください。

CloudFormtationでと決めて走り出したので、目的と手段の混同が見受けられる事や、
Lambda内に登記述しているPythonについてまだ見様見真似のレベルである為、アンチパターン的な要素が含まれる可能性がありますが、ご容赦の上生暖かい目で見ていただければ幸いです。

ざっくりとしたイメージ図。

サンプルテンプレート

長いので畳んであります。

SampleTemplate.yml
SampleTemplate.yml
AWSTemplateFormatVersion: 2010-09-09
Description: Challenge to create custom endpoints.
# ------------------------------------------------------------#
# Metadata
# ------------------------------------------------------------#
Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - 
        Label:
          default: Tags and ResourceName
        Parameters:
          - PJPrefix
      - 
        Label:
          default: VPC and PrivateSubnets
        Parameters:
          - VPCCidrBlock
          - PrivateSubnet1aCidrBlock
          - PrivateSubnet1cCidrBlock
      - 
        Label:
          default: RDS (Aurora)
        Parameters:
          - EngineType
          - MySQLMajorVersion
          - MySQLMinorVersion
          - AllowMajorVersionUpgrade
          - AutoMinorVersionUpgrade
          - PreferredMaintenanceWindow

          - MinCapacityForApplicationAutoscaling
          - MaxCapacityForApplicationAutoscaling
          - TargetValueForApplicationAutoscaling
          - ScaleInCooldownForApplicationAutoscaling
          - ScaleOutCooldownForApplicationAutoscaling
          
          - WriterAndReaderDBInstanceClass
          - CustomEPMemberDBInstanceClass
          
          - NumOfCustomEPMemberDBinstances
          - CustomEndpointName
# ------------------------------------------------------------#
# Parameters
# ------------------------------------------------------------#
Parameters: 
# for Tags and ResourceName
  PJPrefix: 
    Type: String

# for VPC and PrivateSubnets
  VPCCidrBlock: # VPCのCIDR範囲。
    Type: String
    Default: 10.1.0.0/16
    MinLength: 9
    MaxLength: 18
    AllowedPattern: (\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})

  PrivateSubnet1aCidrBlock: # プライベートサブネット1のCIDR範囲。
    Type: String
    Default: 10.1.0.0/24
    MinLength: 9
    MaxLength: 18
    AllowedPattern: (\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})

  PrivateSubnet1cCidrBlock: # プライベートサブネット2のCIDR範囲。
    Type: String
    Default: 10.1.1.0/24
    MinLength: 9
    MaxLength: 18
    AllowedPattern: (\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})

# for RDS (Aurora)
  EngineType: # DBエンジンタイプ。
    Type: String
    Default: aurora-mysql

  MySQLMajorVersion: # MySQLメジャーヴァージョン。
    Type: String
    Default: 8.0

  MySQLMinorVersion: # MySQLマイナーヴァージョン。
    Type: String
    Default: 3.02.2

  MinCapacityForApplicationAutoscaling: # クラスター内のリードレプリカの最小台数。 
    Type: String 
    Default: 1
    AllowedValues: [1,2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]

  MaxCapacityForApplicationAutoscaling: # クラスター内のリードレプリカの最大台数。 
    Type: String 
    Default: 2
    AllowedValues: [1,2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]

  TargetValueForApplicationAutoscaling: # DBのAutoScaling閾値。
    Type: String
    Default: 70

  ScaleInCooldownForApplicationAutoscaling: # スケールインのクールダウン値。
    Type: Number
    Default: 600

  ScaleOutCooldownForApplicationAutoscaling: # スケールアウトのクールダウン値。
    Type: Number
    Default: 300

  WriterAndReaderDBInstanceClass: # ライター或いはリーダーとして動く予定のDBインスタンスクラス。
    Type: String
    Default: db.r6g.large

  CustomEPMemberDBInstanceClass: # 初期カスタムエンドポイント用のDBインスタンスのインスタンスクラス
    Type: String
    Default: db.r6g.large

  AllowMajorVersionUpgrade: # メジャーバージョンのアップグレードを許可するかどうか。
    Type: String
    Default: false
    AllowedValues: [true, false]

  AutoMinorVersionUpgrade: # マイナーバージョンのアップグレードを許可するかどうか。
    Type: String
    Default: true
    AllowedValues:  [true, false]

  PreferredMaintenanceWindow: # 夜間バッチ処理等との干渉を回避する為のメンテナンスウィンドウの設定。
    Type: String
    Default: Fri:18:00-Fri:19:00

  CustomEndpointName: # カスタムエンドポイントの名前。
    Type: String
    Default: sample

  NumOfCustomEPMemberDBinstances: # カスタムエンドポイントのメンバーになるインスタンスの初期台数。
    Type: String
    Default: 1
    AllowedValues: [1, 2]
# ------------------------------------------------------------#
# Conditions
# ------------------------------------------------------------#
Conditions: # 初期にカスタムエンドポイントに追加されるインスタンスを何台必要とするかでリソース作成を分岐。
  CaseNeedNumIs1: !Equals [ !Ref NumOfCustomEPMemberDBinstances, 1 ] 
  CaseNeedNumIs2: !Equals [ !Ref NumOfCustomEPMemberDBinstances, 2 ]   
# ------------------------------------------------------------#
# Resources
# ------------------------------------------------------------#
Resources:
  # ------------------------------------------------------------#
  # IAM
  # ------------------------------------------------------------#
  MakeCustomEPFunctionRole: # カスタムエンドポイントを作成する為のLambda関数用のロール。
    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/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: !Sub ${PJPrefix}-CreateAuroraCustomEndpointPolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - rds:CreateDBClusterEndpoint
                  - rds:DescribeDBClusters
                Resource:
                  - !Sub arn:aws:rds:${AWS::Region}:${AWS::AccountId}:cluster:${AuroraCluster}
                  - !Sub arn:aws:rds:${AWS::Region}:${AWS::AccountId}:cluster-endpoint:*
      Tags:
        - Key: Name
          Value: !Sub ${PJPrefix}-MakeCustomEPFunctionRole
  # ------------------------------------------------------------#
  # VPC
  # ------------------------------------------------------------#
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VPCCidrBlock
      InstanceTenancy: default
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: !Sub ${PJPrefix}-VPC
  # ------------------------------------------------------------#
  # Subnet
  # ------------------------------------------------------------#
#  プライベート
  PrivateSubnet1a:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: !Ref PrivateSubnet1aCidrBlock
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 0, !GetAZs ]
      Tags:
        - Key: Name
          Value: !Sub ${PJPrefix}-PrivateSubnet1a

  PrivateSubnet1c:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock:  !Ref PrivateSubnet1cCidrBlock
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 1, !GetAZs ]
      Tags:
        - Key: Name
          Value: !Sub ${PJPrefix}-PrivateSubnet1c
  # ------------------------------------------------------------#
  # RDS
  # ------------------------------------------------------------#
# DBクラスター
  AuroraCluster: 
    DeletionPolicy: Snapshot
    Type: AWS::RDS::DBCluster
    Properties: 
      DBClusterIdentifier: !Sub ${PJPrefix}-DBCluster
      DBClusterParameterGroupName: !Ref AuroraDBClusterParameterGroup
      DBSubnetGroupName: !Ref AuroraDBSubnetGroup
      Engine: !Ref EngineType
      EngineVersion: !Sub ${MySQLMajorVersion}.mysql_aurora.${MySQLMinorVersion}
      StorageEncrypted: true
      MasterUsername: admin
      ManageMasterUserPassword: True
      VpcSecurityGroupIds:
        - !Ref AuroraDBSG
      PreferredMaintenanceWindow: !Ref PreferredMaintenanceWindow
      AvailabilityZones:
        - !Select [ 0, !GetAZs ]
        - !Select [ 1, !GetAZs ]
      Tags: 
        - Key: Name
          Value: !Sub ${PJPrefix}-AuroraCluster

# DBクラスターパラメーターグループ
  AuroraDBClusterParameterGroup: 
    Type: AWS::RDS::DBClusterParameterGroup
    Properties: 
      Description: Aurora Cluster Parameter Group.
      Family: !Sub ${EngineType}${MySQLMajorVersion}
      Parameters: 
        time_zone: Asia/Tokyo
      Tags: 
        - Key: Name
          Value: !Sub ${PJPrefix}-AuroraDBClusterParameterGroup

# DBパラメーターグループ
  AuroraDBParameterGroup:
    Type: AWS::RDS::DBParameterGroup
    Properties:
      Description: Aurora Parameter Group.
      Family: !Sub ${EngineType}${MySQLMajorVersion}
      Tags: 
        - Key: Name
          Value: !Sub ${PJPrefix}-AuroraDBParameterGroup

# DBサブネットグループ
  AuroraDBSubnetGroup: 
    Type: AWS::RDS::DBSubnetGroup
    Properties: 
      DBSubnetGroupName: !Sub ${PJPrefix}-AuroraDBSubnetGroup
      DBSubnetGroupDescription: Aurora SubnetGroup
      SubnetIds: 
        - !Ref PrivateSubnet1a
        - !Ref PrivateSubnet1c
      Tags: 
        - Key: Name
          Value: !Sub ${PJPrefix}-AuroraDBSubnetGroup

# DBインスタンス
  AuroraDBInstance1: # 初期にライターとなるインスタンス。
    Type: AWS::RDS::DBInstance
    Properties: 
      AllowMajorVersionUpgrade: !Ref AllowMajorVersionUpgrade
      AutoMinorVersionUpgrade: !Ref AutoMinorVersionUpgrade
      DBClusterIdentifier: !Ref AuroraCluster
      DBInstanceClass: !Ref WriterAndReaderDBInstanceClass
      DBParameterGroupName: !Ref AuroraDBParameterGroup
      DBSubnetGroupName: !Ref AuroraDBSubnetGroup
      Engine: !Ref EngineType
      EngineVersion: !Sub ${MySQLMajorVersion}.mysql_aurora.${MySQLMinorVersion}
      PubliclyAccessible: false
      PreferredMaintenanceWindow: !Ref PreferredMaintenanceWindow
      PromotionTier: 1
      AvailabilityZone: !Select [ 0, !GetAZs ]
      Tags: 
        - Key: Name
          Value: !Sub ${PJPrefix}-AuroraDBInstance1

  AuroraDBInstance2: # リーダーとなるインスタンス。
    DependsOn: AuroraDBInstance1
    Type: AWS::RDS::DBInstance
    Properties:
      AllowMajorVersionUpgrade: !Ref AllowMajorVersionUpgrade
      AutoMinorVersionUpgrade: !Ref AutoMinorVersionUpgrade
      DBClusterIdentifier: !Ref AuroraCluster
      DBInstanceClass: !Ref WriterAndReaderDBInstanceClass
      DBParameterGroupName: !Ref AuroraDBParameterGroup
      DBSubnetGroupName: !Ref AuroraDBSubnetGroup
      Engine: !Ref EngineType
      EngineVersion: !Sub ${MySQLMajorVersion}.mysql_aurora.${MySQLMinorVersion}
      PubliclyAccessible: false
      PreferredMaintenanceWindow: !Ref PreferredMaintenanceWindow
      PromotionTier: 1
      AvailabilityZone: !Select [ 1, !GetAZs ]
      Tags: 
        - Key: Name
          Value: !Sub ${PJPrefix}-AuroraDBInstance2

  CustomEPMemberDBInstance1: # リーダー且つカスタムエンドポイントのメンバーとなるインスタンス1。
    DependsOn: AuroraDBInstance1
    Type: AWS::RDS::DBInstance
    Properties:
      AllowMajorVersionUpgrade: !Ref AllowMajorVersionUpgrade
      AutoMinorVersionUpgrade: !Ref AutoMinorVersionUpgrade
      DBClusterIdentifier: !Ref AuroraCluster
      DBInstanceClass: !Ref CustomEPMemberDBInstanceClass
      DBParameterGroupName: !Ref AuroraDBParameterGroup
      DBSubnetGroupName: !Ref AuroraDBSubnetGroup
      Engine: !Ref EngineType
      EngineVersion: !Sub ${MySQLMajorVersion}.mysql_aurora.${MySQLMinorVersion}
      PubliclyAccessible: false
      PreferredMaintenanceWindow: !Ref PreferredMaintenanceWindow
      PromotionTier: 14
      AvailabilityZone: !Select [ 1, !GetAZs ]
      Tags: 
        - Key: Name
          Value: !Sub ${PJPrefix}-CustomEPMemberDBInstance

  CustomEPMemberDBInstance2: # リーダー且つカスタムエンドポイントのメンバーとなるインスタンス2。(初期に二台目を希望する場合のみ作成。)
    DependsOn: AuroraDBInstance1
    Condition: CaseNeedNumIs2
    Type: AWS::RDS::DBInstance
    Properties:
      AllowMajorVersionUpgrade: !Ref AllowMajorVersionUpgrade
      AutoMinorVersionUpgrade: !Ref AutoMinorVersionUpgrade
      DBClusterIdentifier: !Ref AuroraCluster
      DBInstanceClass: !Ref CustomEPMemberDBInstanceClass
      DBParameterGroupName: !Ref AuroraDBParameterGroup
      DBSubnetGroupName: !Ref AuroraDBSubnetGroup
      Engine: !Ref EngineType
      EngineVersion: !Sub ${MySQLMajorVersion}.mysql_aurora.${MySQLMinorVersion}
      PubliclyAccessible: false
      PreferredMaintenanceWindow: !Ref PreferredMaintenanceWindow
      PromotionTier: 14
      AvailabilityZone: !Select [ 0, !GetAZs ]
      Tags: 
        - Key: Name
          Value: !Sub ${PJPrefix}-CustomEPMemberDBInstance2
  # ------------------------------------------------------------#
  # ApplicationAutoScaling
  # ------------------------------------------------------------#
# スケーラブルターゲット
  ScalableTarget: 
    Type: AWS::ApplicationAutoScaling::ScalableTarget
    Properties:
      MaxCapacity: !Ref MaxCapacityForApplicationAutoscaling
      MinCapacity: !Ref MinCapacityForApplicationAutoscaling
      RoleARN: !Sub arn:aws:iam::${AWS::AccountId}:role/aws-service-role/rds.application-autoscaling.amazonaws.com/AWSServiceRoleForApplicationAutoScaling_
      ServiceNamespace: rds
      ScalableDimension: rds:cluster:ReadReplicaCount
      ResourceId: !Sub cluster:${AuroraCluster}

# スケ-リングポリシー
  ScalingPolicyDBCluster:
    Type: AWS::ApplicationAutoScaling::ScalingPolicy
    Properties:
      PolicyName: !Sub ${AuroraCluster}-asg
      PolicyType: TargetTrackingScaling
      ScalingTargetId: !Ref ScalableTarget
      TargetTrackingScalingPolicyConfiguration:
        TargetValue: !Ref TargetValueForApplicationAutoscaling
        PredefinedMetricSpecification:
          PredefinedMetricType: RDSReaderAverageCPUUtilization
        ScaleInCooldown: !Ref ScaleInCooldownForApplicationAutoscaling
        ScaleOutCooldown: !Ref ScaleOutCooldownForApplicationAutoscaling
        DisableScaleIn: False
  # ------------------------------------------------------------#
  # CustomResource
  # ------------------------------------------------------------#
# カスタムリソース 
  CustomResourcePattern1: # 初期に作成されるカスタムエンドポイントのメンバーになるリーダーインスタンスは一台で良い場合。
    Condition: CaseNeedNumIs1
    DependsOn: 
        - AuroraDBInstance2
        - CustomEPMemberDBInstance1
    Type: Custom::CustomResource
    Properties:
      ServiceToken: !GetAtt MakeCustomEPFunction.Arn

  CustomResourcePattern2: # 二台必要な場合。
    Condition: CaseNeedNumIs2
    DependsOn: 
        - AuroraDBInstance2
        - CustomEPMemberDBInstance1
        - CustomEPMemberDBInstance2
    Type: Custom::CustomResource
    Properties:
      ServiceToken: !GetAtt MakeCustomEPFunction.Arn
  # ------------------------------------------------------------#
  # Lambda
  # ------------------------------------------------------------#
# 関数
  MakeCustomEPFunction: # カスタムエンドポイント作成+メンバーに追加。
    Type: AWS::Lambda::Function
    Properties:
      Code:
        ZipFile: | 
          import boto3
          import cfnresponse
          import os
          
          db_cluster_identifier = os.environ['DB_CLUSTER_IDENTIFIER']
          db_cluster_custom_endpoint = os.environ['DB_CLUSTER_CUSTOM_ENDPOINT']
          client = boto3.client('rds')
          
          CREATE = 'Create'
          response_data = {}
          
          def lambda_handler(event, context):
            try:
              if event['RequestType'] == CREATE:
                response = client.describe_db_clusters(
                  DBClusterIdentifier=db_cluster_identifier
                  )
                Tier_14_instances = [member for member in response['DBClusters'][0]['DBClusterMembers'] if member['IsClusterWriter'] == False and member['PromotionTier'] == 14]
                DBInstanceIdentifier_list =[]
                
                for Tier_14_instance in Tier_14_instances:
                  DBInstanceIdentifier_list.append(Tier_14_instance['DBInstanceIdentifier'])
                
                create_response = client.create_db_cluster_endpoint(
                  DBClusterIdentifier=db_cluster_identifier,
                  DBClusterEndpointIdentifier=db_cluster_custom_endpoint,
                  EndpointType='READER',
                  StaticMembers=DBInstanceIdentifier_list)
                print(create_response)
                response_data[db_cluster_custom_endpoint] = create_response['Endpoint']
              
              cfnresponse.send(event, context, cfnresponse.SUCCESS, response_data)
              
            except Exception as e:
              print(e)
              cfnresponse.send(event, context, cfnresponse.FAILED, response_data)
      Environment:
        Variables:
          DB_CLUSTER_CUSTOM_ENDPOINT: !Ref CustomEndpointName
          DB_CLUSTER_IDENTIFIER: !Ref AuroraCluster
      FunctionName: !Sub ${PJPrefix}-MakeCustomEPfunction
      Handler: index.lambda_handler
      Runtime: python3.9
      Role: !GetAtt MakeCustomEPFunctionRole.Arn
      Tags: 
        - Key: Name
          Value: !Sub ${PJPrefix}-MakeCustomEPFunction
  # ------------------------------------------------------------#
  # SecurityGroup
  # ------------------------------------------------------------#
# AuroraDBクラスター用。
  AuroraDBSG: 
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: !Ref VPC
      GroupName: !Sub ${PJPrefix}-AuroraDBSG
      GroupDescription: Allow Inbound access.
      Tags:
        - Key: Name
          Value: !Sub ${PJPrefix}-AuroraDBSG

  # Ingress
  AuroraDBSGIngressMakeCustomEPFunctionSG:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      Description: Allow AuroraMySQL access from MakeCustomEPFunctionSG
      FromPort: 3306
      ToPort: 3306
      IpProtocol: tcp
      SourceSecurityGroupId: !Ref MakeCustomEPFunctionSG
      GroupId: !GetAtt AuroraDBSG.GroupId

# カスタムエンドポイント作成関数用
  MakeCustomEPFunctionSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: !Ref VPC
      GroupName: !Sub ${PJPrefix}-MakeCustomEPFunctionSG
      GroupDescription: Allow Outbound access.
      Tags:
        - Key: Name
          Value: !Sub ${PJPrefix}-MakeCustomEPFunctionSG

  # Egress
  MakeCustomEPFunctionSGEgressforAuroraDBSG:
    Type: AWS::EC2::SecurityGroupEgress
    Properties:
      Description: Allow AuroraMySQL access to MakeCustomEPFunctionSG.
      GroupId: !GetAtt MakeCustomEPFunctionSG.GroupId
      IpProtocol: tcp
      FromPort: 3306
      ToPort: 3306
      DestinationSecurityGroupId: !GetAtt AuroraDBSG.GroupId
# ------------------------------------------------------------#
# Outputs
# ------------------------------------------------------------#
Outputs:
# ライター(クラスター)エンドポイント。
  WriterEndpoint: 
    Value: !GetAtt AuroraCluster.Endpoint.Address

# リーダーエンドポイント。
  ReadEndpoint: 
    Value: !GetAtt AuroraCluster.ReadEndpoint.Address

# カスタムエンドポイント(一台の場合)
  CustomEndpoint: 
    Condition: CaseNeedNumIs1
    Value: !GetAtt
      - CustomResourcePattern1
      - !Ref CustomEndpointName

# カスタムエンドポイント(二台の場合)
  CustomEndpoint2: 
    Condition: CaseNeedNumIs2
    Value: !GetAtt
      - CustomResourcePattern2
      - !Ref CustomEndpointName

スタック実行

スタック名と、一番上のPJPrefix欄に任意の文字(なんでもいいです)を入れて先へ進んでください。


※沢山あるパラメーターですがひとつを除いて全てデフォルト値が入っています。

※カスタムエンドポイントのメンバーとなる初期インスタンスを二台立ち上げたい場合は"NumOfCustomEPMemberDBinstances"というパラメーターを2に変更出来ます。



クラスターリソースが作成され、それぞれ意図した役割についています。


カスタムエンドポイントが作成されました。


無事に意図したインスタンスがメンバーになっています。

スタックの出力欄にもライター・リーダー・作成したカスタムのそれぞれが出力されています。

以上となります。


ポイントの説明

ポイントの説明

①初期にライター・リーダーとなるインスタンスは意図的に選択したい。

説明 DB2のDependsOnでDB1を指定する事で同時作成を避け、必ず1がライターに昇格するようにした。
  AuroraDBInstance2:
    DependsOn: AuroraDBInstance1
    # 後略

※デメリット:
・DBインスタンスが段階的に作られるせいでスタック全体の完成に時間がかかる。
・恐らくあまり意味がない(元々他のインスタンスと役割が入れ替わる事が前提の概念の為)


②カスタムエンドポイントメンバーのインスタンスは出来る限り昇格して欲しくない。
(カスタムエンドポイントのメンバーが0になる可能性を極力少なくしたい。)

説明 カスタムエンドポイントのメンバーのインスタンスのPromotionTierを低く設定。
CustomEPMemberDBInstance1:
    Type: AWS::RDS::DBInstance
    Properties:
    # 中略
      PromotionTier: 14

※15でもよかったのですが、オートスケーリングで立ち上がるリードレプリカは15である為14を設定。

※PromotionTierについては以下記事を参照
https://dev.classmethod.jp/articles/amazon-aurora-failover-order/


このPromotionTierが14である値を、Lambda関数内カスタムエンドポイントに追加するインスタンスかどうかの判断に使いました。

['PromotionTier'] == 14

③カスタムエンドポイントのメンバーになるインスタンスは単一障害点を避ける意味で、初期に作成台数を選択可能にしたい(一旦二台)。

説明 コンディションズで初期にカスタムエンドポイントに追加されるインスタンスを何台必要とするかでリソース作成を分岐。
Conditions: 
  CaseNeedNumIs1: !Equals [ !Ref NumOfCustomEPMemberDBinstances, 1 ] 
  CaseNeedNumIs2: !Equals [ !Ref NumOfCustomEPMemberDBinstances, 2 ] 
説明 Conditionで希望が二台である場合だけ二台目のインスタンスを作成する。
  CustomEPMemberDBInstance2: 
    DependsOn: AuroraDBInstance1
    Condition: CaseNeedNumIs2
    # 後略
説明 同じく単純に行数の少ないカスタムリソースを二種類作りConditionを当て分岐に利用(DependsOn内で!IF分岐の利用が叶わなかった為)
  CustomResourcePattern2: # 二台必要な場合。
    Condition: CaseNeedNumIs2
    # 後略

④のインスタンスクラスは割愛して、

最後に⑤のスケールの話ですが、可能であればそれぞれの役割毎にターゲットスケーリングが出来ればと思い、ドキュメントを確認しましたが…

説明 カスタムエンドポイントのメンバーインスタンスのみをターゲットにスケーリング出来ないか以下のプロパティを確認。
 Type: AWS::ApplicationAutoScaling::ScalableTarget
    Properties:
    # 中略
     ScalableDimension: rds:cluster:ReadReplicaCount

結果はやはり難しいようでした。

という事で結果としてごちゃごちゃと手を入れた割には最適解は見出せずという結果に終わりました。


参考にした記事

https://awstut.com/2022/11/06/create-aurora-custom-endpoints-with-cfn-custom-resources/

最後に

負荷分散やクラスターという概念についてもっと理解を持ちたいと感じました。

お付き合いいただき有難うございました!

デベキャン

Discussion