昨年末のAurora Serverless Data APIの登場で、Lambdaからの利用が簡単になりました。この記事ではServerless Framework、CloudFormationを用いて、Aurora Serverless Data APIとLambdaを使ったAPIを構築する方法を紹介します。
使ったコードはすべてGitHubに上げています。記事内ではわかりやすさのためコードを抜粋して記載しますのでコード全体を確認したい場合はそちらを参照してください。GitHubのリンクは記事の最後に貼っています。
環境
- python: 3.7
- boto3: 1.9.156
- serverless framework: 1.43.0
- serverless-python-requirements: 4.3.0
アーキテクチャ構成
全体のアーキテクチャ構成は次のようになります。API Gateway, Lambda, Aurora Serverless, Secret ManagerをすべてServerless Frameworkで構築します。 Secret ManagerはAurora Serverless Data APIの利用に必ず必要になります。
Aurora Serverless Data APIとは
Aurora Serverless Data APIとは、一言で言うと、ネットワークに関する設定や制約なしでAurora Serverlessへアクセスができる機能です。詳細はAWSの公式ドキュメントやクラスメソッドさんのブログを読んでいただくのが良いかと思います。
クラスメソッドさんのブログにかかれている通り、今までLambdaからRDSを使うのは一苦労だったのですが、Aurora Serverless Data APIによって敷居が大幅に下がりました。
この記事ではLambdaからAurora Serverless Data APIを使うアプリケーションとその環境を、実際にアプリケーションを作りながら、説明します。
Serverlessプロジェクト作成&CloudFormationで環境構築
それでは、アプリケーションを構築していきましょう。 まずServerlessプロジェクトを作成します。
sls create -t aws-python3 -n serverless-aurora-serverless-example
作成したら下記のようにCloudFormationを記載してデプロイします。 これでAurora ServerlessとSecret Managerの環境構築をすることができます。
serverless.yml
custom: default_stage: dev region: us-east-1 stage: ${opt:stage, self:custom.default_stage} db_id: ${self:service}-db-${self:custom.stage} provider: name: aws runtime: python3.7 stage: ${self:custom.stage} region: ${self:custom.region} resources: Resources: SecretAurora: Type: AWS::SecretsManager::Secret Properties: GenerateSecretString: SecretStringTemplate: '{"username": "admin"}' GenerateStringKey: 'password' PasswordLength: 16 ExcludeCharacters: '"@/\' Aurora: Type: AWS::RDS::DBCluster Properties: DBClusterParameterGroupName: !Ref DBClusterParameterGroup DBClusterIdentifier: ${self:custom.db_id} MasterUsername: !Join ['', ['{{resolve:secretsmanager:', !Ref SecretAurora, ':SecretString:username}}' ]] MasterUserPassword: !Join ['', ['{{resolve:secretsmanager:', !Ref SecretAurora, ':SecretString:password}}' ]] Engine: aurora EngineMode: serverless ScalingConfiguration: AutoPause: true MaxCapacity: 8 MinCapacity: 1 SecondsUntilAutoPause: 3600 DBClusterParameterGroup: Type: AWS::RDS::DBClusterParameterGroup Properties: Description: A parameter group for ${self:service}-db-${self:custom.stage} Family: aurora5.6 Parameters: character_set_client: utf8mb4 character_set_connection: utf8mb4 character_set_database: utf8mb4 character_set_results: utf8mb4 character_set_server: utf8mb4 time_zone: Asia/Tokyo
ここでポイントが2つあります。
1つ目は、Data APIは現時点(2019/05時点)でus-east-1しか対応していないのでリージョンは必ずus-east-1にしてください。
2つ目は、 AWS::RDS::DBClusterParameterGroup
です。ここで文字コードなどの設定を行うことができるので必要であれば設定しましょう。今回は文字コードをutf8にしています。
後はデプロイするだけです。
sls deploy
デプロイが完了したらコンソールからAurora ServerlessとSecret Managerが作成されていることを確認できます。
Data APIを有効にする
環境構築で1つだけ手作業が必要な箇所があります。デフォルトで無効になっているData APIを有効にするところです。Data APIはまだBeta版なので、CloudFormationで有効にすることができません。
下記の手順でコンソールからData APIを有効にしてください。
データベースを選択して「変更」をクリック。
「ネットワーク&セキュリティ」でData APIを有効にする。
設定を保存して変更が反映されるとData APIが使えるようになります。
Lambda実装
それではアプリケーション側(Lambda)の実装に移ります。
最新版のboto3を使う
まず、残念なことにLambdaからデフォルトで使えるboto3のVersionではData APIを使うことができません。 serverless-python-requirementsを使って最新版のboto3をデプロイしましょう。
sls plugin install -n serverless-python-requirements
serverless.yml
plugins: - serverless-python-requirements custom: pythonRequirements: usePipenv: true noDeploy: [pytest, jmespath, docutils, pip, python-dateutil, setuptools, s3transfer, six]
Pipfile
[[source]] url = "https://pypi.org/simple" verify_ssl = true name = "pypi" [packages] "boto3" = "*"
sls deploy
これで準備完了です。
Data APIを使ったAurora Serverlessへのアクセス
Aurora ServerlessへのアクセスはData APIを使って次のように行うことができます。
hander.py
def execute_sql(sql, db_exists=True): client = boto3.client('rds-data', region_name='us-east-1') args = { "awsSecretStoreArn": SECRET_ARN, "dbClusterOrInstanceArn": DB_CLST_ARN, "sqlStatements": sql } if db_exists: args['database'] = DB_NAME response = client.execute_sql(**args) return response
ここで、 SECRET_ARN
, DB_CLST_ARN
は環境変数でServerlessを使って下記のように渡しています。このようにCloudFormationで作成した環境のプロパティをアプリケーション側に簡単に渡せるのがServerlessの非常に便利なところです。
serverless.yml
custom: region: us-east-1 db_cluster_arn: !Join - "" - - "arn:aws:rds:${self:custom.region}:" - !Ref AWS::AccountId - ":cluster:" - !Ref Aurora provider: environment: DB_CLST_ARN: ${self:custom.db_cluster_arn} SECRET_ARN: !Ref SecretAurora TEST: !Ref AWS::AccountId
データベース準備
APIで使うデータベースを準備していきましょう。 今回はデータベース・テーブルの作成・ダミーデータの作成をするSQLとそれを呼び出す関数を書きました。
handler.py
DB_NAME = "test_db" TABLE_NAME = "test_table" create_db_sql = f""" CREATE DATABASE IF NOT EXISTS `{DB_NAME}`; """ create_table_sql = f""" CREATE TABLE IF NOT EXISTS `{TABLE_NAME}` ( `id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, `content` TEXT DEFAULT NULL, `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` TIMESTAMP NOT null DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); """ insert_sql = """ INSERT INTO `{table_name}` (`content`) VALUES ( "{content}" ); """ def setup(event, content): execute_sql(create_db_sql, db_exists=False) execute_sql(create_table_sql) for i in range(20): sql = insert_sql.format(table_name=TABLE_NAME, content=i) execute_sql(sql) return { "statusCode": 200, "body": "success" }
serverless.yml
functions: setup: handler: handler.setup
あとは実行するだけです。エラーにならなければデータベース・テーブルが作成されダミーデータが入っていると思います。
sls deploy
sls invoke -f setup
APIの作成
それでは今入れたダミーデータを返すAPIを作成します。ここまで来たら、あとはかなり簡単にできてしまいます。
handler.py
select_sql = f""" SELECT * FROM `{TABLE_NAME}` ; """ def index(event, context): response = execute_sql(select_sql) return { "statusCode": 200, "body": json.dumps(parse_aurora(response)) }
serverless.yml
functions: index: handler: handler.index events: - http: path: index method: get cors: true
sls deploy # (略) # endpoints: # GET - https://xxxxxxxxxxx.execute-api.us-east-1.amazonaws.com/dev/index # (略)
あとは今表示されたURLにアクセスするとAPIがデータが返ってくるのを確認できます。
curl https://xxxxxxxxxxx.execute-api.us-east-1.amazonaws.com/dev/index
# [{"id": 1, "content": "0", "created_at": "2019-05-25 09:44:42.0", "updated_at": "2019-05-25 09:44:42.0"}, ... (略)
無事APIを実装することができました。
現時点での問題点
Aurora Serverless Data APIとLambdaの組み合わせを実装してみて感じた問題点を書いておきます。
まず、セキュリティの問題が一番大きいと思います。現状では、SQLのprepared statementが未サポートでプレースホルダを使うことができません。また、複数クエリも同時実行できてしまいます。 これは、アプリケーション側でインジェクション対策を適切に行う必要があることを意味していて、とてもリスキーです。
もう一点はData APIのResponseのデータ構造が複雑で直感的でないことです。
今回、 parse_aurora(response)
というData APIのResponseをパースする関数を書いたのですが、正直構造がわかりやすいとは言い難く、苦労しました。
また、上記の問題点に加えてパフォーマンスの問題点などもあり、下記のエントリに詳しく書かれています。興味がある方は読んでみてください。
これらの課題はGAになるときには解決されていることを願っています。というか解決されないと本番運用は難しいケースが多いかもしれません😅
さいごに
Serverless Frameworkを活用して、Aurora Serverless Data APIとLambdaの組み合わせでAPIを作る方法を紹介しました。最後に書いたようにまだ問題点もありますが、Aurora Serverless Data APIはサーバーレスなアプリケーションを作る上で非常な便利な存在になると思います。今後のアップデートに心から期待しています。アップデートがあればまた記事を書きたいと思います。
今回使ったコードは下のリポジトリにすべてあげてあります。