CloudFormation Tips for つまづきやすいところ

#何この記事
CloudFormationでそれなりな規模の構成を作成することになったので、困って調べて分かったことをつらつらと書いていって後で見直せるようにした記事。なので自分用メモの色が強いです。が、たまたま記事を見て解決になったら良いかなともちょっと思います。
そんな性質の記事なのでふとした瞬間に増えたり減ったりします。

#CloudFormationとは
AWSの公式ドキュメントかQiitaでCloudFormationタグを漁ってください

#Tips

## スタックを小分けにする際の1案
今はいくつか方法がありますが、1案として。

- 共通のIDを決定し、Parametersに設定してスタック作成時に入力をする
 - 同じスクリプトを使って2つ以上の環境を作成する際、共通のIDを各インスタンスの名前に利用すると楽
- 他のスタックで必要そうな情報を`Outputs`に記載してエクスポート
- 他のスタックでエクスポートした情報は`Fn::ImportValue`と`!Sub`を用いてインポート

**例** VPC作成

```
Parameters:
  ProjectId:
    Description: "Project name id."
    Type: String
    AllowedPattern: '^[a-zA-Z0-9-/:-@\[-\`\{-\~]+$'
    ConstraintDescription: "InvalidValue[ProjectId]"

Resources:
  MyVPC:
    Type: "AWS::EC2::VPC"
    Properties:
      CidrBlock: "10.0.0.0/16"
      EnableDnsSupport: true
      EnableDnsHostnames: true
      InstanceTenancy: "default"
      Tags:
        - Key: "Name"
          Value: !Sub "${ProjectId}-vpc"

Outputs:
  MyVPC:
    Value: !Ref MyVPC
    Export:
      Name: !Sub "${ProjectId}-vpc"
```

**例** SecurityGroup作成

```
Parameters:
  ProjectId:
    Description: "Project name id."
    Type: String
    AllowedPattern: '^[a-zA-Z0-9-/:-@\[-\`\{-\~]+$'
    ConstraintDescription: "InvalidValue[ProjectId]"

Resources:
  MySecurityGroup:
    Type: "AWS::EC2::SecurityGroup"
    Properties:
      VpcId: {"Fn::ImportValue": !Sub "${ProjectId}-vpc"}
      GroupName: !Sub "${ProjectId}-sg"
      GroupDescription: "pass ssh from specific IP"
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: "x.x.x.x/32"
      Tags:
        - Key: Name
          Value: !Sub "${ProjectId}-sg"
```

## Parametersとの文字結合
`Fn::Join`の方がネット上のノウハウ的に多いけど、せっかく新しい`Fn::Sub`ができたんだし使っていきましょ

**`Fn::Sub`**
`${Param}`で`Param`パラメータの文字列が展開される。
複雑な文字結合の場合はListを作成すると楽。
1つ目に、生成する文字列
2つ目に、生成する文字列に対してマッピングする変数定義
(たぶん例を見た方が早い)

例
**簡単な結合**
`MyParam=testparam`と設定した状態で`testparam-instance`という文字列を作成

```
!Sub "${MyParam}-instance
```

**複雑な結合**
`DBEngine=postgres` `DBVersion=9.6.3`と設定した状態で`postgres9.6`という文字列を作成

```
!Sub
  - ${DBEngine}${DBVersion1}.${DBVersion2}
  - DBEngine: !Ref DBEngine
    DBVersion1: !Select [0, !Split [".", !Ref DBVersion] ]
    DBVersion2: !Select [1, !Split [".", !Ref DBVersion] ]
```
Joinの場合より可読性も高い（と思う）

## Policy作る時は基本的には管理ポリシーを作成
CFnに限った話ではないですが、インラインポリシーではなく管理ポリシーを作りましょうという話。

**インラインポリシー : `AWS::IAM::Policy`**
Role/User/Groupと1対1の関係。作成した後に別のエンティティにアタッチできない
**管理ポリシー : `AWS::IAM::ManagedPolicy`**
ポリシーは単体で存在し、複数のエンティティにアタッチできる

##IAMRoleをEC2につけたい
InstanceProfileを作成してIAMRoleとEC2をつなげる必要がある。
AWSコンソールからはInstanceProfileを意識せずアタッチできるので気づきにくい。

**例** (抜粋 parametersで指定して!Refで参照してるところもあります)

```
Resources:
  MyRole:
    Type: "AWS::IAM::Role"
    Properties:
      RoleName: "my-role"
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal:
              Service:
               - "ec2.amazonaws.com"
            Action: "sts:AssumeRole"
      Path: "/"

  MyInstanceProfile:
    Type: "AWS::IAM::InstanceProfile"
    DependsOn: MyRole
    Properties:
      InstanceProfileName: "my-instance-profile"
      Path: "/"
      Roles:
        - !Ref MyRole

  MyEC2:
    Type: "AWS::EC2::Instance"
    DependsOn: MyInstanceProfile
    Properties:
      ImageId: !Ref MyEC2AmiId
      IamInstanceProfile: !Ref MyInstanceProfile
      NetworkInterfaces:
        - AssociatePublicIpAddress: true
          DeviceIndex: "0"
          PrivateIpAddress: "10.0.0.250"
          SubnetId: !Ref MySubnet
          GroupSet:
            - !Ref MySecurityGroup
      InstanceType: !Ref MyEC2Type
      KeyName: !Ref EC2KeyPair
      Tags:
        - Key: "Name"
          Value: "MyEC2"
```

## RDSのセキュリティグループ
`AWS::RDS::DBInstance`のPropertiesにセキュリティグループ関連のものは二つ

- `DBSecurityGroups`
- `VPCSecurityGroups`

ちなみにどちらも指定することはできない。

**`DBSecurityGroups`**
DB用に独自にSecurityGroupを作成して反映したい場合

**`VPCSecurityGroups`**
`AWS::EC2::SecurityGroup`をアタッチする場合
こちらの方が利用シーンは多そう

## DBParameterGroup, DBSubnetGroupのName
- `AWS::RDS::DBParameterGroup`
- `AWS::RDS::DBSubnetGroup`

の名前が現時点では設定できなさそう・・・？
AWSのドキュメントにも名前を設定するプロパティはなさげ。
aws-cliで作成する場合は名前設定する用のプロパティあるのに。

ちなみに作成すると論理IDとランダム文字列が結合した名前が生成されて設定されます。

## ELBのLogを保管する先のS3のBucketPolicy
これもCFnに限った話ではないですが。
ELBのログ出力を設定する場合(`AccessLoggingPolicy`プロパティにて設定)、S3にログファイルを吐くのは特定のAWSアカウントなので、それを許可するようS3のBucketyPolicyを設定する必要があります。
詳しくはこのあたり
[AWS - Classic Load Balancer のアクセスログの有効化 - ステップ 2: S3 バケットにポリシーをアタッチする](http://docs.aws.amazon.com/ja_jp/elasticloadbalancing/latest/classic/enable-access-logs.html#attach-bucket-policy)

**例** (抜粋, ap-northeast-1リージョンの場合)

```
Resources:
  ELBLogS3:
    Type: "AWS::S3::Bucket"
    Properties:
      BucketName: "hogehoge-server-log"

  ELBLogS3BucketPolicy:
    Type: "AWS::S3::BucketPolicy"
    DependsOn: ELBLogS3
    Properties:
      Bucket: !Ref ELBLogS3
      PolicyDocument:
        Version: "2012-10-17"
        Id: "ELBLogS3BucketPolicy"
        Statement:
          - Sid: "ELBLogWriteAccess"
            Action:
              - "s3:PutObject"
            Effect: "Allow"
            Resource: !Sub "arn:aws:s3:::${ELBLogS3}/AWSLogs/${AWS::AccountId}/*"
            Principal:
              AWS: "arn:aws:iam::582318560864:root"
```

#### 以降、何かあったら追記予定