CloudFormationスタックをリファクタリングしてみた

1つのCloudFormationスタックを、ネットワークレイヤとアプリレイヤの2つに分割します。CloudFormationにインポート機能が追加されたことでリファクタリングができるようになりました!
2020.02.13

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

CloudFormationにインポート機能が追加されたことで作成済みスタックのリファクタリングができるようになりました。今回は実際にリファクタリングしてみたいと思います。

目次

概要

上記のように1つのスタックですべてのリソースを管理しているとします。これを下記のように2つのスタックに分割したいと思います。分割の方針としてはネットワークレイヤとアプリケーションレイヤというイメージです。

準備

1つのテンプレートでリソースを作成

すべてのリソースが1つのテンプレートで作成されている状態にします。下記のテンプレートをdemo.ymlとして保存します。今回はマネジメントコンソールを利用し、スタック名をdemo-stackとします。これで準備は完了です。

demo.yml

AWSTemplateFormatVersion: '2010-09-09'

Resources:
  # VPC
  DemoVpc:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      Tags:
      - Key: Name
        Value: demo-vpc
  # サブネット
  DemoPriSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: 'ap-northeast-1a'
      CidrBlock: 10.0.1.0/24
      VpcId: !Ref DemoVpc
      Tags:
      - Key: Name
        Value: demo-pri-subnet
  # セキュリティグループ
  DemoSG:
    Type: "AWS::EC2::SecurityGroup"
    Properties:
      GroupDescription: DemoSG
      GroupName: demo-sg
      VpcId: !Ref DemoVpc
      Tags:
        - Key: 'Name'
          Value: 'demo-sg'
      SecurityGroupIngress:
      - IpProtocol: tcp
        FromPort: '22'
        ToPort: '22'
        CidrIp: 0.0.0.0/0
      - IpProtocol: tcp
        FromPort: '80'
        ToPort: '80'
        CidrIp: 0.0.0.0/0
  #インスタンス
  DemoWeb:
    Type: 'AWS::EC2::Instance'
    Properties:
      ImageId: 'ami-079e6fb1e856e80c1'
      InstanceType: 't2.micro'
      SecurityGroupIds:
        - !GetAtt DemoSG.GroupId
      SubnetId: !Ref DemoPriSubnet
      Tags:
        - Key: 'Name'
          Value: 'demo-web'

リファクタリング

リソースが削除されないようにする

これからリファクタリングを行っていきますが、このまま管理外にするとセキュリティグループとEC2が削除されてしまいます。管理外にする前にDeletionPolicy属性をRetainに更新します。これによりテンプレートからリソースの記述を削除した場合でもリソースが保持されます。下記が更新したdemo.ymlです。

demo.yml

AWSTemplateFormatVersion: '2010-09-09'

Resources:
  # VPC
  DemoVpc:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      Tags:
      - Key: Name
        Value: demo-vpc
  # サブネット
  DemoPriSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: 'ap-northeast-1a'
      CidrBlock: 10.0.1.0/24
      VpcId: !Ref DemoVpc
      Tags:
      - Key: Name
        Value: demo-pri-subnet
  # セキュリティグループ
  DemoSG:
    Type: "AWS::EC2::SecurityGroup"
    DeletionPolicy: Retain               # ← 追加行
    Properties:
      GroupDescription: DemoSG
      GroupName: demo-sg
      VpcId: !Ref DemoVpc
      Tags:
        - Key: 'Name'
          Value: 'demo-sg'
      SecurityGroupIngress:
      - IpProtocol: tcp
        FromPort: '22'
        ToPort: '22'
        CidrIp: 0.0.0.0/0
      - IpProtocol: tcp
        FromPort: '80'
        ToPort: '80'
        CidrIp: 0.0.0.0/0
  #インスタンス
  DemoWeb:
    Type: 'AWS::EC2::Instance'
    DeletionPolicy: Retain              # ← 追加行
    Properties:
      ImageId: 'ami-079e6fb1e856e80c1'
      InstanceType: 't2.micro'
      SecurityGroupIds:
        - !GetAtt DemoSG.GroupId
      SubnetId: !Ref DemoPriSubnet
      Tags:
        - Key: 'Name'
          Value: 'demo-web1'

マネジメントコンソールからdemo-stackを選択 → 「更新する」→「既存のテンプレートを置き換える」→「テンプレートファイルのアップロード」より上記の更新したテンプレートをアップロードします。

「変更はありません」と表示されていますが、そのままスタックを更新します。

イベントを確認するとEC2とセキュリティグループが更新されていることがわかります。

スタックから分割するリソースを管理外にする

リソースを管理外にするにはテンプレートからリソースを削除してスタックを更新します。下記はEC2とセキュリティグループの記述を削除したものです。加えて、分割するテンプレートからVPCとサブネットをクロススタック参照できるように、VPCとSubnetのIDを出力しています。

demo-nw.yml

AWSTemplateFormatVersion: '2010-09-09'

Resources:
  # VPC
  DemoVpc:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      Tags:
      - Key: Name
        Value: demo-vpc
  # サブネット
  DemoPriSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: 'ap-northeast-1a'
      CidrBlock: 10.0.1.0/24
      VpcId: !Ref DemoVpc
      Tags:
      - Key: Name
        Value: demo-pri-subnet

Outputs:
  DemoVpc:
    Value: !Ref DemoVpc
    Export:
      Name: DemoVpc
  DemoPriSubnet:
    Value: !Ref DemoPriSubnet
    Export:
     Name: DemoPriSubnet

demo-nw.ymlとして保存し、先程と同じようにテンプレートをアップロードしてスタックを更新します。

変更セットにはEC2インスタンスとセキュリティグループが削除されると表示されていますが、このまま更新します。

イベントを確認するとEC2インスタンスとセキュリティグループはDELETE_SKIPPEDとなっており実際には削除されていないことが確認できます。EC2画面でも確認してみてください。

管理外にしたリソースをあたらしいスタックにインポート

新しいスタック用に下記のテンプレートをdemo-app.ymlとして保存します。VpcIdSubnetIdImportValueにしています。また、インポートするリソースはDeletionPolicyRetainにする必要があります。

demo-app.yml

AWSTemplateFormatVersion: '2010-09-09'

Resources:
  # セキュリティグループ
  DemoSG:
    Type: "AWS::EC2::SecurityGroup"
    DeletionPolicy: Retain
    Properties:
      GroupDescription: DemoSG
      GroupName: demo-sg
      VpcId: !ImportValue DemoVpc
      Tags:
        - Key: 'Name'
          Value: 'demo-sg'
      SecurityGroupIngress:
      - IpProtocol: tcp
        FromPort: '22'
        ToPort: '22'
        CidrIp: 0.0.0.0/0
      - IpProtocol: tcp
        FromPort: '80'
        ToPort: '80'
        CidrIp: 0.0.0.0/0
  #インスタンス
  DemoWeb:
    Type: 'AWS::EC2::Instance'
    DeletionPolicy: Retain
    Properties:
      ImageId: 'ami-079e6fb1e856e80c1'
      InstanceType: 't2.micro'
      SecurityGroupIds:
        - !GetAtt DemoSG.GroupId
      SubnetId: !ImportValue DemoPriSubnet
      Tags:
        - Key: 'Name'
          Value: 'demo-web'

次に「既存のリソースを仕様(リソースをインポート)」を選択します。

「概要をインポート」では「次へ」をクリック

上記テンプレートをアップロードし「次へ」をクリック

インポートするEC2インスタンスのInstanceIdとセキュリティグループのGroupIdを入力し「次へ」をクリック

スタック名にdemo-app-stackと入力し「次へ」をクリック

変更にEC2インスタンスとセキュリティグループがImportと表示されていることを確認し「リソースをインポート」をクリック

IMPORT_COMPLETEと表示されたらリソースを確認します。EC2インスタンスとセキュリティグループが管理下にあることが確認できます。

最初に作成したスタックにはVPCとサブネットのみが管理下に置かれています。

これでリファクタリングは完了です。

まとめ

スタックがリファクタリングできることを確認しました。巨大なスタックを管理している場合は分割することでメンテナンスしやすくなることもあります。運用にお困りの方はリファクタリングを検討してみてはいかがでしょうか。

ただ、運用環境で行う場合は、事前に検証を行ってください。