Amazon Web Services ブログ

新機能 – Lambda関数の共有ファイルシステム – Amazon Elastic File System for AWS Lambda

本投稿は AWS の Chief Evangelist (EMEA)であるDanilo Pocciaによる寄稿です。

AWS Lambda関数がAmazon Elastic File System(EFS)をマウントできるようになったことを非常に嬉しく思います。EFSは、高可用性と耐久性のために複数のアベイラビリティーゾーン(AZ)にまたがってデータを格納するスケーラブルでエラスティックなNFSファイルシステムです。このように、使い慣れたファイルシステムインターフェイスを使用して、関数単体、および複数のLambda関数のすべての同時実行環境にわたってデータを保存および共有できます。 EFSは、強力な整合性やファイルロックなどの完全なファイルシステムアクセスセマンティクスをサポートしています。

Lambda関数を使用してEFSファイルシステムを接続するには、EFSアクセスポイントを使用します。これは、ファイルシステムへのアクセス時に使用するオペレーティングシステムのユーザーとグループを含むEFSファイルシステムへのアプリケーション固有のエントリポイント、ファイルシステムのアクセス許可、およびファイルシステム内の特定のパスへのアクセスを制限できます。これにより、ファイルシステム構成をアプリケーションコードから切り離しておくことができます。

同一のアクセスポイント、または異なるアクセスポイントを使用して、複数の関数から同じEFSファイルシステムにアクセスできます。たとえば、異なるEFSアクセスポイントを使用して、各Lambda関数はファイルシステムの異なるパスにアクセスしたり、異なるファイルシステムのアクセス許可を使用したりできます。

同じEFSをAmazon Elastic Compute Cloud(EC2)インスタンス、Amazon ECSAWS Fargateを使用するコンテナ化されたアプリケーションや、オンプレミスサーバーと共有できます。このアプローチに従って、異なるコンピューティングアーキテクチャ(関数、コンテナ、仮想サーバー)を使用して同じファイルを処理できます。たとえば、イベントに反応するLambda関数は、コンテナで実行されているアプリケーションによって読み取られる構成ファイルを更新できます。または、Lambda関数を使用して、EC2で実行されているWebアプリケーションによってアップロードされたファイルを処理できます。

このようにすると、いくつかのユースケースではLambda関数によって実装がより容易になります。例えば:

  • /tmpで使用可能な容量(512MB)より大きいデータを処理またはロードする。
  • 頻繁に変更されるファイルの最新バージョンをロードする。
  • モデルやその他の依存関係をロードするためにストレージ容量を必要とするデータサイエンスパッケージを使用する。
  • 呼び出し間で関数の状態を保存する(一意のファイル名またはファイルシステムロックを使用)。
  • 大量の参照データへのアクセスを必要とするアプリケーションの構築。
  • レガシーアプリケーションをサーバーレスアーキテクチャに移行する。
  • ファイルシステムアクセス用に設計されたデータ集約型ワークロードとの相互作用。
  • ファイルを部分的に更新する(同時アクセス用のファイルシステムロックを使用)。
  • アトミック操作でファイルシステム内のディレクトリとそのすべてのコンテンツを移動する。

EFSの作成

EFSをマウントするには、Lambda関数がEFSマウントターゲットに到達できるAmazon Virtual Private Cloud(VPC)に接続されている必要があります。ここでは、簡単にするために、各AWSリージョンで自動的に作成されるデフォルトのVPC を使用しています。

Lambda関数をVPCに接続する構成にすると、ネットワーク環境の変化に伴う変更が必要になることがある点に注意してください。 Lambda関数がAmazon Simple Storage Service(S3)またはAmazon DynamoDBを使用している場合は、それらのサービスのゲートウェイVPCエンドポイントを作成する必要があります。 Lambda関数がパブリックインターネットにアクセスする必要がある場合、たとえば外部APIを呼び出す場合は、NATゲートウェイを構成する必要があります。通常、デフォルトVPCの構成は変更しません。特定の要件がある場合は、AWS Cloud Development Kitを使用してプライベートおよびパブリックサブネットで新しいVPCを作成するか、これらのAWS CloudFormationサンプルテンプレートのいずれかを使用します。このようにすることで、ネットワークをコードとして管理できます。

EFSコンソールで、[Create file system]を選択し、default のVPCとそのサブネットが選択されていることを確認します。すべてのサブネットで、同じセキュリティグループを使用してVPC内の他のリソースへのネットワークアクセスを提供するデフォルトのセキュリティグループを使用します。

次のステップでは、ファイルシステムにNameタグを付け、他のすべてのオプションをデフォルト値のままにします。

次に、[Add access point]を選択します。User IDとGroup IDに1001を使用し、/messageパスへのアクセスを制限します。最初にアクセスポイントに接続するときにフォルダーを自動的に作成するために使用される[Owner]セクションでは、前述と同じUser IDとGroup IDを使用し、アクセス許可(Permissions)には750を使用しています。この権限があれば、Ownerはファイルの読み取り、書き込み、実行を行うことができます。同じグループのユーザーは読み取りのみが可能です。他のユーザーはアクセスできません。

続けて、ファイルシステムの作成を完了します。

Lambda関数でのEFSの使用

簡単なユースケースから始めるために、テキストメッセージを追加、読み取り、または削除するためのMessageWall APIを実装するLambda関数を作成してみましょう。メッセージはEFS上のファイルに保存されるため、そのLambda関数のすべての同時実行環境は同じコンテンツを参照できます。

Lambdaコンソールで、新しいMessageWall関数を作成し、Python 3.8ランタイムを選択します。 [Permissions]セクションはデフォルトのままにします。これにより、基本的な権限を持つ新しいAWS Identity and Access Management(IAM)ロールが作成されます。

関数が作成されたら、[Permissions]タブでIAMロール名をクリックして、IAMコンソールでロールを開きます。ここでは、ポリシーのアタッチを選択して、AWSLambdaVPCAccessExecutionRoleおよびAmazonElasticFileSystemClientReadWriteAccess AWS管理ポリシーを追加します。本番環境では、特定のVPCおよびEFSアクセスポイントへのアクセスを制限できます。

Lambdaコンソールに戻り、EFSマウントポイントに使用したものと同じdefaultのセキュリティグループを使用して、VPC構成を編集し、MessageWall関数をデフォルトのVPC内のすべてのサブネットに接続します。

次に、関数構成内において新設の[File system]セクションで[Add file system]を選択します。ここでは、前に作成したEFSとアクセスポイントを選択します。ローカルマウントポイントには、/mnt/msgを設定し、[Save]で保存します。これは、アクセスポイントがマウントされるパスであり、EFSの/messageフォルダーに対応しています。

Lambdaコンソールの関数コードエディターで、次のコードを貼り付けて保存します。


import os
import fcntl

MSG_FILE_PATH = '/mnt/msg/content'


def get_messages():
    try:
        with open(MSG_FILE_PATH, 'r') as msg_file:
            fcntl.flock(msg_file, fcntl.LOCK_SH)
            messages = msg_file.read()
            fcntl.flock(msg_file, fcntl.LOCK_UN)
    except:
        messages = 'No message yet.'
    return messages


def add_message(new_message):
    with open(MSG_FILE_PATH, 'a') as msg_file:
        fcntl.flock(msg_file, fcntl.LOCK_EX)
        msg_file.write(new_message + "\n")
        fcntl.flock(msg_file, fcntl.LOCK_UN)


def delete_messages():
    try:
        os.remove(MSG_FILE_PATH)
    except:
        pass


def lambda_handler(event, context):
    method = event['requestContext']['http']['method']
    if method == 'GET':
        messages = get_messages()
    elif method == 'POST':
        new_message = event['body']
        add_message(new_message)
        messages = get_messages()
    elif method == 'DELETE':
        delete_messages()
        messages = 'Messages deleted.'
    else:
        messages = 'Method unsupported.'
    return messages

Add triggerを選択し、Trigger configurationでAmazon API Gatewayを選択します。そして新規にHTTP APIを作成します。簡単にするために、APIエンドポイントSecurityはOpenのままにします。

API Gatewayトリガーが選択された状態で、作成した新しいAPIのエンドポイントをコピーします。

これでcurlを使用してAPIをテストできるようになりました。


$ curl https://1a2b3c4d5e.execute-api.us-east-1.amazonaws.com/default/MessageWall
No message yet.
$ curl -X POST -H "Content-Type: text/plain" -d 'Hello from EFS!' https://1a2b3c4d5e.execute-api.us-east-1.amazonaws.com/default/MessageWall
Hello from EFS!

$ curl -X POST -H "Content-Type: text/plain" -d 'Hello again :)' https://1a2b3c4d5e.execute-api.us-east-1.amazonaws.com/default/MessageWall
Hello from EFS!
Hello again :)

$ curl https://1a2b3c4d5e.execute-api.us-east-1.amazonaws.com/default/MessageWall
Hello from EFS!
Hello again :)

$ curl -X DELETE https://1a2b3c4d5e.execute-api.us-east-1.amazonaws.com/default/MessageWall
Messages deleted.

$ curl https://1a2b3c4d5e.execute-api.us-east-1.amazonaws.com/default/MessageWall
No message yet.

ユーザーごとに一意のファイル名(または特定のサブディレクトリ)を追加し、このシンプルな例をより完全なメッセージングアプリケーションに拡張するのは比較的簡単です。開発者として、コードで使い慣れたファイルシステムインターフェイスを使用することのシンプルさに感謝します。ただし、要件によっては、EFSスループット構成を考慮する必要があります。詳細については、投稿の後半の「EFSパフォーマンスについて」のセクションを参照してください。

それでは、AWS Lambdaの新機能であるEFSサポートを使用して、より興味深いものを構築しましょう。たとえば、EFSで利用可能な追加のスペースを使用して、画像を処理する機械学習推論APIを構築してみましょう。

サーバーレス機械学習推論APIの構築

機械学習の推論を実装するLambda関数を作成するには、コード内で、必要なライブラリをインポートして機械学習モデルをロードできる必要があります。たいてい、そのようなことをすると、それらの依存関係の全体的なサイズは、デプロイメントパッケージサイズの現在のAWS Lambdaの制限を超えます。これを解決する1つの方法は、ライブラリを適切に最小化して関数コードとともにデプロイし、モデルをS3バケットからメモリ(モデルの処理に必要なメモリを含めて最大3 GB)または/tmp(最大512 MB)に配置します。このようなモデルのダウンロードとカスタムが必要な最小化は、実装するのが簡単ではありませんでした。しかし、これからはEFSを使用できます。

今回作成するLambda関数は、事前に学習済みモデルと推論を実行するための画像をダウンロードするためにパブリックなインターネットにアクセスする必要があります。そこで、パブリックサブネットとプライベートサブネットで新しいVPCを作成し、プライベートサブネットで使用されるNATゲートウェイとルートテーブルを構成して、パブリックインターネットへのアクセスを許可します。 AWS Cloud Development Kitを使用すると、ほんの数行のコードになります。

さきほどと同様の構成を使用して、新しいVPCに新しいEFSファイルシステムとアクセスポイントを作成します。今回は、アクセスポイントのパスに/mlを使用します。

次に、さきほど同じ権限を設定して新規にMLInference Lambda関数を作成し、その関数を新しいVPCのプライベートサブネットに接続します。機械学習の推論はかなり重いワークロードなので、メモリには3 GB、タイムアウトには5分を選択します。[File system]の構成で、新しいアクセスポイントを追加し、/mnt/inferenceの下にマウントします。

この関数に使用している機械学習フレームワークはPyTorch であり、推論を実行するために必要なライブラリをEFSに配置する必要があります。新しいVPCのパブリックサブネットでAmazon Linux EC2インスタンスを起動します。インスタンスの詳細で、EFSマウントポイントがあるアベイラビリティーゾーンの1つを選択してから、[Add file system ]を選択して、関数に使用しているのと同じEFSを自動的にマウントします。 EC2インスタンスのセキュリティグループには、defaultのセキュリティグループ(EFSをマウントできるようにする)とSSHへのインバウンドアクセスを与える(インスタンスに接続できるようにする)セキュリティグループを選択します。

SSHを使用してインスタンスに接続し、以下の必要な依存関係を含むrequirements.txtファイルを作成します。

torch
torchvision
numpy

EFSは、EC2によって/mnt/efs/fs1の下に自動的にマウントされます。そこで、/mlディレクトリを作成し、パスの所有者を、現在接続(ec2-user)しているユーザーとグループに変更します。


$ sudo mkdir /mnt/efs/fs1/ml
$ sudo chown ec2-user:ec2-user /mnt/efs/fs1/ml

Python3をインストールし、pipを使用して/mnt/efs/fs1/ml/libパスに依存関係をインストールします。


$ sudo yum install python3
$ pip3 install -t /mnt/efs/fs1/ml/lib -r requirements.txt

最後に、EFSアクセスポイントに使用したユーザーとグループに/mlパス全体の所有権を与えます。


$ sudo chown -R 1001:1001 /mnt/efs/fs1/ml

全体として、今回のEFSの依存関係は、約1.5 GBのストレージを使用しています。

MLInference Lambda関数構成に戻ります。使用するランタイムに応じて、依存関係が展開パッケージに含まれていないか、レイヤーに含まれていない場合に、依存関係を探す場所を指示する方法を見つける必要があります。 Pythonの場合、PYTHONPATH環境変数を/mnt/inference/libに設定しています。

PyTorch Hubを使用して、この事前学習済みの機械学習モデルをダウンロードし、画像内の鳥の種類を認識します。この例で使用しているモデルは比較的小さく、約200 MBです。 EFSファイルシステムでモデルをキャッシュするには、TORCH_HOME環境変数を/mnt/inference/modelに設定します。

すべての依存関係は関数によってマウントされたファイルシステムにあり、関数コードエディターでコードを直接入力できます。次のコードを貼り付けて、機械学習推論APIを作成します。


import urllib
import json
import os

import torch
from PIL import Image
from torchvision import transforms

transform_test = transforms.Compose([
    transforms.Resize((600, 600), Image.BILINEAR),
    transforms.CenterCrop((448, 448)),
    transforms.ToTensor(),
    transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225)),
])

model = torch.hub.load('nicolalandro/ntsnet-cub200', 'ntsnet', pretrained=True,
                       **{'topN': 6, 'device': 'cpu', 'num_classes': 200})
model.eval()


def lambda_handler(event, context):
    url = event['queryStringParameters']['url']

    img = Image.open(urllib.request.urlopen(url))
    scaled_img = transform_test(img)
    torch_images = scaled_img.unsqueeze(0)

    with torch.no_grad():
        top_n_coordinates, concat_out, raw_logits, concat_logits, part_logits, top_n_index, top_n_prob = model(torch_images)

        _, predict = torch.max(concat_logits, 1)
        pred_id = predict.item()
        bird_class = model.bird_classes[pred_id]
        print('bird_class:', bird_class)

    return json.dumps({
        "bird_class": bird_class,
    })

さきほどMessageWall関数に対して行ったのと同様に、API Gatewayをトリガーとして追加します。これで、作成したサーバーレスAPIを使用して鳥の写真を分析できます。私はこの分野の専門家ではないので、ウィキペディアで興味深い画像をいくつか探しました。

APIを呼び出して、次の2つの画像の予測を取得します。


$ curl https://1a2b3c4d5e.execute-api.us-east-1.amazonaws.com/default/MLInference?url=https://path/to/image/atlantic-puffin.jpg

{"bird_class": "106.Horned_Puffin"}

$ curl https://1a2b3c4d5e.execute-api.us-east-1.amazonaws.com/default/MLInference?url=https://path/to/image/western-grebe.jpg

{"bird_class": "053.Western_Grebe"}

正常に動作しました。 Lambda関数のAmazon CloudWatch Logsを見ると、関数がCPUでの推論のために事前学習済みモデルをロードして準備する最初の呼び出しに約30秒かかることがわかります。遅い応答やAPIゲートウェイからのタイムアウトを回避するために、Provisioned Concurrencyを使用して関数を準備します。次の呼び出しには約1.8秒かかります。

EFSパフォーマンスについて

Lambda関数でEFSを使用する場合、EFSのパフォーマンスがどのように動作するかを理解することが非常に重要です。スループットについては、各ファイルシステムをBursting Throughput modeまたはProvisioned Throughput modeを使用するように構成できます。

Bursting Throughput modeを使用する場合、すべてのEFSは、サイズに関係なく、少なくとも100 MiB/秒のスループットまでバーストできます。標準ストレージクラスの1 TiBを超えるものは、ファイルシステムに保存されているデータのTiBあたり100 MiB/秒にバーストできます。 EFSはクレジットシステムを使用して、ファイルシステムがバースト可能なタイミングを決定します。各ファイルシステムは、標準ストレージクラスに格納されているファイルシステムのサイズによって決定されるベースラインレートで、時間の経過とともにクレジットを獲得します。ファイルシステムは、データの読み取りまたは書き込みを行うたびにクレジットを使用します。ベースラインレートは、ストレージのGiBあたり50 KiB /秒です。

CloudWatchでクレジットの使用を監視できます。各EFSファイルシステムにはBurstCreditBalanceメトリックがあります。すべてのクレジットを消費していて、BurstCreditBalanceメトリックがゼロになる場合は、ファイルシステムのProvisioned Throughput modeを1〜1024 MiB/sで有効にする必要があります。Provisioned Throughput modeを使用する場合、ベースラインレートに加えて追加するスループットの量に基づいて、追加のコストが発生します。

クレジットが不足しないようにするには、スループットを1日の平均として考える必要があります。たとえば、10GBのファイルシステムがある場合、ベースラインレートは500 KiB/秒で、毎日 500 KiB /秒 * 3600秒 * 24時間= 43.2 GiB の読み取り/書き込みが可能です。

ライブラリと初期化中にロードする必要があるすべての関数が約2 GiBであり、上記のMLInference Lambda関数のように、関数の初期化中にのみEFSファイルシステムにアクセスする場合は、関数を1日あたり約20回ほど初期化できることを意味します(例:関数の更新、または関数のスケールアップ)。この回数は多くありません。そこで、EFSファイルシステムのProvisioned Throughput modeを構成することを検討して見てください。

10 MiB/秒のProvisioned Throughput がある場合、毎日10 MiB/秒 * 3600秒 * 24時間 = 864 GiBの読み取りまたは書き込みが可能です。関数の初期化時にのみEFSを使用して約2 GBの依存関係を読み取る場合、1日あたり400回の初期化が可能であることを意味します。ユースケースとしてはこれで十分かもしれません。

Lambda関数の構成では、reserve concurrency を使用して、関数が使用する実行環境の最大数を制限することもできます。

誤ってBurstCreditBalanceがゼロになり、そして、ファイルシステムが比較的小さい場合(たとえば、数GiB)、関数がスタックし、タイムアウトに達するまで十分な速度で実行できない可能性があります。その場合は、EFSのProvisioned Throughput modeを有効にする(または増やす)か、reserve concurrencyをゼロに設定して関数を調整し、EFSに十分なクレジットが入るまですべての呼び出しを回避する必要があります。

セキュリティ管理について

AWS LambdaでEFSを使用する場合、複数レベルのセキュリティ制御があります。サーバーレスアプリケーションの設計と実装の際にこれらすべてを考慮する必要があるため、ここで簡単に要約します。EFSでのIAM認証とアクセスポイントの使用に関する詳細は、この投稿をご覧ください。

Lambda関数をEFSに接続するには、次のものが必要です。

  • VPCルーティング/ピアリングとセキュリティグループに関するネットワークの可視性。
  • Lambda関数がVPCにアクセスしてEFSをマウント(読み取り専用または読み取り/書き込み)するためのIAMアクセス許可。
  • Lambda関数が使用できるEFSアクセスポイントをIAMポリシー条件で指定できます。
  • EFSアクセスポイントは、ファイルシステムの特定のパスへのアクセスを制限できます。
  • ファイルシステムのセキュリティ(ユーザーID、グループID、権限)は、Lambda関数によってマウントされた各ファイルまたはディレクトリの読み取り、書き込み、または実行可能アクセスを制限できます。

Lambda関数実行環境とEFSマウントポイントは、標準のTransport Layer Security(TLS)1.2を使用して、転送中のデータを暗号化します。 Amazon EFSをプロビジョニングして、保存されているデータを暗号化できます。保存時におけるデータの暗号化は、書き込み時には透過的に暗号化され、読み取り時には透過的に復号化されるため、アプリケーションを変更する必要はありません。暗号化キーはAWS Key Management Service(KMS)によって管理されるため、安全なキー管理インフラストラクチャを構築および維持する必要がありません。

今すぐご利用可能です

この新機能は、AWS LambdaとAmazon EFSが利用可能なすべてのリージョンで提供されます。ただし、この統合をできるだけ早く利用できるように取り組んでいますが、中国のリージョンは例外です。可用性の詳細については、AWSリージョンの表をご覧ください。詳細については、ドキュメントをご覧ください。

LambdaのEFSは、コンソール、AWSコマンドラインインターフェイス(CLI)AWS SDK、およびAWS サーバーレスアプリケーションモデル(SAM)を使用して構成できます。この機能により、大きなファイルを処理する必要があるデータ集約型のアプリケーションを構築できます。たとえば、1.5 GBのファイルを数行のコードで解凍したり、10 GBのJSONドキュメントを処理したりできます。 AWS Lambdaの250 MBパッケージデプロイメントサイズ制限より大きいライブラリまたはパッケージをロードして、新しい機械学習、データモデリング、財務分析、およびETLジョブシナリオを有効にすることもできます。

Amazon Elastic File System for AWS Lambda は、

などのAWSパートナーネットワークソリューションでローンチ時点でサポートされています。

Lambda関数からEFSを使用するための追加料金はありません。 AWS LambdaとAmazon EFSの標準価格を支払います。 Lambda実行環境は、AZ全体ではなく、常にAZの適切なマウントターゲットに接続します。クロスアカウントVPCを介して同じAZのEFSに接続できますが、そのためのデータ転送コストが発生する可能性があります。 EFSとLambda間のクロスリージョンまたはクロスAZ接続はサポートされていません。

 

翻訳はServerless Specialist Solutions Architect 下川 賢介 ( @_kensh )が担当しました。原文はこちらです。