CFNを使用して、RDSクロスリージョンリードレプリカを作成する

目次

CloudFormationを使用して、RDSクロスリージョンリードレプリカを作成する

AWS SAAの出題範囲の1つである、弾力性に優れたアーキテクチャの設計に関する内容です。

以下のページで、RDSのリードレプリカをご紹介しました。

あわせて読みたい
CFNでRDSリードレプリカを作成する 【CloudFormationを使用して、RDSリードレプリカを作成する】 以下のページで、マルチAZ配置を取り上げました。 https://awstut.com/2023/01/05/rds-multi-az-deploymen...

上記ページではリードレプリカを同一リージョン上に配置しましたが、AWS公式では、レプリカの配置場所について以下のように言及しています。

Amazon RDS では、リードレプリカをソース DB インスタンスとは異なる AWS リージョン に作成できます。

別の AWS リージョン でのリードレプリカの作成

本ページでは、CloudFormationを使用して、クロスリージョンリードレプリカを作成します。

構築する環境

Diagram of RDS Rcorss-Region Read Replica using CloudFormation

2つのリージョンにVPCを作成します。

一方のリージョンにはRDSのDBインスタンスを作成します。
もう一方のリージョンには、このインスタンスのリードレプリカを作成します。
なおDBインスタンスのエンジンはMySQLとします。

各VPCにEC2インスタンスを配置します。
DBインスタンスに接続するクライアントとして使用します。
インスタンスは最新版のAmazon Linux 2です。

リードレプリカ側のリージョンに、Lambda関数を作成し、これをCloudFormationカスタムリソースとして設定します。
この関数の働きは、リードレプリカのセキュリティグループを変更することです。
関数のランタイム環境はPython3.8とします。

2種類のVPCエンドポイントを作成します。

1つ目はSSM用です。
SSM Session Managerを使用して、プライベートサブネット内のEC2インスタンスに接続するためです。

2つ目はS3用です。
S3バケット上に構築されたyumリポジトリにアクセスするためです。

CloudFormationテンプレートファイル

上記の構成をCloudFormationで構築します。
以下のURLにCloudFormationテンプレートを配置しています。

https://github.com/awstut-an-r/awstut-saa/tree/main/01/004

テンプレートファイルのポイント解説

クロスリージョンリードレプリカ

Resources:
  DBInstance:
    Type: AWS::RDS::DBInstance
    DeletionPolicy: Delete
    Properties:
      AvailabilityZone: !Sub "${AWS::Region}${AvailabilityZone}"
      DBInstanceIdentifier: !Sub "${Prefix}-dbinstance"
      DBSubnetGroupName: !Ref DBSubnetGroup
      SourceDBInstanceIdentifier: !Sub "arn:aws:rds:${SourceDBInstanceRegion}:${AWS::AccountId}:db:${SourceDBInstanceIdentifier}"
Code language: YAML (yaml)

リードレプリカに関する基本的な事項は、冒頭でご紹介したページをご確認ください。

クロスリージョンリードレプリカを作成する上で、ポイントとなる設定はSourceDBInstanceIdentifierプロパティです。
本プロパティに、別リージョンに配置されているプライマリインスタンスのARNを指定します。

カスタムリソースとLambda関数

カスタムリソースを使用して、CloudForamtionスタック作成時に、リードレプリカのセキュリティグループを変更します。

これは以下の仕様が原因です。

リードレプリカは、デフォルトのセキュリティグループを使用します。

別の AWS リージョン でのリードレプリカの作成

つまりリードレプリカ作成時には、セキュリティグループは設定できず、デフォルトのものが適用されてしまうということです。
ですからリードレプリカ作成後に、手動で適切なセキュリティグループを設定し直す必要があります。

今回はこれをカスタムリソースに関連付けたLambda関数で実施します。

カスタムリソース

Resources:
  CustomResource:
    Type: Custom::CustomResource
    Properties:
      ServiceToken: !GetAtt Function.Arn
Code language: YAML (yaml)

カスタムリソースに関する基本的な事項については、以下のページをご確認ください。

あわせて読みたい
CloudFormationカスタムリソース入門 【CloudFormationカスタムリソースの挙動を確認する構成】 CloudFormationの機能の1つにカスタムリソースがあります。 カスタムリソースを使用すると、テンプレートにカ...

カスタムリソースを作成し、後述のLambda関数を指定します。

Lambda関数

Resources:
  Function:
    Type: AWS::Lambda::Function
    Properties:
      Environment:
        Variables:
          DB_INSTANCE: !Ref DBInstance
          DB_SECURITY_GROUP: !Ref DBSecurityGroup
          REGION: !Ref AWS::Region
      Code:
        ZipFile: |
          import boto3
          import cfnresponse
          import os

          db_instance = os.environ['DB_INSTANCE']
          db_security_group = os.environ['DB_SECURITY_GROUP']
          region = os.environ['REGION']

          CREATE = 'Create'
          response_data = {}

          def lambda_handler(event, context):
            try:
              if event['RequestType'] == CREATE:
                client = boto3.client('rds', region_name=region)
                response = client.modify_db_instance(
                  DBInstanceIdentifier=db_instance,
                  VpcSecurityGroupIds=[
                    db_security_group
                  ]
                )
                print(response)

              cfnresponse.send(event, context, cfnresponse.SUCCESS, response_data)

            except Exception as e:
              print(e)
              cfnresponse.send(event, context, cfnresponse.FAILED, response_data)
      FunctionName: !Sub "${Prefix}-function"
      Handler: index.lambda_handler
      Runtime: !Ref Runtime
      Role: !GetAtt FunctionRole.Arn
      Timeout: !Ref Timeout
Code language: YAML (yaml)

Lambda関数で実行するコードをインライン形式で記載します。
詳細につきましては、以下のページをご確認ください。

あわせて読みたい
CloudFormationでLambdaを作成する3パータン(S3/インライン/コンテナ) 【CloudFormationでLambdaを作成する】 CloudFormationでLambdaを作成する場合、大別すると以下の3パターンあります。 S3バケットにコードをアップロードする インライ...

実行するコードの内容ですが、RDS クライアントオブジェクトのmodify_db_instanceメソッドを実行し、リードレプリカのセキュリティグループを変更するものです。

この処理がスタック作成時に実行されるように実装します。

IAMロール

Resources:
  FunctionRole:
    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/AWSLambdaVPCAccessExecutionRole
      Policies:
        - PolicyName: ModifyDBInstancePolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - rds:ModifyDBInstance
                Resource:
                  - !Sub "arn:aws:rds:${AWS::Region}:${AWS::AccountId}:pg:*"
                  - !Sub "arn:aws:rds:${AWS::Region}:${AWS::AccountId}:secgrp:*"
                  - !Sub "arn:aws:rds:${AWS::Region}:${AWS::AccountId}:og:*"
                  - !Sub "arn:aws:rds:${AWS::Region}:${AWS::AccountId}:db:*"
Code language: YAML (yaml)

Lambda関数用のIAMロールです。

関数がmodify_db_instanceメソッドを実行するために必要な権限を与えます。

今回は以下のページを参考に設定しました。

https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/security_iam_id-based-policy-examples.html

(参考) EC2インスタンス

Resources:
  Instance:
    Type: AWS::EC2::Instance
    Properties:
      IamInstanceProfile: !Ref InstanceProfile
      ImageId: !Ref ImageId
      InstanceType: !Ref InstanceType
      NetworkInterfaces:
        - DeviceIndex: 0
          SubnetId: !Ref InstanceSubnet
          GroupSet:
            - !Ref InstanceSecurityGroup
      UserData: !Base64 |
        #!/bin/bash -xe
        yum update -y
        yum install -y mariadb
Code language: YAML (yaml)

EC2インスタンスからDBインスタンスにアクセスするために、クライアントパッケージを準備する必要があります。
今回はユーザデータを使用して、パッケージをインストールします。

ユーザデータに関する詳細については、以下のページをご確認ください。

あわせて読みたい
Linuxインスタンスの初期化方法4選 【Linuxインスタンスを初期化する4つの方法】 EC2インスタンスの起動時に初期化処理を実行する方法を考えます。 EC2インスタンスを構築時に初期化する以下の4つの手法を...

Amazon Linux 2からMySQLタイプのRDSにアクセスするためには、mariadbパッケージをインストールする必要があります。

Amazon Linux 2から各種RDSに接続するためのクライアントパッケージについては、以下のページをご確認ください。

あわせて読みたい
AL2で全RDSに接続する方法まとめ 【Amazon Linux 2からRDSの全DBエンジンに接続する方法】 2022年現在、RDSは以下の7種類のDBエンジンを提供しています。 Aurora(PostgreSQL) Aurora(MySQL) PostgreSQL ...

環境構築

CloudFormationを使用して、本環境を構築し、実際の挙動を確認します。

CloudFormationスタックを作成し、スタック内のリソースを確認する

CloudFormationスタックを作成します。
スタックの作成および各スタックの確認方法については、以下のページをご確認ください。

あわせて読みたい
CloudFormationのネストされたスタックで環境を構築する 【CloudFormationのネストされたスタックで環境を構築する方法】 CloudFormationにおけるネストされたスタックを検証します。 CloudFormationでは、スタックをネストす...

今回は以下のコマンドで2つのリージョンにスタックを作成します。

$ aws cloudformation create-stack \
--stack-name saa-01-004-1 \
--template-url https://[bucket-url]/saa-01-004-1.yaml \
--capabilities CAPABILITY_IAM \
--region ap-northeast-1

$ aws cloudformation create-stack \
--stack-name saa-01-004-2 \
--template-url https://[bucket-url]/saa-01-004-2.yaml \
--capabilities CAPABILITY_IAM \
--region us-east-1
Code language: Bash (bash)

各スタックのリソースを確認した結果、今回作成された主要リソースの情報は以下の通りです。

  • 東京リージョンのEC2インスタンス:i-01dff604f44d82f1d
  • 東京リージョンのRDSのエンドポイント:saa-01-004-1-dbinstance.cl50iikpthxs.ap-northeast-1.rds.amazonaws.com
  • ヴァージニアリージョンのEC2インスタンス:i-0ddbcbbbaffb35b48
  • ヴァージニアリージョンのRDSのエンドポイント:saa-01-004-2-dbinstance.csx6ivwzoymt.us-east-1.rds.amazonaws.com
  • CloudFormationカスタムリソース用Lambda関数:saa-01-004-2-

AWS Management Consoleから各リソースを確認します。

東京リージョンのDBインスタンスを確認します。

Detail of RDS 1.

Roleの項目を見ると、このDBインスタンスがプライマリインスタンスとして作成されていることがわかります。

レプリケーションの項目を見ると、後述のヴァージニアリージョンのDBインスタンスがリードレプリカとして存在していることがわかります。

ヴァージニアリージョンのDBインスタンスを確認します。

Detail of RDS 2.

Roleの項目を見ると、このDBインスタンスがリードレプリカインスタンスとして作成されていることがわかります。

レプリケーションの項目を見ると、先ほど確認したプライマリインスタンスをソースとして、レプリケーションが設定されていることがわかります。

またセキュリティグループの項目を見ると、今回作成したセキュリティグループが指定されていることがわかります。
つまりCloudFormationカスタムリソースが正常に動作したことによって、デフォルトのものではなく、適切なものに置き換わったということです。

カスタムリソース用のLambda関数の実行ログを確認します。

Detail of Lambda Function 1.

確かにCloudFormationカスタムリソースによって、CloudFormationスタック作成時に、Lambda関数が実行されていることがわかります。

動作確認

東京リージョンのプライマリインスタンスにアクセス

準備が整いましたので、プライマリインスタンスに接続します。
DBインスタンスへの接続は、東京リージョンのEC2インスタンスから行います。

インスタンスへのアクセスはSSM Session Managerを使用します。

% aws ssm start-session --target i-01dff604f44d82f1d --region ap-northeast-1
...
sh-4.2$
Code language: Bash (bash)

SSM Session Managerの詳細につきましては、以下のページをご確認ください。

あわせて読みたい
LinuxインスタンスにSSM Session Manager経由でアクセスする 【LinuxインスタンスにSSM Session Manager経由でアクセスする】 EC2インスタンスにSSM Session Manager経由でアクセスする構成を確認します。 Session Manager は完全...

ユーザーデータによるEC2インスタンスの初期化処理の実行状況を確認します。

sh-4.2$ sudo yum list installed | grep mariadb
mariadb.aarch64                       1:5.5.68-1.amzn2               @amzn2-core
mariadb-libs.aarch64                  1:5.5.68-1.amzn2               installed

sh-4.2$  mysql -V
mysql  Ver 15.1 Distrib 5.5.68-MariaDB, for Linux (aarch64) using readline 5.1
Code language: Bash (bash)

正常にMySQLクライアントパッケージがインストールされていることがわかります。

このクライアントパッケージを使用して、DBインスタンスに接続します。

sh-4.2$ mysql -h saa-01-004-1-dbinstance.cl50iikpthxs.ap-northeast-1.rds.amazonaws.com  -P 3306 -u testuser -p testdb
Enter password:
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MySQL connection id is 26
Server version: 8.0.27 Source distribution

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MySQL [testdb]>
Code language: Bash (bash)

正常に接続できました。

試しにテストテーブルを作成し、テストデータを保存します。

MySQL [testdb]> CREATE TABLE planet (id INT UNSIGNED AUTO_INCREMENT, name VARCHAR(30), PRIMARY KEY(id));
Query OK, 0 rows affected (0.03 sec)

MySQL [testdb]> INSERT INTO planet (name) VALUES ("Mercury");
Query OK, 1 row affected (0.01 sec)

MySQL [testdb]>
MySQL [testdb]> select * from planet;
+----+---------+
| id | name    |
+----+---------+
|  1 | Mercury |
+----+---------+
1 row in set (0.01 sec)
Code language: Bash (bash)

正常にデータを保存できました。

ヴァージニアリージョンのリードレプリカインスタンスにアクセス

続いてクロスリージョンリードレプリカインスタンスに接続します。
こちらもヴァージニアリージョンのEC2インスタンスからリードレプリカに接続します。

% aws ssm start-session --target i-0ddbcbbbaffb35b48 --region us-east-1
...
sh-4.2$Code language: Bash (bash)

インスタンスにアクセスできました。

リードレプリカインスタンスに接続します。

sh-4.2$ mysql -h saa-01-004-2-dbinstance.csx6ivwzoymt.us-east-1.rds.amazonaws.com  -P 3306 -u testuser -p testdb
Enter password:
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MySQL connection id is 22
Server version: 8.0.27 Source distribution

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MySQL [testdb]>Code language: Bash (bash)

リードレプリカに接続できました。

試しに読み込み/書き込み時の動作を確認します。

MySQL [testdb]> show tables;
+----------------+
| Tables_in_test |
+----------------+
| planet         |
+----------------+
1 row in set (0.00 sec)

MySQL [testdb]> select * from planet;
+----+---------+
| id | name    |
+----+---------+
|  1 | Mercury |
+----+---------+
1 row in set (0.01 sec)

MySQL [testdb]> INSERT INTO planet (name) VALUES ("Venus");
ERROR 1290 (HY000): The MySQL server is running with the --read-only option so it cannot execute this statementCode language: Bash (bash)

読み込みは成功しました。
東京リージョンに設置されているプライマリインスタンスで書き込んだ内容が、ヴァージニアリージョンに設置されているリードレプリカにも反映されています。

一方で、書き込みには失敗しました。
これはこのインスタンスが読み込み専用であるためです。

まとめ

RDSクロスリージョンリードレプリカの作成方法や、動作を確認しました。

目次