こんにちは、屋根裏エンジニアの折戸です。
タイトル長いですが、あしからず。
経緯
以前、CloudFormationの変更セットで本番稼働中のEC2を削除しかけた話 を投稿しました。
blog.serverworks.co.jp
当時の対策としてはCloudFormation(以下、CFn)のスタックの変更は利用せずに手動でコンソールから任意のパラメータを更新することでインスタンスのTerminateを防ぎました。
しかしこの方法は実環境とCFnスタック間でドリフトが発生している状態であり、これはあまり良い状況とは言えません。
そこで今回はそもそもの話、
スタック作成時はパラメータストアから最新AMI取得したい、でもスタック更新時にはAMIは更新させたくない!
という方法をご紹介します。
元のテンプレート
まずは問題となっていたテンプレートをおさらい。
AWSTemplateFormatVersion: 2010-09-09 Description: test-instance Parameters: ImageId: Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id> Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-arm64-gp2 Resources: EC2Instance: Type: AWS::EC2::Instance DeletionPolicy: Delete Properties: BlockDeviceMappings: - DeviceName: /dev/xvda Ebs: DeleteOnTermination: true Encrypted: false VolumeSize: 8 VolumeType: gp3 DisableApiTermination: false ImageId: !Ref ImageId InstanceInitiatedShutdownBehavior: stop InstanceType: t4g.micro Monitoring: false KeyName: test-keypair NetworkInterfaces: - AssociatePublicIpAddress: true DeviceIndex: 0 GroupSet: - !ImportValue SecurityGroup-web SubnetId: !ImportValue SubnetTestPrivateA
肝となるのは以下の部分
ImageId: Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id> Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-arm64-gp2
AWS Systems Manager Parameter Store(以下、パラメータストア)の
/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-arm64-gp2 を参照して最新のAMI IDを取得して設定する部分です。
このテンプレートの仕組みにより、変更セット実行のタイミングによって ImageIdの値が変わってしまうということが原因で、思わぬインスタンスのTerminateを引き起こしてしまう可能性があります。
対策
ネストされたスタックという方法を利用します。
ネストされたスタックは、他のスタックの一部として作成されたスタックです。
…
インフラストラクチャが大きくなるにつれ、複数のテンプレートで同じコンポーネントを宣言する共通パターンができてきます。これらの共通するコンポーネントを他と分類し、専用テンプレートを作成できます。
本来の用途とはズレている気はしますが、今回はこれを利用します。
テンプレート分割
元のテンプレートを、親スタック用と子スタック用へ分割します。 それぞれの役割とテンプレートは以下のとおり
親スタック用テンプレート
役割
最新のAMI IDをパラメータストアから参照し、Stackリソースを通して子スタックへ値を渡す
nest-stack-parent.yml
AWSTemplateFormatVersion: 2010-09-09 Description: nest-stack-parent Parameters: ImageId: Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id> Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-arm64-gp2 Resources: ImageIdStack: Type: AWS::CloudFormation::Stack Properties: TemplateURL: !Sub https://nest-stack-${AWS::AccountId}.s3.ap-northeast-1.amazonaws.com/nest-stack-child.yml Parameters: ImageId: !Ref ImageId
子スタック用テンプレート
役割
親スタックから渡されたAMI IDをString型でParametersへ保持し、Instanceリソースを作成する
nest-stack-child.yml
AWSTemplateFormatVersion: 2010-09-09 Description: nest-stack-child Parameters: ImageId: Type: String Resources: EC2Instance: Type: AWS::EC2::Instance DeletionPolicy: Delete Properties: BlockDeviceMappings: - DeviceName: /dev/xvda Ebs: DeleteOnTermination: true Encrypted: false VolumeSize: 8 VolumeType: gp3 DisableApiTermination: false ImageId: !Ref ImageId InstanceInitiatedShutdownBehavior: stop InstanceType: t4g.nano Monitoring: false KeyName: test-keypair NetworkInterfaces: - AssociatePublicIpAddress: true DeviceIndex: 0 GroupSet: - !ImportValue SecurityGroup-web SubnetId: !ImportValue SubnetTestPrivateA
子スタック用テンプレートをS3へアップロード
コンソールのCFnメニューからは子スタックは作成できません。
親スタックから参照できるように先に子スタック用テンプレートを任意のS3へアップロードしておきます。
適宜バケットを準備してnest-stack-child.ymlをアップロードしてください。
今回は nest-stack-【AWSアカウントID】 の名前でバケットを作成し、そこへnest-stack-child.ymlをアップロードしています。
親スタック用テンプレートの、
… TemplateURL: !Sub https://nest-stack-${AWS::AccountId}.s3.ap-northeast-1.amazonaws.com/nest-stack-child.yml …
の部分がアップロードされているテンプレートを参照します。
親スタックを作成
コンソールのCFnメニューから親スタックを作成します。
CloudFormation > スタック > スタックの作成
テンプレートファイルのアップロード:nest-stack-parent.yml
次へ
スタックの名前:nest-stack
次へ
スタックオプションの設定
次へ
レビュー
スタックの作成
スタック確認
CloudFormation > スタック > nest-stack nest-stack がCREATE_COMPLETEとなってます。
リソースタブ ImageIdStackのStackリソースが作成されてます。
左側のスタック一覧にもネストされたスタックとして、子スタックが確認できます。
CloudFormation > スタック > nest-stack-ImageIdStack-******** ネストされたスタック(子スタック)から、Instanceリソースが作成されていることを確認できます。
パラメータとして、親スタックから渡されたAMI IDがセットされてます。
ここまでで、最新のAMIからインスタンスを作成できました。
ここからが本題です。
前回と同じ様なシナリオでインスタンスのAMI以外のパラメータを更新しようとした場合にインスタンスの置換(AMIが更新されない)が発生しないことを確認しましょう。
子スタックの更新
パラメータの更新は子スタック用のテンプレートを編集します。
子スタック用テンプレート編集
今回はインスタンスタイプを更新する様にテンプレートを書き換えます。
nest-stack-child.yml
ImageId: !Ref ImageId InstanceInitiatedShutdownBehavior: stop -- InstanceType: t4g.nano ++ InstanceType: t4g.small Monitoring: false KeyName: test-keypair
ネストされたスタック(子スタック)の変更セットの作成から更新します。
CloudFormation > スタック > nest-stack-ImageIdStack-******** 変更セットの作成
既存テンプレートを置き換える
テンプレートファイルのアップロード
テンプレートファイルのアップロード:編集した nest-stack-child.yml
次へ
ImageIdはもちろん変更しません。そのまま
次へ
スタックオプションの設定
次へ
レビュー
変更セットの作成
置換の確認
置換ステータスはTrueではなく、Conditionalとなってます。
変更セットの実行
実行 をクリックし、実際にスタックを更新してみます。
インスタンスはTerminateされず、AMIは変更されずにインスタンスタイプのみ変更されていることを確認しました。
実は
この時点で スタック作成 から スタック変更によるパラメータの更新 の間に最新のAMIは変わっていなかったため、本当の意味で確認はできていません。
ただし、子スタック上ではAMI IDがパラメータストアからの参照ではなく固定値で保持されているので、最新のAMIが変わっていたとしても子スタックの更新には影響が及ばないはずです。
最後に
CFnで単純にインスタンスを作成したいだけのケースを考えると今回の方法はS3へアップロードするオペレーションが増えてしまうためやりすぎ感があり、得策とは言いがたいです。
より良い方法があれば続編を書こうと思います。
では。