Help us understand the problem. What is going on with this article?

CloudFormation Resource Importで既存のスタックをリファクタリング!

はじめに

CloudFormation で全世界待望?のリソースインポート機能がリリースされました。

AWS CloudFormation Launches Resource Import
https://aws.amazon.com/jp/about-aws/whats-new/2019/11/aws-cloudformation-launches-resource-import/

CloudFormationで管理されていないリソースをスタックに取り込むことができるようになったわけですが、
このリソースインポート機能を活用することで既存のスタック間でリソースを移動したり
複雑化したスタックのリファクタリングを行ったりすることもできます。

スタック分割を例に考える

既存のスタックを分割しようとする場合、概ね以下のような流れとなります。

  1. ソーステンプレート内の分割対象のリソースに対し DeletionPolicy: Retain を指定し、適用する
  2. 分割対象のリソースをソーステンプレートから削除し、ターゲットテンプレートに追加する
  3. 既存スタックを更新して分割対象のリソースを削除(管理対象外)にする
  4. ターゲットテンプレートを使用して、管理対象外となった分割対象リソースをインポートする

ソーステンプレート: 既存スタックのテンプレート
ターゲットテンプレート: 分割する新しいスタックのテンプレート

やってみる

1つのテンプレートでVPCとEC2を一緒に管理していて、それぞれを別スタックに分割する例を考えてみます。
検証目的であるため、ここでは以下のようなシンプルなテンプレートで試してみます。

source.yaml
AWSTemplateFormatVersion: '2010-09-09'
Description:
  split stack test

Parameters:
  pLatestAmiId:
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
    Default: '/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2'
  pInstanceType:
    Type: String
    Default: 't3.micro'

Resources:
# Create VPC
  rMyVPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsSupport: true
      EnableDnsHostnames: true
      InstanceTenancy: default
      Tags:
      - Key: Name
        Value: test-vpc

# Create Public RouteTable
  rPublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref rMyVPC
      Tags:
      - Key: Name
        Value: test-pubric-route

# Create Public Subnet 
  rPublicSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref rMyVPC
      CidrBlock: 10.0.0.0/24
      AvailabilityZone: !Select [ 0, !GetAZs "" ]
      MapPublicIpOnLaunch: true
      Tags:
      - Key: Name
        Value: test-public-subnet
  rRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref rPublicSubnet
      RouteTableId: !Ref rPublicRouteTable

# Create InternetGateway
  rInternetGateway:
    Type: "AWS::EC2::InternetGateway"
    Properties:
      Tags:
      - Key: Name
        Value: test-igw
  rAttachGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref rMyVPC
      InternetGatewayId: !Ref rInternetGateway

# Route for InternetGateway
  rRoute:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref rPublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref rInternetGateway

# Create EC2 Secuirty Group
  rSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: !Ref rMyVPC
      GroupDescription: Security Group for EC2 Instance
      SecurityGroupIngress:
        - CidrIp: 0.0.0.0/0
          FromPort: 443
          IpProtocol: tcp
          ToPort: 443
      Tags:
        - Key: Name
          Value: test-sg

# Create EC2 Instance
  rMyEC2:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: !Ref pLatestAmiId
      InstanceType: !Ref pInstanceType
      SecurityGroupIds:
        - !Ref rSecurityGroup
      SubnetId: !Ref rPublicSubnet
      Tags:
        - Key: Name
          Value: test-ec2

以降、前述の手順1~4に沿って順番にやっていきます。

手順1

EC2およびセキュリティグループを別スタックに分割するため、ソーステンプレートで
DeletionPolicy: Retain を追加します。
既存のスタックで既に設定されている場合はスキップ可能な作業です。

  rSecurityGroup:
    Type: AWS::EC2::SecurityGroup
+   DeletionPolicy: Retain
    Properties:

  rMyEC2:
    Type: AWS::EC2::Instance
+   DeletionPolicy: Retain
    Properties:

編集後のソーステンプレート
source.yaml
AWSTemplateFormatVersion: '2010-09-09'
Description:
  split stack test add Deletion Policy

Parameters:
  pLatestAmiId:
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
    Default: '/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2'
  pInstanceType:
    Type: String
    Default: 't3.micro'

Resources:
# Create VPC
  rMyVPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsSupport: true
      EnableDnsHostnames: true
      InstanceTenancy: default
      Tags:
      - Key: Name
        Value: test-vpc

# Create Public RouteTable
  rPublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref rMyVPC
      Tags:
      - Key: Name
        Value: test-pubric-route

# Create Public Subnet 
  rPublicSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref rMyVPC
      CidrBlock: 10.0.0.0/24
      AvailabilityZone: !Select [ 0, !GetAZs "" ]
      MapPublicIpOnLaunch: true
      Tags:
      - Key: Name
        Value: test-public-subnet
  rRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref rPublicSubnet
      RouteTableId: !Ref rPublicRouteTable

# Create InternetGateway
  rInternetGateway:
    Type: "AWS::EC2::InternetGateway"
    Properties:
      Tags:
      - Key: Name
        Value: test-igw
  rAttachGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref rMyVPC
      InternetGatewayId: !Ref rInternetGateway

# Route for InternetGateway
  rRoute:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref rPublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref rInternetGateway

# Create EC2 Secuirty Group
  rSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    DeletionPolicy: Retain
    Properties:
      VpcId: !Ref rMyVPC
      GroupDescription: Security Group for EC2 Instance
      SecurityGroupIngress:
        - CidrIp: 0.0.0.0/0
          FromPort: 443
          IpProtocol: tcp
          ToPort: 443
      Tags:
        - Key: Name
          Value: test-sg

# Create EC2 Instance
  rMyEC2:
    Type: AWS::EC2::Instance
    DeletionPolicy: Retain
    Properties:
      ImageId: !Ref pLatestAmiId
      InstanceType: !Ref pInstanceType
      SecurityGroupIds:
        - !Ref rSecurityGroup
      SubnetId: !Ref rPublicSubnet
      Tags:
        - Key: Name
          Value: test-ec2

※クリックで展開

編集後のソーステンプレートを既存スタックに反映させます。
Change Set上は、変更なしでアップデートが完了します。
image.png

手順2

セキュリティグループおよびEC2インスタンスに関連する記述をソーステンプレートから削除します。
また分割先のスタックからVPCやSubnetの情報を参照できるように、リソースIDをExportしておきます。

- Parameters:
-  pLatestAmiId:
-    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
-    Default: '/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2'
-  pInstanceType:
-    Type: String
-    Default: 't3.micro'
-
-  rSecurityGroup:
-    Type: AWS::EC2::SecurityGroup
-    DeletionPolicy: Retain
-    Properties:
-      VpcId: !Ref rMyVPC
-      GroupDescription: Security Group for EC2 Instance
-      SecurityGroupIngress:
-        - CidrIp: 0.0.0.0/0
-          FromPort: 443
-          IpProtocol: tcp
-          ToPort: 443
-      Tags:
-        - Key: Name
-          Value: test-sg
-
-  rMyEC2:
-    Type: AWS::EC2::Instance
-    DeletionPolicy: Retain
-    Properties:
-      ImageId: !Ref pLatestAmiId
-      InstanceType: !Ref pInstanceType
-      SecurityGroupIds:
-        - !Ref rSecurityGroup
-      SubnetId: !Ref rPublicSubnet
-      Tags:
-        - Key: Name
-          Value: test-ec2
+
+Outputs:
+  rMyVPC:
+    Value: !Ref rMyVPC
+    Export
+     Name: rMyVPC
+  rPublicSubnet:
+    Value: !Ref rPublicSubnet
+   Export:
+     Name: rPublicSubnet

編集後のソーステンプレート
source.yaml
AWSTemplateFormatVersion: '2010-09-09'
Description:
  split stack test add vpc only

Resources:
# Create VPC
  rMyVPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsSupport: true
      EnableDnsHostnames: true
      InstanceTenancy: default
      Tags:
      - Key: Name
        Value: test-vpc

# Create Public RouteTable
  rPublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref rMyVPC
      Tags:
      - Key: Name
        Value: test-pubric-route

# Create Public Subnet 
  rPublicSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref rMyVPC
      CidrBlock: 10.0.0.0/24
      AvailabilityZone: !Select [ 0, !GetAZs "" ]
      MapPublicIpOnLaunch: true
      Tags:
      - Key: Name
        Value: test-public-subnet
  rRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref rPublicSubnet
      RouteTableId: !Ref rPublicRouteTable

# Create InternetGateway
  rInternetGateway:
    Type: "AWS::EC2::InternetGateway"
    Properties:
      Tags:
      - Key: Name
        Value: test-igw
  rAttachGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref rMyVPC
      InternetGatewayId: !Ref rInternetGateway

# Route for InternetGateway
  rRoute:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref rPublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref rInternetGateway

Outputs:
  rMyVPC:
    Value: !Ref rMyVPC
    Export:
      Name: rMyVPC
  rPublicSubnet:
    Value: !Ref rPublicSubnet
    Export:
      Name: rPublicSubnet

※クリックで展開

分割するセキュリティグループとEC2に関する記載はターゲットテンプレートとして新規作成します。
VPCIDおよびSubnetIDはクロススタック参照させています。
インポートするリソースにもテンプレートで DeletionPolicy 属性を指定する必要があります。

target.yaml
AWSTemplateFormatVersion: '2010-09-09'
Description:
  split stack test ec2 only

Parameters:
  pLatestAmiId:
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
    Default: '/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2'
  pInstanceType:
    Type: String
    Default: 't3.micro'

Resources:
# Create EC2 Secuirty Group
  rSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    DeletionPolicy: Retain
    Properties:
      VpcId: !ImportValue rMyVPC
      GroupDescription: Security Group for EC2 Instance
      SecurityGroupIngress:
        - CidrIp: 0.0.0.0/0
          FromPort: 443
          IpProtocol: tcp
          ToPort: 443
      Tags:
        - Key: Name
          Value: test-sg

# Create EC2 Instance
  rMyEC2:
    Type: AWS::EC2::Instance
    DeletionPolicy: Retain
    Properties:
      ImageId: !Ref pLatestAmiId
      InstanceType: !Ref pInstanceType
      SecurityGroupIds:
        - !Ref rSecurityGroup
      SubnetId: !ImportValue rPublicSubnet
      Tags:
        - Key: Name
          Value: test-ec2

手順3

更新したソーステンプレートを使用して、既存のスタックを Update します。
image.png

DeletionPolicyにより、リソース自体の削除はスキップされますが、
この時点でセキュリティグループおよび、EC2は既存スタックの管理対象外となります。
image.png

手順4

ターゲットテンプレートを使用して、管理対象外となったEC2とセキュリティグループをインポートします。
スタックの作成で 既存のリソースを使用 を選択します。
image.png

テンプレートの指定で、ターゲットテンプレートをアップロードして次に進みます。
image.png

リソースを識別で、インポート対象のEC2とセキュリティグループのリソースIDを指定します。
image.png

スタックの詳細を指定で、新たに作成するスタックの名前を設定します。
image.png

対象のリソースを確認して、インポートを実行します。
image.png

インポートが完了し、既存のEC2とセキュリティグループが新しいスタックの
管理対象となっていることを確認します。
image.png

無事、VPCとEC2のスタックに分割することができました!
image.png

まとめ

移動するリソースに削除ポリシーを追加し、ソーススタックから削除&ターゲットスタックに
インポートすることでスタック間でのリソースの移動を簡単に行うことができます。
だだし、現地時点でインポートに対応しているリソースは限られているのでご注意ください。

Resources that Support Import Operations
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/resource-import-supported-resources.html
Moving Resources Between Stacks
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/refactor-stacks.html

以上です。
参考になれば幸いです。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away