CloudFormationでEC2作業環境を構築

## 目的

AWSで仮想マシンやオブジェクトストレージ、仮想マシンを置く仮想ネットワーク環境をまるっと作る.
マネジメントコンソールでもできるが、結構手間なのと、同じ環境を再現するのが大変なので、
環境構築をCloudFormationでスクリプト化する

## 構築したい環境

- 仮想ネットワークとしてVPCと一つのパブリックサブネットを用意
- 仮想ネットワーク環境の中にEC2を一つ建てる.
- オブジェクトストレージ(S3)のバケットを作成
- EC2には作成したバケットのみにアクセスできる権限(policy)を付与
- 起動するEC2はubuntu20.04イメージを使用し、awscliを起動時にインストール


### セキュリティ
- EC2へはsshのみ接続のみ許可
- ssh接続時にkeypairを要求


## CloudFrmation

AWSでIaCを実現するツールの一つで、起動したいAWSサービスをまとめてjsonやymlに記述し、AWSで実行させることでまとめてサービスを起動する. CloudFormationで起動される一連のサービス群をstackと呼ぶ


メリットは

- 同じAWS環境を再現できる
- タグが必ず振られるので、コスト管理の際にstackごとのコストが確認できる
- 起動したサービスはまとめて削除できる

など。

AWSでIaCをやりたい場合、Terraformという選択肢もある. が、AWSネイティブのサービスであるCloudFormationを使用. 

## 事前準備

以下は事前に実施しておく必要がある

- AWSのアカウント作成
- IAMユーザーの作成

## ymlの用意

作成したいAWSサービス群(stack)を記載したファイルを作成. jsonでも良いが、ymlのようがコメントかけたりして便利なのでymlを使用.

cloudformationのymlは基本的に以下の構成

```yml
Parameters: # ここに設定値などを記載しておく
Resources: # ここに作成/起動したいサービスとその条件を記載
Outputs: # 実行結果の出力
```

以下に今回作成したymlを格納している
[cloudformation yml sample (github)](https://github.com/yushikmr/Iaas_templates/blob/master/cloudformation/small-env-poc-01.yml)

## yml記載内容説明

### Parameter設定

パラメータ部分は以下のように設定. パラメータの値はAWSでstackを起動する際に使用.


```yml
Parameters:
  PJPrefix:
    Type: String

  VPCCIDR:
    Type: String
    Default: "10.1.0.0/16"

  PublicSubnetACIDR:
    Type: String
    Default: "10.1.10.0/24"
```

### Resources

Resources以下に起動するサービスの条件を設定していく. 

#### ネットワークの設定
```yml
    VPC: 
      Type: "AWS::EC2::VPC"
      Properties: 
        CidrBlock: !Ref VPCCIDR
        EnableDnsSupport: "true"
        EnableDnsHostnames: "true"
        InstanceTenancy: default
        Tags:
        - Key: Name
          Value: !Sub "${PJPrefix}-vpc"

    InternetGateway: 
      Type: "AWS::EC2::InternetGateway"

    InternetGatewayAttachment: 
      Type: "AWS::EC2::VPCGatewayAttachment"
      Properties: 
        InternetGatewayId: !Ref InternetGateway
        VpcId: !Ref VPC

    PublicSubnetA: 
      Type: "AWS::EC2::Subnet"
      Properties: 
        AvailabilityZone: "us-east-1a"
        CidrBlock: !Ref PublicSubnetACIDR
        VpcId: !Ref VPC 

    PublicRouteTableA: 
      Type: "AWS::EC2::RouteTable"
      Properties: 
        VpcId: !Ref VPC 

    PublicRouteA: 
      Type: "AWS::EC2::Route"
      Properties: 
        RouteTableId: !Ref PublicRouteTableA 
        DestinationCidrBlock: "0.0.0.0/0"
        GatewayId: !Ref InternetGateway 

    PublicSubnetARouteTableAssociation: 
      Type: "AWS::EC2::SubnetRouteTableAssociation"
      Properties: 
        SubnetId: !Ref PublicSubnetA 
        RouteTableId: !Ref PublicRouteTableA
```

構成要素はVPC, Subnet, RouteTable, InternetGatway.
InternetGatewayはVPCから外部インターネットと通信する際に必要. subnetは今回public subnetを一つだけ作成した. 

subnetの`AvailabilityZone: "us-east-1a"`ではsubnetのazを指定している。azはリージョンごとに指定できる値が異なる。
CloudFormationはリージョンごとのサービスなので、適宜リージョンを変えて実行.
このymlはバージニア北部リージョンで実行できる. 

#### EC2のセキュリティ周り
ssh key pairとセキュリティグループを以下のように作成

```yml
    DemoKeyPair:
        Type: AWS::EC2::KeyPair
        Properties:
            KeyName: demo-key-pair

    EC2SG:
      Type: AWS::EC2::SecurityGroup
      Properties:
        GroupName: "${PJPrefix}-sg"
        GroupDescription: Allow SSH access
        SecurityGroupIngress:
          - IpProtocol: tcp
            FromPort: 22
            ToPort: 22
            CidrIp: 0.0.0.0/0
```

ssh private keyの情報はAWS system managerのパラメータストアに保存される(後述)

セキュリティーグループはここではsshのみを許可してて、接続元ipは`0.0.0.0/0`(anywhere)としていて、これはセキュリティ的に少々不安なので、CidrIp:の部分を自分のipに設定し直した方が良いかもしれない

#### IAM Role

EC2にアタッチするIAMロールを設定

```yml
    S3AccessRole:
      Type: "AWS::IAM::Role"
      Properties:
        AssumeRolePolicyDocument:
          Version: "2012-10-17"
          Statement: 
            - 
              Effect: "Allow"
              Principal: 
                Service: 
                  - "ec2.amazonaws.com"
              Action: 
                - "sts:AssumeRole"
        Path: "/"
        Tags:
            - Key: Name
              Value: !Sub "iamrole-${PJPrefix}"
  
    S3AccessPolicies:
      Type: AWS::IAM::Policy
      Properties:
        PolicyName: s3access
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Effect: Allow
            Action:
              - "s3:ListAllMyBuckets"
              - "s3:GetBucketLocation"
            Resource: "arn:aws:s3:::*"
          - Effect: Allow
            Action: "*"
            Resource:
              - !Sub "arn:aws:s3:::s3-bucket-${PJPrefix}"
              - !Sub "arn:aws:s3:::s3-bucket-${PJPrefix}/*"
        Roles:
        - !Ref S3AccessRole

    S3AccessInstanceProfile:
      Type: AWS::IAM::InstanceProfile
      Properties:
        Path: "/"
        Roles:
        - !Ref S3AccessRole
```

ここでは
```yml
            Resource:
              - !Sub "arn:aws:s3:::s3-bucket-${PJPrefix}"
              - !Sub "arn:aws:s3:::s3-bucket-${PJPrefix}/*"
```

に対して全ての動作を許可している. 


#### EC2起動

EC2を以下のような設定で起動

```yml
    MyEC2Instance:
      Type: AWS::EC2::Instance
      Properties:
          ImageId: ami-06aa3f7caf3a30282
          InstanceType: t2.micro
          KeyName: !Ref DemoKeyPair
          SecurityGroups:
            - !Ref  EC2SG
          IamInstanceProfile:
            !Ref S3AccessInstanceProfile
          Tags:
            - Key: Name
              Value: !Sub "ec2-${PJPrefix}"
          UserData:
            Fn::Base64: |
              #!/bin/bash
              sudo apt update 
              sudo apt upgrade
              sudo apt  install awscli -y
```

`InstanceType: t2.micro`でインスタンスタイプを設定.

`ImageId: ami-06aa3f7caf3a30282`でimage(osなどベースになるソフトウェア)を指定していて、
今回はubuntu20.04が入ったAMIを指定してる. 

```yml
          UserData:
            Fn::Base64: |
```
に続けて起動時に実行したいコマンドを記載できる.今回はaws cliをインストールしている. 


#### S3


```yml
    S3Bucket:
        Type: "AWS::S3::Bucket"
        Properties: 
            BucketName: !Sub "s3-bucket-${PJPrefix}"

```

デフォルトの設定だとpublic accessはblockされている.今回はそのまま。


## CloudFormation実行

AWS マネジメントコンソールにいき、サービス一覧からAWS CloudFormationを選択. 

`スタックの作成`という場所をクリックする

テンプレートの準備完了、テンプレートファイルのアップロード、を選択して先ほど作成したyml(githubのurl貼ったやつ)をuploadする. 

ここでマネジメントコンソール右上のリージョンが想定通りか確認しておく.

![スクリーンショット 2023-11-05 16.16.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1154503/ac267e4a-5247-0bd2-e83b-97e42695d1db.png)

uploadしたら次へを選択.

ここでは 
- スタック名
- PJPrefix
- VPCCIDR
- PublicSubnetACIDR

を指定する. CIDRについてはデフォルト値のままでよければそのままでよい.


![スクリーンショット 2023-11-05 16.31.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1154503/aa709c7f-ea2c-4f10-1725-1980014e2c63.png)



次の画面はそのまま次へと押せばよく、そうすると最終画面になる. 
一番下の送信を押すと実行されるが、その前に

「AWS CloudFormation によって IAM リソースが作成される場合があることを承認します。」という
確認事項が出てくるのでチェックする

(これはEC2にアタッチするIAM RoleをCloudFormationで作成するため)

![スクリーンショット 2023-11-05 16.26.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1154503/66fb07e9-1233-07c7-e816-a5961bac46c3.png)

送信を押すと、プロセス実行中の画面になるのでしばらく待つと起動が完了する. 
![スクリーンショット 2023-11-05 16.31.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1154503/a242d730-7de6-dbe3-5a50-76d375b303b1.png)

右上のリロードマークを押すと進捗が更新される.

命名したスタック名のプロセスが完了したらOK. 

何かエラーになったプロセスがある場合は「根本原因を抽出」でどのプロセスがエラーの大元かわかる. 

成功した画面はこんな感じ

![スクリーンショット 2023-11-05 16.38.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1154503/455e3bb9-a0b9-378b-0fca-787defe88c1f.png)


## 作成した環境に接続　

### Private Keyの取得

サービス一覧からSystem Managerを選択し、左側のパラメータストアを選択

パラメータストアには先ほどcloudformationで作成したキーが格納されている

![スクリーンショット 2023-11-05 16.41.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1154503/17f0a0fd-a69d-db61-f473-7b3b7fb6e8f8.png)

キーを選択し、「値」の復号化された値を表示を押すと、private keyのテキストが表示される. 

![スクリーンショット 2023-11-05 16.43.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1154503/8f42590d-c469-8bc6-08a7-be60f8990b61.png)


グレーで隠しているが、左下の部分にprivate keyの値が格納されているので、
local pcの適当なpathに `key-for-ec2.pem`を作成して表示された中身を貼り付ける

```key-for-ec2.pem
-----BEGIN RSA PRIVATE KEY-----
表示された中身のの文字列
-----END RSA PRIVATE KEY-----
```
ここで注意なのは、そのままペーストすると、`-----BEGIN RSA PRIVATE KEY-----`と
`-----END RSA PRIVATE KEY-----`が改行されないので、ちゃんと改行する.

### EC2へ接続

EC2>Instanceで作成されたインスタンスを選択する. 選択したらpublic ipをコピーする. 

```bash
chmod 400 <path to key-for-ec2.pem>
ssh -i <path to key-for-ec2.pem> ubuntu@<public ip for ec2>
```

<path to key-for-ec2.pem>には先ほど作成したkeyのパスを<public ip for ec2>にはインスタンスのpubic ipをコピーする. 

接続すると、
```
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])?
```
と聞かれるのでyesとタイプしエンター. 
そうすると接続できているはず。

また、作成したbucketをaws s3 lsでみれていることを確認. 


## 課題事項

stackで作成したkey pairをいちいちコピーするのが面倒. 
downloadはできないっぽい. EC2 Instance Connectとかの方が良いのかも

あとこれstackが巨大になると大変そうだから、cloud formationをpythonで叩けるAWS CDKも試してみたい