CloudFormation で VPC の cidr を変更しようとしてみた

#内容#
CloudFormation で VPC の cidr を変更しようとハマった時のポイントについて記載する。

まず、そもそもの前提として VPC の プライマリの cidr を変更することはできない。(セカンダリとして拡張することは可能。)
参考：[Amazon VPC の IPv4 アドレス範囲を変更するにはどうすればよいですか?](https://aws.amazon.com/jp/premiumsupport/knowledge-center/vpc-ip-address-range/)

その前提で、 VPC、サブネットを CloudFormation から変更を試みるとどんな挙動になるのか。

*基本的にこの記事は、CloudFormation でエラーが表示されたときにどんな対処をすべきか考えていくためのものです。*


#構成#

*検証目的なので、リソースは半端です。実際にスタックを作成してみていただくことをお進めします。*

![スクリーンショット 2021-04-27 18.52.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280929/4156fda1-3dd9-5372-4780-e1def00429e1.png)

```yaml:
AWSTemplateFormatVersion: 2010-09-09
Description: cidr reduction

Resources:

#VPC1

  Vpc1:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 192.168.0.0/20
      EnableDnsSupport: true
      EnableDnsHostnames: true

#Subnet1

  Subnet1:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: !Select 
        - 0
        - !GetAZs
          Ref: AWS::Region
      CidrBlock: 192.168.1.0/24
      VpcId: !Ref Vpc1
  RouteAssociation1:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref RouteTable
      SubnetId: !Ref Subnet1

#Subnet2

  Subnet2:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: !Select 
        - 1
        - !GetAZs 
          Ref: AWS::Region
      CidrBlock: 192.168.2.0/24
      VpcId: !Ref Vpc1
  PriAssociation2:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref RouteTable
      SubnetId: !Ref Subnet2

#RouteTable

  RouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref Vpc1

#ELB

  elb1:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Scheme: internal
      SubnetMappings:
        - PrivateIPv4Address: 192.168.1.10
          SubnetId: !Ref Subnet1
        - PrivateIPv4Address: 192.168.2.10
          SubnetId: !Ref Subnet2
      Type: network
 
  Target1:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      HealthCheckEnabled: true
      HealthCheckPath: /health_check
      HealthCheckPort: 80
      HealthCheckProtocol: HTTP
      Name: elbtg
      TargetType: ip
      Port: 80
      Protocol: TCP
      VpcId: !Ref Vpc1
  PriNlbListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
        - Type: forward
          TargetGroupArn: !Ref Target1
      LoadBalancerArn: !Ref elb1
      Port: 80
      Protocol: TCP


#VPC2

  Vpc2:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsSupport: true
      EnableDnsHostnames: true
      
#VPCPeer

  VpcPeer:
    Type: AWS::EC2::VPCPeeringConnection
    Properties:
      VpcId: !Ref Vpc1
      PeerVpcId: !Ref Vpc2
  ToMainVpcRoute:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: 10.0.0.0/16
      RouteTableId: !Ref RouteTable
      VpcPeeringConnectionId: !Ref VpcPeer

#SG

  Sg1:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Sg
      GroupName: Sg
      SecurityGroupIngress:
            - CidrIp: 192.168.0.0/20
              FromPort: 80
              IpProtocol: tcp
              ToPort: 80
      VpcId: !Ref Vpc1

##VPCep

  EC2Vpce:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      PrivateDnsEnabled: true
      ServiceName: !Sub com.amazonaws.${AWS::Region}.ec2
      SecurityGroupIds:
        - !Ref Sg1
      SubnetIds:
        - !Ref Subnet1
        - !Ref Subnet2
      VpcEndpointType: Interface
      VpcId: !Ref Vpc1
```


#UPDATE_FAILED 1#

まずしないと思いますが、`VPC1` `Subnet1` `Subnet2` `ELB` の Cidr を別のネットワークに変更してみた。

次のような理由で、UPDATE_FAILED となった。

`CloudFormation cannot update a stack when a custom-named resource requires replacing. Rename XXXXXXXXXXXX and update the stack again.`

次のページに書かれている内容を確認してみる。
[AWS CloudFormation の「Cannot update a stack when a custom-named resource requires replacing」エラーを解決する方法を教えてください。](https://aws.amazon.com/jp/premiumsupport/knowledge-center/cloudformation-custom-name/)

まず、CloudFormation の基本的な動作として、
 
- CloudFormation によりリソースの置き換えが発生する場合は、まず置き換え用のリソースを作成し、その後で既存のリソースを削除する動作となっている。
- そのため、一時的に新旧 2 つのリソースが同時に並存する形となり、リソース名の重複が許されないリソースについては、既存のリソースが存在している状態で置き換え用のリソースを作成することができない。

今回、[カスタム名](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-name.html)を利用しているリソースは置き換えが必要であり、いずれかの変更が必要である。

-  カスタム名は必須でないので、そもそも利用しない。
-  リネームする。

今回は後者のリネームで対処してみる。
(例)
      Name: elbtg
↓
      Name: elbtg２

#UPDATE_FAILED 2#
UPDATE_FAILED 1 は解消されたようだが、次は別の問題が記録された。

`Resource handler returned message: "The following target groups are in a different VPC than load balancer`

ロードバランサーが属する VPC と、ロードバランサーに指定したターゲットグループの VPC が異なるためエラーが発生している。

これは、VPC、サブネットの cidr の変更に伴い、VPC、サブネットは置き換えが発生した。

- 置き換えが発生しないロードバランサーは、旧VPCに属する。
- 置き換えが発生するターゲットグループは新VPCに属する。

ということになる。

変更セットから、置Z換えが発生するかは確認可能であり、この辺りの考慮が甘かった。
![スクリーンショット 2021-04-25 17.02.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280929/5e97a52f-0c6d-533b-c1f8-c76c6d58ea3e.png)

- CloudFormation からスタックを更新する場合は、変更セットを利用する。
- ドキュメントで置き換えが発生するか`(Update requires: Replacement)`確認する。

ことが重要だ。

明示的に置き換えが発生するようにロードバランサーのプロパティに [Name](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-elasticloadbalancingv2-loadbalancer.html)  を入れてみた。

変更セットを確認してみると今度はロードバランサーも置き換えられるようだ。

![スクリーンショット 2021-04-25 17.18.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280929/49a59772-3d3c-4355-8908-96528450abea.png)

これでうまくいった、というよりも VPCが新しく作成されると、まるっと入れ替えになるので、やる人も考える人もいないと思うので、トラブルシュートの考え方とし書いておいた。
![スクリーンショット 2021-04-25 17.44.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280929/e695865b-d848-5bef-d600-34e858e5fb31.png)