AutoScalingの「起動設定」をCloudformationで変更するときの手っ取り早いやりかた

2020.01.19

はじめに

こんにちは。大阪オフィスの林です。

早いもので年が明けてからもう3週間程経ってしまってるんですね。1年の約20分の1がもう終わってしまった計算です。 ∑(O_O;)

さっそくですが、タイトルの通り、AutoScalingの「起動設定」をCloudformationで変更するときの手っ取り早いやりかたをまとめておきたいと思います。

この記事を書く理由

なぜこのような内容の記事を書いたかというと、AutoScalingの「起動設定」をCloudformationで変更した時に以下のエラーが発生し、正常終了しませんでした。
CloudFormation cannot update a stack when a custom-named resource requires replacing. Rename asg-test-lc and update the stack again.(訳:CloudFormationは、カスタム名のリソースを置き換える必要がある場合、スタックを更新できません。 asg-test-lcの名前を変更し、スタックを再度更新します。)

結論を先に言うとAutoScalingは、
「AutoScalingGroupを作成した後で起動設定を変更することはできません。」
という制約がありました。
単なる検証環境であれば、一旦コード管理を放棄して作り直したり、GUIで変更しちゃえばもっともっと手っ取り早いのですが、本番環境ではそうもいきません。前述した方法で変更してしまうと、折角コード化した意味が無くなってしまうので、Cloudformationを使って「起動設定」を変更する方法を確立させておきたいと思いこの記事を書かせて頂きました。

AutoScalingの「起動設定」とは?

話を少し戻して、そもそもAutoScalingの「起動設定」とは何か簡単に説明しておきたいと思います。
AutoScaling(以下、AS)には「起動設定」と呼ばれるパラメータがあります。この「起動設定」は、AS環境下でインスタンスが起動されるときに読み込まれるパラメータであり、「インスタンスタイプ」や「ストレージサイズ」や「セキュリティグループ」等の設定を予め定義して指定しておくことができます。 AS環境下のインスタンスに対して共通の設定を適用できるという非常に便利な機能なのですがこの「起動設定」には以下のような制約があります。

一度に Auto Scaling グループに関連付けできる起動設定は 1 つだけであり、グループを作成した後で起動設定を変更することはできません。Auto Scaling グループの起動設定を変更するには、新しい起動設定のベースとして既存の起動設定を使用します。次に、Auto Scaling グループを新しい起動設定を使用するように更新します。 Auto Scaling グループの起動設定を変更すると、以後、新しいインスタンスは新しい設定オプションを使用して起動されますが、既存のインスタンスは影響を受けません。この状況では、Auto Scaling グループの既存のインスタンスを終了して、新しい設定を使用する新しいインスタンスを強制的に起動できます。または、自動スケーリングにより、終了ポリシーに基づいて古いインスタンスを新しいインスタンスに段階的に置き換えることができます。AWS CloudFormation から数回クリックするだけで、更新された起動設定のデプロイを自動化することもできます。公式はこちら

上述の太字部分を簡単に解説しておきたいと思います。

『作成した後で起動設定を変更することはできません。』

まぁそのままなのですが、これは要するに一度作成したASの「起動設定」は内容を変更することが出来ないという事です。 もう少し実態に併せて解説すると、AS環境下で「起動設定」作ったり、AutoScalingGroup(以下、ASG)作ったり、インスタンスを起動したあとに、
『あっ、やっぱりインスタンスタイプにこっちに変えたいな~!』とか、
『あっ、セキュリティグループの設定間違ってた!なおそう!』とか、
設定変えたいと思っても「起動設定」を作ってしまった後では中身をイジれないということです。

『Auto Scaling グループの起動設定を変更すると、以後、新しいインスタンスは新しい設定オプションを使用して起動されますが、既存のインスタンスは影響を受けません。』

新しく作成した「起動設定」は、作成後に起動したインスタンスから反映されるということです。古い「起動設定」で稼働しているインスタンスに変更は反映されません。なのでシステム全体を新しい「起動設定」が反映されたインスタンスにするためには、終了ポリシーを設定したりし古いインスタンスを置き換える必要があります。

前置きが長くなりましたが、今回はCloudFormation&AS環境下において起動設定を変更する手順をまとめておきたいと思います。

やってみた

今回の検証では「起動設定」で設定したセキュリティグループをCloudFormationを使って変更していきたいと思います。 なお、今回のケースではLaunchConfigurationNameを付与する制約があるという前提で検証を進めていきます。

大きな流れ

  • 設定変更前の「起動設定」確認
  • 「起動設定」の変更
  • 新しい「起動設定」でインスタンスを起動
  • 古い「起動設定」を持つインスタンスをスケールイン

    設定変更前の「起動設定」確認

    まず設定変更前の「起動設定」状態を説明します。コード内、2つのセキュリティグループを割り当てているのが確認できます。検証のためスケーリングの台数は最大、最小ともに「1台」としておきます。

    # ------------------------------------------------------------#
    # Launch Configuration
    # ------------------------------------------------------------#
      asgtestLaunchConfiguration:
        Type: AWS::AutoScaling::LaunchConfiguration
        Properties:
          EbsOptimized: true
          IamInstanceProfile: { "Fn::ImportValue": !Join [ "-", [ "Ref":"IdStackName","asgtestInstanceProfile"]] }
          ImageId: !Sub ${ImageID}
          InstanceType: t3.micro
          KeyName: !Sub ${EC2KeyPair}
          BlockDeviceMappings:
            - DeviceName: /dev/xvda
              Ebs:
                VolumeType: gp2
                VolumeSize: 20
          LaunchConfigurationName: !Sub ${SystemName}-${EnvType}-lc
          #2つのセキュリティグループを設定している。
          SecurityGroups:
            - { "Fn::ImportValue": !Join [ "-", [ "Ref":"SGStackName","asgtestSecurityGroup"]] }
            - { "Fn::ImportValue": !Join [ "-", [ "Ref":"SGGroupStackName","allSecurityGroup"]] }
    # ------------------------------------------------------------#
    # AutoScaling Group
    # ------------------------------------------------------------#    
        asgtestAutoScalingGroup:
        Type: AWS::AutoScaling::AutoScalingGroup
        Properties:
          AutoScalingGroupName: !Sub ${SystemName}-${EnvType}
          #検証のため「希望」「最大」「最小」を全て「1台」としておく。
          DesiredCapacity: 1
          LaunchConfigurationName: !Ref asgtestLaunchConfiguration
          MaxSize: 1
          MinSize: 1
          Tags:
            - Key: Name
              Value: !Sub ${SystemName}-${EnvType}
              PropagateAtLaunch: true
          VPCZoneIdentifier:
             - { "Fn::ImportValue": !Join [ "-", [ "Ref":"SubnetStackName","asgtestpriSubneta"]] }
             - { "Fn::ImportValue": !Join [ "-", [ "Ref":"SubnetStackName","asgtestpriSubnetc"]] }
          TargetGroupARNs:
            - { "Fn::ImportValue": !Join [ "-", [ "Ref":"ALBStackName","asgtestTargetGroup"]] }
          TerminationPolicies:
           - NewestInstance
    

    上記コードを含むASG作成のCloudFormationのテンプレートを流すと、作成された1台のインスタンスに2つのセキュリティグループ(asgtest-sg、all-sg)が割り当たっていることが確認できます。

    「起動設定」の変更

    同じ名前では設定変更が出来ないので、LaunchConfigurationNameの末尾にtempの文字を挿入して名前を変えておきます。 そのうえで、設定変更の検証として1つのセキュリティグループを外します。(ここではコメントアウトとしています。)

    # ------------------------------------------------------------#
    # Launch Configuration
    # ------------------------------------------------------------#
      asgtestLaunchConfiguration:
        Type: AWS::AutoScaling::LaunchConfiguration
        Properties:
          EbsOptimized: true
          IamInstanceProfile: { "Fn::ImportValue": !Join [ "-", [ "Ref":"IdStackName","asgtestInstanceProfile"]] }
          ImageId: !Sub ${ImageID}
          InstanceType: t3.micro
          KeyName: !Sub ${EC2KeyPair}
          BlockDeviceMappings:
            - DeviceName: /dev/xvda
              Ebs:
                VolumeType: gp2
                VolumeSize: 20
          #同じ名前で更新できないので末尾に「temp」をつける。もともとの名前は最終的に戻すためコメントアウトとしておきます。
          #LaunchConfigurationName: !Sub ${SystemName}-${EnvType}-lc
          LaunchConfigurationName: !Sub ${SystemName}-${EnvType}-lc-temp
          SecurityGroups:
            - { "Fn::ImportValue": !Join [ "-", [ "Ref":"SGGroupStackName","asgtestSecurityGroup"]] }
            #1つのセキュリティグループを外しておきます。
            #- { "Fn::ImportValue": !Join [ "-", [ "Ref":"SGGroupStackName","allSecurityGroup"]] }
    

    CloudFormationの変更セットでテンプレートを更新したところ、冒頭掲載したエラーは発生せず正常終了したことが確認できます。

    新しい「起動設定」でインスタンスを起動

    既に稼働しているインスタンス(古いインスタンス)の設定は「起動設定」の変更の影響を受けないため、古いインスタンスと置き換える新しい「起動設定」のインスタンスを追加します。現状古いインスタンスが1台起動されている状態のため、「希望」「最大」「最小」を全て「2台」とすることで、新しい「起動設定」のインスタンスが1台追加されます。 なお、「起動設定」の名前もここで元の名前に戻しておきます。

    # ------------------------------------------------------------#
    # Launch Configuration
    # ------------------------------------------------------------#
      asgtestLaunchConfiguration:
        Type: AWS::AutoScaling::LaunchConfiguration
        Properties:
          EbsOptimized: true
          IamInstanceProfile: { "Fn::ImportValue": !Join [ "-", [ "Ref":"IdentityStackName","asgtestInstanceProfile"]] }
          ImageId: !Sub ${ImageID}
          InstanceType: t3.micro
          KeyName: !Sub ${EC2KeyPair}
          BlockDeviceMappings:
            - DeviceName: /dev/xvda
              Ebs:
                VolumeType: gp2
                VolumeSize: 20
          #元の名前に戻します。
          LaunchConfigurationName: !Sub ${SystemName}-${EnvType}-lc
          SecurityGroups:
            - { "Fn::ImportValue": !Join [ "-", [ "Ref":"SecurityGroupStackName","asgtestSecurityGroup"]] }
            #- { "Fn::ImportValue": !Join [ "-", [ "Ref":"SecurityGroupStackName","allSecurityGroup"]] }
    # ------------------------------------------------------------#
    # AutoScaling Group
    # ------------------------------------------------------------#    
        asgtestAutoScalingGroup:
        Type: AWS::AutoScaling::AutoScalingGroup
        Properties:
          AutoScalingGroupName: !Sub ${SystemName}-${EnvType}
          #新しいインスタンスを追加するため「希望」「最大」「最小」を全て「2台」としておく。
          DesiredCapacity: 2
          LaunchConfigurationName: !Ref asgtestLaunchConfiguration
          MaxSize: 2
          MinSize: 2
          Tags:
            - Key: Name
              Value: !Sub ${SystemName}-${EnvType}
              PropagateAtLaunch: true
          VPCZoneIdentifier:
             - { "Fn::ImportValue": !Join [ "-", [ "Ref":"SubnetStackName","asgtestpriSubneta"]] }
             - { "Fn::ImportValue": !Join [ "-", [ "Ref":"SubnetStackName","asgtestpriSubnetc"]] }
          TargetGroupARNs:
            - { "Fn::ImportValue": !Join [ "-", [ "Ref":"ALBStackName","asgtestTargetGroup"]] }
          TerminationPolicies:
           - NewestInstance
    

    CloudFormationの変更セットでテンプレートの更新を掛けます。ここまでで、古いインスタンスが1台、新しいインスタンスが1台の計2台稼働している状態となります。新しく起動したインスタンスはセキュリティグループが1つになっており、新しい「起動設定」で起動されていることが分かります。

    古い「起動設定」を持つインスタンスをスケールイン

    もともと検証用に稼働が必要なインスタンスの台数は「1台」でした。なので「希望」「最大」「最小」を全て「1台」に戻します。 「2台」から「1台」にスケールインする動作になるのですが、AWS::AutoScaling::AutoScalingGroupには、TerminationPoliciesというパラメータがあり、このパラメータでスケールイン時にどのインスタンスを終了させるかを決めることが出来ます。今回スケールインさせたいのは、古い「起動設定」を持つインスタンスなので、TerminationPoliciesOldestLaunchConfigurationとします。この設定によって、古い「起動設定」を持つインスタンスをスケールインさせることが出来ます。

    # ------------------------------------------------------------#
    # AutoScaling Group
    # ------------------------------------------------------------#
      asgtestAutoScalingGroup:
        Type: AWS::AutoScaling::AutoScalingGroup
        Properties:
          AutoScalingGroupName: !Sub ${SystemName}-${EnvType}
          #台数を「1台」に戻す。
          DesiredCapacity: 1
          LaunchConfigurationName: !Ref asgtestLaunchConfiguration
          MaxSize: 1
          MinSize: 1
          Tags:
            - Key: Name
              Value: !Sub ${SystemName}-${EnvType}
              PropagateAtLaunch: true
          VPCZoneIdentifier:
             - { "Fn::ImportValue": !Join [ "-", [ "Ref":"SharedServicesStackName","asgtestpriSubneta"]] }
             - { "Fn::ImportValue": !Join [ "-", [ "Ref":"SharedServicesStackName","asgtestpriSubnetc"]] }
          TargetGroupARNs:
            - { "Fn::ImportValue": !Join [ "-", [ "Ref":"ALBStackName","asgtestTargetGroup"]] }
          #ここで古い「起動設定」を持つインスタンスをスケールインさせるポリシーを定義している。
          TerminationPolicies:
           - OldestLaunchConfiguration
    

    CloudFormationの変更セットでテンプレートの更新を掛け、しばらくすると古い「起動設定」を持つインスタンスのシャットダウンが始まりました。

    状態が「terminated」となり、古い「起動設定」を持つインスタンスがスケールインされました。

    まとめ

    この内容が本当に手っ取り早いのか?と言われると自分でも少々疑問が湧いてしまっているのですが、インフラをしっかりとコードで管理していくには変更ルールや変更手順を事前にしっかりと定めておく必要があると思っています。変更ルールや変更手順を検討する際にこの記事が何かしらの参考になれば幸いです!

    以上、大阪オフィスの林がお送りしました!