AWSを実務で利用する際、サーバーレスアーキテクチャの要となる「AWS Lambda」から、プライベートネットワーク(VPC)内にある「Amazon RDS」へ接続する構成は非常に多く採用されます。しかし、適切なセキュリティ設計やネットワーク設定を行わないと、接続エラーが発生したり、重大なセキュリティリスクを抱えたりすることになります。
この記事では、初心者から中級者のインフラエンジニア向けに、LambdaからVPC内のRDS(PostgreSQL)へ安全に接続するための設計ベストプラクティスと具体的な設定手順をステップバイステップで解説します。
はじめに
LambdaとRDSを連携させる際、セキュリティ要件から「データベースをインターネットに公開したくない」というケースがほとんどです。本記事では、LambdaをVPC内に配置し、安全なプライベートネットワークのみを経由してRDSと通信させるためのセキュアなインフラ構築手順を解説します。
前提知識/必要な理由
なぜLambdaをVPC内に配置する必要があるのか
通常、デフォルト状態のLambda関数は、AWSが管理するパブリックなネットワーク上で実行されます。一方で、機密性の高いデータを保持するデータベース(RDS)は、セキュリティ上の理由からパブリックアクセスを禁止し、VPC内のプライベートサブネットに配置するのが原則です。
デフォルト状態のLambdaからプライベートなRDSへは直接通信ができません。そのため、Lambda関数にVPC設定を施し(VPC Lambda化)、VPC内に仮想的なネットワークインターフェース(ENI)を作成することで、安全なプライベートネットワーク経由での通信を実現します。
ネットワーク設計の基本
LambdaをVPC内に配置する際、以下の3つの要素を正しく設計することが極めて重要です。
- サブネットの選択: データベースの可用性を担保するため、Lambdaは必ず複数のアベイラビリティゾーン(AZ)にまたがるプライベートサブネットに配置します。
- セキュリティグループの制御: 「最小権限の原則」に基づき、LambdaとRDSの間で必要なポート(PostgreSQLの場合は5432)のみを通すように制御します。
- IAMロールの権限: LambdaがVPC内にネットワークインターフェース(ENI)を作成・管理するための専用権限が必要です。
具体的な設定手順・設計方法
1. Lambda用のIAMロール作成
まず、Lambda関数がVPCに接続するために必要な権限を持つIAMロールを作成します。AWS管理ポリシーである AWSLambdaVPCAccessExecutionRole をアタッチしたロールを作成してください。このポリシーには、EC2のネットワークインターフェース(ENI)を作成、取得、削除する権限が含まれています。
2. セキュリティグループの設定
安全な通信経路を確立するため、Lambda用とRDS用にそれぞれセキュリティグループを作成し、以下のように関連付けます。
- Lambda用セキュリティグループ(例: sg-lambda): インバウンドルールは不要です。アウトバウンドルールは「すべてのトラフィック」を許可、またはRDSへの送信ポート(5432)のみを許可します。
- RDS用セキュリティグループ(例: sg-rds): インバウンドルールに、送信元として「sg-lambda(Lambda用のセキュリティグループ)」を指定し、ポート「5432」のアクセスを許可します。IPアドレスではなくセキュリティグループIDを指定するのがベストプラクティスです。
3. 外部パッケージ(psycopg2)の準備
今回はPythonを使用してPostgreSQLに接続します。PythonでPostgreSQLを扱うための標準外ライブラリ psycopg2 を使用しますが、Lambdaの標準ランタイムには含まれていません。そのため、Lambdaレイヤー(Layer)として事前にパッケージングして登録する必要があります。
以下のコマンドを使用し、Amazon Linux 2互換の環境で動作する psycopg2-binary をローカル環境(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関数にアタッチしてください。
4. Lambda関数の作成とVPC設定
Lambda関数の新規作成画面、または作成後の「設定」タブから「VPC」セクションに移動し、以下の設定を行います。
- VPC: RDSが所属しているVPCを選択します。
- サブネット: 冗長性を確保するため、異なるAZのプライベートサブネットを少なくとも2つ以上選択します。
- セキュリティグループ: 手順2で作成したLambda用のセキュリティグループ(sg-lambda)を選択します。
5. 接続テスト用のPythonコード
準備が整ったら、以下のサンプルコードをLambda関数にデプロイします。データベースの接続情報は、Lambdaの「環境変数」に事前に登録しておいてください。
import os
import psycopg2
def lambda_handler(event, context):
# 環境変数から接続情報を取得
db_host = os.environ['DB_HOST']
db_name = os.environ['DB_NAME']
db_user = os.environ['DB_USER']
db_password = os.environ['DB_PASSWORD']
try:
# データベースへの接続
conn = psycopg2.connect(
host=db_host,
database=db_name,
user=db_user,
password=db_password,
connect_timeout=5
)
# クエリの実行テスト
with conn.cursor() as cur:
cur.execute("SELECT version();")
db_version = cur.fetchone()
print(f"接続成功! データベースバージョン: {db_version}")
conn.close()
return {
'statusCode': 200,
'body': 'Database connection successful!'
}
except Exception as e:
print(f"接続エラー: {e}")
return {
'statusCode': 500,
'body': 'Database connection failed.'
}
実務での注意点
コールドスタートと接続オーバーヘッド
Lambdaは実行リクエストに応じてコンテナが起動します(コールドスタート)。VPC Lambdaは過去に「起動が遅い」と言われていましたが、AWSの「Hyperplane ENI」技術の導入により大幅に改善されました。ただし、データベースへの新規コネクション確立処理は依然としてオーバーヘッドが発生します。接続インスタンス(conn)を関数のハンドラー外(グローバルスコープ)で定義することで、コンテナ再利用時にコネクションを使い回す設計にすることをおすすめします。
RDSのコネクション制限とRDS Proxyの導入検討
Lambdaは負荷に応じて即座にスケールし、多数の関数が並行して実行されます。しかし、RDS(特に小規模クラスのインスタンス)には最大同時接続数の制限があります。Lambdaが急激にスケールすると、一瞬でRDSの接続上限に達し、新規接続エラー(Too many connections)が発生します。
実務のプロダクション環境では、この問題を回避するためにAmazon RDS ProxyをLambdaとRDSの間に挟む設計を強く推奨します。RDS Proxyがデータベースの接続プールを管理し、Lambdaからの大量のコネクションを効率的に集約・再利用してくれます。
インターネットアクセスの確保
VPC内に配置されたLambda関数は、デフォルトのままだとインターネットに接続できません。外部のAPI(Slack通知や外部決済サービスなど)や、VPCエンドポイントが用意されていないAWSサービスと通信する必要がある場合は、プライベートサブネットからNATゲートウェイを経由するルートをVPCのルートテーブルに設定する必要があります。
まとめ
LambdaからVPC内のRDSへの接続は、セキュリティグループの相互関連付け、適切なIAMロールの付与、そしてライブラリの適切なパッケージングという基本を抑えれば、確実に構築できます。
実務ではさらに、同時実行数のスパイクに備えた「RDS Proxyの導入」や、外部通信用の「NATゲートウェイの設定」など、システム全体の要件に応じたインフラ設計が必要です。まずは本記事の手順に沿って、最小構成でのセキュアな接続を試してみてください。