AWS CloudFormation EC2起動時にEFSをマウントする

# EFSをマウントするには

EC2のユーザーデータを使うことで、起動時にコマンドを実行することができます。今回はマウントコマンドを実行します。

https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/user-data.html

主に以下の公式ドキュメントを参考にしましたが、今回書いた記事では起動設定をテンプレートには含めません。

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/quickref-efs.html

# 必要なリソース

- VPC
- パブリックサブネット(プライベートサブネットでも同じ)
- インターネットゲートウェイ
- ルートテーブル
- EC2
- EFS
- セキュリティグループ

これらをCloudFormationで作成します。その際、以下の設定もリソースとしてテンプレートに含めることになります。

- ルートテーブルにサブネットを関連付ける
- ルートテーブルをVPCにアタッチする
- マウントターゲットを設定する

※インターネットゲートウェイ、ルートテーブル、セキュリティグループの3点は必須ではありません。これらはマウントできているかどうかをEC2にログインして確かめるためだけに使います。

# テンプレート

```json
{
    "AWSTemplateFormatVersion": "2010-09-09",
    "Resources": {
        "VPC": {
            "Type": "AWS::EC2::VPC",
            "Properties": {
                "CidrBlock": "10.0.0.0/16",
                "EnableDnsHostnames": true,
                "EnableDnsSupport": true
            }
        },
        "Subnet": {
            "Type": "AWS::EC2::Subnet",
            "Properties": {
                "CidrBlock": "10.0.0.0/24",
                "AvailabilityZone": "ap-northeast-1c",
                "VpcId": {
                    "Ref": "VPC"
                }
            }
        },
        "Instance": {
            "Type": "AWS::EC2::Instance",
            "DependsOn": "MountTarget",
            "Properties": {
                "KeyName": "private",
                "ImageId": "ami-0bc8ae3ec8e338cbc",
                "InstanceType": "t2.micro",
                "AvailabilityZone": "ap-northeast-1c",
                "NetworkInterfaces": [
                    {
                        "AssociatePublicIpAddress": true,
                        "DeviceIndex": "0",
                        "GroupSet": [
                            {
                                "Ref": "SecurityGroup"
                            },
                            {
                                "Fn::GetAtt": [
                                    "VPC",
                                    "DefaultSecurityGroup"
                                ]
                            }
                        ],
                        "SubnetId": {
                            "Ref": "Subnet"
                        }
                    }
                ],
                "UserData": {
                    "Fn::Base64": {
                        "Fn::Join": [
                            "",
                            [
                                "#!/bin/bash\n",
                                "sudo mkdir /mnt/efs\n",
                                {
                                    "Fn::Sub": "sudo mount -t nfs4 -o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2 ${FileSystem}.efs.${AWS::Region}.amazonaws.com:/ /mnt/efs"
                                }
                            ]
                        ]
                    }
                }
            }
        },
        "SecurityGroup": {
            "Type": "AWS::EC2::SecurityGroup",
            "Properties": {
                "GroupDescription": "CloufdFormation test",
                "SecurityGroupEgress": [
                    {
                        "IpProtocol": "-1",
                        "FromPort": "-1",
                        "ToPort": "-1",
                        "CidrIp": "0.0.0.0/0"
                    }
                ],
                "SecurityGroupIngress": [
                    {
                        "IpProtocol": "tcp",
                        "FromPort": "22",
                        "ToPort": "22",
                        "CidrIp": "0.0.0.0/0"
                    }
                ],
                "VpcId": {
                    "Ref": "VPC"
                }
            }
        },
        "FileSystem": {
            "Type": "AWS::EFS::FileSystem",
            "Properties": {
                "Encrypted": true,
                "KmsKeyId": "arn:aws:kms:ap-northeast-1:012345678912:key/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
            }
        },
        "MountTarget": {
            "Type": "AWS::EFS::MountTarget",
            "Properties": {
                "FileSystemId": {
                    "Ref": "FileSystem"
                },
                "SubnetId": {
                    "Ref": "Subnet"
                },
                "SecurityGroups": [
                    {
                        "Ref": "SecurityGroup"
                    },
                    {
                        "Fn::GetAtt": [
                            "VPC",
                            "DefaultSecurityGroup"
                        ]
                    }
                ]
            }
        },
        "Route": {
            "Type": "AWS::EC2::Route",
            "Properties": {
                "RouteTableId": {
                    "Ref": "RouteTable"
                },
                "DestinationCidrBlock": "0.0.0.0/0",
                "GatewayId": {
                    "Ref": "InternetGateway"
                }
            }
        },
        "RouteTable": {
            "Type": "AWS::EC2::RouteTable",
            "Properties": {
                "VpcId": {
                    "Ref": "VPC"
                }
            }
        },
        "InternetGateway": {
            "Type": "AWS::EC2::InternetGateway",
            "Properties": {}
        },
        "VPCGatewayAttachment": {
            "Type": "AWS::EC2::VPCGatewayAttachment",
            "Properties": {
                "InternetGatewayId": {
                    "Ref": "InternetGateway"
                },
                "VpcId": {
                    "Ref": "VPC"
                }
            }
        },
        "SubnetRouteTableAssociation": {
            "Type": "AWS::EC2::SubnetRouteTableAssociation",
            "Properties": {
                "RouteTableId": {
                    "Ref": "RouteTable"
                },
                "SubnetId": {
                    "Ref": "Subnet"
                }
            }
        }
    }
}
```

# 解説

## Ref

リソースのIDを参照するときなどに使います。例えば、ルートテーブルをどのVPCにアタッチするか決めるときに、VPCのIDを参照します。その際、以下のように書くことで作成するVPCのIDを参照することができます。

```
"RouteTable": {
    "Type": "AWS::EC2::RouteTable",
    "Properties": {
        "VpcId": {
            "Ref": "VPC"
        }
    }
}
```

## DependsON

EC2マウントターゲットの作成は、EC2の作成よりも遅いです。このコードを記述したリソースは、`DependsOn`で指定したリソースの作成後に作成されます。

```
"DependsOn": "MountTarget"
```

## 

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-attribute-dependson.html


## Fn::base64

ユーザーデータを使うときに必要です。

https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-base64.html

## Fn::Join

文字列の結合に使います。リストのインデックス0には、区切り文字を指定します。インデックス1には、結合したい文字列のリストを指定します。

```
{ 
　　"Fn::Join": 
      [
        "", ["pwd\n", "ls"]
      ]
}
```

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-join.html

## Fn::Sub

変数に格納した値を展開できます。`${AWS::Region}`は定義せずともリージョンの値が展開されます。

```
{
  "Fn::Sub": "sudo mount -t nfs4 -o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2 ${FileSystem}.efs.${AWS::Region}.amazonaws.com:/ /mnt/efs"
}
```

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-sub.html

# 注意点

EC2インスタンスのセキュリティグループとサブネットIDは、`Properties` の中の `NetworkInterfaces`の中で設定します。

# 参考記事

https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/user-data.html

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/quickref-efs.html

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-attribute-dependson.html

https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-base64.html

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-join.html

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-sub.html