1
votes

When creating a Read Replica on the console, Multi-AZ deployment is available. AWS also announced last year that Read Replicas support MutliAZ

Read replica available in Console

However, when trying to replicate this in Cloudformation, I get this error

enter image description here

Here is a snippet from my Cloudformation:

Resources:
  MasterDB:
    Type: AWS::RDS::DBInstance
    Properties:
      AllocatedStorage: 100
      BackupRetentionPeriod: 10
      DBInstanceClass: !Ref DBInstanceClass
      Engine: postgres
      EngineVersion: '10.6'
      MasterUsername: !Ref DBUsername
      MasterUserPassword: !Ref DBPassword
      MultiAZ: false 
      PubliclyAccessible: true

  ReplicaDB:
    Type: AWS::RDS::DBInstance
    Properties:
      DBInstanceClass: !Ref DBInstanceClass
      SourceDBInstanceIdentifier: !Ref MasterDB
      MultiAZ: true
      PubliclyAccessible: true 

From the documentation, it states:

Creating your Read Replica as a Multi-AZ DB instance is independent of whether the source database is a Multi-AZ DB instance.

... so MasterDB not set to MultiAZ shouldn't be a problem.

Is there a mistake in my Cloudformation template? Or is MultiAZ for Read Replicas not supported with cloudformation?

Thanks!

1

1 Answers

0
votes

I had the same issue. The only way I was able to solve it was by using custom resource lambda in CloudFormation. The lambda takes Id of DB Instance (in this case replica id) and uses boto3 to change it to Multi-Az replica.

This is the template for the lambda function:

# Create a python lambda function which 
# can enable Multi-AZ for read replicas.
#
# This lambda is meant to be executed by custom resource
# from other template in CloudFormaton
---

Resources:

    EnableMultiAz:
        Type: AWS::Lambda::Function
        Properties:
            Description: Create an AMI of an instance
            FunctionName: EnableMultiAzForDBInstance
            Handler: index.lambda_handler
            Role: !GetAtt LambdaExecutionRole.Arn
            Runtime: python3.7
            Timeout: 60
            Code: 
                ZipFile: |
                    import json
                    import boto3
                    import logging
                    import time

                    from botocore.vendored import requests

                    logger = logging.getLogger()
                    logger.setLevel(logging.INFO)

                    SUCCESS = "SUCCESS"
                    FAILED = "FAILED"

                    # from https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-lambda-function-code-cfnresponsemodule.html
                    def send(event, context, responseStatus, responseData, physicalResourceId=None, noEcho=False):

                        responseUrl = event['ResponseURL']

                        print(responseUrl)

                        responseBody = {
                            'Status': responseStatus,
                            'Reason': 'See ' + context.log_stream_name,
                            'PhysicalResourceId': physicalResourceId or context.log_stream_name,
                            'StackId': event['StackId'],
                            'RequestId': event['RequestId'],
                            'LogicalResourceId': event['LogicalResourceId'],
                            'NoEcho': noEcho,
                            'Data': responseData
                        }

                        json_responseBody = json.dumps(responseBody)

                        print("Response body:\n" + json_responseBody)

                        headers = {
                            'content-type' : '',
                            'content-length' : str(len(json_responseBody))
                        }

                        try:
                            response = requests.put(responseUrl,
                                                    data=json_responseBody,
                                                    headers=headers)
                            print("Status code: " + response.reason)
                        except Exception as e:
                            print("send(..) failed executing requests.put(..): " + str(e))

                    def lambda_handler(event, context):

                        print('event: ', json.dumps(event))

                        try:

                            return_data = {}

                            if 'RequestType' in event:

                                rds = boto3.client('rds')

                                dbinstance_id = event['ResourceProperties']['DBinstanceId'] 

                                if event['RequestType'] == 'Create':   

                                    r = rds.modify_db_instance(
                                            DBInstanceIdentifier=dbinstance_id, 
                                            MultiAZ=True, ApplyImmediately=True)        
                            send(event, context, SUCCESS, return_data)            

                        except Exception as e:
                            logger.error('custom response lambda failed due to: ' + str(e))
                            send(event, context, FAILED, {})


    LambdaExecutionRole:
        Type: AWS::IAM::Role
        Properties:
            RoleName: lambda-execution-role-with-rds
            AssumeRolePolicyDocument:
                Version: '2012-10-17'               
                Statement:
                    - Effect: Allow
                      Principal: {'Service': ['lambda.amazonaws.com']}
                      Action: ['sts:AssumeRole']
            ManagedPolicyArns:
                - arn:aws:iam::aws:policy/AWSLambdaExecute
            Path: '/'
            Policies:
                - PolicyName: PassRolePolicy
                  PolicyDocument: 
                      Version: "2012-10-17"
                      Statement: 
                          - Effect: "Allow"
                            Action: "iam:PassRole"
                            Resource: "*"
                - PolicyName: EnableMultiAZReadReplica
                  PolicyDocument: 
                      Version: "2012-10-17"
                      Statement: 
                          - Effect: "Allow"
                            Action: 
                                - "rds:ModifyDBInstance"                           
                            Resource: "*"                            



Outputs:

    LambdaArn:
        Value: !GetAtt EnableMultiAz.Arn

    LambdaRoleArn:
        Value: !GetAtt LambdaExecutionRole.Arn

You place this lambda template in S3, and then the parent template you can:

    # Define nested stack that creates the lambda
    MyCustomLambdaToMakeMultiAzReplicas:
        Type: AWS::CloudFormation::Stack
        Properties:      
            TemplateURL: "<url to lambda template in s3>"
            TimeoutInMinutes: 1    

    # Define a custom resource using the lambda. Replica 
    # db instance id is passed to the lambda and the lambda is
    # executed       
    EnableMultiAzForReplica: 
        Type: "Custom::CustomLambdaToMakeMultiAzReplicas"
        Properties: 
            ServiceToken: !GetAtt MyCustomLambdaToMakeMultiAzReplicas.Outputs.LambdaArn
            DBinstanceId: !Ref ReadReplicaDbInstanceId