Amazon Web Services ブログ

サーバーレス LAMP スタック – Part 2: リレーショナルデータベース

本投稿は AWS サーバーレス アプリケーションのシニアデベロッパーアドボケートである Benjamin Smith による寄稿です。

本シリーズの他のパートは以下のリンクからアクセスできます。また、関連するサンプルコードはこちらの GitHub リポジトリにあります。


この投稿では、サーバーレスアプリケーションで Amazon Aurora MySQLリレーショナルデータベースを使用する方法を学びます。Amazon RDS Proxy を使用してデータベースへの接続をプールおよび共有する方法と、構成を選択する方法を示します。この投稿のコード例は PHP で記述されており、この GitHubリポジトリにあります。なお、この概念自体は、AWS Lambda でサポートされている他のランタイム言語にも適用できますので、PHP に限定しない内容としてお読みいただけます。

サーバーレス LAMP スタック

このサーバーレス LAMP スタックアーキテクチャについては、この記事で説明しています。このアーキテクチャでは、PHP Lambda 関数を使用して、Amazon Aurora MySQL データベースの読み取りと書き込みを行います。

Amazon Aurora は、MySQL および PostgreSQL データベースに高いパフォーマンスと可用性を提供します。基盤となるストレージは、最大64 テビバイト(TiB)まで需要に応じて自動的に拡張されます。 Amazon Aurora DB インスタンスは、パブリックアクセスを防止するために仮想プライベートクラウド(VPC)内に作成されます。 Lambda 関数から Aurora データベースインスタンスに接続するには、同じ VPC にアクセスするようにその Lambda 関数を設定する必要があります。

RDS データベースに直接接続すると、データベースのメモリ不足が発生する可能性があります。これは、データベース接続の急増、または高速で開閉する多数の接続が原因です。これにより、クエリが遅くなり、アプリケーションのスケーラビリティが制限される可能性があります。この問題を解決するために、Amazon RDS Proxy が実装されました。 RDS Proxy は、Amazon RDSのフルマネージドデータベースプロキシ機能です。これは、アプリケーションとリレーショナルデータベースの間に位置するデータベース接続プールを確立し、このプールで接続を再利用します。これにより、毎回新しいデータベース接続を開くことによるメモリと CPU のオーバーヘッドが解消され、データベースがオーバーサブスクリプションから保護されます。データベース接続の認証情報は、AWS Secrets Manager に安全に保存されます。それは AWS Identity and Access Management(IAM)ロールを介してアクセスされます。これにより、DBインスタンス自体のコストのかかる移行作業なしで、データベースアプリケーションに強力な認証要件が適用されます。

以降の手順では、VPC 内で実行されている Amazon Aurora MySQL データベースに接続する方法を示します。データベース接続は PHP を実行する Lambda 関数から行われます。Lambda 関数は、RDS Proxy を介してデータベースに接続します。RDS Proxy が使用するデータベース認証情報は Secrets Manager に保持され、IAM 認証を介してアクセスされます。

IAM 認証とともに動く RDS Proxy

Getting Started

Amazon RDS Aurora MySQL データベースの作成

Aurora DB クラスターを作成する前に、VPC や RDS DB サブネットグループの作成などの前提条件を満たしている必要があります。これをセットアップする方法の詳細については、DBクラスターの前提条件を参照してください。

  1. create-db-cluster AWS CLIコマンドを呼び出して、Aurora MySQL DBクラスターを作成します。
    aws rds create-db-cluster \
    --db-cluster-identifier sample-cluster \
    --engine aurora-mysql \
    --engine-version 5.7.12 \
    --master-username admin \
    --master-user-password secret99 \
    --db-subnet-group-name default-vpc-6cc1cf0a \
    --vpc-security-group-ids sg-d7cf52a3 \
    --enable-iam-database-authentication true
  2. 新しい DB インスタンスをクラスターに追加します。
    aws rds create-db-instance \
        --db-instance-class db.r5.large \
        --db-instance-identifier sample-instance \
        --engine aurora-mysql  \
        --db-cluster-identifier sample-cluster
  3. データベースの認証情報をシークレットとしてAWS Secrets Manager に保存します。
    aws secretsmanager create-secret \
    --name MyTestDatabaseSecret \
    --description "My test database secret created with the CLI" \
    --secret-string '{"username":"admin","password":"secret99","engine":"mysql","host":"<REPLACE-WITH-YOUR-DB-WRITER-ENDPOINT>","port":"3306","dbClusterIdentifier":"<REPLACE-WITH-YOUR-DB-CLUSTER-NAME>"}'

    結果として生じる ARN を後のために書き留めます。

    {
        "VersionId": "eb518920-4970-419f-b1c2-1c0b52062117", 
        "Name": "MySampleDatabaseSecret", 
        "ARN": "arn:aws:secretsmanager:<<region>>:1234567890:secret:MySampleDatabaseSecret-JgEWv1"
    }

    このシークレットは、データベースへの接続プールを維持するためにRDS Proxy によって使用されます。シークレットにアクセスするには、RDS Proxy サービスにアクセス許可を明示的に付与する必要があります。

  4. シークレットへの secretsmanager アクセス許可を提供する IAMポリシーを作成します。
    aws iam create-policy \
    --policy-name my-rds-proxy-sample-policy \
    --policy-document '{
      "Version": "2012-10-17",
      "Statement": [
        {
          "Sid": "VisualEditor0",
          "Effect": "Allow",
          "Action": [
            "secretsmanager:GetResourcePolicy",
            "secretsmanager:GetSecretValue",
            "secretsmanager:DescribeSecret",
            "secretsmanager:ListSecretVersionIds"
          ],
          "Resource": [
            "<the-arn-of-the-secret>”
          ]
        },
        {
          "Sid": "VisualEditor1",
          "Effect": "Allow",
          "Action": [
            "secretsmanager:GetRandomPassword",
            "secretsmanager:ListSecrets"
          ],
          "Resource": "*"
        }
      ]
    }'

    作成されたポリシーの ARN をメモしておきます。これは、新しいロールにアタッチする必要があります。

    {
        "Policy": {
            "PolicyName": "my-rds-proxy-sample-policy", 
            "PermissionsBoundaryUsageCount": 0, 
            "CreateDate": "2020-06-04T12:21:25Z", 
            "AttachmentCount": 0, 
            "IsAttachable": true, 
            "PolicyId": "ANPA6JE2MLNK3Z4EFQ5KL", 
            "DefaultVersionId": "v1", 
            "Path": "/", 
            "Arn": "arn:aws:iam::1234567890112:policy/my-rds-proxy-sample-policy", 
            "UpdateDate": "2020-06-04T12:21:25Z"
         }
    }
  5. RDS Proxy サービスとの信頼関係を持つ IAM ロールを作成します。これにより、RDS Proxy サービスは、データベースの資格情報を取得するためにこの役割を引き受けることができます。
    aws iam create-role --role-name my-rds-proxy-sample-role --assume-role-policy-document '{
     "Version": "2012-10-17",
     "Statement": [
      {
       "Sid": "",
       "Effect": "Allow",
       "Principal": {
        "Service": "rds.amazonaws.com"
       },
       "Action": "sts:AssumeRole"
      }
     ]
    }'
  6. 新しいポリシーをロールにアタッチします。
    aws iam attach-role-policy \
    --role-name my-rds-proxy-sample-role \
    --policy-arn arn:aws:iam::123456789:policy/my-rds-proxy-sample-policy

RDS Proxy の作成

  1. AWS CLI を使用して、新しい RDS Proxy を作成します。–role-arnSecretArn の値を、前の手順で作成した値に置き換えます。
    aws rds create-db-proxy \
    --db-proxy-name sample-db-proxy \
    --engine-family MYSQL \
    --auth '{
            "AuthScheme": "SECRETS",
            "SecretArn": "arn:aws:secretsmanager:<<region>>:123456789:secret:exampleAuroraRDSsecret1-DyCOcC",
             "IAMAuth": "REQUIRED"
          }' \
    --role-arn arn:aws:iam::123456789:role/my-rds-proxy-sample-role \
    --vpc-subnet-ids  subnet-c07efb9a subnet-2bc08b63 subnet-a9007bcf

    RDS Proxy のユーザーに IAM 認証を適用するために、IAMAuth 値を REQUIRED に設定しています。これは、アプリケーションコードベースにデータベース資格情報を埋め込むより安全な方法です。
    Aurora DB クラスターとそれに関連するインスタンスは、Proxy に対するターゲットと呼ばれます。

  2. register-db-proxy-targets コマンドを使用して、データベースクラスタを Proxy に追加します。
    aws rds register-db-proxy-targets \
    --db-proxy-name sample-db-proxy \
    --db-cluster-identifiers sample-cluster

VPC 構成での PHP Lambda 関数のデプロイ

このGitHubリポジトリには、Lambdaレイヤーによって提供される PHP ランタイムと、それを利用する Lambda 関数が含まれています。この関数は、MySQLi PHP 拡張を使用して RDS Proxy に接続します。この拡張機能は、次のコマンドを使用して PHP実行可能ファイルとともにインストールおよびコンパイルされています。

PHP 実行可能ファイルは、Lambda ブートストラップファイルと一緒にパッケージ化され、PHP カスタムランタイムとして作成されます。PHP 用の独自のカスタムランタイムの構築に関する詳細情報は、この投稿を参照してください。

AWSサーバーレスアプリケーションモデル(AWS SAM)CLIを使用してアプリケーションスタックをデプロイします。

sam deploy -g

プロンプトが表示されたら、Aurora DBクラスターの SecurityGroupIdsSubnetIds を入力します。

SAMテンプレートは、VpcConfig サブリソースを使用して、SecurityGroupIds および SubnetIds パラメーターを Lambda 関数にアタッチします。


Lambdaは、VPC 構成された場合、セキュリティグループとサブネットの組み合わせごとにElastic Network Interface を作成します。関数は、そのVPCを介してのみリソースに(および設定によってはインターネットに)アクセスできます。

Lambda 関数への RDS Proxy 設定の追加

  1. Lambdaコンソールに移動します。
  2. デプロイした PHPHelloFunction を選択します。
  3. ページの下部にある [データベースプロキシの追加] を選択します。
  4. [既存のデータベースプロキシの選択] を選び、その後、sample-db-proxy を選択します。
  5. [追加] を押します。

Lambda 関数内からの RDS Proxy の使用

Lambda 関数では、AWS PHP SDK から3つのライブラリをインポートします。これらは、Secrets Manager に格納されているデータベース資格情報からパスワードトークンを生成するために使用されます。

AWS PHP SDK ライブラリは、PHP-example-vendor レイヤーによって提供されるように構成しています。この方法で Lambda レイヤーを使用することで、アプリケーションの進化に応じて追加のライブラリと依存ファイルを組み込むメカニズムが実現できます。
index という名前の関数のハンドラは、関数コードのエントリポイントです。この関数では、まず、 getenv()  を呼び、SAMアプリケーションのデプロイによって設定された環境変数を取得しています。これらはローカル変数として保存され、Lambda 関数の実行中に使用できます。

AuthTokenGenerator クラスは、IAM 認証で使用するための RDS 認証トークンを生成します。これは、資格情報プロバイダーを SDK クライアントのコンストラクタに渡すことによって初期化されます。次に、 createToken() メソッドが呼び出され、プロキシエンドポイント、ポート番号、リージョン、およびデータベースユーザー名がメソッドパラメータとして提供されます。結果の一時トークンは、プロキシへの接続に使用されます。

PHP mysqli クラスは、PHP と MySQL データベースの間の接続を表します。このクラスの  real_connect() メソッドは、RDS Proxy 経由でデータベースへの接続を開くために使用されます。最初のパラメーターとしてデータベースホストエンドポイントを提供する代わりに、プロキシエンドポイントが提供されます。データベースのユーザー名、一時的なトークン、データベース名、ポート番号も提供されます。定数  MYSQLI_CLIENT_SSL  は、接続でSSL暗号化が使用されるようにするために設定されます。

接続が確立されると、接続オブジェクトを使用できます。この例では、 SHOW TABLES クエリを実行しています。その後、接続が閉じられ、結果が JSON にエンコードされて Lambda 関数から返されます。

出力結果はこうなります:

RDS Proxy の監視とパフォーマンスの調整

RDS Proxy を使用すると、アプリケーションコードを変更せずに、接続制限とタイムアウト間隔を監視および調整できます。

接続の借用タイムアウトオプションを使用して、アプリケーションに最適なタイムアウト待機時間を制限できます。これは、タイムアウトエラーになる前に、接続が接続プールに戻される(使用可能になる)までの時間を指定します。

アイドル接続のタイムアウト間隔を調整すれば、アプリケーションが古くなったリソースを取り扱えるようにできます。これにより、アプリケーションが、重要なデータベースリソースを保持する接続を誤って開いたままにしないようにすることができます。

複数のアプリケーションで単一のデータベースにアクセスするケースでは、RDS Proxy を使用して、各アプリケーション間で接続割り当てを分割できます。最大プロキシ接続を  max_connections 構成のパーセンテージとして設定できます(MySQLの場合)。
次の例は、プロキシターゲットグループに対して  MaxConnectionsPercent の設定を変更する方法を示しています。

aws rds modify-db-proxy-target-group \
--db-proxy-name sample-db-proxy \
--target-group-name default \
--connection-pool-config '{"MaxConnectionsPercent": 75 }'

応答結果:

{
    "TargetGroups": [
        {
            "DBProxyName": "sample-db-proxy",
            "TargetGroupName": "default",
            "TargetGroupArn": "arn:aws:rds:<<region>>:####:target-group:prx-tg-03d7fe854604e0ed1",
            "IsDefault": true,
            "Status": "available",
            "ConnectionPoolConfig": {
                "MaxConnectionsPercent": 75,
                "MaxIdleConnectionsPercent": 50,
                "ConnectionBorrowTimeout": 120,
                "SessionPinningFilters": []
            },            
            "CreatedDate": "2020-06-04T16:14:35.858000+00:00",
            "UpdatedDate": "2020-06-09T09:08:50.889000+00:00"
        }
    ]
}

RDS Proxy は、再利用に適さないセッション状態の変化を検出して、セッションが終了するまで同じ接続でセッションを維持する場合があります。この動作はピン留めと呼ばれます。RDS Proxy のパフォーマンスチューニングでは、ピン留めを最小限に抑えて接続の再利用を最大化します。

Amazon CloudWatch のメトリック  DatabaseConnectionsCurrentlySessionPinned  では、ピン留めの発生頻度を監視することができます。

Amazon CloudWatch は、RDS Proxy から生データを収集して処理し、読み取り可能なほぼリアルタイムのメトリックにします。これらのメトリックを使用して、接続数と接続管理に関連するメモリを監視できます。これは、データベースインスタンスまたはクラスターが RDS Proxy を使用して利益を得ているかどうかを識別するのに役立ちます。たとえば、短時間の接続を多数処理している場合や、接続を高い頻度で開閉している場合などです。

まとめ

この投稿では、RDS Proxy を作成して構成し、PHP Lambda 関数から Aurora MySQL データベースへの接続を管理する方法を学びました。Secrets Manager と IAM 認証を使用して強力な認証要件を適用する方法を確認しました。Lambda レイヤーを使用して AWS PHP SDK を依存関係として保存する Lambda 関数をデプロイしました。

リレーショナルデータベースを使用して、安全でスケーラブルで高性能なサーバーレスアプリケーションを作成できます。これを行うには、データベースと Lambda 関数の間に RDS Proxy サービスを配置します。データベースを変更せずに既存の MySQL データベースを Aurora DB クラスターに移行することもできます。RDS Proxy と Lambda を使用すると、少ないコードでサーバーレス PHP アプリケーションをより速く構築できます。

サーバーレス LAMP スタックを使用した PHP の例をもっと探してみましょう。

原文はこちらです。