VPC LambdaからRDSに安全接続する設定とトラブル解決策

はじめに

AWSの実務において、プライベートサブネット内に配置されたRDS(データベース)に対して、AWS Lambdaから安全に接続してデータを操作するアーキテクチャは非常に一般的です。しかし、「LambdaからRDSに接続できずにタイムアウトしてしまう」「セキュリティグループの設定方法がわからない」といったトラブルに直面するエンジニアは少なくありません。

本記事では、初心者から中級者のインフラエンジニア向けに、VPC LambdaからRDS(MySQL)へ安全に接続するための具体的な設定手順、ベストプラクティス、およびよくあるトラブルの解決策をステップバイステップで解説します。

前提知識/必要な理由

デフォルト状態のLambda関数は、AWSが管理するパブリックなネットワーク上で実行されます。そのため、セキュリティ上の理由からプライベートサブネットに閉じ込められているRDSには直接アクセスできません。

LambdaからRDSに接続するためには、Lambda関数にVPCアクセス設定(VPC Lambda)を施し、Lambdaに仮想のネットワークインターフェース(ENI)を付与する必要があります。さらに、適切なセキュリティグループ(SG)を設定して、LambdaからRDSへの通信のみを許可する最小特権の原則を適用することが必須となります。

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

ここでは、VPC内のRDS(MySQL)に対して、Pythonで記述したVPC Lambdaから安全に接続する構成を構築します。

ステップ1:セキュリティグループの設計と作成

最も重要なネットワークセキュリティの設定です。2つのセキュリティグループを作成します。

  • Lambda用セキュリティグループ(sg-lambda):アウトバウンド(送信)はすべてのトラフィックを許可します。インバウンド(受信)は空にします。
  • RDS用セキュリティグループ(sg-rds):インバウンドルールに、sg-lambdaからのポート3306(MySQL)の接続のみを許可する設定を追加します。

ステップ2:Lambda用IAMロールの作成と権限付与

LambdaがVPC内でネットワークインターフェース(ENI)を作成・管理できるようにするため、Lambdaの実行ロールに以下の管理ポリシーをアタッチします。

  • AWSLambdaVPCAccessExecutionRole

また、データベースの接続情報を安全に管理するためにAWS Secrets Managerを使用する場合は、以下のポリシー(例)も追加でアタッチしてください。

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

ステップ3:Lambda関数のVPC設定

Lambda関数の設定タブから「VPC」を選択し、編集を行います。

  • VPC:RDSが所属しているVPCを選択します。
  • サブネット:高可用性を担保するため、複数のアベイラビリティゾーン(AZ)のプライベートサブネットを選択します。※パブリックサブネットは指定しないでください。
  • セキュリティグループ:ステップ1で作成した「sg-lambda」を選択します。

ステップ4:接続用Lambda関数の実装

今回はPythonを使用してRDS(MySQL)に接続するサンプルコードを実装します。外部ライブラリとして pymysql を使用します。

【補足】外部パッケージ(pymysql)の導入手順

Lambdaの標準環境には pymysql が含まれていないため、Lambdaレイヤーとしてアップロードする必要があります。ローカル環境(LinuxやmacOS、またはDocker)で以下のコマンドを実行し、ZIPファイルを作成してLambdaレイヤーに登録してください。

mkdir -p python
pip install pymysql -t python/
zip -r pymysql-layer.zip python/

作成した pymysql-layer.zip をAWSコンソールの「レイヤー」からアップロードし、対象のLambda関数に紐づけます。

Lambda関数のコード例

以下は、Secrets Managerから接続情報を取得し、RDSに接続するPythonコードのテンプレートです。

import json
import boto3
import pymysql

def get_secret():
    secret_name = "my-db-secret"
    region_name = "ap-northeast-1"
    client = boto3.client("secretsmanager", region_name=region_name)
    response = client.get_secret_value(SecretId=secret_name)
    return json.loads(response["SecretString"])

def lambda_handler(event, context):
    # Secrets Managerから認証情報を取得
    db_info = get_secret()
    
    try:
        # RDSへの接続試行
        connection = pymysql.connect(
            host=db_info["host"],
            user=db_info["username"],
            password=db_info["password"],
            database=db_info["dbName"],
            connect_timeout=5
        )
        print("SUCCESS: Connection to RDS succeeded")
        
        with connection.cursor() as cursor:
            cursor.execute("SELECT version();")
            result = cursor.fetchone()
            print(f"Database version: {result}")
            
        connection.close()
        return {"statusCode": 200, "body": "Connection Successful"}
        
    except Exception as e:
        print(f"ERROR: Connection failed due to {e}")
        return {"statusCode": 500, "body": "Connection Failed"}

実務での注意点

1. タイムアウト値の設計

Lambda関数のデフォルトのタイムアウト値は3秒です。VPC Lambdaはコールドスタート時にENIの作成やネットワーク解決に伴い、起動に数秒かかる場合があります。実務では、Lambdaのタイムアウト値を初期値の3秒から30秒〜1分程度に増やしておくことを強く推奨します。

2. サブネットのIPアドレス枯渇問題

VPC Lambdaは同時実行数に応じて自動的にスケールし、その都度プライベートサブネット内のIPアドレスを消費します。Lambda用に割り当てるサブネットは、/24(利用可能IPアドレス数:251)以上の十分な広さを持つサブネットを設計・確保してください。また、RDSと同じサブネットではなく、Lambda専用のプライベートサブネットを分けるのがベストプラクティスです。

3. インターネット通信が必要な場合の解決策

VPC設定を有効にしたLambda関数は、デフォルトでインターネットへのアクセス権を失います。Secrets Managerや外部API(Slack通知など)と通信したい場合、プライベートサブネットにNAT Gatewayを配置するか、該当サービスへのVPCエンドポイント(PrivateLink)を作成してください。

まとめ

VPC LambdaからRDSへの接続は、セキュリティグループの正しい関連付けと、適切なIAM権限の設定が成功の鍵となります。実務で接続エラーが発生した際は、まず「セキュリティグループのインバウンド・アウトバウンド設定」と「サブネットのルーティング(NAT GatewayやVPCエンドポイントの有無)」を疑いましょう。本記事の手順に沿って、セキュアで堅牢なサーバーレスアーキテクチャを構築してください。

上部へスクロール