CloudFormation超入門

# What's CloudFormation?

AWSのクラウドリソースをコードで表現してプロビジョニングできるようにする仕組み。
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/Welcome.html

要するに Infrastructure as Code（IaC）。記述形式は JSON or YAML。
似たようなサービスには terraform, cdk などがある。

# 利点

- インフラストラクチャをコードとして扱える
- コードとして扱えればメンテナンス性が向上する
- ちょっと頑張ればデプロイを自動化できる

# 主なテンプレート解説

超入門なので一部割愛。
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/template-anatomy.html

```yaml
# フォーマットのバージョン(yyyy-mm-dd)を指定する
AWSTemplateFormatVersion: "version date"

# Stack 作成・更新時に受け取る値
Parameters:
  set of parameters

# リソースの定義（EC2,RDS...etc
Resources:
  set of resources

# リソースの生成結果等をエクスポートする
Outputs:
  set of outputs
```

# Sample

### 事前準備

あとで動作確認するためにEC2にSSHしたいので KeyPair を事前に作っておくこと。
https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-services-ec2-keypairs.html

Stack作るときにも参照する。

### やること

- VPC作成
- VPC内にEC2を立ち上げてインターネットに接続できるようにする
- EC2に強めの Instance Profile をアタッチする
- なんとなくS3

### モノリシックな定義

※!Ref と Ref: が混在してるけど同じなので気にしない。

```yaml:main.yaml
AWSTemplateFormatVersion: "2010-09-09"

Parameters:
  KeyPairParam:
    Type: AWS::EC2::KeyPair::KeyName
    Default: thorikoshi
    
  ImageId:
    Type: AWS::EC2::Image::Id
    Default: ami-0064e711cbc7a825e
    
  InstanceTypeParam:
    Type: String
    Default: t2.micro
    AllowedValues:
      - t2.micro
      - m1.small
      - m1.large
    Description: Enter t2.micro, m1.small, or m1.large. Default is t2.micro.
    
  S3BucketNameParam:
    Type: String
    Default: thorikoshi

Resources:
  MyVPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: "10.0.0.0/16"
      InstanceTenancy: "default"
      Tags:
        - Key: Name
          Value: MyVPC

  MyInternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: "MyInternetGateway"

  MyVpcGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId: !Ref MyInternetGateway
      VpcId: !Ref MyVPC

  MyRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref MyVPC
      Tags:
        - Key: Name
          Value: "MyRouteTable"

  MyRoute:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref MyRouteTable
      DestinationCidrBlock: "0.0.0.0/0"
      GatewayId: !Ref MyInternetGateway

  MySubnet:
    Type: AWS::EC2::Subnet
    Properties:
      MapPublicIpOnLaunch: True
      AvailabilityZone: "ap-northeast-1a"
      CidrBlock: "10.0.1.0/24"
      VpcId: !Ref MyVPC
      Tags:
        - Key: Name
          Value: MySubnet

  MySubnetRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref MyRouteTable
      SubnetId: !Ref MySubnet

  MySecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: MySecurityGroup
      GroupName: MySecurityGroup
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: 0.0.0.0/0
      SecurityGroupEgress:
        - IpProtocol: -1
          FromPort: -1
          ToPort: -1
          CidrIp: 0.0.0.0/0
      Tags:
        - Key: Name
          Value: MySecurityGroup
      VpcId: !Ref MyVPC

  MyRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: MyRoleToInstanceProfile
      AssumeRolePolicyDocument:
        Statement:
          - Effect: "Allow"
            Action: "sts:AssumeRole"
            Principal:
              Service:
                - "ec2.amazonaws.com"
      Policies:
        - PolicyName: MyPolicy
          PolicyDocument:
            Statement:
              - Sid: MyUserStmt
                Effect: Allow
                NotAction: "iam:*"
                Resource: "*"

  MyAccessPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: MyAccessPolicy
      PolicyDocument:
        Statement:
          - Sid: MyRoleStmt
            Effect: Allow
            Action: "*"
            Resource: "*"
      Roles:
        - Ref: MyRole

  MyInstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Path: "/"
      Roles:
        - Ref: MyRole

  MyInstance:
    Type: AWS::EC2::Instance
    Properties:
      IamInstanceProfile: !Ref MyInstanceProfile
      ImageId:
        Ref: ImageId
      InstanceType:
        Ref: InstanceTypeParam
      SecurityGroupIds:
        - Ref: MySecurityGroup
      SubnetId: !Ref MySubnet
      KeyName:
        Ref: KeyPairParam
      Tags:
        - Key: Name
          Value: MyInstance

  MyEIP:
    Type: AWS::EC2::EIP
    Properties:
      InstanceId: !Ref MyInstance

  MyS3:
    Type: AWS::S3::Bucket
    Properties:
      BucketName:
        Ref: S3BucketNameParam
```

### Stack の作成

```bash
$ aws cloudformation create-stack \
    --stack-name {{ Your stack name }} --template-body file://$PWD/main.yaml \
    --parameters ParameterKey=S3BucketNameParam,ParameterValue={{ Your  bucket name }}
    --capabilities CAPABILITY_NAMED_IAM
```

もしくは

```bash
aws cloudformation deploy \
    --template $PWD/main.yaml \
    --stack-name {{ Your stack name }} \
    --capabilities CAPABILITY_NAMED_IAM
```

もしくは Web コンソールから...。

### 動作確認

```bash
$ ssh -i /path/to/secret-key.pem ec2-user@{{ Created EIP }}

$ aws s3 ls
// Output your backet name
```

# Stack 間のリソース参照

### Cross Stack Reference

Outputs で Export した結果を参照する。
以下は Export した IAM Role を使用して Instance Profile を定義する Stack 間参照

#### IAM Role の Stack

```yaml:role.yaml
AWSTemplateFormatVersion: "2010-09-09"

Resources:
  MyRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: MyRole
      AssumeRolePolicyDocument:
        Statement:
          - Effect: "Allow"
            Action: "sts:AssumeRole"
            Principal:
              Service:
                - "ec2.amazonaws.com"
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonRoute53FullAccess
        - arn:aws:iam::aws:policy/AmazonS3FullAccess
  MyFullAccessPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: MyFullAccessPolicy
      PolicyDocument:
        Statement:
          - Sid: MyRoleStmt
            Effect: Allow
            Action: "*"
            Resource: "*"
      Roles:
        - Ref: MyRole

# Instance Profile の Name を Export
Outputs:
  MyRoleName:
    Description: The name of My Role
    Value:
      Ref: MyRole
    Export:
      Name: MyRole:NAME
```

#### Instance Profile の Stack

```yaml:ec2.yaml
AWSTemplateFormatVersion: "2010-09-09"

Resources:
  # !importValue KeyName で参照
  MyProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Path: "/"
      Roles:
        - !ImportValue MyRole:NAME
```

#### Note

試してないがパラメータストアにあらかじめ登録された値を参照するという作戦もあるらしい。
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/dynamic-references.html

# テンプレートの再利用

### AWS::CloudFormation::Stack

`AWS::CloudFormation::Stack` というリソース定義がある。
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-stack.html

S3にテンプレート配置しておいてそれを Stack の定義として再利用できる仕組み。
分割した Stack の統合が可能になる。

# あと始末

作成した Stack はいらないなら消しましょう（差もないと請求が来るでしょう）。

```bash
$ aws cloudformation delete-stack --stack-name {{ Your stack name }}
```

# 終わり