LambdaからRDS Proxy経由で安全にDB接続する構築手順

はじめに

サーバーレスアーキテクチャにおいて、AWS LambdaからAmazon RDSなどのリレーショナルデータベースに接続する際、コネクション数の枯渇や接続のオーバーヘッドが課題になります。本記事では、これらの課題を解決する「Amazon RDS Proxy」と、安全な認証情報を管理する「AWS Secrets Manager」を組み合わせた、安全で高効率な接続環境の構築手順をステップバイステップで解説します。

前提知識/必要な理由

Lambdaはリクエストに応じてスケーリングするため、同時に多数のインスタンスが起動します。各インスタンスが個別にDBコネクションを確立すると、DB側の最大接続数を超過し、接続エラーが発生します。また、DBの認証情報をLambdaの環境変数に直接保持することは、セキュリティ上推奨されません。

RDS Proxyを導入することで、コネクションをプール・共有し、接続効率を最大化できます。また、Secrets Managerと統合することで、プログラム内にパスワードをハードコーディングすることなく、安全にDB接続を行うことが可能になります。

具体的な設定手順・設計方法

1. Secrets ManagerでのDB資格情報の作成

まず、RDS ProxyがDBに接続するためのユーザー名とパスワードをSecrets Managerに保存します。

  • AWS管理コンソールから「Secrets Manager」を開き、「新しいシークレットを保存する」をクリックします。
  • シークレットのタイプで「Amazon RDS データベースの資格情報」を選択し、ユーザー名とパスワードを入力します。
  • 対象のRDSデータベースを選択し、シークレットに名前(例:rds/db-credential)をつけて保存します。

2. RDS Proxy用のIAMロールの作成

RDS ProxyがSecrets Managerから認証情報を取得できるようにするためのIAMロールを作成します。信頼ポリシーに rds.amazonaws.com を指定し、以下のポリシーをアタッチします。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "secretsmanager:GetSecretValue",
      "Resource": "arn:aws:secretsmanager:ap-northeast-1:123456789012:secret:rds/db-credential-xxxxxx"
    }
  ]
}

3. RDS Proxyの作成と設定

RDSコンソールに移動し、プロキシを作成します。

  • RDSのメニューから「プロキシ」を選択し、「プロキシの作成」をクリックします。
  • 対象のRDSデータベースと、先ほど作成したSecrets Managerのシークレット、IAMロールを指定します。
  • セキュリティグループは、LambdaおよびRDSへの通信を許可する適切なルールを設定してください。

4. Lambda関数の作成とライブラリの準備

Pythonを使用してRDS Proxy経由でデータベースに接続するLambda関数を作成します。PythonでMySQLに接続する場合、標準外のライブラリである PyMySQL が必要になります。以下は、Lambdaレイヤーを使用した導入手順です。

ローカル環境でライブラリをパッケージ化し、Lambdaレイヤーにアップロードします。以下のコマンドを実行してZIPファイルを作成してください。

mkdir -p python/lib/python3.9/site-packages
pip install pymysql -t python/lib/python3.9/site-packages/
zip -r pymysql-layer.zip python

作成した pymysql-layer.zip をLambdaのレイヤーに登録し、対象のLambda関数にアタッチします。

次に、Lambda関数のコードを以下のように記述します。Secrets Managerから認証情報を取得する必要はなく、RDS Proxyが認証を仲介するため、RDS Proxyのエンドポイントへ通常の接続を行います。ただし、LambdaからProxyへの接続時にもIAM認証を利用することでより強固な設計が可能です。ここではIAM認証を利用した接続コード例を示します。

import os
import boto3
import pymysql

def lambda_handler(event, context):
    rds_client = boto3.client('rds')
    
    # RDS Proxyのエンドポイントとポート
    db_host = os.environ['DB_HOST']
    db_user = os.environ['DB_USER']
    db_name = os.environ['DB_NAME']
    
    # IAM認証トークンの生成
    token = rds_client.generate_db_auth_token(
        DBHostname=db_host,
        Port=3306,
        DBUsername=db_user,
        Region='ap-northeast-1'
    )
    
    try:
        connection = pymysql.connect(
            host=db_host,
            user=db_user,
            password=token,
            database=db_name,
            ssl_ca='/etc/pki/tls/certs/ca-bundle.crt' # SSL接続の強制
        )
        with connection.cursor() as cursor:
            cursor.execute("SELECT VERSION()")
            result = cursor.fetchone()
            return {"statusCode": 200, "body": f"DB Version: {result}"}
    except Exception as e:
        print(e)
        return {"statusCode": 500, "body": "Connection failed"}
    finally:
        if 'connection' in locals():
            connection.close()

実務での注意点

  • VPC設計とセキュリティグループ: Lambda、RDS Proxy、RDSは同一のVPC内(またはピアリングされたVPC)に配置する必要があります。セキュリティグループのインバウンド・アウトバウンドルールで、LambdaからProxy(ポート3306等)、ProxyからRDSへの通信が正確に許可されているか確認してください。
  • ピン留め(Pinning)の回避: SQLのセッション状態を変更する操作(一時テーブルの使用、ロック、特定のセッション変数の変更など)を行うと、RDS Proxyはコネクションを特定のクライアントに「ピン留め」してしまい、プール効率が低下します。可能な限りシンプルなクエリ設計を心がけてください。
  • タイムアウト値の設定: Lambdaのタイムアウト値と、RDS Proxyのアイドルクライアント接続タイムアウトのバランスを考慮してください。通常、ProxyのタイムアウトはLambdaの最大実行時間より短く設定することで、不要なコネクションの保持を防げます。

まとめ

Amazon RDS ProxyとAWS Secrets Managerを組み合わせることで、Lambdaからのデータベース接続において、接続数のスケーラビリティ課題とセキュリティリスクを同時に解決できます。初期設定の手間は多少かかりますが、本番環境での安定したサーバーレス運用のために、ぜひこの設計パターンを導入してください。

上部へスクロール