S3にファイルが作成されたことを検知してLambda関数でファイルを読み込む( `aws-cli` で構築)

Amazon, AWS, Bash

はじめに

S3にファイルがSFTPアップロードされたことを検知してLambdaを起動する( aws-cli で構築)」で、S3にファイルがアップロードされたことをフック条件にしてLambdaファンクションを実行させてみました。

ただ、上記のエントリではLambdaファンクションがCloudWatchLogにログを出力させるまでの実装にとどまっていました。
フック条件となったS3のファイルを読み込むロジックは作成していません。

そこで、今回はフック条件となったS3内のファイル読み込みを試してみました。

ゴールとする構成

  1. ファイルがアップロードされる
  2. アップロードを検知してLambdaファンクションを呼び出し
  3. S3のファイルを読み込む
  4. CloudWatchLogにログ出力

必要なもの

  • aws コマンドが使えること
  • jq コマンドが使えること
  • npm コマンドが使えること

検証環境

$ uname -moi
x86_64 MacBookPro10,1 Darwin

$ bash -version
GNU bash, バージョン 5.0.3(1)-release (x86_64-apple-darwin18.2.0)

$ aws --version
aws-cli/1.16.120 Python/3.7.2 Darwin/18.2.0 botocore/1.12.110

$ npm --version
6.7.0

作業開始

以下のエントリから今回必要な部分のみピックアップして検証していきます。

設定パラメータの整理

今回は以下の3つのパラメータ設定が必要です。
以下のような名前で設定しておきます。コマンドラインに流し込み、変数を作成しておきます。

# S3のバケット名
$ export S3_BUCKET_NAME='g-trigger-and-read-bucket'
# Lambdaに設定するロール名
$ export IAM_ROLE_NAME_LAMBDA='g-trigger-and-read-lambda-role'
# Lambdaのファンクション名
$ export LAMBDA_FUNCTION_NAME='g-trigger-and-read-lambda-function'

「S3バケット」の作成

$ aws s3 mb s3://${S3_BUCKET_NAME}

「Lambdaファンクションに割り当てるIAMロール」の作成

# まずは初期ポリシーファイルを作成
$ cat <<'EOF' >"/tmp/${IAM_ROLE_NAME_LAMBDA}-policy.json"
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF

$ IAM_ROLE_ARN_LAMBDA=$(
  aws iam create-role \
    --role-name "${IAM_ROLE_NAME_LAMBDA}" \
    --assume-role-policy-document file:///tmp/${IAM_ROLE_NAME_LAMBDA}-policy.json | jq -r '.Role.Arn'
)

# 正常に作成できたか確認
$ echo "${IAM_ROLE_ARN_LAMBDA}"

作成されたLambdaファンクション用のロールにポリシー(権限)を追加で割り当てます。
今回はLambdaファンクションから「CloudWatchLog」、「S3」にアクセスするため、2つのポリシーを追加しています。

(ここでは「CloudWatchLog」、「S3」ともに フルアクセス 可能としていますが、この部分はできる限り限定した権限に抑えましょう。)

# CloudWatchLogのポリシー追加
$ aws iam attach-role-policy \
  --role-name ${IAM_ROLE_NAME_LAMBDA} \
  --policy-arn arn:aws:iam::aws:policy/CloudWatchLogsFullAccess

# S3のポリシー追加
$ aws iam attach-role-policy \
  --role-name ${IAM_ROLE_NAME_LAMBDA} \
  --policy-arn arn:aws:iam::aws:policy/AmazonS3FullAccess

「Lambdaファンクション」の作成

Lambdaファンクションを作成します。
LambdaからS3のファイルを読み込むためには、 Node.js で利用可能な AWS SDK を使います。

# 作業用フォルダの作成
$ mkdir lambda-sample

# 作業用フォルダへ移動
$ cd lambda-sample

$ npm install aws-sdk

次にプログラムコードを作成します。
好きなエディタで index.js というファイルを作成します。

$ vi index.js

以下のロジックを記述します。
event 変数から通知を上げたS3バケット名、S3ファイル名(キー名)が取得できます。
これを使ってファイルの中身を読み込み、 console.log を使って「CloudWatchLog」に出力しています。

const aws = require('aws-sdk')

exports.handler = function (event, context) {
  const s3 = new aws.S3()

  let source = event.Records[0]

  s3.getObject(
    {
      Bucket: source.s3.bucket.name,
      Key: source.s3.object.key,
    },
    (err, data) => {
      if (err) {
        console.log(err)
        callback(err)
      }

      console.log('===s3取得結果を表示===')
      console.log(`ファイル名 = ${source.s3.bucket.name}/${source.s3.object.key}`)
      console.log(data.Body.toString())
    },
  )
}

ファイルをアップロードします。
以下の2点がポイントです。

  • ファイルの権限としてオーナー以外も読めるようにしておく
  • npm でインストールしたライブラリも同梱しておく
chmod 644 index.js
chmod 755 $(find node_modules -type d)
chmod 644 $(find node_modules -type f)

# デプロイパッケージを作成
[[ -f function.zip ]] && rm -f function.zip
zip -r function.zip index.js node_modules

chmod 666 function.zip

LAMBDA_FUNCTION_ARN=$(
  aws lambda create-function \
    --function-name ${LAMBDA_FUNCTION_NAME} \
    --zip-file fileb://function.zip \
    --handler index.handler \
    --runtime nodejs8.10 \
    --role "${IAM_ROLE_ARN_LAMBDA}" | jq -r ".FunctionArn"
)

# 正常に作成できたか確認
echo "${LAMBDA_FUNCTION_ARN}"

「S3」に「Lambdaファンクション」を呼び出すためのイベント設定

最後に「S3」にファイルが作成されたときのイベント設定を行います。

aws lambda add-permission \
  --function-name "${LAMBDA_FUNCTION_NAME}" \
  --statement-id "s3-put-event" \
  --action "lambda:InvokeFunction" \
  --principal "s3.amazonaws.com" \
  --source-arn "arn:aws:s3:::${S3_BUCKET_NAME}"

cat <<EOF >/tmp/${S3_BUCKET_NAME}-event.json
{
  "LambdaFunctionConfigurations": [
    {
      "LambdaFunctionArn": "${LAMBDA_FUNCTION_ARN}",
      "Events": ["s3:ObjectCreated:*"]
    }
  ]
}
EOF

aws s3api put-bucket-notification-configuration \
  --bucket ${S3_BUCKET_NAME} \
  --notification-configuration file:///tmp/${S3_BUCKET_NAME}-event.json

テスト

テストをしてみます。

アップロード用のS3ファイルを作成し、S3にアップロードします。

# アップロード用ファイルを作成
$ echo 'HELLO AWS!' > sample.txt

# S3にファイルアップロード
$ aws s3 cp sample.txt s3://${S3_BUCKET_NAME}

「CloudWatchLog」のログページを確認すると、無事出力されていることがわかります。

ゴミ掃除

検証が終わったのでゴミ掃除。

$ aws lambda delete-function \
  --function-name ${LAMBDA_FUNCTION_NAME}

$ aws iam detach-role-policy \
  --role-name ${IAM_ROLE_NAME_LAMBDA} \
  --policy-arn arn:aws:iam::aws:policy/CloudWatchLogsFullAccess

$ aws iam detach-role-policy \
  --role-name ${IAM_ROLE_NAME_LAMBDA} \
  --policy-arn arn:aws:iam::aws:policy/AmazonS3FullAccess

$ aws iam delete-role \
  --role-name ${IAM_ROLE_NAME_LAMBDA}

$ aws s3 rb \
  --force s3://${S3_BUCKET_NAME}

ひとこと

手順になれてきたので、ここまで5分程度で設定できました。

Terraform というツールを教えてもらったのでこちらを勉強中。

Amazon, AWS, Bash