注目の記事

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

2018.11.21

大栗です。

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がデータストアとして選択肢に上ってきたと思います。