入門 AWS CloudFormation #5（条件設定編）

インフラやクラウドの専門というわけではないのですがそろそろIaC周りもちゃんと勉強しておきたい・・・という感じなのでAWSのCloudFormationについて入門しつつ復習として記事にまとめておきます。

※とりあえず最初ということでごく基本的なところを中心に記事を書いていきます。

# 注意事項と前記事までの振り返り

本記事の処理を動かすとEC2関係などで色々と追加になったり起動したりします。その辺はリソースの停止や削除などをしないとお金がかかったりしてくる可能性があるためご注意ください。

また、本記事は5記事目となります。前回までの内容を踏襲していく形となっているのでご注意ください。

1記事目:

https://qiita.com/simonritchie/items/330391e741f394897550

2記事目:

https://qiita.com/simonritchie/items/a1922199a5b6d131dca9

3記事目:

https://qiita.com/simonritchie/items/8b1c3046474ca7c6863c

4記事目:

https://qiita.com/simonritchie/items/5163abaf516902a55f30

# 本記事で触れること

各種条件分岐設定（`Conditions`や`Condition`設定など）や分岐設定用の各種関数（`Fn::Equals`や`Fn::If`など）について触れていきます。

# 条件設定の機能の概要と出来ること

- CloudFormation条件設定ではパラメータなどを参照して条件を満たす場合 / 満たさない場合でテンプレートの挙動を分岐させることができる機能です。
- 特定の条件を満たした場合に一部のリソースを作成するようにしたり、リソースに設定する属性値を変動させたり、出力値を分岐させたりすることができます。

# 条件設定の基本的な書き方

条件設定の各種条件部分は`Conditions`セクションに書きます。以下のような書き方になります。

```yaml
Conditions:
  <条件の任意の論理名>:
    <条件判定用のFn::Equalsなどの関数指定>
```

例えばElastic IPアドレスを作成するかどうかという条件を定義したい場合で、条件の論理名を`CreateElasticIPCondition`として条件がパラメータに指定された環境が本番環境の場合としたい場合以下のようになります（パラメータ名が`Environment`、本番の場合のパラメータを`Prd`とします）。`Fn::Equals`（短縮系は`!Equals`）関数とかは後の節で詳しく触れます。

```yaml
Conditions:
  CreateElasticIPCondition:
    !Equals [!Ref Environment, Prd]
```

この作成した条件を参照してリソースを作成するかどうかを指定したい場合には以下のようになります。

```yaml
Resources:
  <リソースの任意の論理名>:
    Type: <リソースのタイプ>
    Condition: <対象条件の論理名>
    ...
```

上記のケースでは`Condition`部分には`Conditions`セクション内で定義した論理名を指定します（前述の例で言うと`CreateElasticIPCondition`など）。この場合`Condition`で指定した条件を満たした場合にのみこのリソースが作成されます。

例えば条件を満たしたら`MyPrdElasticIP`という論理名のElastic IPアドレスのリソースを作成する（条件には`CreateElasticIPCondition`を指定）・・・みたいな形にしたい場合以下のような記述になります。

```yaml
...
Resources:
  ...
  MyPrdElasticIP:
    Type: AWS::EC2::EIP
    Condition: CreateElasticIPCondition
    Properties:
      ...
```

実際に変更セットを作成・反映してみて確認してみます。パラメータには開発環境（`Dev`）もしくは本番環境（`Prd`）かを選択する`Environment`という論理名のものを用意し、もしそのパラメータが本番環境の`Prd`である場合にElastic IPアドレスのリソースを作成しEC2インスタンスのリソースに設定する・・・という形でYAMLを書きます。

```yaml
AWSTemplateFormatVersion: "2010-09-09"
Parameters:
  Environment:
    Type: String
    AllowedValues:
      - Dev
      - Prd
Conditions:
  CreateElasticIPCondition:
    !Equals [!Ref Environment, Prd]
Resources:
  MyEC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: ami-0590f3a1742b17914
      InstanceType: t3.nano
  MyElasticIP:
    Type: AWS::EC2::EIP
    Condition: CreateElasticIPCondition
    Properties:
      InstanceId: !Ref MyEC2Instance
```

YAMLをアップロードして変更セットを反映する前にEC2のElastic IP アドレスのページを確認しておきます。現時点では以下のように1つもElastic IP アドレスが存在しない形になっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/2930b4db-2416-8804-e273-7889d13210f8.png)

変更セットをアップロードして反映していきます。

パラメータの`Environment`の値はまずは`Dev`を選択します。この場合`Conditions`のCreateElasticIPConditionの条件を満たさなくなるのでElastic IP アドレス関係のリソースは作成されなくなります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/a0124ec6-7ec7-102e-0249-f9fcd092fa8f.png)

反映してイベントが`UPDATE_COMPLETE`になるまで待った後に確認してみてもElastic IP アドレス関係のイベントは発生していないことが確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/cc09e9cf-2321-78cd-c9c8-b15104d85c59.png)

EC2のElastic IP アドレスのページを見てみても特に追加などはされていないことが確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/bbd7009f-f488-de71-fbc8-dba87075adcf.png)

続いて同じYAMLテンプレートで、`Environment`のパラメータだけ`Prd`に変更してみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/23393666-e81f-19d3-f00a-4fa1f7073ce9.png)

変更セットの確認画面でもElastic IP アドレス関係のアクションがAddになっていることを確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/20638fbe-d9b7-6d7d-e691-1cece593399d.png)

反映後のイベント画面でも各種Elastic IP アドレス関係のものが実行されていることが確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/6eb2dbb0-51d8-4b21-ae3a-75a171741cd1.png)

EC2のElastic IP アドレスのページを見てみると正常に追加がされていることが確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/ed12a608-0a36-227d-ccfc-5961b983e854.png)

EC2インスタンスのページで対象のインスタンスを確認してみても追加となったElastic IP アドレスが反映されていることが確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/ef966ae2-b4a7-ddb3-ff9b-0667294dbe64.png)

このように同じYAMLテンプレートでありながら`Conditions`の設定による分岐でリソースを生成を分岐させることができました。

## 分岐条件によってリソースが作成されなくなった場合の挙動

既に作成済みのリソースが選択したパラメータなどによって作成されなくなった場合にどうなるのかを見てみます。

前節で使ったYAMLテンプレートとスタックをそのまま使用していきます。再度`Environment`のパラメータに`Dev`を選択します。前回は`Prd`でElastic IP アドレスが作成する条件になっていたものが反映されていますが今度はそのリソースが作成されなくなります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/1508fb7f-9297-507a-2b33-9d50c9d8f25f.png)

変更セットを作成して確認してみると対象のElastic IP アドレスが削除（Remove）される形になりました。どうやら作成済みのリソースが分岐条件によって作成されなくなった場合にはそのまま残る形ではなく削除されるようです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/3b1a5177-0ac0-cc6e-47de-03eafc3ce217.png)

反映してみるとElastic IP アドレスなどが削除されていることが確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/6e1886f3-ab6a-5840-4f36-3f5a879e5752.png)

EC2のElastic IP アドレスのページを確認してみても一覧から消えていることが確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/4a05c27f-5263-b86c-e711-5b0d633a71b0.png)

# `Fn::Equals`関数

前節までで使ってきましたが`Fn::Equals`関数はリスト内の2つの値が等しいかどうかの真偽値を取得する関数です。一致していればtrue、一致していなければfalseの真偽値を返します。主に`Conditions`セクションなどで使います。

値は2つのインデックスを持つリストで指定して、比較用の各値を2つのインデックス位置に設定します。

例えば`Fn::Equals [<1つ目の値>, <2つ目の値>]`といったように書きます。短縮系では`!Equals [<1つ目の値>, <2つ目の値>]`といった感じになります。

前節までで`!Equals`関数を使って変更セットの作成などまで対応しているのでこの節ではその辺は省略しますが、例えば`Environment`というパラメータが`Prd`かどうかという条件であれば以下のような記述となります。

```yaml
Conditions:
  CreateElasticIPCondition:
    !Equals [!Ref Environment, Prd]
```

# `Fn::And`関数

`Fn::And`関数はリスト内の各インデックスの条件（`Condition`）の真偽値が全てtrueであればtrueとなる関数です。3つ以上の条件を指定することもできます。基本的に真偽値を得るために`Fn::Equals`関数などをリスト内に入れる形となります。また、`Conditions`セクションで定義されている条件単体（`Condition`）の値もドキュメントを見ている感じ`!Condition`という記述で指定できるようです（`Fn::Condition`という関数があるのだろうか？と軽く検索してみたのですがヒットしなかったのでこの場合は`!Condition`の短縮系のみある感じでしょうか・・・？）。

例えば以下のような記述になります。

```yaml
Fn::And:
  - Fn::Equals: [<1つ目の値>, <2つ目の値>]
  - !Condition <定義されている条件の論理名>
```

短縮系では`!And [<各条件の値>]`という形です。以下のような記述となります。

```yaml
!And
  - !Equals [<1つ目の値>, <2つ目の値>]
  - !Condition <定義されている条件の論理名>
```

試しとして雑ですが以下のYAMLで`UserName`というユーザー名選択用のパラメータと環境選択用の`Environment`という2つのパラメータを用意しました。また、環境が本番（`Prd`）且つ対象ユーザーがB（`UserB`）の場合にElastic IP アドレスのリソースを設定する（リソースでその辺を作成する）という条件を指定しています。

お試しなので`!And`関数内では`!Equals`と`!Condition`の関数を両方使う形で設定してみます。

```yaml
AWSTemplateFormatVersion: "2010-09-09"
Parameters:
  Environment:
    Type: String
    AllowedValues:
      - Dev
      - Prd
  UserName:
    Type: String
    AllowedValues:
      - UserA
      - UserB
Conditions:
  IsPrdEnv:
    !Equals [!Ref Environment, Prd]
  IsUserBAndPrdEnv:
    !And
      - !Equals [!Ref UserName, UserB]
      - !Condition IsPrdEnv
Resources:
  MyEC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: ami-0590f3a1742b17914
      InstanceType: t3.nano
  MyElasticIP:
    Type: AWS::EC2::EIP
    Condition: IsUserBAndPrdEnv
    Properties:
      InstanceId: !Ref MyEC2Instance
```

変更セットの作成や反映をしていきます。試しにまずは環境のパラメータは`Prd`、ユーザー名のパラメータは`UserB`を選択します。環境とユーザー名両方の条件で`!And`関数の条件を満たすためElastic IP アドレスのリソースが作成される想定です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/db8039df-34b8-384b-8604-1a8be96ead88.png)

変更セットの変更内容を見てみるとElastic IP アドレス関係が追加される表示になっていることを確認できます。反映前に`!And`の条件の結果がどうなるかとかが見れるのは良いですね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/27758bda-696a-a5f2-158d-b3ac7daad820.png)

反映後のイベントもElastic IP アドレス関係の更新が走っていることが確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/27936a26-8d12-1a5b-9fa2-27f1a8c0b701.png)

今度は同じテンプレートで再度変更セットを作成して環境のパラメータは`Dev`の方を選択します。`!And`関数の条件を満たさなくなるためElastic IP アドレスのリソースが作成されず削除される想定です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/9e169e1c-5479-b210-0b7a-f2876540e553.png)

変更セットの変更内容の確認画面では想定通りElastic IP アドレスのアクションがRemoveになっていることが確認できます（そのまま変更セットを反映しておきます）。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/bccd396a-8759-de9c-e17a-817e979c0085.png)

# `Fn::Or`関数

`Fn::And`関数があるなら`Or`関係の関数もあります・・・ということで`Fn::Or`関数について触れていきます。

書き方的には`Fn::And`関数と同じような形でリストで複数の条件（`Condition`として定義したものや`Fn::Equals`関数など）を以下のように指定します。

```yaml
Fn::Or:
  - Fn::Equals: [<1つ目の値>, <2つ目の値>]
  - !Condition <定義されている条件の論理名>
```

短縮系では`!Or [<各条件>]`といった具合で以下のようになります。

```yaml
!Or
  - Fn::Equals: [<1つ目の値>, <2つ目の値>]
  - !Condition <定義されている条件の論理名>
```

試しに以下のようにYAMLテンプレートを調整してみます。

- 環境のパラメータ（`Environment`）にステージングとして`Stg`の選択肢を追加します。
- `Conditions`に`IsPrdOrStgEnv`という名前の条件で選択されパラメータが`Prd`もしくは`Stg`かどうかという条件を追加しました。
- もし`IsPrdOrStgEnv`の条件が満たされていればElastic IP アドレスのリソースが生成・設定されるようにしています。

```yaml
AWSTemplateFormatVersion: "2010-09-09"
Parameters:
  Environment:
    Type: String
    AllowedValues:
      - Dev
      - Stg
      - Prd
Conditions:
  IsPrdOrStgEnv:
    !Or
      - !Equals [!Ref Environment, Prd]
      - !Equals [!Ref Environment, Stg]
Resources:
  MyEC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: ami-0590f3a1742b17914
      InstanceType: t3.nano
  MyElasticIP:
    Type: AWS::EC2::EIP
    Condition: IsPrdOrStgEnv
    Properties:
      InstanceId: !Ref MyEC2Instance
```

変更セットを作成し、まずはパラメータで`Prd`を選択してみます。Elastic IP アドレスのリソースが作成・設定される想定です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/3bad700a-7eec-697e-979c-095deb599134.png)

変更セットの変更内容の確認画面では想定通りElastic IP アドレスが設定される形になっています（このまま反映します）。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/0e45d456-b212-828f-9506-3e30ec4caaef.png)

続いてYAMLテンプレートはそのままで今度は環境のパラメータで`Dev`を選択します。Elastic IP アドレスが削除される想定です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/338d4be3-21eb-377c-3638-69e9fbe381f7.png)

変更確認画面でElastic IPアドレスのアクションが想定通りRemoveになっていることが確認できます（このまま反映します）。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/0ca6b85d-89ad-4c9f-0fd8-c2a541939889.png)

最後に環境のパラメータで`Stg`を選択します。`!Or`関数的にこの場合もElastic IP アドレスのリソースが作成・設定される想定です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/a6e841e2-65fd-fa6e-79fa-5d646c9d796b.png)

確認画面で想定通りElastic IP アドレスのアクションがAddになっていることが確認できました（このまま反映しておきます）。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/ae33a3b2-4476-0040-c927-f17501e82618.png)

# `Fn::Not`関数

`Fn::Not`関数では指定された条件（`Condition`）の逆の真偽値を取得します。例えばtrueとなる条件であればfalse、falseであればtrueの真偽値を取得できます。

書き方としては`Fn::Not: [<条件の指定>]`といった具合になります。短縮系では`!Not [<条件の指定>]`となります。

条件部分には`Fn::Equals`関数や`Conditions`のセクションに定義した条件などを指定します。例えば`!Not [!Equals [!Ref Environment, Prd]]`といった記述となります。

以下の例では環境のパラメータ（`Environment`）で開発環境（`Dev`）以外を選択したかどうかという条件で`IsNotDevEnv`という名前で`Conditions`セクションに条件を追加しています。条件は`!Not`関数を使って`!Not [!Equals [!Ref Environment, Dev]]`としています。

また、もし該当の条件を満たす場合にはElastic IP アドレスの設定がEC2に設定されるようにしています。

```yaml
AWSTemplateFormatVersion: "2010-09-09"
Parameters:
  Environment:
    Type: String
    AllowedValues:
      - Dev
      - Stg
      - Prd
Conditions:
  IsNotDevEnv:
    !Not [!Equals [!Ref Environment, Dev]]
Resources:
  MyEC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: ami-0590f3a1742b17914
      InstanceType: t3.nano
  MyElasticIP:
    Type: AWS::EC2::EIP
    Condition: IsNotDevEnv
    Properties:
      InstanceId: !Ref MyEC2Instance
```

変更セットの作成と反映を行っていきます。まずはパラメータで`Dev`を選択し、条件がfalseとなってElastic IP アドレスが反映されない想定で進めます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/fcd4ab40-af43-0837-3ab1-55f5fa3c701d.png)

変更セットの変更内容を見ると、想定通りElastic IP アドレスがRemoveになっていることが確認できます（そのまま反映します）。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/d994c14c-e3c9-70e8-af06-8a367bf3b7df.png)

続いてもう一度同じYAMLテンプレートを追加して、環境のパラメータに`Prd`を選択します。`Dev`以外が選択されたので条件がtrueとなるのでElastic IP アドレスが追加される想定です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/d22fe9a4-ae44-b85b-a4c2-a8e763556788.png)

変更確認画面で想定通り条件がtrueとなりElastic IP アドレスが追加になっていることが確認できました（そのまま反映します）。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/23a6734c-cb2a-493e-1d06-70e05e52d395.png)

# `Fn::If`関数

`Fn::If`関数は条件を満たした場合と満たさない場合のそれぞれの値の分岐を設定できる関数です。特定の属性部分などで使用します。

書き方としては`Fn::If: [<条件の論理名>, <真の時に設定する値>, <偽の時に設定する値>]`といったように値はリストで指定し、最初のインデックスには`Conditions`セクションで定義した条件の論理名、2番目のインデックスには条件を満たす場合に設定する値、3番目のインデックスには条件を満たさない場合に設定する値の3つを指定します。

短縮系では`!If [<条件の論理名>, <真の時に設定する値>, <偽の時に設定する値>]`という書き方になります。

以下の例では環境選択用の`Environment`というパラメータを設けて、選択されたパラメータが開発環境（`Dev`）かどうかの条件（`IsDevEnv`）を`Conditions`セクションに設けています。

さらに、その`IsDevEnv`の条件を満たした場合にはEC2インスタンスのインスタンスタイプを`t3.nano`、満たさない場合には`t3.micro`に値を分岐するようにしています。

```yaml
AWSTemplateFormatVersion: "2010-09-09"
Parameters:
  Environment:
    Type: String
    AllowedValues:
      - Dev
      - Prd
Conditions:
  IsDevEnv:
    !Equals [!Ref Environment, Dev]
Resources:
  MyEC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: ami-0590f3a1742b17914
      InstanceType: !If [IsDevEnv, t3.nano, t3.micro]
```

変更セットを作って試してみます。まずはパラメータには`Prd`を選択します（条件を満たさないのでインスタンスタイプが`t3.micro`になる想定）。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/039130e1-d7e3-9e01-bfb6-1d26857dcfd4.png)

反映後のイベントが完了してからEC2インスタンスのページに遷移してみるとインスタンスタイプが想定通り`t3.micro`になっていることを確認できます。

続いて同じYAMLテンプレートでパラメータで`Dev`を選択してみます。条件を満たすのでインスタンスタイプが`t3.nano`になる想定です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/1e26b862-d548-be77-e614-fe2c1f28430c.png)

再度EC2インスタンスのページを開いて見ると想定通りインスタンスタイプが`t3.nano`になっていることが確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/d5422310-b5be-278a-7275-efbbeb2ec5f8.png)

# 参考文献・参考サイト・参考講座まとめ

https://www.udemy.com/course/aws-cloudformation-simplified-hands-on-learning/

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/conditions-section-structure.html

https://oreout.hatenablog.com/entry/aws/cloudformation/6

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-conditions.html