CloudFormationでElasticFileSystem(EFS)をEC2にマウントさせる


前回EFSの立ち上げだけやりましたので、今回はEC2をマウントさせます。


## CloudFormation Template
めっちゃ長いです。あとしれっとus-east-1決め打ちです。
AMIは使い慣れてるAMIMOTO AMIを使いました。

```ec2-single-with-efs.json
{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Description": "AMIMOTO EFS Maunted Stack Example\n",
  "Resources": {
    "IAMRoleForEC2Instance": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Version": "2012-10-17",
          "Statement": {
            "Effect": "Allow",
            "Principal": {
              "Service": "ec2.amazonaws.com"
            },
            "Action": [
              "sts:AssumeRole"
            ]
          }
        },
        "Path": "/"
      }
    },
    "IAMForEC2": {
      "Type": "AWS::IAM::InstanceProfile",
      "Properties": {
        "Path": "/",
        "Roles": [
          {
            "Ref": "IAMRoleForEC2Instance"
          }
        ]
      }
    },
    "EC2WaitHandle": {
      "Type": "AWS::CloudFormation::WaitConditionHandle"
    },
    "EC2WaitCondition": {
      "Type": "AWS::CloudFormation::WaitCondition",
      "DependsOn": "EC2",
      "Properties": {
        "Handle": {
          "Ref": "EC2WaitHandle"
        },
        "Timeout": "1800"
      }
    },
    "SecurityGroupPublic": {
      "Type": "AWS::EC2::SecurityGroup",
      "Properties": {
        "GroupDescription": "SG for ELB",
        "SecurityGroupIngress": [
          {
            "IpProtocol": "tcp",
            "FromPort": "80",
            "ToPort": "80",
            "CidrIp": "0.0.0.0/0"
          }
        ],
        "Tags": [
          {
            "Key": "Application",
            "Value": {
              "Ref": "AWS::StackName"
            }
          }
        ],
        "VpcId": {
          "Ref": "VpcId"
        }
      }
    },
    "SecurityGroupInstance": {
      "Type": "AWS::EC2::SecurityGroup",
      "Properties": {
        "GroupDescription": "SG for EC2 Instance",
        "SecurityGroupIngress": [
          {
            "IpProtocol": "tcp",
            "FromPort": "22",
            "ToPort": "22",
            "CidrIp": {
              "Ref": "SSHLocation4EC2"
            }
          }
        ],
        "Tags": [
          {
            "Key": "Application",
            "Value": {
              "Ref": "AWS::StackName"
            }
          }
        ],
        "VpcId": {
          "Ref": "VpcId"
        }
      }
    },
    "SecurityGroupInternal": {
      "Type": "AWS::EC2::SecurityGroup",
      "Properties": {
        "GroupDescription": "Allow connect between EC2 Instance and ELB",
        "Tags": [
          {
            "Key": "Application",
            "Value": {
              "Ref": "AWS::StackName"
            }
          }
        ],
        "VpcId": {
          "Ref": "VpcId"
        }
      }
    },
    "SecurityGroupInternalSelfIngress80": {
      "Type": "AWS::EC2::SecurityGroupIngress",
      "DependsOn": "SecurityGroupInternal",
      "Properties": {
        "GroupId": {
          "Ref": "SecurityGroupInternal"
        },
        "SourceSecurityGroupId": {
          "Ref": "SecurityGroupInternal"
        },
        "IpProtocol": "tcp",
        "ToPort": "80",
        "FromPort": "80"
      }
    },
    "SecurityGroupInternalSelfIngress443": {
      "Type": "AWS::EC2::SecurityGroupIngress",
      "DependsOn": "SecurityGroupInternal",
      "Properties": {
        "GroupId": {
          "Ref": "SecurityGroupInternal"
        },
        "SourceSecurityGroupId": {
          "Ref": "SecurityGroupInternal"
        },
        "IpProtocol": "tcp",
        "ToPort": "443",
        "FromPort": "443"
      }
    },
    "EC2": {
      "Type": "AWS::EC2::Instance",
      "Properties": {
        "AvailabilityZone": {
          "Ref": "AvailabilityZone"
        },
        "BlockDeviceMappings": [
          {
            "DeviceName": "/dev/sdb",
            "VirtualName": "ephemeral0"
          },
          {
            "DeviceName": "/dev/sdc",
            "VirtualName": "ephemeral1"
          }
        ],
        "DisableApiTermination": "false",
        "EbsOptimized": "false",
        "InstanceInitiatedShutdownBehavior": "stop",
        "ImageId": "ami-0c417066",
        "InstanceType": {
          "Ref": "InstanceType"
        },
        "KeyName": {
          "Ref": "KeyName"
        },
        "Monitoring": "false",
        "Tenancy": "default",
        "SecurityGroupIds": [
          {
            "Ref": "SecurityGroupInstance"
          },
          {
            "Ref": "SecurityGroupInternal"
          }
        ],
        "IamInstanceProfile": {
          "Ref": "IAMForEC2"
        },
        "SubnetId": {
          "Ref": "SubnetId"
        },
        "Tags": [
          {
            "Key": "Name",
            "Value": {
              "Ref": "AWS::StackName"
            }
          }
        ],
        "UserData": {
          "Fn::Base64": {
            "Fn::Join": [
              "",
              [
                "#!/bin/bash\n",
                "/opt/aws/bin/cfn-init -s ",
                {
                  "Ref": "AWS::StackName"
                },
                " -r EC2 ",
                " --region ",
                {
                  "Ref": "AWS::Region"
                },
                "\n",
                "yum install -y nfs-utils\n",
                "mount -t nfs4 -o nfsvers=4.1 $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone).",
                {
                    "Ref": "EFSFileSystem"
                },
                ".efs.us-east-1.amazonaws.com:/ /var/www/vhosts\n",
                "/opt/aws/bin/cfn-signal -e $? -r \"WordPress setup complete\" '",
                {
                  "Ref": "EC2WaitHandle"
                },
                "'\n",
                "\n"
              ]
            ]
          }
        }
      }
    },
    "EIP": {
      "Type": "AWS::EC2::EIP",
      "Properties": {
        "InstanceId": {
          "Ref": "EC2"
        },
        "Domain": "vpc"
      }
    },
    "ELB": {
      "Type": "AWS::ElasticLoadBalancing::LoadBalancer",
      "Properties": {
        "Subnets": [
          {
            "Ref": "SubnetId"
          }
        ],
        "HealthCheck": {
          "HealthyThreshold": "2",
          "Interval": "30",
          "Target": "TCP:80",
          "Timeout": "10",
          "UnhealthyThreshold": "2"
        },
        "Listeners": [
          {
            "InstancePort": "80",
            "LoadBalancerPort": "80",
            "Protocol": "HTTP",
            "InstanceProtocol": "HTTP"
          }
        ],
        "Instances": [
          {
            "Ref": "EC2"
          }
        ],
        "SecurityGroups": [
          {
            "Ref": "SecurityGroupInternal"
          },
          {
            "Ref": "SecurityGroupPublic"
          }
        ]
      }
    },
    "EFSFileSystem": {
      "Type": "AWS::EFS::FileSystem",
      "Properties": {
        "FileSystemTags": [
          {
            "Key": "Name",
            "Value": "EFSTest"
          }
        ]
      }
    },
    "EFSMoutTarget": {
      "Type": "AWS::EFS::MountTarget",
      "Properties": {
        "FileSystemId": {
          "Ref": "EFSFileSystem"
        },
        "SecurityGroups": [
          {
            "Ref": "SecurityGroupFileSystem"
          }
        ],
        "SubnetId": {
          "Ref": "SubnetId"
        }
      }
    },
    "SecurityGroupFileSystem": {
      "Type": "AWS::EC2::SecurityGroup",
      "Properties": {
        "GroupDescription": "SG for EFS",
        "SecurityGroupIngress": [
          {
            "IpProtocol": "tcp",
            "FromPort": "2049",
            "ToPort": "2049",
            "CidrIp": "0.0.0.0/0"
          }
        ],
        "Tags": [
          {
            "Key": "Application",
            "Value": {
              "Ref": "AWS::StackName"
            }
          }
        ],
        "VpcId": {
          "Ref": "VpcId"
        }
      }
    }
  },
  "Parameters": {
    "KeyName": {
      "Description": "Name of an existing EC2 key pair to enable SSH access to the instances",
      "Type": "AWS::EC2::KeyPair::KeyName",
      "ConstraintDescription": "Must be the name of an existing EC2 KeyPair."
    },
    "InstanceType": {
      "Description": "EC2 instance type",
      "Type": "String",
      "AllowedValues": [
        "t2.micro",
        "t2.small",
        "t2.medium",
        "m3.medium",
        "m3.large",
        "m3.xlarge",
        "m3.2xlarge",
        "m4.large",
        "m4.xlarge",
        "m4.2xlarge",
        "m4.4xlarge",
        "m4.10xlarge",
        "c3.large",
        "c3.xlarge",
        "c3.2xlarge",
        "c3.4xlarge",
        "c3.8xlarge",
        "c4.large",
        "c4.xlarge",
        "c4.2xlarge",
        "c4.4xlarge",
        "c4.8xlarge",
        "g2.2xlarge",
        "hi1.4xlarge",
        "hs1.8xlarge",
        "i2.xlarge",
        "i2.2xlarge",
        "i2.4xlarge",
        "i2.8xlarge",
        "r3.large",
        "r3.xlarge",
        "r3.2xlarge",
        "r3.4xlarge",
        "r3.8xlarge"
      ],
      "ConstraintDescription": "Must be a valid EC2 instance type."
    },
    "AvailabilityZone": {
      "Description": "Pick an AvailabilityZone for the Primary Subnet. The public subnet will be created in this zone.",
      "Type": "AWS::EC2::AvailabilityZone::Name"
    },
    "SSHLocation4EC2": {
      "Description": "The IP address range that can be used to SSH to the EC2 instances",
      "Type": "String",
      "MinLength": "9",
      "MaxLength": "18",
      "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})",
      "ConstraintDescription": "Must be a valid IP CIDR range of the form x.x.x.x/x."
    },
	"SubnetId": {
	  "Description": "Your VPC Subnet ID",
	  "Type": "String"
	},
	"VpcId": {
	  "Description": "Your VPC ID",
	  "Type": "String"
    }
  },
  "Outputs": {
  }
}
```

## kumogataから起動する
```/bin/bash
$ kumogata create ec2-single-with-efs.json STACK_NAME  \
 -s AWS_SECRET_KEY \
 -k AWS_ACCESS_KEY \
 -r  us-east-1 \
 -p  "KeyName=KEY_NAME,SSHLocation4EC2=0.0.0.0/0,AvailabilityZone=us-east-1a,InstanceType=t2.small,VpcId=vpc-XXXXX,SubnetId=subnet-XXXXX" \
 --capabilities=CAPABILITY_IAM

```

## ざっくり中身

### EFSの起動
EFS自体は以下のようにするだけでOKです。

```
{
  "EFSFileSystem": {
    "Type": "AWS::EFS::FileSystem",
    "Properties": {
      "FileSystemTags": [
        {
          "Key": "Name",
          "Value": "EFSTest"
        }
      ]
    }
  }
}
```

### EFSをマウントする

EFSのマウント先は以下のように設定します。
EFSのリソースIDとVPCのサブネットID、あとセキュリティグループを指定します。

```efs_mount.json
{
  "EFSMoutTarget": {
    "Type": "AWS::EFS::MountTarget",
    "Properties": {
      "FileSystemId": {
        "Ref": "EFSFileSystem"
      },
      "SecurityGroups": [
        {
          "Ref": "SecurityGroupFileSystem"
        }
      ],
      "SubnetId": {
        "Ref": "SubnetId"
      }
    }
  }
}
```
セキュリティグループはこんな感じ、2049のポートを解放させます。
CiderIpを0.0.0.0/0にしていますが、可能ならばIP列挙にしたほうがいいかも。

```sg_for_efs.json
{
  "SecurityGroupFileSystem": {
    "Type": "AWS::EC2::SecurityGroup",
    "Properties": {
      "GroupDescription": "SG for EFS",
      "SecurityGroupIngress": [
        {
          "IpProtocol": "tcp",
          "FromPort": "2049",
          "ToPort": "2049",
          "CidrIp": "0.0.0.0/0"
        }
      ],
      "Tags": [
        {
          "Key": "Application",
          "Value": {
            "Ref": "AWS::StackName"
          }
        }
      ],
      "VpcId": {
        "Fn::FindInMap": [
          "MPVpcId",
          {
            "Ref": "AWS::Region"
          },
          "ID"
        ]
      }
    }
  }
}
```

最後にEC2のUserDataにてEFSのマウントを設定しています。
とりあえずWebドキュメントルートだけマウントさせようということで、パスは「/var/www/vhosts」を指定しました。

```
                "yum install -y nfs-utils\n",
                "mount -t nfs4 -o nfsvers=4.1 $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone).",
                {
                    "Ref": "EFSFileSystem"
                },
                ".efs.us-east-1.amazonaws.com:/ /var/www/vhosts\n",
```

うまくいけば、SSH接続後こんな感じになります。

```
[ec2-user@ip-172-30-0-149 vhosts]$ df -h
ファイルシス                                         サイズ  使用  残り 使用% マウント位置
/dev/xvda1                                             9.8G  4.2G  5.6G   43% /
devtmpfs                                               993M   60K  993M    1% /dev
tmpfs                                                 1002M     0 1002M    0% /dev/shm
us-east-1a.fs-7111eb38.efs.us-east-1.amazonaws.com:/   8.0E     0  8.0E    0% /var/www/vhosts
```

## 今後
ここまできたらDBもRDSにしてAutoScaleさせるしかないよねーと思うわけです。

goofys版の記事(http://qiita.com/wokamoto/items/c470a51f711f2373f6f3 )がすでにあるので、パフォーマンス比較まで持って行きたいところ・・・