公式のテンプレートスニペットでAurora Autoscaingを設定しようと思ったらハマった話

2021.03.04

どーもsutoです。

AuroraのリードレプリカのAutoscaling設定をCloudFormationで作成しようとAWS公式ページのAutoscalingテンプレートスニペットをコピペしてスタック作成したところ、エラーとなってスタックが完了しなかった話をします。

ハマったところ

こちら公式ページの「Aurora DB クラスターのスケーリングポリシーの宣言」の内容でAuroraのAutoscaling設定を作成できるか検証してみたところ、

No scalable target registered for 〜〜」というエラーメッセージが表示され、ロールバックしてしまいました。

多少数値を変えたりDBクラスター識別子を自分の検証用の名前に変えたりはしましたが、公式ページとまったく同じプロパティを使っているのに何故なんだ〜!

当初は「まさか公式で紹介しているコードが間違っているわけないよな」と思い込み、7〜8回再試行してみましたが何故か1回だけCREATE_COMPLETEでき、残りの試行は全てROLLBACKに。

むしろ何故1回だけ成功した?

これって低確率で成功するテンプレなのか。。。(そんなわけがないだろ!)とテンパリつつも、冷静にエラーメッセージを確認。

対策について

scalable targetが見つからないというエラーだから、テンプレート内にある「ScalableTarget」が先にリソース作成されないとダメなのか。ということはリソース作成の優先順位をしっかりつければいいいのだろうけど、プロパティのなかで他に代替できる項目があるかな〜?

てなわけで公式ページのリファレンスを眺めてみると、

あー、この「ScalindTargetId」に置き換えればいいんじゃない?と思い、以下のようにテンプレートを修正しました。

  ScalableTarget:
    Type: AWS::ApplicationAutoScaling::ScalableTarget
    Properties:
      MaxCapacity: 3
      MinCapacity: 1
      RoleARN:
        Fn::Sub: 'arn:aws:iam::${AWS::AccountId}:role/aws-service-role/rds.application-autoscaling.amazonaws.com/AWSServiceRoleForApplicationAutoScaling_RDSCluster'
      ServiceNamespace: rds
      ScalableDimension: 'rds:cluster:ReadReplicaCount'
      ResourceId: !Sub cluster:${SampleDBCluster}
  ScalingPolicyDBCluster:
    Type: AWS::ApplicationAutoScaling::ScalingPolicy
    Properties:
      PolicyName: !Sub ${SampleDBCluster}-asg
      PolicyType: TargetTrackingScaling
      ScalingTargetId: !Ref ScalableTarget
      TargetTrackingScalingPolicyConfiguration:
        TargetValue: 80
        PredefinedMetricSpecification:
          PredefinedMetricType: RDSReaderAverageCPUUtilization
        ScaleInCooldown: 600
        ScaleOutCooldown: 300

{SampleDBCluster}は既存のAuroraDBクラスター識別子が入ります。

これでスタックを実行してみると、

無事STACK_COMPLETEになりました!(もちろん成功率100%です!!)

リードレプリカがAutoscalingするAuroraDBを作成するテンプレート

せっかくトラブルが解決できたので、Auroraクラスターもまとめて新規作成できるようなテンプレートを作ってみました。

  • 構成は、クラスター:1台、Writerノード:1台、Readerノード:1台
  • CPU使用率70%以上でReaderノードがスケール
  • スケールキャパシティは、最小:1、最大:3
  • クールダウンは、スケールイン:10分、スケールアウト:5分
  • masterユーザ名/パスワードはSecret Managerで自動生成
  • "DeletionProtection"は、本番利用時にはコメント(#)を外して設定するようにしましょう。
AWSTemplateFormatVersion: "2010-09-09"
Description: Provision of Aurora MySQL

Parameters:
  ProjectName:
    Description: ProjectName
    Type: String
  EngineVersion:
    Type: String
  InstanceClass:
    Type: String
  VPCId:
    Type: String
    Description: ID for VPC.
  PrimarySubnetId:
    Type: String
    Description: Subnet id on which Aurora instance locates.
  SecondarySubnetId:
    Type: String
    Description: Subnet id on which Aurora instance locates.
  EC2SGId:
    Type: String
    Description: ID for EC2 Security Group.

Resources:
  SampleDBCluster:
    Type: AWS::RDS::DBCluster
    Properties:
      DBClusterIdentifier: !Sub ${ProjectName}-auroradb
      DBClusterParameterGroupName: !Ref 'SampleDBClusterParameterGroup'
      DBSubnetGroupName: !Ref SampleDBSubnetGroup
      Engine: aurora-mysql
      EngineVersion: !Ref 'EngineVersion'
      MasterUserPassword: !Sub "{{resolve:secretsmanager:${AuroraDBSecret}:SecretString:password::}}"
      MasterUsername: !Sub '{{resolve:secretsmanager:${AuroraDBSecret}:SecretString:username}}'
      VpcSecurityGroupIds:
        - !GetAtt 'SampleSecurityGroup.GroupId'
      #DeletionProtection: 'True'
      PreferredMaintenanceWindow: 'Sun:14:30-Sun:15:00'
      Tags:
        - Key: Name
          Value: !Sub ${ProjectName}-aurora
        - Key: backup
          Value: 'True'
  SampleDBClusterParameterGroup:
    Type: AWS::RDS::DBClusterParameterGroup
    Properties:
      Description: !Sub ${ProjectName}-aurora
      Family: aurora-mysql5.7
      Parameters:
        time_zone: Asia/Tokyo
  SampleDBInstanceA:
    Type: AWS::RDS::DBInstance
    Properties:
      DBClusterIdentifier: !Ref 'SampleDBCluster'
      DBInstanceClass: !Ref 'InstanceClass'
      Engine: aurora-mysql
      PreferredMaintenanceWindow: 'Sun:15:00-Sun:15:30'
  SampleDBInstanceB:
    Type: AWS::RDS::DBInstance
    Properties:
      DBClusterIdentifier: !Ref 'SampleDBCluster'
      DBInstanceClass: !Ref 'InstanceClass'
      Engine: aurora-mysql
      PreferredMaintenanceWindow: 'Sun:15:30-Sun:16:00'
  SampleDBSubnetGroup:
    Type: AWS::RDS::DBSubnetGroup
    Properties:
      DBSubnetGroupDescription: sample-aurora
      DBSubnetGroupName: !Sub ${ProjectName}-aurora-nw
      SubnetIds:
        - !Ref PrimarySubnetId
        - !Ref SecondarySubnetId
  SampleSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: !Sub ${ProjectName}-aurora
      SecurityGroupIngress:
        - SourceSecurityGroupId: !Ref EC2SGId
          FromPort: 3306
          IpProtocol: tcp
          ToPort: 3306
      VpcId: !Ref VPCId

  AuroraDBSecret:
    Type: AWS::SecretsManager::Secret
    Properties:
      GenerateSecretString:
        SecretStringTemplate: '{"username": "admin"}'
        GenerateStringKey: "password"
        PasswordLength: 16
        ExcludeCharacters: '"@/\'
      Name: !Sub ${ProjectName}-aurora-secret

  ScalableTarget:
    Type: AWS::ApplicationAutoScaling::ScalableTarget
    Properties:
      MaxCapacity: 3
      MinCapacity: 1
      RoleARN:
        Fn::Sub: 'arn:aws:iam::${AWS::AccountId}:role/aws-service-role/rds.application-autoscaling.amazonaws.com/AWSServiceRoleForApplicationAutoScaling_RDSCluster'
      ServiceNamespace: rds
      ScalableDimension: 'rds:cluster:ReadReplicaCount'
      ResourceId: !Sub cluster:${SampleDBCluster}
  ScalingPolicyDBCluster:
    Type: AWS::ApplicationAutoScaling::ScalingPolicy
    Properties:
      PolicyName: !Sub ${SampleDBCluster}-asg
      PolicyType: TargetTrackingScaling
      ScalingTargetId: !Ref ScalableTarget
      TargetTrackingScalingPolicyConfiguration:
        TargetValue: 80
        PredefinedMetricSpecification:
          PredefinedMetricType: RDSReaderAverageCPUUtilization
        ScaleInCooldown: 600
        ScaleOutCooldown: 300

Outputs:
  GetSecretValueByCLI:
    Value: !Sub |+
        aws secretsmanager get-secret-value
          --secret-id ${AuroraDBSecret}
          --region ${AWS::Region}
          --query SecretString

まとめ

冒頭で紹介したように、公式ページのスニペットをそのまま使おうとすると私と同じエラーを経験をすることになるかと思うのでご注意を。

同じ境遇になった方や、これから似たようなテンプレを作ろうとしている方に本記事が参考になれば幸いです。