AWS Day3: Lambda × Go

f:id:dombri:20200505210310j:plain

こんにちは。

AWS入門第三弾です。今回は投稿が遅くならないように着実に進めていく所存です・・・

今回の内容は以下です。


AWS Lambda の基本

そもそも Lambda って何ぞや、という話からさっとまとめていきます。

AWS Lambda とは

  • サーバ管理なくコードを実行できる
    • コードを準備すれば、高可用性・スケーリングなどはLambdaが担ってくれる
  • 課金が時間・呼び出し回数単位
  • AWSの他サービスをトリガにするようにも、他アプリ・Webなどから呼び出すようにもできる

いわゆるサーバレス!ワクワクしますね。


Lambdaが動くまで

  1. コードを書きアップロード or コードエディタを使って書く
  2. コードの呼び出し元を設定
  3. 呼び出された時に必要なだけのリソースを確保し実行

ほぅ・・・便利そうですが 3. の動きが気になります。その「呼び出し」に遅延は感じないのだろうか。今回はそこまでのコードを実行しないので確かめようがないけれど。


ユースケース

公式サイト(https://aws.amazon.com/jp/lambda/)に載ってます。

主にデータの加工・抽出などを行う処理やバックエンド処理をサーバレスで実行するものなどが挙げられています。


AWS CLI で Lambda 関数を作成

以下の手順を進めていきます。参考:https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/gettingstarted-awscli.html

まずはチュートリアル通りに、コーディング部分はサンプルを使って流れを理解していきます。

  1. 実行ロールの作成
  2. 関数を作成
  3. 関数によって出力されたものを確認
  4. 作成した関数のリストを取得
  5. Lambda関数を取得


AWS CLI が使用できるターミナルがあることとして進めていきます。


1. 実行ロールの作成

実行ロール AWSのリソースにアクセスするためのアクセス権。これを関数に付与します。


①trust-policy.json を作成

ロールの作成には、信頼ポリシー を定義するJSONファイルが必要になります。

※インラインで信頼ポリシーを指定することも可能ですが、ここでは「何を指定するか」を理解するためにファイルにします

・trust-policy.json

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
  • lamnda.amazonaws.com へのアクセス許可


②実行ロールを作成

以下のコマンドで①のポリシーファイルを読み込み、実行ロールを作成します。

❯ aws iam create-role --role-name lambda-ex --assume-role-policy-document file://trust-policy.json
{
    "Role": {
        "Path": "/",
        "RoleName": "lambda-ex",
        "RoleId": "AROAYHSN4WF**********",
        "Arn": "arn:aws:iam::566025302349:role/lambda-ex",
        "CreateDate": "2020-06-13T09:47:43+00:00",
        "AssumeRolePolicyDocument": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Effect": "Allow",
                    "Principal": {
                        "Service": "lambda.amazonaws.com"
                    },
                    "Action": "sts:AssumeRole"
                }
            ]
        }
    }
}

実行ロールが作成できました。


③アクセス許可を上記のロールに追加

②で作成したロールに対して、アクセス許可設定を行います。

❯ aws iam attach-role-policy --role-name lambda-ex --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
  • attach-policy-to-role コマンドで、ロールにアクセス許可を設定
  • AWSLambdaBasicExecutionRole を追加してる


ここまでで、Lambda関数がAWSリソースにアクセスする用ロールを作成しました。


2. 関数を作成

①関数を作成

Lambdaの主役となる関数を作成します。今回はサンプルコードで一旦進めます。

・index.js

exports.handler = async function(event, context) {
  console.log("ENVIRONMENT VARIABLES\n" + JSON.stringify(process.env, null, 2))
  console.log("EVENT\n" + JSON.stringify(event, null, 2))
  return context.logStreamName
}


②デプロイパッケージを作成

zipに固めます。

❯ zip function.zip index.js


③Lambda関数を作成

以下のコマンドで、arn:aws:iam:: hogehoge 部分を先に出力された アカウントID に置き換えます。

❯ aws lambda create-function --function-name my-function \
--zip-file fileb://function.zip --handler index.handler --runtime nodejs12.x \
--role arn:aws:iam::123456789012:role/lambda-ex

You must specify a region. You can also configure your region by running "aws configure".
  • create-function でLambda関数を作成


・・・うげ。リージョン指定しろって怒られた。すみません。

❯ aws configure
AWS Access Key ID [****************]: 
AWS Secret Access Key [****************]: 
Default region name [None]: ap-northeast-1
Default output format [None]: json


もう一度実行したら、以下の結果が返ってきました。

{
    "FunctionName": "my-function",
    "FunctionArn": "arn:aws:lambda:ap-northeast-1:123456789012:function:my-function",
    "Runtime": "nodejs12.x",
    "Role": "arn:aws:iam::123456789012:role/lambda-ex",
    "Handler": "index.handler",
    "CodeSize": 322,
    "Description": "",
    "Timeout": 3,
    "MemorySize": 128,
    "LastModified": "2020-06-13T10:17:17.924+0000",
    "CodeSha256": "M0TsZslRTH6XjlyiputXgVNun2KTcWrno9TeqPZsOhI=",
    "Version": "$LATEST",
    "TracingConfig": {
        "Mode": "PassThrough"
    },
    "RevisionId": "6a213101-7e86-4639-baf7-519a6ece909a",
    "State": "Active",
    "LastUpdateStatus": "Successful"
}


④ 関数を呼び出し、そのログを取得

❯ aws lambda invoke --function-name my-function out --log-type Tail
{
    "StatusCode": 200,
    "LogResult": "U1RBUlQgUmVxdWVzdElkOiBkZTc3YzI0OS04ZTdkLTRkNTA...",
    "ExecutedVersion": "$LATEST"
}
  • lambda invoke で関数を呼び出す
  • --log-type オプションでログを取得する
    • Base64 なので意味は不明


ログをデコードしてみます。

❯ aws lambda invoke --function-name my-function out --log-type Tail \
--query 'LogResult' --output text |  base64 -d

START RequestId: a1b23675-cec8-4123-841c-275a3552526f Version: $LATEST
2020-06-13T10:27:47.541Z    a1b23675-cec8-4123-841c-275a3552526f    INFO    ENVIRONMENT VARIABLES
{
  "AWS_LAMBDA_FUNCTION_VERSION": "$LATEST",
  "AWS_SESSION_TOKEN": "IQoJb3J...",
  :
  "AWS_LAMBDA_FUNCTION_NAME": "my-function",
  "PATH": "/var/lang/bin:/usr/local/bin:/usr/bin/:/bin:/opt/bin",
  "AWS_DEFAULT_REGION": "ap-northeast-1",
  "AWS_REGION": "ap-northeast-1",
  "_X_AMZN_TRACE_ID": "Root=1-5ee4aa23-bef1b3c06a9649a0dc2dd280;Parent=2360ae02038b73cc;Sampled=0"
}
2020-06-13T10:27:47.541Z    a1b23675-cec8-4123-841c-275a3552526f    INFO    EVENT
{}
END RequestId: a1b23675-cec8-4123-841c-275a3552526f
REPORT RequestId: a1b23675-cec8-4123-841c-275a3552526f  Duration: 49.00 ms  Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 65 MB
  • base64 ユーティリティでデコード
  • 関数の出力にログストリーム名を設定できる

いろいろ見れました。


3. 作成したLambda関数をリストアップ

❯ aws lambda list-functions --max-items 10
{
    "Functions": [
        {
            "FunctionName": "my-function",
            "FunctionArn": "arn:aws:lambda:ap-northeast-1:123456789012:function:my-function",
            "Runtime": "nodejs12.x",
            "Role": "arn:aws:iam::123456789012:role/lambda-ex",
            "Handler": "index.handler",
            "CodeSize": 322,
            "Description": "",
            "Timeout": 3,
            "MemorySize": 128,
            "LastModified": "2020-06-13T10:17:17.924+0000",
            "CodeSha256": "M0TsZslRTH6XjlyiputXgVNun2KTcWrno9TeqPZsOhI=",
            "Version": "$LATEST",
            "TracingConfig": {
                "Mode": "PassThrough"
            },
            "RevisionId": "6a213101-7e86-4639-baf7-519a6ece909a"
        }
    ]
}
  • list-functions コマンドでリスト
    • --max-items で表示件数を指定

当たり前ですが、先ほど登録した関数の情報が表示されました。


4. Lambda関数を取得

❯ aws lambda get-function --function-name my-function
{
    "Configuration": {
        "FunctionName": "my-function",
        "FunctionArn": "arn:aws:lambda:ap-northeast-1:123456789012:function:my-function",
        "Runtime": "nodejs12.x",
        "Role": "arn:aws:iam::123456789012:role/lambda-ex",
        "Handler": "index.handler",
        "CodeSize": 322,
        "Description": "",
        "Timeout": 3,
        "MemorySize": 128,
        "LastModified": "2020-06-13T10:17:17.924+0000",
        "CodeSha256": "M0TsZslRTH6XjlyiputXgVNun2KTcWrno9TeqPZsOhI=",
        "Version": "$LATEST",
        "TracingConfig": {
            "Mode": "PassThrough"
        },
        "RevisionId": "6a213101-7e86-4639-baf7-519a6ece909a",
        "State": "Active",
        "LastUpdateStatus": "Successful"
    },
    "Code": {
        "RepositoryType": "S3",
        "Location": "https://awslambda-ap-ne-1-tasks.s3.ap-northeast-1.amazonaws.com/snapshots/566025302349/my-function-a6bee001-..."
    }
}
  • get-function コマンドでLambda関数メタデータ・署名付きURLを取得(有効期間10分)
    • 関数のデプロイパッケージをDLする時に使用する情報


5. 関数を削除

❯ aws lambda delete-function --function-name my-function
  • delete-function コマンドでLambda関数を削除

いとも簡単に消え去りました。


Go でやってみる

はい、恒例の Go タイムです(ここまででちょっと疲れてる・・・)元気出してくぞ!

基本的には上記の手順と変わらないですが、以下の観点が追加で必要になります。

  • Lambda関数デプロイパッケージとの依存関係が成立しているGo実行ファイル(.zip)


①Go のプログラムを作成

今回はGo自体のプログラムの内容というよりかは、LambdaでGo実行するには何が必要か?だけを確かめていきます。

・main.go

package main

import (
        "fmt"
        "context"
        "github.com/aws/aws-lambda-go/lambda"
)

type MyEvent struct {
        Name string `json:"name"`
}

// Lambdaハンドラ署名と実際に実行されるコード
func HandleRequest(ctx context.Context, name MyEvent) (string, error) {
        return fmt.Sprintf("Hello %s!", name.Name ), nil
}

// 実処理のエントリポイント
func main() {
        lambda.Start(HandleRequest)
}
  • github.com/aws/aws-lambda-go/lambda ライブラリをインポート

参考:https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/golang-handler.html


②GoのLambdaライブラリを取得し、コンパイル

go get github.com/aws/aws-lambda-go/lambda
  • go get でGoのライブラリをDL


GOOS=linux go build main.go


③パッケージ化

zip function.zip main


④Lambda関数を作成

❯ aws lambda create-function --function-name my-function \
--zip-file fileb://function.zip --handler main --runtime go1.x \
--role arn:aws:iam::123456789012:role/lambda-ex
{
    "FunctionName": "my-function",
    "FunctionArn": "arn:aws:lambda:ap-northeast-1:123456789012:function:my-function",
    "Runtime": "go1.x",
    "Role": "arn:aws:iam::566025302349:role/lambda-ex",
    "Handler": "main",
    "CodeSize": 10196815,
    "Description": "",
    "Timeout": 3,
    "MemorySize": 128,
    "LastModified": "2020-06-13T11:47:17.915+0000",
    "CodeSha256": "lDb5drLqJYm6y8/NKPOA2nBPowOgOln9Pt0cF87eiyY=",
    "Version": "$LATEST",
    "TracingConfig": {
        "Mode": "PassThrough"
    },
    "RevisionId": "f76012d3-2ff2-4f30-9b94-650dd39cfa7b",
    "State": "Active",
    "LastUpdateStatus": "Successful"
}
  • ハンドラのパラメータ(ここでいうと main)は、ハンドラを含む実行可能ファイル名と一致する
  • --role は適宜書き換える


⑤Hello ○○ してみる

# JSON形式のInputをbase64でエンコードしておく
❯ echo '{"name":"don"}' | base64
eyJuYW1lIjoiZG9uIn0K

❯ aws lambda invoke --function-name my-function out --payload eyJuYW1lIjoiZG9uIn0K
{
    "StatusCode": 200,
    "ExecutedVersion": "$LATEST"
}

❯ cat out
"Hello don!"


無事Hello donできました(自分に挨拶)。


終わりに

コードさえ用意できれば、気軽にサーバレスな処理を追加していけるLambdaの良さを味わうことができました。

今回は手動で関数を呼び出しましたが、次回以降はAWSの他リソースの処理をトリガにして呼び出すようなものに挑戦しようと思います。