LambdaでDataSyncのタスクの実行を自動化させる

# 概要
AWS DataSyncで作成したタスクをコンソール上ではなく、イベント駆動型でAmazon S3からAmazon EFSにデータ転送を行います。

具体的には下記の流れを想定しています。

1. Amazon S3にファイルをアップロードします。

2. それがトリガーとなってAWS LambdaがAWS DataSyncを実行します。

3. AWS DataSyncでアップロードされたファイルをAmazon EFSに同期します。

4. Amazon EC2上でマウントされたEFSファイルシステム内を確認します。

![datatranfer.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/429912/1af07d1c-190b-4cb3-9044-ba69cc08ea83.png)


# 注意事項
* AWSサービスに知見のある方を対象にしていますが、そうでもない方にも試して頂けると嬉しいです！
* 一つのリージョンでリソースを構築します。今回は東京リージョンで作成します。
* 検証目的のため、細かいチューニングは行いません。
* Amazon VPCはデフォルトではなく、新規作成したものを利用しています。
* Amazon EC2はAmazon Linux 2を利用しています。

# 利用するAWSサービス
* Amazon S3 (以下 S3)
* Amazon EFS (以下 EFS)
* AWS DataSync (以下 DataSync)
* AWS Lambda (以下 Lambda)
* Amazon EC2 (以下 EC2)
* Amazon VPC (以下 VPC)

# 手順
### S3バケットの準備
ファイルをアップロードするためのバケットを作成します。

:::note info
既存バケットがあればそちらを活用していただいても問題ありません。
:::

![スクリーンショット 2023-01-20 19.41.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/429912/f5100cdb-5f1f-8e58-875d-021150e2a15d.png)

### EFSの準備
#### 1. セキュリティグループの作成
NFSのインバウンドTCPポートからのみアクセスを許可するために、セキュリティグリープを先に作成します。セキュリティ上、ソースはEC2が存在するVPCからに限定することにしました。
| タイプ | プロトコール | ポート範囲 | ソース |
|:-:|:-:|:-:|:-:|
| NFS | TCP | 2049  | VPCのCIDR  |

#### 2. EFSファイルシステムの作成
デフォルトではないセキュリティグループを指定したいので、EFSコンソール -> 「ファイルシステムの作成」のカスタマイズから設定していきます。
今回は既存VPCがありますのでそちらを活用します。マウントターゲットのセキュリティグループは先ほど作成したセキュリティグループを選択します。

:::note warning
VPCはEC2 インスタンスをファイルシステムに接続するVPCを指定して下さい
:::

![スクリーンショット 2023-01-19 18.13.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/429912/9ae0f515-fe1a-21c9-b063-0b8621198c40.png)

■作成後
![スクリーンショット 2023-01-20 19.45.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/429912/13f56349-7467-2a75-1e9e-fc57ed6febe3.png)

#### 3. EFSファイルシステムのマウント
EC2上で作成したファイルシステムをマウントしておきます。

```
$ sudo mkdir /mnt/efs
$ sudo mount -t nfs4 fs-0ec13a55a0fa1ae9a.efs.ap-northeast-1.amazonaws.com:/ /mnt/efs/
$ df -h /mnt/efs/
ファイルシス                                            サイズ  使用  残り 使用% マウント位置
fs-0ec13a55a0fa1ae9a.efs.ap-northeast-1.amazonaws.com:/   8.0E     0  8.0E    0% /mnt/efs
```

:::note info
EFSファイルシステムをマウントする方法は下記をご参照下さい
https://docs.aws.amazon.com/ja_jp/efs/latest/ug/mounting-fs.html
:::

これでEFSファイルシステムの準備が終わりました。
## DataSyncの準備
#### 1. ロケーションの作成
送信元ロケーションと送信先ロケーションをそれぞれ下記の通り、作ります。
- 送信元ロケーション
  - ロケーションタイプ : Simple Storage Service (Amazon S3)
  - S3バケット : 先ほど作成したS3バケット
  - フィルター : なし
  - IAMロール : 自動生成する

■作成後
![スクリーンショット 2023-01-20 20.11.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/429912/36b1318e-b528-41af-b0a8-63546f0b3163.png)

- 送信先ロケーション
  - ロケーションタイプ : Amazon EFS ファイルシステム
  - ファイルシステム : 「EFSの準備 - 2. EFSファイルシステムの作成」で作成したEFSファイルシステムを選択
  - マウントパス : なし
  - サブネット : 「EFSの準備 - 2. EFSファイルシステムの作成」で指定したVPCのサブネットを選択
  - セキュリティグループ : 「EFSの準備 - 1. セキュリティグループの作成」で作成したセキュリティグループを選択

■作成後
![スクリーンショット 2023-01-20 20.11.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/429912/829413db-734d-7777-ab7e-1bea9cf42e2d.png)

#### 2. タスクの作成
ロケーションの作成が完了したら次はタスクを作成していきましょう。
1. 送信元のロケーションを設定する
既存のロケーションを選択する - > 既存のロケーション -> 上記作成した送信元ロケーション (S3)を指定

2. 送信先ロケーションを設定する
既存のロケーションを選択する -> 既存のロケーション -> 上記作成した送信先ロケーション (EFS) を指定

3. 設定を構成する
タスク名 : 任意の名前を入力
※検証目的なので他の設定はデフォルトのままでOKです

作成が完了したら下記のようにタスクができました。
![スクリーンショット 2023-01-20 20.19.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/429912/9b70a2aa-463e-70e1-9f96-d548962496dd.png)


のちほどタスク IDとタスク ARNが必要になりますのでメモを取っておきます。

:::note info
タスク ARNはタスク IDをクリックすることでコピーができます。

![スクリーンショット 2023-01-20 14.30.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/429912/6c267b9f-12b7-93e3-666f-9997336e6d33.png)
:::
### Lambdaの準備
#### 1. IAMロールの作成
DataSyncのタスク実行とS3へのアクセスを許可するためのIAMロールを作成します。

IAMロールに下記ポリシーを付けます。
**※ account-id,task-id,S3バケット名は正しいものに書き換えて下さい。**
```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "ec2:DescribeNetworkInterfaces",
            "Resource": "*"
        },
        {
            "Sid": "VisualEditor1",
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": "arn:aws:logs:ap-northeast-1:account-id:log-group:/aws/lambda/*:*"
        },
        {
            "Sid": "VisualEditor2",
            "Effect": "Allow",
            "Action": [
                "s3:GetObject",
                "s3:ListBucket",
                "logs:CreateLogGroup",
                "datasync:StartTaskExecution"
            ],
            "Resource": [
                "arn:aws:datasync:ap-northeast-1:account-id:task/task-id",
                "arn:aws:s3:::S3バケット名",
                "arn:aws:s3:::S3バケット名/*",
                "arn:aws:logs:ap-northeast-1:account-id:*"
            ]
        }
    ]
}
```
#### 2. Lambda関数の作成
- 関数名
  - 任意の名前
- ランタイム
  - Python 3.9 (記事執筆時) 
- 実行ロール
  - 上記1.で作成したIAMロール 
- コード

**※ account-id,task-idは正しいものに書き換えて下さい。**

```python:lambda_function.py
import json
import boto3
import os
import logging

log = logging.getLogger()
log.setLevel(logging.INFO)
  
DataSync = boto3.client('datasync')
      
def lambda_handler(event, context):
    objectKey = ''
    try:  
        objectKey = event["Records"][0]["s3"]["object"]["key"]
          
        response = DataSync.start_task_execution(
            TaskArn="arn:aws:datasync:ap-northeast-1:accound-id:task/task-id",
            OverrideOptions={
            },
            Includes=[
                 {
                    'FilterType': 'SIMPLE_PATTERN',
                    'Value': '/' + objectKey
                 }
            ]
        )

        log.info('Response:: ' + json.dumps(response))

    except Exception as e:
      log.error('Error occurred:: ' + str(e))
```
#### 3.トリガーの追加
トリガーとして「S3バケットの準備」で用意したS3バケットを追加します
検証のため、指定のS3バケットの配下にアップロードされた全てのオブジェクトを対象にします。
![スクリーンショット 2023-01-20 16.42.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/429912/1beb0344-7a5e-86e2-8f0f-dcc6663bce24.png)

:::note info
S3の特定のprefixに特定のsuffixのファイルのみがトリガーされるようにしたい場合はPrefixとSuffixオプションをご活用下さい。
:::

# 一連の検証
1. S3バケットにMP4ファイルをアップロードします。
![スクリーンショット 2023-01-20 18.23.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/429912/5a710015-dd15-d828-f52f-f2090c1c2280.png)

2. DataSyncのタスクを確認します。
下図のようにステータスが`実行中`であれば、Lambdaからの呼び出しが成功していて現在EFSへのコピーを実行している状態です。
![スクリーンショット 2023-01-20 18.23.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/429912/7ba25116-75fe-c32e-511f-601d3eaa8629.png)

ステータスが`利用可能`に切り替ってタスク履歴のステータスも`成功`になれば、無事実行できたことになります。
![スクリーンショット 2023-01-20 18.26.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/429912/2ff7190b-1a8d-ef2f-cd0c-c8997318a196.png)

![スクリーンショット 2023-01-20 18.27.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/429912/2440de1b-df43-3f5b-0832-cb896a9e84bc.png)
もしDataSyncのステータスが`実行中`に切り替わらない場合、Lambdaからの呼び出しに失敗した可能性が考えられるため、Amazon CloudWatchログの内容
を確認してみて下さい。
![スクリーンショット 2023-01-20 21.03.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/429912/ac458e38-b7b8-418e-d817-0f9866264e09.png)

3. EC2上でマウントされているEFSのディレクトリを確認します。無事コピーされていることを確認できました。

```
$ ls -la /mnt/efs/
合計 18020
drwxr-xr-x 3 nfsnobody nfsnobody     6144  1月  1  1970 .
drwxr-xr-x 4 root      root            31  1月 19 18:33 ..
drwx------ 2 root      root          6144  1月 20 18:25 .aws-datasync
-rwxr-xr-x 1 nfsnobody nfsnobody 18441734  1月 20 18:22 caminandes_llamigos_480p.mp4
```
# 終わりに
いかがでしたでしょうか。今回はLambdaを活用することにより、DataSyncにおけるS3からEFSへのデータ同期の実行を自動化する方法を記載しました。関連するサービスは複数ありますが、どれもよく利用されているサービスばかりですし、DataSyncによるデータ同期も活用場面も多岐に渡ると思いますので知っておくと損はないです。最後までお読みいただきありがとうございます。この記事が一人でも多くの方の参考になれば幸いです。
# 参考
https://aws.amazon.com/jp/datasync/
https://aws.amazon.com/jp/efs/
https://aws.amazon.com/jp/lambda/