はじめに
AWSを用いたサーバーレスアーキテクチャにおいて、AWS LambdaからAmazon RDS(Aurora含む)への接続は非常によく使われるパターンです。しかし、Lambdaの特性である「リクエストに応じた急激なスケールアウト」は、データベース接続数の枯渇という深刻な問題を引き起こします。本記事では、この問題を解決する「Amazon RDS Proxy」を導入し、Lambdaから安全かつ安定してプライベートサブネット内のデータベースに接続するための設計と具体的な設定手順をステップバイステップで解説します。
前提知識とRDS Proxyが必要な理由
なぜ直接接続ではダメなのか?
Lambda関数は、同時に実行されるリクエスト数に応じてインスタンス(コンテナ)が自動的に増減します。Lambdaからデータベースに直接接続(1コンテナ=1コネクション)すると、アクセス急増時にデータベースの最大同時接続数(max_connections)を一瞬で超過してしまい、接続エラー(Too many connections)が発生します。
RDS Proxyが解決する課題
Amazon RDS Proxyは、LambdaとRDSの間に立ち、データベース接続のプール(使い回し)を管理するフルマネージドなプロキシサービスです。これにより以下のメリットが得られます。
- コネクションプール管理:Lambdaが大量に起動しても、データベースへの実際の接続数を一定以下に制限・維持します。
- フェイルオーバーの高速化:マルチAZ構成のRDSがフェイルオーバーした際、接続の切り替えを最大66%高速化します。
- セキュリティ向上:データベースの認証情報をLambdaの環境変数に直接持たせず、AWS Secrets Managerと統合して安全に管理できます。
具体的な設定手順・設計方法
今回は、プライベートサブネット内にある「Aurora PostgreSQL」に、AWS Lambdaから「RDS Proxy」を経由して安全に接続する手順を解説します。
1. VPC・セキュリティグループの設計
まずは、安全な通信経路を確保するためのセキュリティグループ(SG)を設定します。
- Lambda用SG(sg-lambda):アウトバウンドは全ての通信を許可(VPC外への通信やSecrets Managerへのアクセス用)。インバウンドは不要。
- RDS Proxy用SG(sg-proxy):インバウンドルールで、sg-lambdaからのポート5432(PostgreSQL)への接続を許可。
- RDS用SG(sg-db):インバウンドルールで、sg-proxyからのポート5432への接続を許可。※Lambdaからの直接接続は拒否します。
2. Secrets Managerでのデータベース資格情報の登録
RDS Proxyがデータベースに接続するための認証情報をSecrets Managerに保存します。
- AWS Secrets Managerコンソールで「新しいシークレットを保存する」を選択します。
- シークレットのタイプで「Amazon RDSデータベースの資格情報」を選択し、ユーザー名とパスワードを入力します。
- 対象のRDSデータベースを選択し、シークレットを保存します(例:
db-creds-postgres)。
3. RDS Proxyの作成と設定
次に、RDSコンソールからRDS Proxyを作成します。
- RDSコンソールの「プロキシ」メニューから「プロキシを作成」をクリックします。
- ターゲットグループ設定:対象のRDSデータベースインスタンスを選択します。
- 認証:先ほどSecrets Managerに作成したシークレット(
db-creds-postgres)を選択します。IAMロールは新規作成(ProxyがSecrets Managerにアクセスするためのポリシーが自動付与されます)を選択します。 - 接続:前述の「RDS Proxy用SG(sg-proxy)」を割り当てます。
4. Lambda関数の作成と接続コードの実装
Lambda関数をRDS Proxyと同じVPCのプライベートサブネットに配置し、接続用コードを実装します。
外部ライブラリ(pg8000)の導入手順
PythonでPostgreSQLに接続するため、軽量な純粋Pythonライブラリであるpg8000を使用します。デプロイパッケージに含めるため、ローカル開発環境で以下のコマンドを実行し、モジュールをソースコードと同ディレクトリにインストールしてzipファイルに固めてデプロイします。
# 作業ディレクトリでライブラリをカレントディレクトリにインストール
pip install pg8000 -t .
# コード(lambda_function.py)と一緒にzip圧縮してLambdaにアップロード
zip -r deployment-package.zip .
Lambda接続コード例(Python 3.x)
環境変数 DB_PROXY_ENDPOINT には、RDS Proxyの「エンドポイント」を設定します。認証にはSecrets Managerを利用したパスワード接続の例を示します。
import os
import boto3
import json
import pg8000.dbapi
# Secrets Managerから認証情報を取得する関数
def get_secret():
secret_name = os.environ['SECRET_NAME']
region_name = os.environ['AWS_REGION']
session = boto3.session.Session()
client = session.client(service_name='secretsmanager', region_name=region_name)
get_secret_value_response = client.get_secret_value(SecretId=secret_name)
return json.loads(get_secret_value_response['SecretString'])
def lambda_handler(event, context):
# 1. 認証情報の取得
db_creds = get_secret()
# 2. RDS Proxy経由でのデータベース接続
conn = None
try:
conn = pg8000.dbapi.connect(
host=os.environ['DB_PROXY_ENDPOINT'],
database=db_creds['dbname'],
user=db_creds['username'],
password=db_creds['password'],
port=5432,
timeout=5
)
cursor = conn.cursor()
cursor.execute("SELECT version();")
db_version = cursor.fetchone()
cursor.close()
return {
'statusCode': 200,
'body': f"Connection successful! DB Version: {db_version}"
}
except Exception as e:
print(f"Error connecting to database: {e}")
raise e
finally:
if conn is not None:
conn.close()
実務での注意点
接続タイムアウトとリソース管理
RDS Proxyを導入しても、Lambda関数内でconn.close()を呼び出して明示的にコネクションを閉じない場合、接続がリークして一時的にプールが枯渇する原因になります。上記のコード例のように、必ずfinallyブロックを活用し、処理終了時には確実に接続を切断するよう実装してください。
コストの考慮
RDS Proxyは非常に強力ですが、起動している時間と処理されたデータベーストラフィック量に基づいた課金が発生します。開発環境や、同時実行数が極めて少なくトラフィックが予測可能な小規模システムでは、コスト対効果を見極めて導入を判断してください。本番環境やスパイクアクセスが発生するシステムでは必須級のサービスです。
まとめ
LambdaからRDSへの接続における「コネクション枯渇問題」は、インフラ設計において必ず考慮すべきポイントです。RDS Proxyを導入し、セキュリティグループを「Lambda ➔ RDS Proxy ➔ RDS」の階層構造で適切に絞ることで、セキュアで高可用なサーバーレスシステムを構築することができます。ぜひ実務のインフラ設計に役立ててください。