VPC内のLambdaからRDS Proxy経由でRDSに接続する

RDS Proxy経由でVPC内のLambdaからRDSに接続する

VPC内のLambdaからRDS Proxy経由でRDSに接続する

VPC内にLambdaを配置し、RDSに接続する構成を考えます。
LambdaからRDSにアクセスする際は、直接接続するのではなく、RDS Proxyを介する形で接続することがベストプラクティスとなります。

最新のサーバーレスアーキテクチャに構築されたアプリケーションなどのアプリケーションの多くは、データベースサーバーへの接続を多数開くことができます。このとき、データベース接続の開閉が高頻度で実行されて、データベースメモリやコンピューティングリソースを消耗する可能性があります。Amazon RDS Proxy では、アプリケーションがデータベースと確立した接続をプールおよび共有でき、データベースの効率とアプリケーションのスケーラビリティが向上します。

Amazon RDS Proxy

今回はRDS Proxyを使用して、LambdaからRDSに接続する構成を確認します。

構築する環境

Diagram of connect from Lambda in VPC via RDS Proxy

VPC内に3つのサブネットを作成します。
1つはLambda用です。通常はVPC外に作成するLambdaですが、今回のようにVPC内に作成することもできます。
残り2つはRDS用です。今回は1つDBインスタンスを作成します。

Lambda関数は直接DBインスタンスのエンドポイントに接続するのではなく、RDS Proxyエンドポイントに接続します。
RDS ProxyにターゲットグループとしてDBインスタンスを登録し、RDS Proxyを経由して、DBインスタンスに接続することができるようにします。
なお関数はPython3.8で作成します。

API Gatewayを作成し、バックエンドとしてLambda関数を設定します。
API Gatewayのエンドポイントにアクセスすることで、Lambda関数が実行されるように設定するということです。

今回作成する構成の挙動を簡単に説明します。
DBインスタンスに日時データを保存するテーブルを作成します。
API GatewayにHTTPリクエストが届くと、バックエンドのLambdaが呼び出され、現在日時を取得します。
取得した日時データをRDS Proxy経由でDBインスタンスに保存します。
加えてDBインスタンスに保存されている全データを取得し、HTTPレスポンスとして返します。

CloudFormationテンプレートファイル

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

https://github.com/awstut-an-r/awstut-fa/tree/main/037

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

プロキシ本体とターゲットグループを作成する

本ページはRDS Proxyに関する内容を取り上げます。
RDSそのものに関しては、以下のページをご確認ください。

あわせて読みたい
AL2で全RDSに接続する方法まとめ 【Amazon Linux 2からRDSの全DBエンジンに接続する方法】 2022年現在、RDSは以下の7種類のDBエンジンを提供しています。 Aurora(PostgreSQL) Aurora(MySQL) PostgreSQL ...
あわせて読みたい
RDS接続時の挙動をセキュリティグループのパターンごとに確認 【RDS用のセキュリティグループを確認する構成】 RDSに適用するセキュリティグループを確認します。 EC2からRDSにアクセスする場合、ポイントとなるのはセキュリティグ...

まずRDS Proxy本体を確認します。

Resources:
  DBProxy:
    Type: AWS::RDS::DBProxy
    Properties:
      Auth:
        - IAMAuth: DISABLED
          AuthScheme: SECRETS
          SecretArn: !Ref Secret
      DBProxyName: !Sub "${Prefix}-DBProxy"
      EngineFamily: !Ref DBProxyEngineFamily
      IdleClientTimeout: 120
      RequireTLS: false
      RoleArn: !GetAtt DBProxyRole.Arn
      VpcSecurityGroupIds:
        - !Ref DBProxySecurityGroup
      VpcSubnetIds:
        - !Ref DBSubnet1
        - !Ref DBSubnet2
Code language: YAML (yaml)

AuthプロパティでRDS(DBインスタンス)に接続する際の認証方式を設定します。
今回はSecrets Managerのシークレットを使用して認証を行うように設定します。

EngineFamilyプロパティは接続先のDBインスタンスのタイプを指定するものです。
今回はMySQLタイプのDBインスタンスですので、「MYSQL」を設定します。

VpcSecurityGroupIdsプロパティでRDS Proxyに適用するセキュリティグループを設定します。
今回はMySQLタイプのDBインスタンスに接続しますので、Lambdaサブネットからの3306/tcpのインバウンド通信を許可するセキュリティグループを適用します。

VpcSubnetIdsプロパティで、RDS Proxyを関連づけるサブネットを指定します。
対象となるサブネットはDBインスタンスが設置されているものとなります。

RDS Proxyの接続先となるDBインスタンスの指定は、ターゲットグループで定義します。

Resources:
  DBProxyTargetGroup:
    Type: AWS::RDS::DBProxyTargetGroup
    Properties:
      DBProxyName: !Ref DBProxy
      DBInstanceIdentifiers:
        - !Ref DBInstance
      TargetGroupName: default
      ConnectionPoolConfigurationInfo:
        MaxConnectionsPercent: 100
        MaxIdleConnectionsPercent: 50
        ConnectionBorrowTimeout: 120
Code language: YAML (yaml)

DBInstanceIdentifiersプロパティでDBインスタンスを指定します。

Secrets Managerのシークレットを確認します。

Resources:
  Secret:
    Type: AWS::SecretsManager::Secret
    Properties:
      Name: !Sub "${Prefix}-Secret"
      SecretString: !Sub '{"username":"${DBMasterUsername}","password":"${DBMasterUserPassword}"}'
Code language: YAML (yaml)

DBインスタンスに設定したユーザー名・パスワードをJSON形式で指定し、シークレットを作成します。

RDS Proxyに関連づけるIAMロールを作成します。

Resources:
  DBProxyRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Action: sts:AssumeRole
            Principal:
              Service:
                - rds.amazonaws.com
      Policies:
        - PolicyName: !Sub "${Prefix}-DBProxyPolicy"
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action: secretsmanager:GetSecretValue
                Resource: !Ref Secret
              - Effect: Allow
                Action: kms:Decrypt
                Resource: '*' # use default key.
Code language: YAML (yaml)

先ほど定義したシークレットから暗号化された値を取得する権限と、そのために使用するKMSキーの復号化の権限を付与します。
以下のページを参照して設定しました。

https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/rds-proxy-setup.html#rds-proxy-secrets-arns

RDS Proxyに接続するLambda関数

Lambda関数を確認します。

Resources:
  Function:
    Type: AWS::Lambda::Function
    Properties:
      Environment:
        Variables:
          DB_ENDPOINT_PORT: !Ref MySQLPort
          DB_NAME: !Ref DBName
          DB_PASSWORD: !Ref DBMasterUserPassword
          DB_PROXY_ENDPOINT_ADDRESS: !Ref DBProxyEndpointAddress
          DB_TABLENAME: !Ref DBTableName
          DB_USER: !Ref DBMasterUsername
          REGION: !Ref AWS::Region
      Code:
        S3Bucket: !Ref CodeS3Bucket
        S3Key: !Ref CodeS3Key
      FunctionName: !Sub "${Prefix}-function"
      Handler: index.lambda_handler
      Runtime: python3.8
      Role: !GetAtt FunctionRole.Arn
      Timeout: 10
      VpcConfig:
        SecurityGroupIds:
          - !Ref FunctionSecurityGroup
        SubnetIds:
          - !Ref FunctionSubnet
Code language: YAML (yaml)

今回はS3バケットにアップロードしたデプロイパッケージ(Zipファイル)からLambda関数を作成します。
Lambda関数の基本的な事項は以下のページをご確認ください。

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

ポイントは2つあります。

1つ目は環境変数の設定です。
EnvironmentおよびVariablesプロパティで関数内で使用可能な変数を定義できます。
今回はRDS ProxyおよびDBインスタンスに接続するためのRDS Proxyエンドポイント、ユーザー名やパスワード、テーブル名等を定義します。

2つ目はVPCに関する設定です。
今回はVPC内にLambda関数を設定しますので、設置するサブネットと適用するセキュリティグループを指定します。
セキュリティグループの内容ですが、HTTPS(443/tcp)のインバウンド通信を許可する内容です。

関数で実行するスクリプトを確認します。

import boto3
import datetime
import json
import mysql.connector
import os


db_name = os.environ['DB_NAME']
db_password = os.environ['DB_PASSWORD']
db_proxy_endpoint_address = os.environ['DB_PROXY_ENDPOINT_ADDRESS']
db_tablename = os.environ['DB_TABLENAME']
db_user = os.environ['DB_USER']
region = os.environ['REGION']


def lambda_handler(event, context):
    conn = mysql.connector.connect(
        host=db_proxy_endpoint_address,
        port=db_endpoint_port,
        user=db_user,
        password=db_password,
        database=db_name
        )
    cur = conn.cursor()

    table_sql = 'create table if not exists {db}.{tbl} (dt datetime);'.format(
        db=db_name,
        tbl=db_tablename
        )
    cur.execute(table_sql)

    now = datetime.datetime.now()
    now_str = now.strftime('%Y-%m-%d %H:%M:%S')
    write_sql = 'insert into {tbl} values ("{now}");'.format(
        tbl=db_tablename,
        now=now_str
    )
    cur.execute(write_sql)

    cur.close()
    conn.commit()

    cur = conn.cursor()
    read_sql = 'select * from {tbl};'.format(tbl=db_tablename)
    cur.execute(read_sql)
    content = [record[0].strftime('%Y-%m-%d %H:%M:%S') for record in cur]

    cur.close()
    conn.close()


    return {
        'statusCode': 200,
        'body': json.dumps(content, indent=2)
    }
Code language: Python (python)

本ページはRDS Proxyを中心に取り上げますので、Lambda関数そのものの詳細は割愛します。
RDS Proxyに接続するためのポイントを確認します。

os.environから環境変数を取得します。
これらは先述のLambda関数の定義時に設定した変数です。
特に重要な変数はRDS Proxyのエンドポイントです。
DBインスタンスに作成されるエンドポイントではなく、RDS Proxyエンドポイントに向かって接続する形になります。

PythonからMySQLに接続するために、MySQL公式のドライバであるmysql-connector-pythonを使用します。
デプロイするZipファイルを作成する際に、以下のコマンドでローカルにパッケージをダウンロードし、まとめてデプロイします。

pip3 install mysql-connector-python -t .

zip -r deploy.zip .
Code language: Bash (bash)

コードの内容を簡単にご紹介します。

  1. 環境変数を参照して、RDS Proxy経由でDBインスタンスに接続する。
  2. 初回のみ、現在日時を保存するためのテーブルを作成する。
  3. 現在日時を取得し、テーブルに保存する。
  4. テーブルに保存されている日時データを取得し、ユーザに返す。

API GatewayのバックエンドにLambdaを設定する

API Gatewayを作成するためは、以下の5つのリソースを作成する必要があります。

  • API Gateway本体
  • ステージ
  • 統合
  • ルート
  • IAMロール/パーミッション

詳細は以下のページをご確認ください。

あわせて読みたい
LambdaとAPI Gatewayを使用したサーバーレスアプリ – HTTP API 【API GatewayとLambdaでサーバーレスアプリケーションを作成する】 2つのリソースを組み合わせて、シンプルなサーバーレスWebアプリケーションを作成します。 1つ目はL...

環境構築

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

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

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

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

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

  • DBインスタンス:fa-037-dbinstance
  • RDS Proxy:fa-037-dbproxy
  • API Gatewayのエンドポイント:https://oeebpxb1r2.execute-api.ap-northeast-1.amazonaws.com/

AWS Management Consoleからも、リソースの作成状況を確認します。
RDS Proxyを確認します。

RDS Proxy Detail 1
RDS Proxy Detail 2

RDS Proxyが正常に作成されています。

エンドポイントが作成されています。
このエンドポイントに対してLambda関数はアクセスすることになります。

RDS ProxyのターゲットグループにDBインスタンスが関連づいています。
このRDS Proxyに接続すると、関連づいているDBインスタンスに接続することになります。

認証方式はSecrets Managerを使用する方法で設定されています。

動作確認

準備が整いましたので、API Gatewayにアクセスします。

RDS Proxy test 1
RDS Proxy test 2

正常にレスポンスが返ってきました。
アクセスする度に日時データの表示が増えていきます。
このようにLambdaからDBインスタンスにアクセスする際に、RSD Proxyを介して接続することができます。

まとめ

LambdaからRDS Proxyを経由してDBインスタンスに接続する方法を確認しました。