AWS LambdaからRDS Proxy経由でAmazon RDSに接続してみる

記事タイトルとURLをコピーする

DS課の古川です。

RDS Proxyの登場により、AWS LambdaとAmazon RDS間接続のボトルネックを回避できるようになりました。

今回は、RDS Proxyを経由してAWS LambdaからAmazon RDSに接続する手順を試してます。

はじめに

最大同時接続数

RDBには最大同時接続数が存在し、CPUやメモリに依存する。つまり、アクティブな接続数を増やしたい場合、それなりのスペックのDBを用意する必要がある。

LambdaとRDBの接続

AWSLambdaは、ウォームスタートを除き1リクエストあたり1コンテナ作られる。

つまり、リクエストがあるとコンテナが作られて起動され、同時にDBへのコネクションを張る。

ウォームスタートの場合は、グローバルスコープ(グローバル変数)を利用することで接続オーバーヘッドを減らすことが可能。

1コンテナあたり1コネクション張るので、リクエストが多いサービスでは、RDBの最大同時接続数に達してしまう。

コネクションプールを使ってコネクションを使いまわせばいいのでは?と思うが、Lambdaでコネクションプーリングを実現するのは難しい。

RDS Proxy

RDS Proxyが、LambdaとRDBの仲介役となることで、必要となるDBへのコネクションプールを確立および管理し、既存のコネクションを再利用することで、LambdaからのDB接続を少なく抑える。

また、RDS Proxyは自動的にスケーリングされるため、コネクション管理に必要なメモリとCPUリソースを効率化できる。

また、RDS Proxyはフェイルオーバーに対して耐障害性が高く、元のDBインスタンスが使用できなくなると、アイドル状態のアプリケーション接続を切断せずにスタンバイDBに接続する。

リソース

f:id:swx-furukawa:20220113193350p:plain

  • ネットワ-ク
    • VPC
    • Security Group
      • Lambda用
      • RDS Proxy用
      • RDS用
  • Amazon RDS
  • RDS Proxy
  • Amazon Secrets Manager
  • IAM Role(RDS Proxy用)
  • AWS Lambda

※各Security Groupのパラメータを以下に記載

Security Group

Lambda用

Inbound

Protocol Port Range Source

outbound

Protocol Port Range Source
all all anywhere

RDS Proxy用

Inbound

Protocol Port Range Source
tcp 3306 Lambda用Security Group

outbound

Protocol Port Range Source
all all anywhere

RDS用

Inbound

Protocol Port Range Source
tcp 3306 RDS Proxy用Security Group、踏み台用Security Group

outbound

Protocol Port Range Source
all all anywhere

RDSとRDS Proxyを同じSecurity Groupに紐付けると疎通エラーとなるので、個別に分けた方が良いかと思います。

参考

  • 「このセキュリティグループは、プロキシの接続先のデータベースへのアクセスを許可する必要があります。同じセキュリティグループが、アプリケーションからプロキシへのイングレスと、プロキシからデータベースへのエグレスに使用されます。
    例えば、データベースとプロキシに同じセキュリティグループを使用するとします。この場合は必ず、そのセキュリティグループ内のリソースが同じセキュリティグループ内の他のリソースと通信できるように指定してください。」

手順

1. データベースを作成

  • Amazon RDS for MySQL(MySQL 8.0.23, db.t2.micro)を使用。(Auroraでも可)
  • VPC, DB Subnet Group, Security Groupは事前に用意
  • シングル構成
  • DB認証はパスワード認証

参考) DB インスタンスを作成する

2. 接続テスト

踏み台サーバー or Cloud9を用意

DBにテストデータを作成するために、同じVPC内に踏み台サーバーを用意する。

参考)踏み台サーバー作成方法

今回はSSH接続ではなく、Session Manager接続で踏み台サーバにアクセスします。

詳しい説明はこちらです。

RDSに接続

1. データベースを作成で作成したRDSのエンドポイントを参照し、DBにログインします。

$ mysql -u admin -h <RDSのエンドポイント> -p

テスト用のテーブルを作成

mysql> show databases;
mysql> create database test;
mysql> create table test.sample(id int, value varchar(10));
mysql> INSERT INTO test.sample VALUE(1, "taro");
mysql> INSERT INTO test.sample VALUE(2, "hanako");
mysql> select * from test.sample;

+------+--------+
| id   | value  |
+------+--------+
|    1 | taro   |
|    2 | hanako |
+------+--------+
2 rows in set (0.00 sec)

テストデータが作成されていることを確認できました。

3. Secrets Managerの設定

RDS Proxyへの接続は、RDSのパスワードではなくSecrets Managerと連携します。

f:id:swx-furukawa:20220113202301p:plain

シークレットのタイプにて「Amazon RDSデータベースの認証情報」にチェックします。

認証情報にて、今回は1. データベースを作成で作成したユーザーとパスワードを設定します。

暗号化キーはDefaultのKMSキーを使用します。

f:id:swx-furukawa:20220113202324p:plain


データベースにて1. データベースを作成で作成したDBを選択します。

f:id:swx-furukawa:20220113202634p:plain


シークレットの値を確認したところ、以下のシークレットが自動生成されます。

  • username
  • password
  • engine
  • host(RDSのエンドポイント)
  • port
  • dbInstanceIdentifier

f:id:swx-furukawa:20220113202959p:plain

4. RDS Proxyの起動


RDSコンソール画面にて、プロキシを作成します。 f:id:swx-furukawa:20220119182250p:plain
プロキシの設定にて、任意のプロキシ識別子を入力、エンジンの互換性はMySQL、他の項目はデフォルトで設定します。 f:id:swx-furukawa:20220119180931p:plain
ターゲットグループの設定にて、プロキシに関連付けるRDSインスタンスを選択します。

今回は、1. データベースを作成で作成したRDSインスタンスを選択し、最大接続数は100%に設定します。 f:id:swx-furukawa:20220119181136p:plain


接続にて、3. Secrets Managerの設定で作成したシークレットを選択します。

IAMロールは、「IAMロールを作成」を選択することで新規作成します。

※重要
IAM 認証は、今回はRDS Proxyへパスワード認証でアクセスするために、「無効」を選択します。「有効」にすると、後の手順が少し変わるので、注意が必要です。


サブネットは、DBサブネットグループに登録している2つのサブネットを選択します。
※サブネットは少なくとも2つ以上設定する必要があります。

f:id:swx-furukawa:20220119182515p:plain


最後に、「拡張されたログ記録を有効にする」にチェックを入れ、プロキシによって処理されたクエリの詳細ログを記録します。 f:id:swx-furukawa:20220119182943p:plain


RDSコンソール画面にて、プロキシが追加されステータスが「利用可能」になっていることを確認し、作成されたプロキシを選択します。 f:id:swx-furukawa:20220119183009p:plain

ターゲットグループを確認し、ステータスが「利用可能」になっていることを確認します。

※Tips
ターゲットグループのステータスが「使用不可」になっていることがあります。

f:id:swx-furukawa:20220119185522p:plain


その場合は、AWS CloudShellにてエラー理由を見ることができます。

 $ aws rds describe-db-proxy-targets --db-proxy-name test-lambda-rds-proxy


f:id:swx-furukawa:20220119190553p:plain

参考)RDS Proxyのトラブルシューティング


自動生成されたIAM Roleを確認します。

f:id:swx-furukawa:20220119191853p:plain


以下のポリシーが作られました。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "GetSecretValue",
            "Action": [
                "secretsmanager:GetSecretValue"
            ],
            "Effect": "Allow",
            "Resource": [
                "arn:aws:secretsmanager:ap-northeast-1:*******:secret:lambda_rds_proxy-P1Hnp9"
            ]
        },
        {
            "Sid": "DecryptSecretValue",
            "Action": [
                "kms:Decrypt"
            ],
            "Effect": "Allow",
            "Resource": [
                "arn:aws:kms:ap-northeast-1:*******:key/5af55ba5-a7de-47a9-95c3-7abd677ac6b0"
            ],
            "Condition": {
                "StringEquals": {
                    "kms:ViaService": "secretsmanager.ap-northeast-1.amazonaws.com"
                }
            }
        }
    ]
}

5. Lambda関数の作成

今回は、Severless FrameworkとPython3.9にてAWS Lambdaを作成します。

環境変数にDBの認証情報を設定してますが、本来はSecrets Mangaerにて管理するのが望ましいです。
※AWSLambdaPowertoolsPythonにてSecretManagerからシークレットを取得する方法を、次回別記事にて投稿します。

serverless.yml

service: poc
frameworkVersion: '2'

provider:
  name: aws
  runtime: python3.9
  lambdaHashingVersion: 20201221
  region: ${opt:region, "ap-northeast-1"}
  stackName: ${self:service}

package:
 patterns:
    - '!.devcontainer/**'
    - '!.venv/**'
    - '!node_modules/**'
    - '!.npmignore'
    - '!package-lock.json'
    - '!package.json'
    - '!Pipfile'
    - '!Pipfile.lock'
    - '!workspace.code-workspace'
    - '!lambda-rds-proxy.md'

plugins:
  - serverless-python-requirements

functions:
  LambdaRDSProxyFunction:
    handler: src.lambda_rds_proxy.handler
    name: ${self:service}-lambda-rds-proxy
    description: RDSプロキシ接続用
    memorySize: 256
    timeout: 30
    layers:
      - arn:aws:lambda:${self:provider.region}:017000801446:layer:AWSLambdaPowertoolsPython:4
    environment:
      DB_ENDPOINT: <RDS Proxyのエンドポイント>
      DB_USER: <DB認証ユーザー>
      DB_PASSWORD: <DB認証パスワード>
      DB_NAME: <テスト用DB>
    vpc:
      securityGroupIds:
        - <Lambda用セキュリティグループ>
      subnetIds:
        - <アクセスしたいサブネット>

lambda_rds_proxy.py

Python上でSQLを実行するために、 pymysqlモジュールを使用します。
今回はシンプルにsampleテーブルのデータを全て取得します。

import os
import sys
                               
import pymysql
from aws_lambda_powertools import Logger
from aws_lambda_powertools.utilities.typing import LambdaContext
 
DB_HOST = os.environ["DB_ENDPOINT"]
DB_USER = os.environ["DB_USER"]
DB_PASSWORD = os.environ["DB_PASSWORD"]
DB_NAME = os.environ["DB_NAME"]
 
logger = Logger(level='INFO', service=__name__)
 
 
@logger.inject_lambda_context()
def handler(event=None, context=LambdaContext):
    try:
        # RDS Proxyに接続
        connect = pymysql.connect(host=DB_HOST, user=DB_USER, passwd=DB_PASSWORD, db=DB_NAME, connect_timeout=5)
        with connect.cursor() as cursor:
            cursor.execute("select * from sample")
            result = cursor.fetchall()
            # selectのみなので、commit()は不要
            # conn.commit()
            logger.info(result)
        connect.close()
    except pymysql.MySQLError as e:
        logger.error("ERROR: Unexpected error: Could not connect to MySQL instance.")
        logger.error(e)
        sys.exit()
    except Exception:
        logger.error("ERROR: Unexpected error: Could not get value")
        sys.exit()
 
    return result


以下のコマンドにてAWSリソースをデプロイします。

$ npx sls deploy


Lambdaコンソール画面にて、Lambda関数がデプロイされていることを確認できました。
f:id:swx-furukawa:20220119195359p:plain


※重要

RDS ProxyへIAMRole認証をする場合、Lambdaコンソール画面にて、データベースプロキシの追加を設定する必要があります。
しかし、今回はパスワード認証を選択したので、データベースプロキシの追加不要 となります。

f:id:swx-furukawa:20220119195936p:plain

6. Lambdaを実行

Lambdaコンソール画面にて、テストを実行します。

[
  [
    1,
    "taro"
  ],
  [
    2,
    "hanako"
  ]
]

無事テストデータを取得できました。

気をつけるポイント

  • RDS ProxyのパスワードはSecrets Managerで管理する
  • RDS Proxyのサブネットは2つ以上設定する
  • Security GroupをRDSとRDS Proxyで同じものを使用すると、RDS Proxyのターゲットに登録できない
  • RDSにクエリを実行する場合、boto3ではなくpymysql
  • RDS Proxyへのアクセス設定をパスワード認証にすると、LambdaとRDS Proxyの関連付けが不要になる


手順は以上となります。

参考

古川敏光 (執筆記事の一覧)

アプリケーションサービス部・ディべロップメント課

AWSによるサーバレス開発をメインに日々研鑽しております。 最近ハマっている趣味はサーフィンです。