DataSyncを利用したEFSからS3のデータ転送時の注意点

# この記事は何か

DataSyncを利用して、EFSとS3のデータ転送を行う際に、つまづきポイントがあったので、簡単にまとめる。
今回DataSyncは初利用だったので、見事にハマったなーという思いと、公式ドキュメントに明記されていなかった部分なので、もしも同じ部分でつまづいた人がいたら、参考になれば嬉しい。

## DataSyncとは

AWSが提供するオンラインデータ転送のサービス。
昨年からAWSのストレージサービス間ならエージェント不要でデータ転送が可能となった。めでたい。
つまり、EC2やECSなどのリソースは不要で、マネージドサービスだけでデータの転送が可能　:tada:
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19911/a166de0b-564b-26d3-8bb8-4b3eac87e528.png)

## 何をしようとしたか

### EFSのデータを定期的にS3転送する

極力マネージドサービスで完結したかったので、DataSync TaskでEFSのデータをS3に定期的に転送しようとした

<img width="200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19911/82ad1aee-6311-b452-4813-d6ce187d203d.png">   →   <img width="200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19911/1a77b91e-3111-52be-4238-ff79692d02ad.png">

#### EFSの設定状況

- VPC内に配置されている
- File System Policyが設定され許可したIAMRoleしかMountやWriteができないように制御している
- ECSで稼働中のサービスからマウントされている

## 進め方

初のDataSyncの利用だったので、素直に公式ドキュメントをみてリソースの作成と設定を試みた。

### 参考にしたドキュメント
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/AWS_DataSync.html
https://docs.aws.amazon.com/datasync/latest/userguide/create-efs-location.html
https://docs.aws.amazon.com/datasync/latest/userguide/create-s3-location.html

### リソース作成、実行、そして失敗

EFSからS3へのデータ同期となるので、SourceLocationとしてEFS、DestinationLocationとしてS3のlocationをそれぞれ作成し、後はDataSyncのTaskを作成すれば良いと理解。
IAMRoleやSecurityGrouoなどはつまづきがちな部分は公式でもきっちりフォローされているので大きなトラブルはなさそうと思い、作業をスタート。なお、リソースの作成はCloudFormationで行った。

- Source　Location(EFS)の作成

```yaml
  DataSyncLocationEFS:
    Type: AWS::DataSync::LocationEFS
    Properties:
      Ec2Config:
          SecurityGroupArns:
            - !Sub 'arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:security-group/${EC2SecurityGroupDataSyncEFS}'
          SubnetArn: !Sub
            - 'arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:subnet/${SubnetId}'
            - SubnetId:　${SubnetId}
      EfsFilesystemArn: !GetAtt EFSFileSystemSessions.Arn
      Subdirectory: '/dirctory'
```

- Destination Location(S3)の作成

```yaml
  DataSyncLocationS3:
    Type: AWS::DataSync::LocationS3
    Properties:
      S3BucketArn: !GetAtt S3BucketImageData.Arn
      S3Config:
          BucketAccessRoleArn: !GetAtt IAMRoleDataSyncLocationS3.Arn
      S3StorageClass: STANDARD
```

- DataSync　Taskの作成

```yaml
  DataSyncEFStoS3:
    Type: AWS::DataSync::Task
    Properties:
      DestinationLocationArn: !GetAtt DataSyncLocationS3.LocationArn
      Name: !Sub 'EFStoS3'
      Schedule:
          ScheduleExpression: "cron(55 * * * ? *)"
      SourceLocationArn: !GetAtt DataSyncLocationEFS.LocationArn
```

- IAM Roleの作成

```yaml
  IAMRoleDataSyncLocationS3:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Effect: 'Allow'
            Principal:
              Service: 'datasync.amazonaws.com'
            Action: 'sts:AssumeRole'
      Policies:
        - PolicyDocument:
            Statement:
              - Effect: 'Allow'
                Action:
                  - 's3:GetBucketLocation'
                  - 's3:ListBucket'
                  - 's3:ListBucketMultipartUploads'
                Resource:
                  - !GetAtt S3BucketImageData.Arn
              - Effect: 'Allow'
                Action:
                  - 's3:AbortMultipartUpload'
                  - 's3:DeleteObject'
                  - 's3:GetObject'
                  - 's3:ListMultipartUploadParts'
                  - 's3:GetObjectTagging'
                  - 's3:PutObjectTagging'
                  - 's3:PutObject'
                Resource: !Sub '${S3BucketImageData.Arn}/*'
          PolicyName: 'datasync-location-s3'
```

- Security GroupでDataSyncタスクのSubnetからEFSに対して2049のポートアクセスを許可
- EFS側のFile System Policyに今回作成したDataSync TaskのIAMRoleを追加

ここまででリソース作成が完了した形....が、いざタスクを実行すると失敗 :cry: 

具体的にはDataSyncのタスクで以下のエラーが発生した。

**`Task failed to access location loc-XXXXXXXXXXX: x40016: mount.nfs: Connection timed out`**

### 原因調査

エラーメッセージを見るとコネクションタイムアウトとあるので、ネットワーク的な部分で遮断されているように見える。
エラーメッセージが表示されるケースも調べたが、やはり所属するSubnetかSecurity Groupの設定ミスが原因とあり、ネットワーク周りの設定ミスが原因に見えた。

https://docs.aws.amazon.com/efs/latest/ug/troubleshooting-efs-mounting.html

ネットワーク的な問題と思いSubnetの指定ミスやSecurity Groupの設定ミスを疑い確認したが、少なくとも設定ミスはなさそう :thinking: 
問題の切り分けのためにSecurity Groupの制限を緩めたりしてみたけれど、エラーは変わらず :cry: 
ネットワークの制限を緩めてもエラーが変わらなかったので、おそらくネットワークではないどこかでつまづいていると思い、サポートに問い合わせを投げた :pray: 

### 失敗の原因

サポートからの返答を抜粋。

> DataSync のタスクが EFS をマウントする際に Connection timed out にてエラーが発生する事象について調査のご報告をいたします。
お客様の設定状況を拝見いたしましたところ、DataSync ロケーションの設定や、EFS マウントターゲットのセキュリティグループやサブネット等の設定に問題は見受けられませんでした。
一方で、調査の結果、当該 EFS に設定されたファイルシステムポリシーが原因となり、当該エラーが発生している可能性が考えられました。
> DataSync では、大変恐縮ではございますが、現時点において EFS のアクセスポリシーでの制限をサポートいたしておりません。
また、こちらの制約に関して現在有効なワークアラウンドはなく、EFS のファイルシステムポリシーを削除いただく、あるいは、DataSync を利用せずにコマンドラインツール等にて EFS からデータを転送いただくことが代替案となります。

つまり、 **EFSのファイルシステムポリシーを利用しているとDataSyncが使えない** という結論。完全に盲点だった。
検証のため、ファイルシステムポリシーを削除したところ、問題なくDataSyncのTaskが成功したので、まさにこのサポートの回答通りの挙動だった。


### 代替案

DataSyncを利用した代替案はないので（File System Policyを削除すればできるけれど、それを削除するわけにはいかなかったので）、LambdaでEFSからS3にデータ転送を行うスクリプトを書いて定期的に実行することにした。
今回、この同期のスクリプトは本筋ではないので割愛。

### まとめ

DataSyncを利用したEFSからのデータ転送やEFSへのデータ転送を行う際、File System Policyを利用している場合は現時点だとできないので、気をつけよう！
反面、今回のようにFile System Policyが利用されているEFSでなければ、S3への定期的なデータ転送が自前のスクリプトなしでできたので、DataSyncがNoOps的な側面で嬉しい機能であることに間違いないなと思えた。