サブネットもセキュリティグループも考慮不要!Lambda関数からData API for Redshiftでクエリ実行してみた。

Lambda関数からRedshiftへDataAPIでクエリを投げてみました。 Dockerによる最新のboto3 LambdaLayer作成方法付き。
2020.10.30

こんにちは、平野です。

先日利用可能となりました、Data API for Redshift(以下DataAPI)、 みなさん使いこなしていらっしゃるでしょうか?

[アップデート] API で非同期な SQL クエリが実行できる!Amazon Redshift で Data API が利用可能になりました

今回Lambda関数からDataAPIを利用する機会があり、挑戦してみました。 ちょっとハマったポイントもありつつ、うまくクエリを流すことができたので流れをご紹介します。

検証用Redshift

まずは検証用にRedshiftを新しく立てました。 ノードタイプはdc2.largeです。一部のノードタイプではDataAPIに非対応なので、注意が必要です。

DataAPIでは、AWSのIAMによる認証でクラスタにクエリを投げられるというのがウリなので、 出来るだけ外からは接続できない場所にクラスタを立てます。 具体的には

  • Internet Gatewayなし
  • パブリックアクセスの許可なし
  • セキュリティグループはデフォルト設定
    • インバウンドは全てクローズ

という孤立した状況のクラスタを立ててみました。

Lambdaを準備

早速Lambda関数を準備していきましょう。 DataAPIはIAMで認証されるので、Redshiftと同じVPC内に作る必要はありません! VPCなしのLambda関数を作成します。

サンプルコード

Pythonでの実装コード例です。

DataAPIはクエリを非同期で投げるので、投げるだけ投げたら次の処理に進みます。 今回はサンプルなので同期的に終了を待つようなコードを書きましたが、 Lambdaは実行時間で課金されるので、 長くかかるような処理であれば結果を受け取る処理を別のLambda関数にするなどの方法も考えられるかと思います。

import json
import time
import boto3

# Redshift接続情報
CLUSTER_NAME='cluster_name'
DATABASE_NAME='sample_db'
DB_USER='dbuser'

# 実行するSQLを設定
sql = '''
    SELECT * FROM public.sample_table;
'''

def lambda_handler(event, context):
    # Redshiftにクエリを投げる。非同期なのですぐ返ってくる
    data_client = boto3.client('redshift-data')
    result = data_client.execute_statement(
        ClusterIdentifier=CLUSTER_NAME,
        Database=DATABASE_NAME,
        DbUser=DB_USER,
        Sql=sql,
    )

    # 実行IDを取得
    id = result['Id']
    #print('id = {}'.format(id))

    # クエリが終わるのを待つ
    statement = ''
    status = ''
    while status != 'FINISHED' and status != 'FAILED' and status != 'ABORTED':
        statement = data_client.describe_statement(Id=id)
        #print(statement)
        status = statement['Status']
        time.sleep(1)

    # 結果の表示
    if status == 'FINISHED':
        if int(statement['ResultSize']) > 0:
            # select文等なら戻り値を表示
            statement = data_client.get_statement_result(Id=id)
            print(json.dumps(statement['Records']))
        else:
            # 戻り値がないものはFINISHだけ出力して終わり
            print('QUERY FINSHED')
    elif status == 'FAILED':
        # 失敗時
        print('QUERY FAILED\n{}'.format(statement))
    elif status == 'ABORTED':
        # ユーザによる停止時
        print('QUERY ABORTED: The query run was stopped by the user.')

Redshiftに接続する際、ユーザのパスワードの入力がないという点も大きいですね。

Lambda関数のboto3ではDataAPIは実行できない!

さて、ロールの設定はまだだけど、とりあえず試しにポチッと行きたい所なのですが、 実はLambda関数からDataAPIを利用する際には大きな問題があります。 というのも、このブログ執筆時点(2020/10下旬)では、 Lambda関数のデフォルトのboto3はまだDataAPIには対応していません!

ということで、最新のboto3を使うために、一度LambdaLayerを作成する必要があります。

Dockerを使ってLambdaLayerを作成

最新のboto3のLambdaLayerを作成して行きます。 Dockerが使えればすぐに作成できるのでサクッと作ってしまいます。1 Dockerfileと、実行するシェルスクリプトを用意したので、 シェルスクリプトを実行すればLambdaLayerのzipファイルが手に入ります。

Dockerfile

FROM lambci/lambda-base

RUN rm -fr ./python
RUN curl -sSL https://bootstrap.pypa.io/get-pip.py -o get-pip.py && python get-pip.py
RUN pip install -t ./python boto3
RUN yum -y install zip
RUN zip -r boto3.zip ./python

make_boto3_layer.sh

#!/usr/bin/env bash
set -eu

docker build --no-cache -t create-latest-boto3-layer .
docker run --rm create-latest-boto3-layer cat ./boto3.zip > boto3.zip

実行例です。

$ bash make_boto3_layer.sh
$ ls -lh boto3.zip
-rw-r--r--  1 hirano.shigetoshi  staff   7.9M 10 30 11:29 boto3.zip
# => およそ8MBのzipファイルが作られる

boto3.zipファイルが出力されますので、これをLambdaLayerにアップロードし、 Lambda関数でそのLayerを使うように設定します。 これでLambdaのデフォルトのboto3ではなく、最新のboto3が利用できるようになります。

Lambda関数のIAMロール

さて、あと一息です。 Lambda関数からDataAPIを利用するにはLambda関数にアタッチされたロールの設定が必要です。 マネージドポリシーを見るとAmazonRedshiftDataFullAccessというのがありますので、これを設定してみます。

これでOKと思ったのですが、これだけではダメでした。 結論としてはAmazonRedshiftFullAccessDataがつかない)を設定することで接続できました。 Dataにアクセスする前に、前提としてまずRedshiftにアクセスできないとダメよ、ということなのでしょう。

なお、AmazonRedshiftFullAccessのポリシーを見てみると、 AmazonRedshiftDataFullAccessで付与される権限も全て付与されるので、 結局の所AmazonRedshiftFullAccessだけ設定すれば大丈夫なようです。

ようやく実行

以上で準備は完了です。 Lambda関数をテスト実行してみます!

成功しました! ちゃんとデータが取れていることが確認できました!!

まとめ

LambdaからDataAPIを使ってRedshiftでクエリを実行してみました。 従来はLambdaからRedshiftへクエリを実行したい場合、 Lambda関数はVPC内に設置しなければならない場合が多く、 そのLambdaから他のサービスに接続するような際にはVPCエンドポイントを準備しなければならないなど、 いくつか面倒なこともありました。 DataAPIによってLambdaとRedshiftの繋がりがより疎になり、シンプルな構成にすることができました!

AWS内のサービス連携なのでIAMが仲を取り持ってくれると、 各サービスの連携がシンプルになるので、今後もこういうアップデートは是非活用していきたいですね!

参考リンク


  1. 今回初めてDockerfileを書いたから、実はサクッとはできていないのはナイショだ