【AWS】Amazon EFSを作成して、EC2とLambdaから使用する

Amazon EFSを作成し、EC2にマウント、Lambdaからアクセスできるようにしてみました。
# 前提条件
- VPC、EFS、EC2は同一リージョンに作成する（今回は東京リージョンを使用）
- LambdaはEFSに到達できるVPCに接続する必要がある
- プライベートサブネットが作成済みの状態から開始する
- EC2へSSH接続を行うためのキーペアが作成済みの状態から開始する

## この記事のゴール
- Amazon EFSを作成し、LambdaからEFS内のファイルを操作する
- EC2にEFSをマウントし、EFS内のファイルを操作する

以下の構成をイメージしています。
![aws.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620994/c52c837e-aeef-5991-f56a-9b8ab492f950.png)

## この記事で扱わないこと
- VPCの作成方法
- パブリックサブネット、プライベートサブネットの作成・設定方法
- EC2インスタンスへのSSH接続方法

## 必要なもの
- IAMユーザ

## 環境情報
```:
# AWSリージョン
東京（ap-northeast-1）

# EC2
AMI:Amazon Linux 2 AMI (HVM), SSD Volume Type

# Lambda
ランタイム：Java 11(Corretto)
```

# 手順
AWSコンソールにて作成、設定してみる
　① セキュリティグループの作成
　② EFSの作成
　③ EC2の作成
　④ Lambdaの作成
　⑤ EC2から接続
　⑥ Lambdaから接続

# AWSコンソールにて作成、設定してみる
## ① セキュリティグループの作成
（参考）[公式ユーザーガイド - EFSリソースの使用 - セキュリティグループの作成](https://docs.aws.amazon.com/ja_jp/efs/latest/ug/accessing-fs-create-security-groups.html)
### EC2用のセキュリティグループ
![01_EC2sg_in.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620994/544334cc-03f8-2342-cd79-3ed351dfedf5.jpeg)
![02_EC2sg_out.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620994/8eaa6adb-c781-963f-8845-6a59820dcf33.jpeg)
### Lambda用セキュリティグループ
Lambdaにはデフォルトのセキュリティグループを使用するため作成しません。
### EFS用セキュリティグループ
EC2用セキュリティグループと、Lambda用セキュリティグループからのインバウンドアクセスを許可します。
![03_EFSsg_in.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620994/ef37e77a-540d-8ea5-7f01-37e1e9987923.jpeg)
![04_EFSsg_out.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620994/7b4ea4be-6094-0e20-9ff9-f2f3750d9b2b.jpeg)

## ② EFSの作成
（参考）[公式ユーザーガイド - EFS リソースの使用 - ファイルシステムの作成](https://docs.aws.amazon.com/ja_jp/efs/latest/ug/creating-using-create-fs.html)
EFSコンソールで「カスタマイズ」を選択してファイルシステムを作成します。
![05_EFS_create-customize.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620994/c5bf1927-729e-b849-cf57-ca2937ff36f3.jpeg)
### ファイルシステム設定
要件に応じて適宜設定します。
![06_EFS_filesystem.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620994/e77244e0-3739-344b-cf93-f24878318d8c.jpeg)
### ネットワークアクセス設定
- VPC：Lambdaを接続するVPCを選択します。（今回はデフォルトVPCを使用）
- アベイラビリティーゾーン：デフォルトではリージョンの各アベイラビリティーゾーンが選択されています。不要なアベイラビリティゾーンがある場合は削除します。
- サブネットID：Lambdaを作成するサブネットを選択します。（今回はプライベートサブネットを使用）
- セキュリティグループ：①で作成したEFS用セキュリティグループを選択します。

![06_EFS_network.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620994/ad518fc7-73d5-919c-dc21-6c8352e1da8c.jpeg)
### ファイルシステムポリシー
EFSファイルシステムへ接続する際に、IAMロールを使用して、読み取り専用アクセス、書き込みアクセス、ルートアクセスなどを許可することができます。
今回はすべてのクライアントへのフルアクセスを許可するため、デフォルトのまま変更しません。
![07_EFS_policy.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620994/bbd851a7-7d32-da68-bbbf-f67819342f89.jpeg)
### アクセスポイントの作成
LambdaからEFSへアクセスするために必要なアクセスポイントを作成します。
Lambdaからアクセスする際のユーザIDや、EFSで最初にルートディレクトリパスを作成する際のアクセス権限などを指定できます。
※今回はLambdaからアクセスする際のユーザIDをec2-userとするため、ユーザーID `1000` としていますが、[公式ユーザーガイド](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/configuration-filesystem.html#configuration-filesystem-setup)などでは `1001` を指定しています。
![08_EFS_accesspoint.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620994/d98710e5-e9dc-f021-3163-3799a6bddf14.png)

## ③ EC2の作成
①で作成したEC2用セキュリティグループを適用したEC2インスタンスを作成します。
![09_EC2-1a.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620994/ee6152dd-9e60-1752-9f18-d707b6191776.jpeg)
今回、キーペアは既存のキーペアを選択します。
![10_EC2_keypair.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620994/c3495d7a-6c70-524b-ee5e-1852e58c02d0.jpeg)

## ④ Lambdaの作成
（参考）[公式ユーザーガイド - Lambda 関数のファイルシステムアクセスの設定](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/configuration-filesystem.html?icmpid=docs_lambda_rss)
（参考）[公式ユーザーガイド - VPC 内のリソースにアクセスするための Lambda 関数の設定](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/configuration-vpc.html)

VPCに接続したLambdaを作成します。
今回は、セキュリティグループにはデフォルトセキュリティグループを指定し、プライベートサブネットを使用しています。
![11_Lambda_vpc.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620994/7b326b25-c362-9263-afcd-759d19ebdd39.jpeg)
実行ロールに以下のポリシーをアタッチします。

- `AWSLambdaVPCAccessExecutionRole`（VPCに接続するため）
- `AmazonElasticFileSystemClientReadWriteAccess`（ファイルシステムに接続するため）

![13_Lambda_role.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620994/5595a189-e28e-0e0a-ffd0-57337cf5a6ad.jpeg)
「ファイルシステムの追加」から、②で作成したEFSファイルシステム、アクセスポイントを指定します。
また、Lambda関数からファイルシステムにアクセスする際に使用する `/mnt/`で始まるローカルマウントパスを設定します。
![12_Lambda_filesystem.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620994/49b85648-6f53-b520-3c09-62249b088dfe.png)

## ⑤ EC2から接続
EFS マウントヘルパーを使用してEFS ファイルシステムをマウントします。
### amazon-efs-utils パッケージのインストール
（参考）[公式ユーザーガイド - Amazon Linux で amazon-efs-utils パッケージをインストールする](https://docs.aws.amazon.com/ja_jp/efs/latest/ug/installing-amazon-efs-utils.html)

EC2インスタンスにSSH接続し、以下コマンドでamazon-efs-utilsパッケージをインストールします。
※Amazon Linux以外のLinuxディストリビューションの場合は[こちらを参照](https://docs.aws.amazon.com/ja_jp/efs/latest/ug/installing-other-distro.html)

```:コマンド
sudo yum install -y amazon-efs-utils
```

``` :実行結果（抜粋）
Installed:
  amazon-efs-utils.noarch 0:1.28.2-1.amzn2

Dependency Installed:
  stunnel.x86_64 0:4.56-6.amzn2.0.3

Complete!
```

### EFSをマウント
（参考）[公式ユーザーガイド - EFS マウントヘルパーを使用してマウントする](https://docs.aws.amazon.com/ja_jp/efs/latest/ug/mounting-fs-mount-helper.html)

EFSをマウントするディレクトリを作成します。

```:コマンド
sudo mkdir /mnt/efs
```

マウント前のファイルシステムを確認します。

```:コマンド
df -h
```

```:実行結果
Filesystem      Size  Used Avail Use% Mounted on
devtmpfs        482M     0  482M   0% /dev
tmpfs           492M     0  492M   0% /dev/shm
tmpfs           492M  456K  492M   1% /run
tmpfs           492M     0  492M   0% /sys/fs/cgroup
/dev/xvda1      8.0G  1.4G  6.7G  18% /
tmpfs            99M     0   99M   0% /run/user/0
tmpfs            99M     0   99M   0% /run/user/1000
```

EFSコンソールにてファイルシステムIDを確認します。
![14_EFS_filesystemID.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620994/0a01f2e8-90de-ce07-b37d-fc8f4b7dbdf5.jpeg)
ファイルシステムをマウントします。

```:形式
sudo mount -t efs [ファイルシステムID]:/ [ESをマウントするディレクトリ]
```

```:コマンド
sudo mount -t efs fs-00945420:/ /mnt/efs
```

マウント後のファイルシステムを確認します。

```:コマンド
df -h
```

```:実行結果
Filesystem                                      Size  Used Avail Use% Mounted on
devtmpfs                                        482M     0  482M   0% /dev
tmpfs                                           492M     0  492M   0% /dev/shm
tmpfs                                           492M  460K  492M   1% /run
tmpfs                                           492M     0  492M   0% /sys/fs/cgroup
/dev/xvda1                                      8.0G  1.4G  6.7G  18% /
tmpfs                                            99M     0   99M   0% /run/user/0
tmpfs                                            99M     0   99M   0% /run/user/1000
fs-00945420.efs.ap-northeast-1.amazonaws.com:/  8.0E     0  8.0E   0% /mnt/efs
```

`fs-00945420.efs.ap-northeast-1.amazonaws.com:/` が出現し、`/mnt/efs` にマウントが行えたことが確認できました。

## ⑥ Lambdaから接続
Lambdaからファイルシステムにファイルを作成し、文字列を書き込み、書き込んだ文字列を読み込みしてみます。

以下のコードを使用します。

```Java:Lambda関数のコード
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.ScheduledEvent;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class EfsTestHandler implements RequestHandler<ScheduledEvent, String> {

    private static final Logger logger = LogManager.getLogger(EfsTestHandler.class);

    @Override
    public String handleRequest(ScheduledEvent event, Context context) {

        // ファイル名
        String filepath = "/mnt/data/LambdaSample.txt";
        // 書き込む文字列
        String str = "This was written from Lambda.";

        File file = new File(filepath);
        // ファイル書き込み
        FileWriter fileWriter;
        try {
            fileWriter = new FileWriter(file);
            file.setExecutable(true);
            fileWriter.write(str);

            fileWriter.close();

        } catch (IOException e) {
            e.printStackTrace();
            return "Error";
        }

        // ファイル読み込み
        FileReader reader;
        try {
            reader = new FileReader(file);
            BufferedReader br = new BufferedReader(reader);

            String readStr = "";
            while ((readStr = br.readLine()) != null) {
                logger.info(readStr);
            }

            br.close();
        } catch (FileNotFoundException e1) {
            e1.printStackTrace();
            return "Error";
        } catch (IOException e) {
            e.printStackTrace();
            return "Error";
        }

        return "Complete";
    }
}
```

Lambda関数をテスト実行します。
ファイルシステムへの接続が成功していれば、実行結果に`Complete`と表示され、ログに`This was written from Lambda.`と出力されます。
![15_Lambda_execute.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620994/90700e1e-08f9-0f85-6fec-a7137fe8c2ae.jpeg)
Lambdaからファイルシステムへのファイル書き込み、書き込んだファイルの読み込みが行えることが確認できました。

Lambdaから書き込んだファイルを、EC2からも確認してみます。

```:コマンド
ll /mnt/efs/data
```

```:実行結果
-rwxrw-r-- 1 ec2-user ec2-user 29 Feb 14 05:39 LambdaSample.txt
```
`/mnt/efs/data`配下に、Lambdaから作成したファイルが存在することが確認できました。
※Lambdaからはアクセスポイントを使用してファイルシステムに接続しているため、Lambda関数内で`/mnt/data`と指定することにより、実体としてはEFSの`/data`にアクセスしています。

ファイルの中身を確認してみます。

```:コマンド
cat /mnt/efs/data/LambdaSample.tx
```

```:実行結果
This was written from Lambda.
```
Lambdaから書き込んだ文字列が確認できました。


# おわりに
設定、接続はあっけなく完了しました。
しかし、アクセスポイント作成時のPOSIXユーザIDを何にするのが適切か、など
各設定値について把握しきれず、学習が必要です。

CloudFormationを使用しての設定手順や、マウント失敗談、EC2やLambdaからファイル削除しようとしたら失敗した話も記載予定でしたが、長くなったため別記事へ記載したいと思います。


# 参考
AWS Web Serviceブログ

- [新機能 – Lambda関数の共有ファイルシステム（Amazon Web Services ブログ）](https://aws.amazon.com/jp/blogs/news/new-a-shared-file-system-for-your-lambda-functions/)


公式ユーザーガイド

- [EFSリソースの使用 - セキュリティグループの作成](https://docs.aws.amazon.com/ja_jp/efs/latest/ug/accessing-fs-create-security-groups.html)
- [EFS リソースの使用 - ファイルシステムの作成](https://docs.aws.amazon.com/ja_jp/efs/latest/ug/creating-using-create-fs.html)
- [Lambda 関数のファイルシステムアクセスの設定](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/configuration-filesystem.html?icmpid=docs_lambda_rss)
- [VPC 内のリソースにアクセスするための Lambda 関数の設定](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/configuration-vpc.html)
- [Amazon Linux で amazon-efs-utils パッケージをインストールする](https://docs.aws.amazon.com/ja_jp/efs/latest/ug/installing-amazon-efs-utils.html)
- [他の Linux ディストリビューションで amazon-efs-utils パッケージをインストールする](https://docs.aws.amazon.com/ja_jp/efs/latest/ug/installing-other-distro.html)
- [EFS マウントヘルパーを使用してマウントする](https://docs.aws.amazon.com/ja_jp/efs/latest/ug/mounting-fs-mount-helper.html)

参考にさせていただいた記事

- [AWS Lambda 新機能「EFS 統合」の検証と、新しいユースケースの開拓](https://qiita.com/nasuvitz/items/02c4d762ebf3a5a32b96)
- [AWS Lambda から Amazon EFS へのアクセス](https://qiita.com/leomaro7/items/cfebe8a566b4aa8ee8e3)
- [[アップデート] Lambda から共有ファイルストレージの EFS が利用可能になりました！](https://dev.classmethod.jp/articles/lambda-support-for-amazon-elastic-file-system/)
- [（初心者向け）EC2インスタンスにEFSをマウントしてみた](https://dev.classmethod.jp/articles/ec2-efs-mount-for-beginner/)
- [EFSマウントヘルパーの利用有無でマウントコマンドの違い等をみてみた](https://dev.classmethod.jp/articles/efs-mount-helper/)