注目の記事

Amazon Aurora ServerlessでHTTPSエンドポイントができ本当にサーバーレスアーキテクチャで利用可能になる!

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

大栗です。

Aurora Serverlessは発表時にServerlessアーキテクチャ用のRDBMSが出た!と勘違いされることが多くありました。今回Data APIというHTTPSのエンドポイントができました。これによりLambdaなどからの接続が行いやすくなります!

Data API for Aurora Serverless (Beta)

今までLambdaからAurora Serverlessへアクセスするためには、以下の2通りがありました。

  1. Auroraをパブリック接続可能にする
  2. LambdaをVPCアクセス可能にする

Auroraをパブリック接続可能にする場合は、Auroraがインターネットに面しているのでセキュリティに問題があります。通常のログインを禁止にしてIAM認証を有効化することも可能ですが、1秒あたり20接続しか行えないなどの制限があります。LambdaをVPCアクセス可能にする場合には、コンテナの初期起動時にENIを作成するため起動が遅くなったり、VPC内のIPアドレスが枯渇するという問題の可能性がありました。

またLambdaを使用するとコネクションプールが使用しづらいためRDBMSへの接続数が膨大になってしまう恐れがあります。

つまりLambdaとRDBMSは相性が悪いと言われていました。しかし、今回利用できる様になったData APIはそれらの問題点を払拭できる可能性があります。

VPC経由でLambdaからAurora Serverlessへアクセスする場合は以下のようにLambdaのコンテナごとにENIを作成していました。

Data APIを利用するとLambdaからData APIにアクセスするので、ENIを作成しません。そのためENIの作成時間やIPアドレスの枯渇が発生しません。Data APIのエンドポイントでコネクションのプーリングや流量制御をしてくれると助かるのですが、どの様な動作になるのか確認できていません。

このData APIはAppSyncからも利用可能になっています。

AuroraのIAM認証と何が違うの?

類似の機能としてIAM認証があるため比較してみました

Aurora Serverless Data API Aurora IAM認証
接続数 接続数制限の規定無し 1秒あたり20接続
トランザクション 無し 有り
コネクション リクエスト毎で分かれる 永続的
接続方法 HTTPS MySQLプロトコル
対象DB Aurora Serverless Aurora MySQL, Aurora PostgreSQL, MySQL, PostgreSQL
アクセス先 専用のパブリックなエンドポイントへアクセス DBに直接ログイン

※: 本表は現在確認できる情報から筆者が独自に作成しています。

注意

  • Aurora ServerlessのData APIは現在Beta版のため、変更される可能性があります。
  • Data APIではトランザクションはサポートされません。
  • レスポンスは最大1,000行で1MBのサイズ制限があります。
  • 現在利用可能なリージョンは米国東部 (バージニア北部)のみです。
  • コネクションは最大1分でタイムアウトします。
  • 認証情報はSecrets Managerを使ってDBに接続します。

やってみた

事前設定

以下の状態を前提とします。

  • リージョン: 米国東部 (バージニア北部)
  • Auroraエンジン: Aurora Serverless 5.6.10a

まず、Aurora Serverlessを起動します。Aurora Serverlessの起動方法は以下のエントリを御覧ください。

Aurora Serverlessが一般利用可能になったので試してみた

Aurora Serverlessの起動が完了したら、クラスタの変更を行いData APIを有効化します。

対象のクラスタを選択してクラスターの変更をクリックします。

ネットワーク & セキュリティWeb Service Data API - Betaという項目が増えているので、Data APIをチェックします。

ここではすぐに適用を選択してクラスターの変更を実行します。

これでData APIが有効になりました。

実行してみる

まず単純なselect now();というクエリをAWS CLIから実行してみます。AWS CLIではaws rds-data execute-sqlというコマンドを使用します。

$ aws rds-data execute-sql \
>   --aws-secret-store-arn arn:aws:secretsmanager:us-east-1:123456789012:secret:aurora/serverless/data-api-a1b2c3 \
>   --db-cluster-or-instance-arn arn:aws:rds:us-east-1:123456789012:cluster:serverless \
>   --sql-statements "select now();" \
>   --region us-east-1
{
    "sqlStatementResults": [
        {
            "numberOfRecordsUpdated": -1,
            "resultFrame": {
                "records": [
                    {
                        "values": [
                            {
                                "stringValue": "2018-11-21 04:37:56.0"
                            }
                        ]
                    }
                ],
                "resultSetMetadata": {
                    "columnCount": 1,
                    "columnMetadata": [
                        {
                            "isAutoIncrement": false,
                            "name": "now()",
                            "nullable": 0,
                            "isCurrency": false,
                            "precision": 19,
                            "arrayBaseColumnType": 0,
                            "label": "now()",
                            "typeName": "DATETIME",
                            "scale": 0,
                            "isCaseSensitive": false,
                            "isSigned": false,
                            "schemaName": "",
                            "type": 93
                        }
                    ]
                }
            }
        }
    ]
}

上記のようにデータは配列の配列としてレコードが返されます。

次にデータベースとテーブルを作成してみます。

まずデータベースmydbを作成します。

$ aws rds-data execute-sql \
>   --aws-secret-store-arn arn:aws:secretsmanager:us-east-1:123456789012:secret:aurora/serverless/data-api-a1b2c3 \
>   --db-cluster-or-instance-arn arn:aws:rds:us-east-1:123456789012:cluster:serverless \
>   --sql-statements "create database mydb" \
>   --region us-east-1
{
    "sqlStatementResults": [
        {
            "numberOfRecordsUpdated": 1
        }
    ]
}

次にテーブルsample_tableを作成します。

$ aws rds-data execute-sql \
>   --aws-secret-store-arn arn:aws:secretsmanager:us-east-1:123456789012:secret:aurora/serverless/data-api-a1b2c3 \
>   --database mydb \
>   --db-cluster-or-instance-arn arn:aws:rds:us-east-1:123456789012:cluster:serverless \
>   --sql-statements "create table sample_table (col1 int, col2 varchar(20), col3 datetime);" \
>   --region us-east-1
{
    "sqlStatementResults": [
        {
            "numberOfRecordsUpdated": 0
        }
    ]
}

1件データも挿入してみます。

$ aws rds-data execute-sql \
>   --aws-secret-store-arn arn:aws:secretsmanager:us-east-1:123456789012:secret:aurora/serverless/data-api-a1b2c3 \
>   --database mydb \
>   --db-cluster-or-instance-arn arn:aws:rds:us-east-1:123456789012:cluster:serverless \
>   --sql-statements "insert into sample_table (col1, col2, col3 ) VALUES (1, 'Aurora Serverless', now());" \
>   --region us-east-1
{
    "sqlStatementResults": [
        {
            "numberOfRecordsUpdated": 1
        }
    ]
}

ちゃんとデータも確認できます。

$ aws rds-data execute-sql \
>   --aws-secret-store-arn arn:aws:secretsmanager:us-east-1:123456789012:secret:aurora/serverless/data-api-a1b2c3 \
>   --database mydb \
>   --db-cluster-or-instance-arn arn:aws:rds:us-east-1:123456789012:cluster:serverless \
>   --sql-statements "select * from sample_table;" \
>   --region us-east-1
{
    "sqlStatementResults": [
        {
            "numberOfRecordsUpdated": -1,
            "resultFrame": {
                "records": [
                    {
                        "values": [
                            {
                                "intValue": 1
                            },
                            {
                                "stringValue": "Aurora Serverless"
                            },
                            {
                                "stringValue": "2018-11-21 05:26:57.0"
                            }
                        ]
                    }
                ],
                "resultSetMetadata": {
                    "columnCount": 3,
                    "columnMetadata": [
                        {
                            "isAutoIncrement": false,
                            "name": "col1",
                            "nullable": 1,
                            "tableName": "sample_table",
                            "isCurrency": false,
                            "precision": 11,
                            "arrayBaseColumnType": 0,
                            "label": "col1",
                            "typeName": "INT",
                            "scale": 0,
                            "isCaseSensitive": false,
                            "isSigned": true,
                            "schemaName": "",
                            "type": 4
                        },
                        {
                            "isAutoIncrement": false,
                            "name": "col2",
                            "nullable": 1,
                            "tableName": "sample_table",
                            "isCurrency": false,
                            "precision": 20,
                            "arrayBaseColumnType": 0,
                            "label": "col2",
                            "typeName": "VARCHAR",
                            "scale": 0,
                            "isCaseSensitive": false,
                            "isSigned": false,
                            "schemaName": "",
                            "type": 12
                        },
                        {
                            "isAutoIncrement": false,
                            "name": "col3",
                            "nullable": 1,
                            "tableName": "sample_table",
                            "isCurrency": false,
                            "precision": 19,
                            "arrayBaseColumnType": 0,
                            "label": "col3",
                            "typeName": "DATETIME",
                            "scale": 0,
                            "isCaseSensitive": false,
                            "isSigned": false,
                            "schemaName": "",
                            "type": 93
                        }
                    ]
                }
            }
        }
    ]
}

Lambdaからアクセスしてみる

以下の環境でLambdaを作成します。

  • リージョン: 米国東部 (バージニア北部)
  • ランタイム: Python 3.7
  • Lambda実行のIAM権限: AmazonRDSDataFullAccessポリシーを設定

以下のコードでLambda関数を作成します。Data APIを使用するにはboto3でRDSDataServiceを使用します。

import json
import pprint
import boto3

def lambda_handler(event, context):

    client = boto3.client('rds-data')
    response = client.execute_sql(
        awsSecretStoreArn='arn:aws:secretsmanager:us-east-1:123456789012:secret:aurora/serverless/data-api-hsbd0Q',
        database='mydb',
        dbClusterOrInstanceArn='arn:aws:rds:us-east-1:123456789012:cluster:serverless',
        sqlStatements='select * from sample_table;'
        )
    pprint.pprint(response)

    # TODO implement
    return {
        'statusCode': 200,
        'body': json.dumps('Hello Aurora Serverles Data API !')
    }

適当なテストイベントを設定してテスト実行してみます。すると以下のようなエラーが発生しました。2018年11月21日現在ではLambdaのSDKでrds-dataサービスにまだ対応していないようです。早くSDKのバージョンが上がりrds-dataに対応してほしいです。

{
  "errorMessage": "Unknown service: 'rds-data'. Valid service names are: acm, acm-pca, alexaforbusiness, apigateway, application-autoscaling, appstream, appsync, athena, autoscaling, autoscaling-plans, batch, budgets, ce, chime, cloud9, clouddirectory, cloudformation, cloudfront, cloudhsm, cloudhsmv2, cloudsearch, cloudsearchdomain, cloudtrail, cloudwatch, codebuild, codecommit, codedeploy, codepipeline, codestar, cognito-identity, cognito-idp, cognito-sync, comprehend, config, connect, cur, datapipeline, dax, devicefarm, directconnect, discovery, dlm, dms, ds, dynamodb, dynamodbstreams, ec2, ecr, ecs, efs, eks, elasticache, elasticbeanstalk, elastictranscoder, elb, elbv2, emr, es, events, firehose, fms, gamelift, glacier, glue, greengrass, guardduty, health, iam, importexport, inspector, iot, iot-data, iot-jobs-data, iot1click-devices, iot1click-projects, iotanalytics, kinesis, kinesis-video-archived-media, kinesis-video-media, kinesisanalytics, kinesisvideo, kms, lambda, lex-models, lex-runtime, lightsail, logs, machinelearning, macie, marketplace-entitlement, marketplacecommerceanalytics, mediaconvert, medialive, mediapackage, mediastore, mediastore-data, mediatailor, meteringmarketplace, mgh, mobile, mq, mturk, neptune, opsworks, opsworkscm, organizations, pi, pinpoint, pinpoint-email, polly, pricing, rds, redshift, rekognition, resource-groups, resourcegroupstaggingapi, route53, route53domains, s3, sagemaker, sagemaker-runtime, sdb, secretsmanager, serverlessrepo, servicecatalog, servicediscovery, ses, shield, signer, sms, snowball, sns, sqs, ssm, stepfunctions, storagegateway, sts, support, swf, transcribe, translate, waf, waf-regional, workdocs, workmail, workspaces, xray",
  "errorType": "UnknownServiceError",
  "stackTrace": [
    "  File \"/var/task/lambda_function.py\", line 7, in lambda_handler\n    client = boto3.client('rds-data')\n",
    "  File \"/var/runtime/boto3/__init__.py\", line 91, in client\n    return _get_default_session().client(*args, **kwargs)\n",
    "  File \"/var/runtime/boto3/session.py\", line 263, in client\n    aws_session_token=aws_session_token, config=config)\n",
    "  File \"/var/runtime/botocore/session.py\", line 809, in create_client\n    client_config=config, api_version=api_version)\n",
    "  File \"/var/runtime/botocore/client.py\", line 69, in create_client\n    service_model = self._load_service_model(service_name, api_version)\n",
    "  File \"/var/runtime/botocore/client.py\", line 104, in _load_service_model\n    api_version=api_version)\n",
    "  File \"/var/runtime/botocore/loaders.py\", line 132, in _wrapper\n    data = func(self, *args, **kwargs)\n",
    "  File \"/var/runtime/botocore/loaders.py\", line 378, in load_service_model\n    known_service_names=', '.join(sorted(known_services)))\n"
  ]
}

さいごに

今までLambdaからRDBMSにアクセスする構成は高負荷でない場面などに限られおり、。そのためサーバーレスアーキテクチャの中でデータストアに対して複雑なクエリを発行するのが難しく、アプリ作成の難易度が上がる場合がしばしばありました。今回のアップデートによりサーバーレスアーキテクチャでもRDBMSがデータストアとして選択肢に上ってきたと思います。