AWS Lambda 新機能「EFS 統合」の検証と、新しいユースケースの開拓

2020/6/16のアップデートで、AWS Lambda から Amazon Elastic File System (EFS) が利用可能になりました。
本記事ではこの EFS との統合機能を検証し、今回のアップデートによって生まれる新しいユースケースとは何かを考えます。

# アップデートの概要
AWS Lambda 関数が Amazon Elastic File System（EFS）をマウントできるようになりました。
これまでの EFS の主たる用途は、複数の EC2 インスタンスで共有するファイルシステムです。
端的に言えば、1つまたは複数の同時実行される Lambda 関数、従来から対応している EC2 と Fargate を跨ってデータ (ファイル) を永続的に保存および共有できるようになります。

# 前提条件 (構成図)
EFS は VPC 内のリソースとなりますので、VPC Lambda である必要があります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211986/44a9754a-ce64-6f80-76a8-34990fb565aa.png)
また、VPC Lambda から EFS にアクセスするために、EFS のアクセスポイントが必要です。
簡単な構成図を作ると、以下のようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211986/6ec2dcbe-cfce-9ec8-8c11-107f16774ccd.png)

また、EFS はリージョナルなサービスであるため、EFS と Lambda 関数は同一リージョンに存在する必要があります。

# Lambda から EFS を使ってみる
使い始めるのはそれほど難しくありません。
以下の画面を追って見ていただければ、すぐに使えます。

まず、Lambda 関数にアタッチするセキュリティグループと、EFS にアタッチするセキュリティグループを作っておきます。
以下は、EFS にアタッチするセキュリティグループの例です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211986/ba9a39e5-edfe-5f4a-3baa-66e2159a4275.png)
次に EFS を作ります。 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211986/25b3536b-5277-ab88-b42e-1690b1b0eb2c.png)
この時、Lambda 関数から EFS にアクセスするには、アクセスポイントを作成する必要があります。
ここで、アクセス許可等を適切に付与します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211986/e590dce6-a6f0-cdba-6ea4-63d94c3cae88.png)
EFS が出来たら、Lambda 関数を作ります。
今回、Lambda 関数を Python 3.8 で開発します。
実行ロールのポリシーは、AWSLambdaBasicExecutionRole と AWSLambdaENIManagementAccess をベースに、以下とします。

```json:Policy
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:CreateNetworkInterface",
                "ec2:DescribeNetworkInterfaces",
                "ec2:DeleteNetworkInterface"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "elasticfilesystem:ClientMount",
                "elasticfilesystem:ClientWrite"
            ],
            "Resource": "*"
        }
    ]
}
```

Lambda 関数の管理画面には、新たに「ファイルシステム」という欄が追加されていますので、ここから設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211986/8d2887df-6bb5-a47b-f04b-71daf0a6684f.png)
敢えて分かりやすいようにエラーが出ている画面を掲載して説明しますが、ローカルマウントパスは /mnt で始まる必要があります。
今回は /mnt/data として作成しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211986/c9396401-5b6a-73c1-73dc-63610b07d8aa.png)

テスト用の Lambda 関数のコードは以下です。

```python:lambda_functon.py
def lambda_handler(event, context):
    # テストファイルのパスを指定
    path = '/mnt/data/helloworld.txt'
    
    # テストファイルに書き込む文字列を指定
    s = 'Hello, world!'
    
    # テストファイルに書き込み
    with open(path, mode='w') as f:
        f.write(s)
        
    # 書き込んだテストファイルの読み込み
    with open(path) as f:
        print(f.read())
```

実行すると、以下の通りの結果となり、EFS ファイルシステムとの間でファイルの読み書きができました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211986/1c48b8db-d12f-9363-6efe-d767ef95ca48.png)

もう1つ Lambda 関数を作成し、同じ EFS ファイルシステムを参照するように設定し、読み取り専用のコードを書きます。

```python:lambda_function.py
def lambda_handler(event, context):
    # テストファイルのパスを指定
    path = '/mnt/data/helloworld.txt'

    # 書き込んだテストファイルの読み込み
    with open(path) as f:
        print(f.read())
```

実行すると、先ほど書き込んだファイルの中身が表示されることが分かります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211986/840a64a3-34e1-bacc-237a-93e9b23b11ff.png)

書き込んだファイルが EC2 からも見えるかどうか確認します。

```bash
$ sudo mount -t efs fs-352d2b14:/ efs

$ cd efs

$ ls -al
total 12
drwxr-xr-x  3 root     root     6144 Jun 17 11:37 .
drwxr-xr-x 23 ec2-user ec2-user 4096 Jun 17 11:49 ..
drwxr-xr-x  2     1001     1001 6144 Jun 17 11:37 data

$ cd data

$ ls -al
total 12
drwxr-xr-x 2 1001 1001 6144 Jun 17 11:37 .
drwxr-xr-x 3 root root 6144 Jun 17 11:37 ..
-rw-rw-r-- 1 1001 1001   13 Jun 17 11:45 helloworld.txt

$ cat helloworld.txt 
Hello, world!
```

このように、EFS を利用して、複数の Lambda 関数、さらには EC2 インスタンスをまたいで、ファイルの共有と読み書きができました。

# 新機能で広がる可能性
このように Lambda 関数から EFS が使えるようになったのは素晴らしいことです。
新機能によって、新たにできるようになったことをまとめてみます。
## 一貫性の担保
Lambda 関数が EFS と統合したことにより、これまでメジャーであった S3 を介したファイルの読み書きに加え、新たな選択肢が広がりました。
S3 を使う場合には結果整合性を許容する必要がありましたが、EFS を使えば一貫性を担保する形でファイルの共有ができます。

## OSネイティブなファイル操作
これまでも /tmp に対して実施できたことではありますが、永続的にデータを保管できる EFS ファイルシステムに対しても同じことができるようになり、サーバレスアーキテクチャにおけるデータ (ファイル) の永続保管を実装する選択肢が増えたと言えます。
AWS の SDKよりも、各プログラミング言語のネイティブモジュールに習熟している人にとっては、今回の EFS 統合はありがたいかもしれません。

## 512MBを超えるファイルの操作 

以前から、Lambda 関数には 512 MB の容量で /tmp ストレージが用意されていましたが、512 MB では足りなくなるケースがあり、この制約がユースケースを制限していました。
例えば、GB 単位で容量を消費する機械学習のモデルや、大量の rpm ファイルなどがこのケースにあたります。
今回、Lambda で EFS が使えることで、/tmp ストレージのサイズによって断念していたユースケースにも適用できるようになったと言えます。

# 現実的なユースケース
では「EFS のお陰で 512 MB に縛られなくなった」と言うからには、どんどん Lambda 関数で巨大なファイルを扱っていこう、という流れが起きるのかというと、そうではないと考えられます。

選択肢が広がったという意味では確かにその通りですが、Lambda 関数ならではの実行時間やメモリの制約があります。
何でも Lambda 関数でやれば良いというものではなく、新機能の登場以前にも存在していた Lambda 関数の特徴を知ったうえで、有効活用を考える必要があります。

では現実を考え、どんなユースケースが良いのかというと**「EC2で実行しようとしていた、ちょっとしたファイル処理を、Lambda 関数にオフロードする」**というのが良いのでないかと思います。
従来、EC2 が EFS ファイルシステムに書き込んだファイルを処理するには、EC2 にスクリプト等を配置し、EC2 自らのリソースを使う必要がありました。
このスクリプトを、EC2 から Lambda 関数にオフロードできるようになります。

先ほどの構成図に一部書き加え、赤丸をスクリプトとすると、以下のようなイメージとなります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211986/b90ce5f2-1636-d7db-d2d2-7ad6b46f1101.png)

ただし、ここで Lambda 関数に担わせようとしている機能が Lambda 関数の制約内で実行できるかどうかと、Lambda 関数で実行したときのパフォーマンスが要求を満たしているかは、意識しておく必要があります。
Lambda 関数でクリアできなければ、[オフロード先を Fargate にする](https://aws.amazon.com/jp/blogs/containers/developers-guide-to-using-amazon-efs-with-amazon-ecs-and-aws-fargate-part-1/)など、別の選択肢を検討します。

# まとめ
今回の新機能によって、Lambda 関数を使った時のファイル操作の選択肢と可能性は広がりました。
ただ「Lambda 関数で EFS が使えるようになったから何でもかんでも使おう」ではなく、サービスの特徴を踏まえた使いどころを考え、採用していくべきです。
見方を変えれば、従来の S3 や Lambda の /tmp ストレージで要件を満たして対応できるのであれば、無理に EFS を使う必要はないと言えますので、まずは従来の方法ではどうしようもない場合に検討するのが良いでしょう。
また、Lambda 起点で考えるのではなく、従来 EC2 で持っていた処理を Lambda にオフロードするという矢印で見ると、より活用の可能性が見えてくるでしょう。

# 参考文献
[New – A Shared File System for Your Lambda Functions](https://aws.amazon.com/jp/blogs/aws/new-a-shared-file-system-for-your-lambda-functions/)
[EFS ファイルシステムをマウントする](https://docs.aws.amazon.com/ja_jp/efs/latest/ug/mounting-fs.html)