Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

RubyでAWS Lambda関数を構築・テスト・デプロイする完全ガイド

ソフトウェア開発は決して簡単ではありませんが、それ以上に大変なのが保守運用です。保守にはソフトウェアのパッチ適用やサーバーの管理が含まれます。この記事では、その中でも特にサーバー管理と運用に焦点を当てて解説します。

従来、サーバーはオンプレミス環境に設置され、物理ハードウェアの購入と維持管理が必要でした。しかしクラウドコンピューティングの登場により、物理的なサーバーを所有する必要はなくなりました。2006年、AmazonがAWSを開始しEC2サービスを発表したことで、現代のクラウドコンピューティングの時代が幕を開けました。この種のサービスにより、物理サーバーの管理やハードウェアのアップグレードから解放されました。多くの問題は解決しましたが、依然としてサーバーのメンテナンスやリソース管理は利用者自身の責任でした。そして、その発展をさらに一歩進めたものが「サーバーレステクノロジー」です。

サーバーレステクノロジーとは?

サーバーレステクノロジーは、サーバーの管理やプロビジョニングといった作業をクラウドプロバイダーにオフロードできる仕組みです。本記事ではAWSを例に取り上げます。

「サーバーレス」という言葉は、サーバーが一切存在しないという意味ではありません。実際にはサーバーが存在しますが、その管理はすべてクラウドプロバイダーが担います。ある意味で、サーバーレステクノロジーの利用者から見れば、サーバーは「見えない」存在です。サーバーは直接目に見えず、その管理業務はクラウドプロバイダーによって自動化されています。サーバーレスと呼ばれる主な特徴は以下の通りです。

  • 運用管理不要 — サーバーへのパッチ適用や高可用性のための管理が不要
  • 必要に応じたスケーリング — 数人のユーザー対応から数百万人規模まで柔軟に拡張可能
  • 従量課金 — 利用した分だけコストが発生する料金体系

サーバーレステクノロジーは以下のように分類できます。

  • コンピュート(例:Lambda、Fargate)
  • ストレージ(例:S3)
  • データストア(例:DynamoDB、Aurora)
  • 統合サービス(例:API Gateway、SNS、SQS)
  • 分析(例:Kinesis、Athena)

なぜサーバーレステクノロジーを使うのか

コスト

従量課金制は、サーバーレステクノロジーの最大のメリットの一つです。トラフィック量が予測できない場合、利用パターンに応じてサーバーをスケールアップ/ダウンさせる必要がありますが、自己管理型のオートスケーリングでは難しく非効率になりがちです。AWS Lambdaのようなサーバーレスコンピューティングなら、アイドル時間に課金されることがないため、コスト削減に大きく貢献します。

開発者の生産性

サーバーレスコンピューティングはクラウドプロバイダーが提供するフルマネージドサービスなので、開発者はサーバーのプロビジョニングやサーバーアプリケーションの開発を行う必要がありません。サーバー管理を意識することなく、すぐにコーディングを始められます。サーバーへのパッチ適用やオートスケーリングの管理も不要になるため、これらの時間を節約でき、開発者の生産性向上につながります。

弾力性(エラスティシティ)

サーバーレスコンピューティングは非常に高い弾力性を持ち、使用状況に応じて自動的にスケールアップ/ダウンします。ユーザー数の急増にも容易に対応でき、これは大きな利点であり、開発者の時間を大幅に節約してくれます。

高可用性

コンピューティングがサーバーレスでクラウドプロバイダーによって管理されている場合、高いアップタイムが保証され、フェイルオーバーも自動的に処理されます。こうした問題への対処には専門的なスキルが必要ですが、サーバーレスアプローチなら、インフラ運用担当者と開発者の仕事を一人でこなせるようになります。

Rubyでサーバーレス機能を実装する方法

AWSによると、RubyはAWS上で最も広く使われている言語の一つです。Lambdaは2018年11月からRubyをサポートしています。ここでは、AWSが提供するサーバーレステクノロジーのみを使用して、RubyでWeb APIを構築していきます。

AWS上にサーバーレスインフラを作成するだけなら、AWSコンソールにログインして作成を始めるだけで済みます。しかし、私たちが目指すのは、テストが容易で災害復旧にも対応できる構成です。そこで、サーバーレス機能をコードとして記述します。そのためにAWSが提供しているのが「Serverless Application Model(SAM)」です。SAMはAWS上でサーバーレスアプリケーションを構築するためのフレームワークで、Lambda、データベース、APIを設計するためのYAMLベースの構文を提供します。AWS SAMアプリケーションはAWS SAM CLIを使って構築できます。CLIはこちらのリンクからダウンロードできます。

AWS SAM CLIはAWS CloudFormationをベースに構築されています。CloudFormationでIaC(Infrastructure as Code)を書いた経験があれば、非常に簡単に取り組めるでしょう。また、代替手段としてServerless Frameworkを使うこともできます。本記事ではAWS SAMを使用します。

SAM CLIを使う前に、以下の準備が整っていることを確認してください。

  • AWSプロファイルの設定
  • Dockerのインストール
  • SAM CLIのインストール

それでは、サーバーレスアプリケーションの開発を始めましょう。まず、DynamoDBやLambdaといったサーバーレスインフラをいくつか作成します。最初はデータベースから着手します。

DynamoDB

DynamoDBは、AWSが管理するサーバーレスのデータベースサービスです。サーバーレスであるため、セットアップが非常に迅速かつ簡単です。DynamoDBを作成するには、次のようにSAMテンプレートを定義します。

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Resources:
  UsersTable:
    Type: AWS::Serverless::SimpleTable
    Properties:
      PrimaryKey:
        Name: id
        Type: String
      TableName: users

SAM CLIと上記のテンプレートを使えば、基本的なDynamoDBテーブルを作成できます。まず、サーバーレスアプリのパッケージをビルドする必要があります。そのために、以下のコマンドを実行します。このコマンドはパッケージをビルドし、S3へプッシュします。コマンド実行前に、serverless-users-bucketという名前のS3バケットを作成しておいてください。

$ sam package --template-file sam.yaml \
              --output-template-file out.yaml \
              --s3-bucket serverless-users-bucket

これでS3が、サーバーレスアプリのテンプレートとコードのソースになります。詳細については、後ほどLambda関数を作成する際に説明します。

続いて、このテンプレートをデプロイしてDynamoDBを作成します。

$ sam deploy --template-file out.yaml \
             --stack-name serverless-users-app \
             --capabilities CAPABILITY_IAM

これでDynamoDBのセットアップが完了しました。次に、このテーブルを利用するLambdaを作成していきます。

Lambda

LambdaはAWSが提供するサーバーレスコンピューティングサービスです。実際のサーバー管理を行うことなく、必要に応じてコードを実行できます。Lambdaは非同期処理、REST API、定時実行ジョブなど、さまざまな用途に活用できます。必要なのはハンドラー関数を書いてAWS Lambdaにプッシュすることだけです。あとはLambdaがイベントに基づいてタスクの実行を担ってくれます。イベントはAPI Gateway、SQS、S3などさまざまなソースからトリガーでき、別のコードベースから呼び出すことも可能です。トリガーされると、Lambda関数はイベントとコンテキストのパラメータを受け取ります。これらのパラメータの中身はトリガーのソースによって異なります。手動またはプログラムからイベントをハンドラーに渡して、Lambda関数を起動することもできます。ハンドラーは2つの引数を受け取ります。

Event(イベント) — イベントは通常、トリガーのソースから渡されるキーバリュー形式のハッシュです。SQS、Kinesis、API Gatewayなどの各種ソースからトリガーされた場合は、これらの値が自動的に渡されます。手動でトリガーする場合は、ここにイベントを渡せます。イベントにはLambda関数ハンドラーへの入力データが含まれます。たとえばAPI Gatewayの場合、リクエストボディがこのイベントの中に格納されます。

Context(コンテキスト) — コンテキストはハンドラー関数の第2引数です。トリガーのソース、Lambda関数名、バージョン、リクエストIDなど、実行に関する具体的な詳細情報が含まれています。

ハンドラーの出力は、Lambda関数をトリガーしたサービスへ返されます。Lambda関数の出力とは、ハンドラー関数の戻り値のことです。

AWS LambdaはRubyを含む7つの言語でのコーディングをサポートしています。ここでは、AWS Ruby SDKを使ってDynamoDBに接続します。

コードを書く前に、SAMテンプレートでLambda用のインフラを作成しましょう。

AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: "Serverless users app"

Resources:
  CreateUserFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: users.create
      Runtime: ruby2.7
      Policies:
        - DynamoDBWritePolicy:
            TableName: !Ref UsersTable
      Environment:
        Variables:
          USERS_TABLE: !Ref UsersTable

ハンドラーには、実行する関数への参照を Handler: <ファイル名>.<メソッド名> の形式で記述します。

Lambdaにアタッチするポリシーは、使用するリソースに応じてサーバーレスポリシーテンプレートを参照してください。今回のLambda関数はDynamoDBに書き込むため、Policiesセクションで DynamoDBWritePolicy を使用しています。

また、指定されたデータベースへリクエストを送信できるよう、環境変数USERS_TABLEをLambda関数に渡しています。

以上がLambdaインフラに必要な設定です。次に、Lambda関数が実行する、DynamoDBにユーザーを作成するコードを書きましょう。

Gemfileにaws-recordを追加します。

# Gemfile
source 'https://rubygems.org' do
  gem 'aws-record', '~> 2'
end

入力データをDynamoDBに書き込むコードを追加します。

# users.rb
require 'aws-record'

class UsersTable
  include Aws::Record
  set_table_name ENV['USERS_TABLE']
  string_attr :id, hash_key: true
  string_attr :body
end

def create(event:,context:)
  body = event["body"]
  id = SecureRandom.uuid
  user = UsersTable.new(id: id, body: body)
  user.save!
  user.to_h
end

とてもシンプルですね。AWSはDynamoDBへアクセスするための aws-record gemを提供しており、Railsの activerecord によく似た使い勝手です。

次に、以下のコマンドで依存関係をインストールします。

注意:Lambdaで定義したものと同じバージョンのRubyを使用してください。この例では、マシンにRuby 2.7がインストールされている必要があります。

# install dependencies

$ bundle install
$ bundle install --deployment

変更内容をパッケージ化します。

$ sam package --template-file sam.yaml \
              --output-template-file out.yaml \
              --s3-bucket serverless-users-bucket

デプロイします。

sam deploy --template-file out.yaml \
             --stack-name serverless-users-app \
             --capabilities CAPABILITY_IAM

これで、入力データをデータベースに書き込むLambdaが稼働しました。HTTPリクエスト経由でアクセスできるよう、Lambdaの前にAPI Gatewayを追加できます。API Gatewayはレート制限や認証など、多くのAPI管理機能を提供しますが、利用量によってはコストが高くなる可能性があります。より安価な選択肢として、API管理機能を持たないHTTP APIのみを使う方法もあります。ユースケースに応じて最適な方を選びましょう。

AWS Lambdaにはいくつかの制限があります。変更可能なものと固定のものがあります。

  • メモリ — デフォルトでは実行時に128MB。64MB刻みで最大3,008MBまで増やせます。
  • タイムアウト — コード実行には時間制限があります。デフォルトは3秒で、最大900秒まで延長できます。
  • ストレージ — Lambdaは /tmp ディレクトリをストレージとして提供します。上限は512MBです。
  • リクエスト・レスポンスサイズ — 同期トリガーで最大6MB、非同期トリガーで最大256MB。
  • 環境変数 — 最大4KB。

Lambdaにはこうした制限があるため、制限内に収まるコードを書くことが望ましいです。収まらない場合は、あるLambdaが別のLambdaをトリガーするようにコードを分割できます。さらに、複数のLambda関数を順次実行するために、AWSが提供するStep Functionsを利用することもできます。

サーバーレスアプリケーションをローカルでテストする方法

サーバーレスアプリケーションでは、マネージドなサーバーレスサービスを提供するベンダーが必要です。つまり、アプリケーションのテストにおいてAWSに依存することになります。テストのために、AWSはいくつかのローカル実行オプションを提供しています。また、AWSサーバーレステクノロジーと互換性のあるオープンソースツールを使ってローカルでテストすることも可能です。

それでは、Lambda関数とDynamoDBをテストしてみましょう。そのために、これらをローカルで実行します。

まず、Dockerネットワークを作成します。このネットワークが、Lambda関数とDynamoDB間の通信を可能にします。

$ docker network create lambda-local --docker-network lambda-local

DynamoDB Localは、AWSが提供するDynamoDBのローカル版で、ローカルでのテストに利用できます。以下のDockerイメージを実行してDynamoDB Localを起動します。

$ docker run -p 8000:8000 --network lambda-local --name dynamodb amazon/dynamodb-local

user.rb ファイルに以下の行を追加します。これにより、LambdaがローカルのDynamoDBに接続されるようになります。

local_client = Aws::DynamoDB::Client.new(
  region: "local",
  endpoint: 'https://dynamodb:8000'
)
UsersTable.configure_client(client: local_client)

Lambdaへの入力データを含む input.json ファイルを追加します。

{
  "name": "Milap Neupane",
  "location": "Global"
}

Lambdaを実行する前に、ローカルのDynamoDBにテーブルを作成しておく必要があります。そのために、aws-recordが提供するマイグレーション機能を使います。migrate.rbファイルを作成し、以下のマイグレーションを追加しましょう。

require 'aws-record'
require './users.rb'

local_client = Aws::DynamoDB::Client.new(
  region: "local",
  endpoint: 'https://localhost:8000'
)
migration = Aws::Record::TableMigration.new(UsersTable, client: local_client)

migration.create!(
  provisioned_throughput: {
    read_capacity_units: 5,
    write_capacity_units: 5
  }
)
migration.wait_until_available

最後に、以下のコマンドでLambdaをローカルで実行します。

$ sam local invoke "CreateUserFunction" -t sam.yaml \
                                        -e input.json \
                                        --docker-network lambda-local

これで、DynamoDBテーブルにユーザーデータが作成されます。

なお、LocalStackのようなツールを使えば、AWSスタック全体をローカルで実行することもできます。

サーバーレスコンピューティングはいつ使うべきか

サーバーレスコンピューティングを採用するかどうかを判断する際には、そのメリットとデメリットの両方を把握しておく必要があります。以下の観点から、サーバーレスアプローチを使うべきタイミングを判断できます。

コスト

  • アイドル時間がありトラフィックが不安定なアプリケーションでは、コスト削減に役立つためLambdaが適しています。
  • 一方、トラフィック量が安定しているアプリケーションでは、AWS Lambdaのコストが割高になる可能性があります。

パフォーマンス

  • パフォーマンスが重要でないアプリケーションであれば、AWS Lambdaは良い選択肢です。
  • Lambdaにはコールドブート時間があり、コールドスタート時にレスポンスが遅くなることがあります。

バックグラウンド処理

  • バックグラウンド処理にはLambdaが適しています。Sidekiqなどのオープンソースツールには、サーバーのスケーリングや保守のオーバーヘッドが伴います。AWS LambdaとAWS SQSキューを組み合わせれば、サーバー保守の手間なくバックグラウンドジョブを処理できます。

並行処理

  • ご存知の通り、Rubyで並行処理を実現するのは容易ではありません。Lambdaを使えば、プログラミング言語側のサポートなしに並行処理を実現できます。Lambdaは同時に実行でき、パフォーマンス向上に役立ちます。

定期実行・単発スクリプトの実行

  • Rubyコードの実行にはcronジョブを使うことが多いですが、大規模アプリケーションではcronジョブのサーバー保守が難しくなることがあります。イベント駆動のLambdaを活用すれば、アプリケーションのスケーリングが容易になります。

以上が、サーバーレスアプリケーションにおけるLambda関数の代表的なユースケースです。すべてをサーバーレスで構築する必要はありません。上記のユースケースに応じてハイブリッドモデルを採用することもできます。それによりアプリケーションのスケーリングが容易になり、開発者の生産性も向上します。サーバーレステクノロジーは進化を続けており、今後ますます改善されていくでしょう。AWS FargateやGoogle Cloud Runなど、AWS Lambdaの制限を受けない他のサーバーレステクノロジーも存在します。

  1. Netlify Edge FunctionsとUpstashサーバーレスRedisで始めるエッジアプリ開発

    Netlifyは最近、Denoランタイム上でエッジロケーションにてコードを実行できる「Edge Functions」を発表しました。これにより、グローバルに低レイテンシでコードを実行することが可能になります。この記事では、Netlify Edge Functionsを使ったシンプルなアプリを構築し、データストアとしてUpstash Redisへアクセスする方法を紹介します。 Upstash RedisがNetlify Edge Functionsと相性抜群である理由は、以下の点にあります。 Upstash Redisには「Global」データベースタイプがあり、Redisレプリカが世界中に分

  2. AWS LambdaとサーバーレスRedisで構築するReact Nativeアプリ:リーダーボード機能の実装ガイド

    この記事では、React Native、Serverless Framework、Upstashを組み合わせて、リーダーボード(ランキング)の閲覧・更新ができるモバイルアプリケーションを開発する方法を解説します。 モバイルアプリの開発にはReact Nativeを使用し、バックエンドはServerless Frameworkで構築します。バックエンドは、AWS Lambda上で動作するPython関数で構成されています。 1. Upstash Redisを活用する 一般的なリーダーボードアプリでは、ユーザー情報と各ユーザーのスコアを保存する必要があります。これらのデータはスコア順にソートされる