AWS Lambda

公式チュートリアル「Amazon S3 で AWS Lambda を使用する」をオールCLIでやってみた

はじめに

どうも、SAAの試験は合格できたけど前日はドキドキで中々寝付けなかったSRE課の甫立です。

今回はAWSサーバレスアーキテクチャで王道となっているLambdaを使った公式のチュートリアル
「Amazon S3でAWS Lambdaを使用する」をハンズオンしてLambdamへの理解を深めていこうと思います。

ただ実施するだけでは面白くないのでコンソールを使わず、AWS CLIをだけを使って進めていきます。

公式チュートリアルのURLはこちら

チュートリアル概要

バケットにアップロードされる各画像ファイルのサムネイルを作成するとします。オブジェクトの作成時に Amazon S3 が呼び出すことができる Lambda 関数 (CreateThumbnail) を作成できます。その後、Lambda 関数はソースバケットから画像オブジェクトを読み取り、ターゲットバケットにサムネイル画像を作成できます。

このチュートリアルではS3にソースバケットとターゲットバケット、LambdaにはS3のイベントをトリガーとして実行する関数を作成します。

チュートリアルをやった後にはイベントをトリガーとするLambda関数はどのように自動で実行されるように設定できるかが学べる内容となっています。

lamda関数図

それでは、以下からハンズオンに移っていきます。

バケット作成と画像のアップロード

まず初めにS3のバケットを作り画像データをアップしていきます。

気をつけた方がいいのはS3のバケット名は一意じゃないといけないので日付を入れるなどして被らなそうな名前をつけましょう。

チュートリアルには設定とかは特に書いてないので、デフォルトの設定で作りました。

ハンズオン

ローカルから画像データをアップロード
sftp ec2-user@[ipアドレス] -i hodate.pem

>put ハンバーガー.jpg

ソースバケットの作成
aws s3 mb s3://lambda-test-20210309

S3へアップロード
aws s3 cp "ハンバーガー.jpg" s3://lambda-test-20210309

ターゲットバケットの作成
aws s3 mb s3://lambda-test-20210309-resized

確認
aws s3 ls

2021-03-09 02:30:48 lambda-test-20210309
2021-03-09 02:31:19 lambda-test-20210309-resized

aws s3 ls s3://lambda-test-20210309

2021-03-09 03:51:59    2893831 ハンバーガー.jpg

ソースバケットとターゲットバケットを作成した際のarnは今後のステップで何回か使うことになるのでメモしておきましょう。

ちなみに「lambda-test-20210309」の方にアップした画像

ハンバーガー

おいしかったハンバーガーの画像です。お腹空かせながらやっていきます笑

ポリシーの作成

IAMのコンソールからポリシーを作成します。
ここでの手順は以下の通りです。

  1. lambdaからS3への操作を許可するポリシーの元となるJSONファイルを作成
  2. 作ったファイルを元に「AWSLambdaS3Policy」を作成
  3. AssumeRoleの元となるJSONファイルを作成
    4.AssumeRoleを適用した空のロールを作成
  4. AWSLambdaS3Policyをロールにアタッチ

参考:AWS のサービスにアクセス許可を委任するロールの作成

チュートリアルのJSONテキストをそのまま使っていますが、Resourceは今回作成したリソースバケットとターゲットバケットのarnが一致している必要があるので、メモしたものと置き換えましょう。

ポイント

手順を見て、IAMのコンソール上ではポリシーを作成してそのままロール作成に移行して作成したポリシーをアタッチするだけなのに工程が少し多いなと思った方もいるかもしれません。

ここで注目していただきたいのは手順ではAssumeRoleという言葉が登場していることです。

聞き慣れない言葉かもしれませんが、ここでは権限を委譲する先のエンティティを指定するSTSのアクションの一つと覚えていただくと良いです。
ロールの引き受けというのがサービスに行われています。

手順では「create-role」コマンドの「--assume-role-policy-document」オプションにあたる部分ですね。CLIからロールを作成する場合はこのオプションは必須ですので、JSONを作ってからそれを元にロールを作っています。

AssumeRoleについて詳しく知りたい場合は以下で解説されています。

IAMロール徹底理解 〜 AssumeRoleの正体

ハンズオン

lambdaからS3への操作を許可するポリシーの元となるJSONファイルを作成
vim AWSLambdaS3Policy.json

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "logs:PutLogEvents",
                "logs:CreateLogGroup",
                "logs:CreateLogStream"
            ],
            "Resource": "arn:aws:logs:*:*:*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetObject"
            ],
            "Resource": "arn:aws:s3:::lambda-test-20210309/*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject"
            ],
            "Resource": "arn:aws:s3:::lambda-test-20210309-resized/*"
        }
    ]
}

作ったファイルを元に「AWSLambdaS3Policy」を作成(arnはメモしておく)
aws iam create-policy --policy-name AWSLambdaS3Policy --policy-document file://AWSLambdaS3Policy.json

{
    "Policy": {
        "PolicyName": "AWSLambdaS3Policy", 
        "PermissionsBoundaryUsageCount": 0, 
        "CreateDate": "2021-03-09T05:14:00Z", 
        "AttachmentCount": 0, 
        "IsAttachable": true, 
        "PolicyId": "ANPAQRDKMOOXWQSADML7T", 
        "DefaultVersionId": "v1", 
        "Path": "/", 
        "Arn": "arn:aws:iam::036730336175:policy/AWSLambdaS3Policy", 
    "UpdateDate": "2021-03-09T05:14:00Z"
    }
}

AssumeRoleの元となるJSONファイルを作成
vim lambda-assume-role.json

{
  "Version": "2012-10-17",
  "Statement": {
    "Effect": "Allow",
    "Principal": {"Service": "lambda.amazonaws.com"},
    "Action": "sts:AssumeRole"
  }
}

AssumeRoleを適用した空のロールを作成(ロールのarnは後で使うのでメモ)
aws iam create-role --role-name lambda-s3-role --assume-role-policy-document file://lambda-assume-role.json

{
    "Role": {
        "AssumeRolePolicyDocument": {
            "Version": "2012-10-17", 
            "Statement": {
                "Action": "sts:AssumeRole", 
                "Effect": "Allow", 
                "Principal": {
                    "Service": "lambda.amazonaws.com"
                }
            }
        }, 
        "RoleId": "AROAQRDKMOOXTK5OGTKSF", 
        "CreateDate": "2021-03-09T06:43:23Z", 
        "RoleName": "lambda-s3-role", 
        "Path": "/", 
        "Arn": "arn:aws:iam::036730336175:role/lambda-s3-role"
    }
}

AWSLambdaS3Policyをロールにアタッチ
aws iam attach-role-policy --role-name lambda-s3-role --policy-arn arn:aws:iam::036730336175:policy/AWSLambdaS3Policy

関数の作成

こちらは記述しているものからコピペで問題ありません。

vim index.js

// dependencies
const AWS = require('aws-sdk');
const util = require('util');
const sharp = require('sharp');

// get reference to S3 client
const s3 = new AWS.S3();

exports.handler = async (event, context, callback) => {

    // Read options from the event parameter.
    console.log("Reading options from event:\n", util.inspect(event, {depth: 5}));
    const srcBucket = event.Records[0].s3.bucket.name;
    // Object key may have spaces or unicode non-ASCII characters.
    const srcKey    = decodeURIComponent(event.Records[0].s3.object.key.replace(/\+/g, " "));
    const dstBucket = srcBucket + "-resized";
    const dstKey    = "resized-" + srcKey;

    // Infer the image type from the file suffix.
    const typeMatch = srcKey.match(/\.([^.]*)$/);
    if (!typeMatch) {
        console.log("Could not determine the image type.");
        return;
    }

    // Check that the image type is supported  
    const imageType = typeMatch[1].toLowerCase();
    if (imageType != "jpg" && imageType != "png") {
        console.log(Unsupported image type: ${imageType});
        return;
    }

    // Download the image from the S3 source bucket. 

    try {
        const params = {
            Bucket: srcBucket,
            Key: srcKey
        };
        var origimage = await s3.getObject(params).promise();

    } catch (error) {
        console.log(error);
        return;
    }  

    // set thumbnail width. Resize will set the height automatically to maintain aspect ratio.
    const width  = 200;

    // Use the Sharp module to resize the image and save in a buffer.
    try { 
        var buffer = await sharp(origimage.Body).resize(width).toBuffer();

    } catch (error) {
        console.log(error);
        return;
    } 

    // Upload the thumbnail image to the destination bucket
    try {
        const destparams = {
            Bucket: dstBucket,
            Key: dstKey,
            Body: buffer,
            ContentType: "image"
        };

        const putResult = await s3.putObject(destparams).promise(); 

    } catch (error) {
        console.log(error);
        return;
    } 

    console.log('Successfully resized ' + srcBucket + '/' + srcKey +
        ' and uploaded to ' + dstBucket + '/' + dstKey); 
};

デプロイパッケージの作成

以下の手順で実施します。

  1. Node.jsをインストール
  2. 「lambda-s3」ディレクトリを作成しindex.jsを移動
  3. sharpライブラリのインストール
  4. チュートリアル通りのファイル構造になっているか確認
  5. zipで依存関係のあるファイル群の圧縮を実行し、パッケージ化

ハンズオン

Node.jsをインストール
sudo yum install https://rpm.nodesource.com/pub_12.x/el/7/x86_64/nodesource-release-el7-1.noarch.rpm

sudo yum install nodejs

バージョンチェック
node --version
v12.21.0

npm --version
6.14.11

インストール方法の参考記事:
CentOS 7.5 に Node.js 12 系の最新版を yum インストールする

「lambda-s3」ディレクトリを作成しindex.jsを移動
mkdir lambda-s3
mv index.js lambda-s3/
cd lambda-s3/

sharpライブラリのインストール
npm install sharp

チュートリアル通りのディレクトリ構造になっているか確認
ll

合計 32
-rw-rw-r--  1 ec2-user ec2-user  2271  3月  9 06:57 index.js
drwxrwxr-x 71 ec2-user ec2-user  4096  3月  9 07:12 node_modules
-rw-rw-r--  1 ec2-user ec2-user 21949  3月  9 07:12 package-lock.json

zipで依存関係のあるファイル群の圧縮を実行し、パッケージ化
zip -r function.zip .

Lambda関数を作成する

手順は以下です。

  1. Lambda関数の作成
  2. Lambda関数の更新

Lambda関数の作成は「create-function」のコマンドで可能ですが、「--role」の後のarnは環境ごとに違うので先ほどロールの作成結果でメモしたarnに置き換えて実行する必要があります。

ハンズオン

Lambda関数の作成
aws lambda create-function --function-name CreateThumbnail \
--zip-file fileb://function.zip --handler index.handler --runtime nodejs12.x \
--timeout 10 --memory-size 1024 \
--role arn:aws:iam::036730336175:role/lambda-s3-role

{
    "FunctionName": "CreateThumbnail",
    "FunctionArn": "arn:aws:lambda:ap-northeast-1:036730336175:function:CreateThumbnail",
    "Runtime": "nodejs12.x",
    "Role": "arn:aws:iam::036730336175:role/lambda-s3-role",
    "Handler": "index.handler",
    "CodeSize": 13498537,
    "Description": "",
    "Timeout": 10,
    "MemorySize": 1024,
    "LastModified": "2021-03-09T07:32:46.558+0000",
    "CodeSha256": "QMq9uL49yo+PoyAPsPtoj4RfgRFdwYHhLxB66snVvv0=",
    "Version": "$LATEST",
    "TracingConfig": {
        "Mode": "PassThrough"
    },
    "RevisionId": "d9900dbd-88ac-4cfb-aef5-21befd991682",
    "State": "Active",
    "LastUpdateStatus": "Successful",
    "PackageType": "Zip"
}

lambda関数の更新
aws lambda update-function-configuration --function-name CreateThumbnail --timeout 30

Lambda関数をテストする

以下は手順です。

  1. inputファイルの作成
  2. invokeコマンドで実行
  3. 上手くいったか確認

チュートリアルの指示通り、sourcebucketはソースバケット名に、.jpgオブジェクトを最初にアップした画像データ名で修正してファイルを作成します。

この時点ではS3のイベントの通知設定をしていないので、関数にイベントをインプットして手動で関数をテストする必要があります。

ハンズオン

inputファイルの作成
vim inputFile.txt

{
  "Records":[
    {
      "eventVersion":"2.0",
      "eventSource":"aws:s3",
      "awsRegion":"us-west-2",
      "eventTime":"1970-01-01T00:00:00.000Z",
      "eventName":"ObjectCreated:Put",
      "userIdentity":{
        "principalId":"AIDAJDPLRKLG7UEXAMPLE"
      },
      "requestParameters":{
        "sourceIPAddress":"127.0.0.1"
      },
      "responseElements":{
        "x-amz-request-id":"C3D13FE58DE4C810",
        "x-amz-id-2":"FMyUVURIY8/IgAtTv8xRjskZQpcIZ9KG4V5Wp6S7S/JRWeUWerMUE5JgHvANOjpD"
      },
      "s3":{
        "s3SchemaVersion":"1.0",
        "configurationId":"testConfigRule",
        "bucket":{
          "name":"lambda-test-20210309",
          "ownerIdentity":{
            "principalId":"A3NL1KOZZKExample"
          },
          "arn":"arn:aws:s3:::lambda-test-20210309"
        },
        "object":{
          "key":"ハンバーガー.jpg",
          "size":1024,
          "eTag":"d41d8cd98f00b204e9800998ecf8427e",
          "versionId":"096fKKXTRTtl3on89fVO.nfljtsv6qko"
        }
      }
    }
  ]
}

invokeコマンドで実行
aws lambda invoke --function-name CreateThumbnail --invocation-type Event \
--cli-binary-format raw-in-base64-out --payload file://inputFile.txt outputfile.txt

{
    "StatusCode": 202
}

ステータスコードが202なら成功!

上手くいったか確認
aws s3 ls s3://lambda-test-20210309-resized/

2021-03-09 07:45:18       8330 resized-ハンバーガー.jpg

頭にresizedがついてサムネイル画像データが作成されていることが確認できたら、処理が完了した画像ファイルということになり、テストは完了します。

Amazon s3を設定してイベントを発行する

  1. アカウントIDチェック
  2. 関数にポリシー適用
  3. 通知設定用のJSONファイル作成
  4. S3に通知設定適用

チュートリアルにある通りlambdaの「add-permission」コマンドで関数ポリシーで関数にs3へのアクセス権限を付与することができます。

関数にS3への「lambda:InvokeFunction」の権限を付与しており、S3のイベントをトリガーに関数を呼び出すための権限を調整しているということになります。

ハンズオン

アカウントIDチェック
aws sts get-caller-identity

関数にポリシー適用(--source-accountの後は先ほどチェックしたアカウントIDに置き換える)
aws lambda add-permission --function-name CreateThumbnail --principal s3.amazonaws.com \
--statement-id s3invoke --action "lambda:InvokeFunction" \
--source-arn arn:aws:s3:::lambda-test-20210309 \
--source-account 036730336175

{
    "Statement": "{\"Sid\":\"s3invoke\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"s3.amazonaws.com\"},\"Action\":\"lambda:InvokeFunction\",\"Resource\":\"arn:aws:lambda:ap-northeast-1:036730336175:function:CreateThumbnail\",\"Condition\":{\"StringEquals\":{\"AWS:SourceAccount\":\"036730336175\"},\"ArnLike\":{\"AWS:SourceArn\":\"arn:aws:s3:::lambda-test-20210309\"}}}"
}

ポリシーチェック
aws lambda get-policy --function-name CreateThumbnail

上と出力結果は同じです。
見方としてはEffect\":\"Allowでその後にlambda:InvokeFunctionが続いていたらOKとなります。

通知設定用のJSONファイル作成(LambdaFunctionArnの部分は関数作成時にメモしたものと置き換え)

vim notification.json

{
    "LambdaFunctionConfigurations": [
        {
            "LambdaFunctionArn": "arn:aws:lambda:ap-northeast-1:036730336175:function:CreateThumbnail",
            "Events":[
                "s3:ObjectCreated:Put"
            ]
        }
    ]

}

S3に通知設定適用
aws s3api put-bucket-notification-configuration --bucket lambda-test-20210309 --notification-configuration file://notification.json

「put-bucket-notification-configuration」コマンドの参考はこちら

セットアップをテストする

  1. ソースバケットに新たに画像ファイルをアップ
  2. 関数が実行されたかをチェック

うまくいったか最終テストです。

新たな画像「ラーメン.jpg」をソースバケット「lambda-test-20210309」にアップしたらイベントが発行されてターゲットバケット「lambda-test-20210309-resized」にサムネイル用の画像のオブジェクトが生成されるかをテストします。

ちなみに新たにアップする画像はラーメンです。
ラーメンはとんこつこそ正義ですね。
ラーメン

ハンズオン

ソースバケットに新たに画像ファイルをアップ
aws s3 cp "ラーメン.jpg" s3://lambda-test-20210309

aws s3 ls s3://lambda-test-20210309/ラーメン.jpg

2021-03-09 14:31:24     273121 ラーメン.jpg

関数が実行されたかをチェック
aws s3 ls s3://lambda-test-20210309-resized/

2021-03-09 07:45:18       8330 resized-ハンバーガー.jpg
2021-03-09 14:31:27       7918 resized-ラーメン.jpg

ターゲットバケットに「resized-ラーメン.jpg」が作成されているのが確認できました。
無事成功です!

リソースのクリーンアップ

残しておくと料金が発生するものもあるので使い終わったリソースを削除していきます。

ハンズオン

  • Lambda関数の削除
    aws lambda delete-function --function-name CreateThumbnail

  • ロールからポリシーをデタッチ(arnは置き換え)
    aws iam detach-role-policy --role-name lambda-s3-role --policy-arn arn:aws:iam::036730336175:policy/AWSLambdaS3Policy

  • ポリシー削除(arnは置き換え)
    aws iam delete-policy --policy-arn arn:aws:iam::036730336175:policy/AWSLambdaS3Policy

  • ロール削除
    aws iam delete-role --role-name lambda-s3-role

  • オブジェクトの削除
    aws s3 rm s3://lambda-test-20210309/ハンバーガー.jpg
    delete: s3://lambda-test-20210309/ハンバーガー.jpg

    aws s3 rm s3://lambda-test-20210309/ラーメン.jpg
    delete: s3://lambda-test-20210309/ラーメン.jpg

    aws s3 rm s3://lambda-test-20210309-resized/resized-ハンバーガー.jpg
    delete: s3://lambda-test-20210309-resized/resized-ハンバーガー.jpg

    aws s3 rm s3://lambda-test-20210309-resized/resized-ラーメン.jpg
    delete: s3://lambda-test-20210309-resized/resized-ラーメン.jpg

  • バケット削除
    aws s3 rb s3://lambda-test-20210309
    remove_bucket: lambda-test-20210309

    aws s3 rb s3://lambda-test-20210309-resized
    remove_bucket: lambda-test-20210309-resized

最後に

公式チュートリアルをやってみての感想としては、サーバレスへの理解が触っていない時よりも進んだと感じます。

仕事柄普段はインフラ部分にのみ触れているので、プログラミングへの理解もっと深める必要がありますが、もっと便利に使えるようになりたいと感じました。

また、今回はオールCLIでハンズオンしましたが、思ったよりJSONファイルを作らないといけなかったりして個人で実施する分にはWEBUIよりも時間がかかる部分もありますが、複数回行う分には後から見てどんな操作をしたかが一目でわかるのがやはりCLIの強みだと感じました。

返信を残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA