軽作業用インスタンス構築に AWS CloudFormation を利用しました

# 背景
軽作業のためにインスタンスが欲しくなるときがあります。
そういうケースは、マネコンからEIP、t3.nano、セキュリティグループリソースを手動で構成しています。
不要になると同じようにマネコンから手動で削除しています。
このオペレーションをAWS CloudFormationのテンプレートを利用して自動化化した記録。

# AWS CloudFormation
公式サイトからの引用
> AWS CloudFormation は、クラウド環境で AWS とサードパーティ製アプリケーションリソースのモデリングおよびプロビジョニングをする際の、共通的な手法を提供します。AWS CloudFormation では、プログラミング言語またはシンプルなテキストファイルを使用して、あらゆるリージョンとアカウントでアプリケーションに必要とされるすべてのリソースを、自動化された安全な方法でモデル化し、プロビジョニングできます。これは、AWS とサードパーティ製のリソースに真に単一のソースを与えます。

[AWS公式サイト AWS CloudFormation](https://aws.amazon.com/jp/cloudformation/)

# 今回、実現したいこと
* パブリックからつながるEC2インスタンス
* 特定IPからのみ ssh できるEC2インスタンス
* aws cli を使いたい
* aws cli の config に東京リージョンを指定したい
* git コマンド使いたい
* リソースへの権限がEC2インスタンスに欲しい
  * IAM
  * CloudFormation
  * EC2
  * S3
  * Lambda

# テンプレート
YAML形式で記述しました

デザイナーを利用する場合の注意事項が記載されています。
> デザイナー のテンプレートに # YAML コメントを追加しないことをお勧めします。YAML テンプレートに # コメントがある場合、デザイナー は、テンプレートを JSON に変換する際にコメントを保持しません。また、デザイナー でテンプレートを変更する場合 (たとえば、キャンバスにリソースを移動する場合)、コメントは失われます。

[AWS公式サイト AWS CloudFormation テンプレート形式](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/template-formats.html)

テンプレートは、


## メタデータ
パラメータをグルーピングして表示できるよう指定しました。
今回は、ネットワーク関連と作業用インスタンス関連の２グループです。

``` sample.yaml
#------------------------------------------------------------------------------
# metadata
#------------------------------------------------------------------------------
Metadata:
  LICENSE: Apache License, Version 2.0
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: 'Input network information'
        Parameters:
          - VpcId
          - PublicSubnetId
          - EC2KeyPairName
          - WorkHostSecurityGroupCidr
          - WorkHostSecurityGroupCidrDescription
      - Label:
          default: 'Input work instance information'
        Parameters:
          - WorkHostName
          - WorkHostAmiOs
          - WorkHostAmiImageId
          - WorkHostInstanceType
          - GitUserName
          - GitUserEmail
```

## パラメータ
ネットワーク関連のパラメータは、任意に指定できるようにし、OSには、「Amazon-Linux2-HVM」のAMIをデフォルト指定しています。
インスタンスタイプには、「t3.nano」をデフォルト指定しています。

``` sample.yaml
#------------------------------------------------------------------------------
# parameter
#------------------------------------------------------------------------------
Parameters: 
  #------------------------------------------------------------------------------
  # network information
  #------------------------------------------------------------------------------
  #-- Vpc
  VpcId:
    Type: String
    Default: ''
  #-- subnet
  PublicSubnetId:
    Type: String
    Default: ''
  #-- key pair
  EC2KeyPairName:
    Type: String
    Default: ''
  #-- security group cidr
  WorkHostSecurityGroupCidr:
    Type: String
    Default: ''
  #-- security group cidr description
  WorkHostSecurityGroupCidrDescription:
    Type: String
    Default: 'from limited'
  #------------------------------------------------------------------------------
  # work instance information
  #------------------------------------------------------------------------------
  #-- host name
  WorkHostName:
    Type: String
    Default: 'tmpwork-tokyo'
  #-- AMI
  WorkHostAmiOs:
    Type: String
    Default: 'Amazon-Linux2-HVM'
  WorkHostAmiImageId:
    Type: String
    Default: 'ami-011facbea5ec0363b'
  #-- instance type
  WorkHostInstanceType:
    Type: String
    Default: 't3.nano'
  #-- git user name
  GitUserName:
    Type: String
    Default: ''
  #-- git user email
  GitUserEmail:
    Type: String
    Default: ''
```

## リソース
構成するリソースは、次の通りです

* IAMロール設定
* セキュリティグループ設定
* EIP設定
* EC2インスタンス設定
  * IAMロール
  * セキュリティグループ設定
* EIP関連付け

<details>
<summary>サンプルコードを見る</summary>
<div>

``` sample.yaml
#------------------------------------------------------------------------------
# resource
#------------------------------------------------------------------------------
Resources:
  #------------------------------------------------------------------------------
  # IAM role
  #------------------------------------------------------------------------------
  WorkHostIamRole: 
    Type: "AWS::IAM::Role"
    #--- properties
    Properties:
      RoleName: !Sub '${WorkHostName}-role'
      Path: '/'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:   
          - 
            Action:   
            - "sts:AssumeRole"  
            Principal:   
              Service:   
              - "ec2.amazonaws.com"  
            Effect: 'Allow' 
      ManagedPolicyArns:
        - 'arn:aws:iam::aws:policy/AWSCloudFormationFullAccess'
        - 'arn:aws:iam::aws:policy/IAMFullAccess'
        - 'arn:aws:iam::aws:policy/AmazonEC2FullAccess'
        - 'arn:aws:iam::aws:policy/AmazonS3FullAccess'
        - 'arn:aws:iam::aws:policy/AWSLambdaFullAccess'
  #------------------------------------------------------------------------------
  # profile
  #------------------------------------------------------------------------------
  WorkHostInstanceProfile: 
    Type: "AWS::IAM::InstanceProfile"
    #--- properties
    Properties:
      Path: "/"
      Roles:
        - !Ref WorkHostIamRole
  #------------------------------------------------------------------------------
  # Security Group
  #------------------------------------------------------------------------------
  WorkHostSecurityGroup:
    Type: "AWS::EC2::SecurityGroup"
    #--- properties
    Properties:
      VpcId: !Sub "${VpcId}"
      GroupName: !Sub "${WorkHostName}-sg"
      GroupDescription: !Sub "${WorkHostName}-sg"
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: !Sub "${WorkHostSecurityGroupCidr}"
          Description: !Sub "${WorkHostSecurityGroupCidrDescription}"
      Tags:
        - Key: 'Name'
          Value: !Sub "${WorkHostName}-sg"
  #------------------------------------------------------------------------------
  # Elastic IP
  #------------------------------------------------------------------------------
  WorkHostEIP:
    Type: "AWS::EC2::EIP"
    Properties:
      Domain: 'vpc'
      Tags:
        - Key: 'Name'
          Value: !Sub "${WorkHostName}-eip"
  #------------------------------------------------------------------------------
  # EC2 Instance
  #------------------------------------------------------------------------------
  WorkHostInstance:
    Type: "AWS::EC2::Instance"
    #--- metadata
    Metadata:
      AWS::CloudFormation::Init:
        configSets: 
          Install: 
            - "Install"
            - "AwsConfig"
            - "AwsCommand"
            - "GitConfig"
            - "GitCommand"
          Install:
          packages:
            yum:
              git: []
        AwsConfig:
          files:
            /home/ec2-user/.aws/config:
              content: !Sub |
                [default]
                region=${AWS::Region}
              mode: "000600"
              owner: 'ec2-user'
              group: 'ec2-user'
        AwsCommand:
          commands:
            dotaws_chown:
               command: 'chown ec2-user:ec2-user /home/ec2-user/.aws'
            dotaws_chmod:
               command: 'chmod 775 /home/ec2-user/.aws'
        GitConfig:
          files:
            /home/ec2-user/.gitconfig:
              content: !Sub |
                [user]
                name=${GitUserName}
                email=${GitUserEmail}
              mode: "000600"
              owner: 'ec2-user'
              group: 'ec2-user'
        GitCommand:
          commands:
            file_chown:
               command: 'chown ec2-user:ec2-user /home/ec2-user/.gitconfig'
            file_chmod:
               command: 'chmod 664 /home/ec2-user/.gitconfig'
    #--- properties
    Properties:
      ImageId: !Sub "${WorkHostAmiImageId}"
      InstanceType: !Sub "${WorkHostInstanceType}"
      IamInstanceProfile: !Ref WorkHostInstanceProfile
      SubnetId: !Sub "${PublicSubnetId}"
      SecurityGroupIds:
        - !Ref WorkHostSecurityGroup
      KeyName: !Sub "${EC2KeyPairName}"
      Tags:
        - Key: 'Name'
          Value: !Sub "${WorkHostName}01"
      UserData:
        Fn::Base64: !Sub |
          #!/bin/bash -xe
          yum install -y aws-cfn-bootstrap
          /opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource WorkHostInstance --configsets Install --region ${AWS::Region}
  #------------------------------------------------------------------------------
  # associate public ip
  #------------------------------------------------------------------------------
  WorkHostEIPAssociate:
    Type: "AWS::EC2::EIPAssociation"
    #--- properties
    Properties: 
      AllocationId: !GetAtt WorkHostEIP.AllocationId
      InstanceId: !Ref WorkHostInstance
```
</div>
</details>

# スタックの登録

## 事前作業
手動対応をあらかじめ実施します

* VPCの構築
* サブネットの構築
  * インターネットと通信できるサブネット(IGWがアタッチされるサブネット）
* キーペアの設定
  * sshログインの鍵

## マネージドコンソールにテンプレートをアップロードして実行

###「スタックの作成」＞「新しいリソースを使用（標準）」を選択
![screenshot-ap-northeast-1.console.aws.amazon.com-2020.01.31-12_20_54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100110/f19f9ba2-b951-bd31-a186-4f7102ea5280.png)

### テンプレートを指定してアップロード
![screenshot-ap-northeast-1.console.aws.amazon.com-2020.01.31-12_24_42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100110/d7a02dec-186a-a1a6-6bf0-d4ef30ee4a21.png)

### パラメータ入力
スタック名、VpcId、PublicSubnetId、EC2KeyPairName、WorkHostSecurityGroupCidrをそれぞれ入力します
![screenshot-ap-northeast-1.console.aws.amazon.com-2020.01.31-12_28_34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100110/89f7237e-062e-4cc3-c586-0697760f0329.png)

### スタックオプションの設定
タグ、アクセス許可とも特に指定しません
![screenshot-ap-northeast-1.console.aws.amazon.com-2020.01.31-14_05_48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100110/30ccfbbf-7282-4d20-1f8a-24eeccd924b0.png)

### 詳細オプション
スタックポリシーは、デフォルト「スタックポリシーなし」のまま
ロールバック設定、通知オプションとも特に指定しません
スタック作成オプションは、失敗時のロールバック・削除保護ともデフォルトのまま
必要がありましたら適時変更ください

![screenshot-ap-northeast-1.console.aws.amazon.com-2020.01.31-14_06_34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100110/565ae604-2372-e489-6896-5894ff20e2c7.png)

![screenshot-ap-northeast-1.console.aws.amazon.com-2020.01.31-14_07_06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100110/e04f932e-bf73-2295-852d-308f32435959.png)

## レビュー {スタック名}
指定した内容の確認フォームとなります

今回テンプレートには、「AWS::IAM::Role」が含まれるため、下記の確認が表示されます
チェックして次に進みます
![screenshot-ap-northeast-1.console.aws.amazon.com-2020.01.31-14_13_52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100110/076eb232-42da-ee79-4c25-a26f15bd1410.png)

# スタックのステータス
テンプレートに記載された内容で構成が開始されたらイベント情報でスタックのステータスが確認できます
![screenshot-ap-northeast-1.console.aws.amazon.com-2020.01.31-14_17_09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100110/e12a89b3-8e03-63a0-d30e-95231c617807.png)

スタック名のステータスが「CREATE_COMPLETE」になれば、完了です。

# はまったこと・悩んだこと
* EC2インスタンス内のパッケージインストールやファイル操作

# メモ・考察
* スタックの削除でテンプレートから構成されたリソースがすべて削除されるので安心してリソース削除が行える
  * 削除確認を促す場合は、削除保護チェックをオンにすると良い
* 簡素な構成であってもリソース起動・削除が安価になった

# 参考
* AWS公式サイト
  * [AWS CloudFormation](https://aws.amazon.com/jp/cloudformation/)
  * [テンプレートスニペット](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/CHAP_TemplateQuickRef.html)
* [AWS CloudFormationでEC2を構築](https://qiita.com/tyoshitake/items/c5176c0ef4de8d7cf5d8)