Lambda 関数を Ruby で作る

AWS Lambda の関数を Ruby でつくってみます。
最近、Railsの実装で進めるときにシステムのモノリス感がひどいなぁと感じたりで、サーバーレス関連が気になっていて、その一環です。

参考

作成

Lambda 関数の作成

  • Lambda の設定を開き「関数の作成」を押下

  • 「一から作成」を選択し、関数名とランタイムを設定
    任意の関数名と、ランタイムを選択します。

  • トリガーを追加する

  • トリガーを設定します
    API Gateway、HTTP API、セキュリティをオープンで設定しました。

  • APIエンドポイント を確認

  • 確認したエンドポイントにアクセス
    Hello from Lambda! の表示が確認きます。

Hello from Lambda! の呼び出しはどこから?

Hello from Lambda! の記載があるコードは、「関数コード」のエディタ部分で確認できます。
これは定義だけで、呼び出し部分は「ランタイム設定」のハンドラ部分に記載がありました。

関数コードを編集し、「Deploy」で反映できます。

アップロード 1

Lambda関数でAPIを作ることはできましたが、「関数コード」のエディターだけで作業を続けるのは、苦しいです。
ローカルで開発してアップロードできるようにします。

IAM ロールの作成

ローカルからアップロードを行うために、IAM ロールを作成します。

  • IAM の設定を開き、「ユーザーを追加」を押下

  • 設定 1
    任意のユーザー名と アクセスの種類に「プログラムによるアクセス」を設定します。

  • 設定 2
    アクセス許可設定で、AWSCodeDeployRoleForLambdaを設定します。

  • 設定 3
    「タグ追加」がありますが、今回は特に不要なのでスキップします。

  • 設定 4
    内容確認して進めます。

  • 設定 5
    アクセスキーと、シークレットアクセスキーを後で使うので控えておきます。

AWS CLI の導入

AWS CLI 各プラットフォームに即して導入します。

AWS CLI バージョン 2 のインストール、更新、アンインストール

今回は、Windows 環境に導入しました。

続けて AWS CLI に鍵情報を含めた設定をするため、以下の通り実行します。

1
2
3
4
5
$ aws configure
AWS Access Key ID [None]: **** # <= 控えておいたアクセスキー
AWS Secret Access Key [None]: **** # <= 控えておいたシークレットアクセスキー
Default region name [None]: ap-northeast-1 # <= とりあえず東京リージョンに
Default output format [None]: json # <= json text yaml table の中から選択

開発環境作成

開発環境の用意に当たり、今回もDockerで環境を用意しました。

Dockerfile
1
2
3
4
5
6
FROM amazon/aws-lambda-ruby

RUN yum update -y && \
yum install -y zip

EXPOSE 8000
docker-compose.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
version: "3"
services:
app:
build:
context: .
dockerfile: Dockerfile
privileged: true
command: /sbin/init
ports:
- "127.0.0.1:8000:8000"
volumes:
# :cached を付与して、高速化
- .:/var/task:cached
# vendor以下などホストと連携しなくてもよいものを、上書きして永続化の対象外にする。
# exclude volumes
- /var/task/vendor
- /var/task/tmp
- /var/task/log
- /var/task/.git
tty: true

volumes:
bundle:
driver: local

(port設定があるのは、作った関数をローカルでの呼び出せないか試みた名残です)

以下操作で使用します。

1
2
3
$ docker-compose build
$ docker-compose up -d
$ docker-compose exec app bash

lambda_function.rbを作成します。

lambda_function.rb
1
2
3
4
5
require 'json'

def lambda_handler(event:, context:)
{ statusCode: 200, body: JSON.generate('Hello from Lambda by local build!!') }
end

とりあえず、自動作成されたメソッドと同じもので比較用に出力文字列変えたものを用意します。
(意味合い上英文は合っていないやもしれない。)

コンテナ内で、以下を実行し、lambda_function.rbをlambda.zipに固めます。

1
$ zip lambda.zip lambda_function.rb

コンテナから出るか、別のコンソールで以下コマンドを実行しアップロードします。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ aws lambda update-function-code --function-name test_func --zip-file fileb://lambda.zip
{
"FunctionName": "test_func",
"FunctionArn": "arn:aws:lambda:ap-northeast-1:*************:function:test_func",
"Runtime": "ruby2.7",
"Role": "**********************************************************",
"Handler": "lambda_function.lambda_handler",
"CodeSize": 3427400,
"Description": "",
"Timeout": 3,
"MemorySize": 128,
"LastModified": "2021-02-11T09:32:55.333+0000",
"CodeSha256": "*************************************************",
"Version": "$LATEST",
"TracingConfig": {
"Mode": "PassThrough"
},
"RevisionId": "*******************************************",
"State": "Active",
"LastUpdateStatus": "Successful",
"PackageType": "Zip"
}

--function-nameの部分は関数の名前です。
fileb://の後ろにアップロードするファイル名を指定します。

エンドポイントにアクセスすると、表示内容が変更されています。

ローカルで作成したファイルをアップロードできました。

アップロード 2

現在の実行内容は標準のランタイムに含むパッケージしか使用していません。
外部のgemを導入してみます。

コンテナ内で、以下を実行します。

1
2
3
4
5
6
7
8
9
10
$ bundle init

# Gemfile に以下を追記
# gem 'uuid'
# gem 'dotenv'

bundle config set --local path 'vendor/bundle'
bundle install

zip -r lambda.zip .

.env を作成し以下を記載します。

.env
1
NAME=TEST-APP

lambda_function.rb を導入した、uuid dotenv を使用するように書き換えます。

lambda_function.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 標準パッケージ
require 'json'
require 'date'

# 外部パッケージ
require 'uuid'
require 'dotenv'

def lambda_handler(event:, context:)
Dotenv.load

res = {
env: ENV['NAME'],
uuid: UUID.new.generate,
time: DateTime.now
}

{ statusCode: 200, body: JSON.generate(res) }
end

できたら、以下コマンドで、zipに固めます。

1
$ zip -r lambda.zip .

.を指定して、vendor以下もまとめてzipに固めています。

再度コンテナから出るか、別のコンソールで以下コマンドを実行しアップロードします。

1
2
3
4
5
6
$ aws lambda update-function-code --function-name test_func --zip-file fileb://lambda.zip
{
"FunctionName": "test_func",
# ~~ 省略 ~~
"PackageType": "Zip"
}

エンドポイントにアクセスすると、以下のように表示されます。

外部のgemを利用した、Lambda関数が作成できました。

パラメーターを受け付ける

API にはパラメータを投げつけたくなるものです。
パラメーターを付与してみます。

API Gateway を REST API に変更

最初に作ったのは、HTTP APIでした。
こちらをREST APIに切り替えます。

  • 一度作成したAPIを削除

  • 「トリガーを追加」

  • REST APIを作成

  • エンドポイントを確認しておく

実装

lambda_function.rb をパラメータを受け付けるように書き換えます。

lambda_function.rb
1
2
3
4
5
6
7
8
9
10
11
require 'json'

def lambda_handler(event:, context:)

res = {
string_params: event["queryStringParameters"],
multi_params: event["multiValueQueryStringParameters"]
}

{ statusCode: 200, body: JSON.generate(res) }
end

event["queryStringParameters"] は、文字列のパラメータ。
event["multiValueQueryStringParameters"] は配列のパラメータを引き受けます。

確認

パラメータを渡すために、REST クライアントでアクセスします。

引き渡したパラメータを、レスポンスとして返してくるようになりました。
同じ名前でパラメータを渡すと、event["multiValueQueryStringParameters"] では上書きせず、配列として処理してくれました。

今回の実装ではやっていませんが、event には HTTP メソッドの情報も載ってくるのでそれらでのコントロールも可能でした。


今回は、AWS Lambda を触ってみました。
普段触る AWS の機能があまりにも限定的なので、権限回りなど四苦八苦でしたがどうにか使えました。

実は今回、Faker gem をAWS Lambdaで動作させようとしたものの、どうしても動作できず断念しています。
理由をつかめていないところは残念です。

今度は、AWS Lambda から 何かしらDBへの接続を試みてみたいところです。

ではでは。