8
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

AWS Lambda 待望の RDS Proxy を CloudFormation で作ってみる

Last updated at Posted at 2020-07-04

2020/06/30、RDS Proxy がプレビューを終了し、ついに GA (正式リリース) となったことが発表されました。

これまでアンチパターンと言われていた、AWS Lambda と RDS との接続について解決策のひとつとなることから、サーバーレス界隈では待望の機能であったと思います。

この記事では、CloudFormation でこの RDS Proxy を作ってみたいと思います。が、その前に、RDS Proxy について少し振り返ってみたいと思います。

RDS Proxy は AWS Lambda にとって何が嬉しいのか

AWS Lambda はいわゆるサーバーレスのサービスです。そのため、常在的に起動しているリソースは基本的にありません。なんらかのイベントが発生したことを契機に、Lambda 上で実装したアプリケーションを動作させるためのリソースがスピンアップするので、例えば HTTP 要求が着信した、特定の時刻になった、というようなイベントごとに、計算リソースが立ち上がります。

そういったサービスの性質上、同時に発生するイベントの数が多くなればなるほど、起動する AWS Lambda の数が増えることになります。500 件同時に HTTP 要求が着信すれば、500 個の AWS Lambda リソースが立ち上がっている可能性もあります。

この性質と、リレーショナルなデータベース製品との相性が、これまではアンチパターンと言われていました。Lambda のリソースは互いに疎なかたちで起動されることから、誤解を恐れずに言えば、立ち上がった Lambda の数だけデータベースとのコネクションが生成されることになり、これは一般的なフレームワークに搭載されている、いわゆる「コネクションプール」の仕組みとは大きく異なっています。

コネクションが多く生成されるほど、データベースサーバー側の消費メモリも増えることになりますが、上記のような Lambda というサービスの性質上、実装のなかでコネクションプールのような仕組みをもたせることはできず、サービスの外にコネクションプールの役割を果たす何らかのレイヤーを置く必要があるということが、容易に想像できるのではないかと思います。

このコネクションプールとしてのレイヤーを提供する役割を果たすのが、今回 GA された RDS Proxy となります。

RDS Proxy の制約

このように、AWS Lambda としては待望のリリースである RDS Proxy ですが、もちろん銀の弾丸というわけではなく、制約を理解して使っていく必要があります。いくつか、注意すべき制約をピックアップしてみます。

  1. Aurora を含む、RDS for MySQL 5.6 and 5.7、もしくは RDS for PostgreSQL 10.11 and 11.5 に対応しており、RDS MySQL 8.0 や RDS Oracle などの DB エンジンでは利用できない
  2. 自身で EC2 インスタンス上に構築した RDB と RDS Proxy を接続することはできない
  3. 書き込み用のインスタンスのみに対応しており、 読み込み専用のインスタンス (リードレプリカ) に対する RDS Proxy は作れない
  4. Aurora Serverless や Aurora Multi-Master と Proxy を接続することはできない

特に 3. のリードレプリカと Proxy が接続できない制約は、データベースへの読み込みワークロードをスケールできないので、現時点では大きいと思います。そのため、読み込みに関しては、Proxy を通さないエンドポイントを使う必要があります。ここは今後の対応に期待ですね。

CloudFormation で作ってみる

今回は、MySQL 5.7 互換の Aurora クラスターを構築し、RDS Proxy を CloudFormation から作成してみました。Aurora を配置する VPC やサブネットの ID などは、適宜変更したうえでそのまま使えるかと思います。

なお、CDK でも、2020/07/02 にリリースされた v1.49.0 から L2 Construct が追加され、RDS Proxy がかなり簡潔に書けるようになっています。こちらの README に例があるので、こちらも試してみるとよいかもしれません。

AWSTemplateFormatVersion: 2010-09-09

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: "Database Configuration"
        Parameters:
          - AuroraUserName
          - AuroraDatabaseName
    ParameterLabels:
      AuroraUserName:
        default: "Master User Name"
      AuroraDatabaseName:
        default: "Database Name"

Parameters:
  AuroraUserName:
    Type: String
    AllowedPattern: "[a-zA-Z0-9-]+"
    ConstraintDescription: "must be between 1 to 16 alphanumeric characters."
    Description: "The database admin account user name, between 1 to 16 alphanumeric characters."
    MaxLength: 16
    MinLength: 1
    Default: "root"

  AuroraDatabaseName:
    Type: String
    AllowedPattern: "[a-zA-Z0-9_]+"
    ConstraintDescription: "must be between 1 to 64 alphanumeric characters."
    Description: "The database name, between 1 to 64 alphanumeric characters."
    MaxLength: 64
    MinLength: 1
    Default: "test"

Resources:
  ### Aurora MySQL のパスワードを生成 ###
  AuroraMasterPassword:
    Type: AWS::SecretsManager::Secret
    Properties:
      GenerateSecretString:
        GenerateStringKey: password
        PasswordLength: 16
        SecretStringTemplate: !Sub "{\"username\":\"${AuroraUserName}\"}"
        ExcludeCharacters: "\"@/\\"
 
  AuroraSecretAttachment:
    Type: AWS::SecretsManager::SecretTargetAttachment
    Properties:
      SecretId: !Ref AuroraMasterPassword
      TargetId: !Ref AuroraCluster
      TargetType: AWS::RDS::DBCluster

  ### Aurora MySQL の生成 ###
  AuroraSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: vpc-0123456789abcedfg
      GroupDescription: "Security Group for Aurora"
      SecurityGroupIngress:
        - IpProtocol: tcp
          SourceSecurityGroupId: !Ref RdsProxySecurityGroup
          FromPort: 3306
          ToPort: 3306

  AuroraSubnetGroup:
    Type: AWS::RDS::DBSubnetGroup
    Properties:
      DBSubnetGroupDescription: "Aurora Subnet Group"
      SubnetIds:
        - subnet-0123456789abcedfg
        - subnet-abcdefg0123456789

  AuroraClusterParameterGroup:
    Type: AWS::RDS::DBClusterParameterGroup
    Properties:
      Description: "Aurora Cluster Parameter Group"
      Family: aurora-mysql5.7
      Parameters:
        time_zone: UTC

  AuroraCluster:
    Type: AWS::RDS::DBCluster
    Properties:
      DatabaseName: !Ref AuroraDatabaseName
      DBClusterParameterGroupName: !Ref AuroraClusterParameterGroup
      Engine: aurora-mysql
      MasterUsername: !Ref AuroraUserName
      MasterUserPassword: !Sub "{{resolve:secretsmanager:${AuroraMasterPassword}:SecretString:password::}}"
      DBSubnetGroupName: !Ref AuroraSubnetGroup
      VpcSecurityGroupIds:
        - !Ref AuroraSecurityGroup

  AuroraParameterGroup:
    Type: AWS::RDS::DBParameterGroup
    Properties:
      Description: "Aurora Parameter Group"
      Family: aurora-mysql5.7

  AuroraInstance1:
    Type: AWS::RDS::DBInstance
    Properties:
      DBClusterIdentifier: !Ref AuroraCluster
      DBInstanceClass: db.t3.small
      DBParameterGroupName: !Ref AuroraParameterGroup
      Engine: aurora-mysql

  ### RDS Proxy の生成 ###
  ##### RDS Proxy が Aurora へ接続するためのセキュリティグループ #####
  RdsProxySecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: vpc-01234567890abcdefg
      GroupDescription: "Security Group for RDS Proxy"

  ##### RDS Proxy が Aurora へ接続するためのパスワードを取得する IAM ロール #####
  RdsProxyRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service: rds.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: AllowGetSecretValue
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action: 
                  - secretsmanager:GetSecretValue
                  - secretsmanager:DescribeSecret
                Resource: !Ref AuroraSecretAttachment

  RdsProxy:
    Type: AWS::RDS::DBProxy
    Properties:
      DBProxyName: rds-proxy-for-aurora
      EngineFamily: MYSQL
      RoleArn: !GetAtt RdsProxyRole.Arn
      Auth:
        - AuthScheme: SECRETS
          SecretArn: !Ref AuroraSecretAttachment
          IAMAuth: DISABLED
      VpcSecurityGroupIds:
        - !Ref RdsProxySecurityGroup
      VpcSubnetIds:
        - subnet-0123456789abcedfg
        - subnet-abcdefg0123456789
  
  RdsProxyTargetGroup:
    Type: AWS::RDS::DBProxyTargetGroup
    DependsOn:
      - AuroraCluster
      - AuroraInstance1
    Properties:
      DBProxyName: !Ref RdsProxy
      DBClusterIdentifiers:
        - !Ref AuroraCluster
      TargetGroupName: default

Outputs:
  RDSProxyEndpoint:
    Value: !GetAtt RdsProxy.Endpoint
    Export:
      Name: rds-proxy-endpoint

  AuroraMasterPassword:
    Value: !Ref AuroraMasterPassword
    Export:
      Name: aurora-master-password

RDS Proxy が Aurora に接続するための接続情報を、認証情報を管理するサービスである Secrets Manager に格納する必要がある点や、RDS Proxy の Target Group を Aurora クラスターおよびインスタンスが立ち上がったあとに作成するよう、DependsOn 句で明示的に指定してあげる必要がある点などが、注意が必要です。

Lambda から利用するときは、最後に Outputs で出力した RDS Proxy の接続エンドポイントを、Lambda の環境変数などにインポートし、認証情報は Secrets Manager から取得するように実装すると良いと思います。

まとめ

CloudFormation で RDS Proxy を作る実例はあまり見かけなかったので、本記事にて取り上げてみました。次回は、実際に RDS Proxy を通した環境と通さない環境で、Lambda から接続してみてパフォーマンスがどう変わるのか、試してみたいと思います。

8
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?