はじめに
VPC内のAmazon RDSに対してAWS Lambdaからアクセスする構成は、サーバーレスアーキテクチャにおいて非常に一般的です。しかし、セキュリティグループの設計ミスによる接続エラーや、不適切なIAM権限設定など、実務でトラブルが発生しやすいポイントでもあります。本記事では、セキュリティのベストプラクティスに基づいた、LambdaからRDS(VPC内)への安全な接続手順を分かりやすく解説します。
前提知識/必要な理由
なぜVPC Lambdaが必要なのか?
デフォルトのLambda関数は、AWSが管理するシステムVPC上で実行され、インターネット経由でリソースにアクセスします。プライベートサブネットに配置されたRDSにアクセスするには、Lambda関数を同じVPC(またはルーティング可能なVPC)内に配置し、ENI(Elastic Network Interface)を経由してプライベート通信を行わせる必要があります。
最小特権の原則(Least Privilege)
LambdaからRDSに接続する際、以下のセキュリティ対策が必須となります。
- セキュリティグループによる通信制限:Lambda専用のセキュリティグループから、RDS専用のセキュリティグループへの通信のみを許可します。
- Secrets Managerによるデータベース認証情報の安全な管理:ソースコードへのパスワード直書きを禁止し、IAMロールを介して安全に認証情報を取得します。
具体的な設定手順・設計方法
ここでは、Lambda(Python環境)からVPC内のPostgreSQL(RDS)に接続する具体的な手順を解説します。
ステップ1: セキュリティグループの作成
接続元と接続先用に、以下の2つのセキュリティグループ(SG)を作成します。
- Lambda用SG(例: sg-lambda-service)
- インバウンドルール: なし
- アウトバウンドルール: すべて(またはRDSのプライベートIP宛てのポート5432)
- RDS用SG(例: sg-rds-db)
- インバウンドルール: ポート「5432」、ソースに「sg-lambda-service(Lambda用SGのID)」を指定
- アウトバウンドルール: 任意
ステップ2: Lambda関数のVPC設定
Lambda関数がVPC内のRDSにアクセスできるようにネットワークを設定します。
- AWS管理コンソールで対象のLambda関数を開き、「設定」タブ > 「VPC」 > 「編集」をクリックします。
- RDSが配置されているVPCを選択します。
- サブネットとして、RDSへのルーティングが可能なプライベートサブネット(冗長性を確保するため、異なるアベイラビリティーゾーンから2つ以上)を選択します。
- セキュリティグループに、ステップ1で作成した「Lambda用SG(sg-lambda-service)」を指定して保存します。
ステップ3: 外部パッケージ(psycopg2)の導入手順
PythonからPostgreSQLに接続するために、psycopg2-binaryライブラリを使用します。Lambdaで外部ライブラリを実行するには、Lambdaレイヤーを作成して適用する必要があります。ローカル環境(またはDocker環境)で以下のコマンドを実行し、レイヤー用のZIPファイルを作成してください。
# ディレクトリ構造を維持してインストール
mkdir -p python/lib/python3.9/site-packages
pip install psycopg2-binary -t python/lib/python3.9/site-packages/
# ZIPファイルに圧縮
zip -r psycopg2_layer.zip python/
作成した psycopg2_layer.zip をAWSコンソールの「レイヤー」からアップロードし、対象のLambda関数に紐付けます。※Lambda関数のPythonランタイム(例: Python 3.9)と同一のバージョンでビルドしてください。
ステップ4: Lambda関数のコード実装
AWS Secrets Managerからデータベース接続情報をセキュアに取得し、RDSへ接続するサンプルコードです。Lambda関数の実行ロールには、該当するSecretの読み取り権限(secretsmanager:GetSecretValue)を付与しておきます。
import json
import boto3
import psycopg2
from botocore.exceptions import ClientError
def get_secret():
secret_name = "my-rds-secret"
region_name = "ap-northeast-1"
session = boto3.session.Session()
client = session.client(service_name='secretsmanager', region_name=region_name)
try:
get_secret_value_response = client.get_secret_value(SecretId=secret_name)
except ClientError as e:
raise e
return json.loads(get_secret_value_response['SecretString'])
def lambda_handler(event, context):
db_info = get_secret()
try:
conn = psycopg2.connect(
host=db_info['host'],
database=db_info['dbname'],
user=db_info['username'],
password=db_info['password'],
port=db_info['port']
)
with conn.cursor() as cur:
cur.execute("SELECT version();")
db_version = cur.fetchone()
print(f"Connected to: {db_version}")
conn.close()
return {
"statusCode": 200,
"body": json.dumps("Database connection successful!")
}
except Exception as e:
print(f"Error connecting to database: {e}")
return {
"statusCode": 500,
"body": json.dumps("Database connection failed.")
}
実務での注意点
- プライベートIPアドレスの枯渇:かつてはVPC Lambdaの起動時にENI作成が発生し、起動遅延(コールドスタート)が問題視されていましたが、現在はAWS Hyperplaneによる改善で解決されています。ただし、Lambdaの同時実行数に応じてプライベートIPが消費されるため、サブネットのCIDR設計には余裕を持たせてください。
- タイムアウト値の調整:初期接続やDBの応答待ちによるタイムアウトを防ぐため、Lambdaのタイムアウト設定をデフォルトの3秒から「15秒〜30秒」程度に増やしておくことを推奨します。
- RDS Proxyの導入検討:Lambdaはイベント駆動で急激に同時実行数が増加するため、RDSへのコネクション数が上限に達しシステムダウン(接続エラー)を引き起こす可能性があります。本番環境など、アクセス集中が予想される場合は「Amazon RDS Proxy」を経由した接続設計を強く推奨します。
まとめ
LambdaからVPC内のRDSへ安全に接続するためには、セキュリティグループの「相互参照」設定と、Secrets Managerによる認証情報の非公開化が不可欠です。インフラ設計の基本である「最小特権の原則」を徹底することで、セキュリティリスクを極限まで抑えた堅牢なシステムを構築することができます。