見出し画像

ここで困ったCloudFormation その1


はじめに


およそ一年ぶりの投稿となります、SHIFT ITソリューション部の三好です。

みなさん、CloudFormation使ってますか。AWSの提供しているIaC(Infrastructure as a Code)のサービス。事前にJSONかYAMLでテンプレートさえ書いておけば、ものの一瞬でリソースが作成可能。再現性も担保され、環境が複数ある場合も使いまわせるため、とっても便利です。 私もここ一年、業務で都度CloudFormationにお世話になってきたのですが、何かと用語が多かったり細かな仕様があったりで、引っ掛かることも多々ありました。そしてなかなかちょうどいいドキュメントも見つからないもの。 そこで今回のブログでは、CloudFormationを使っていて困ったことを実体験に基づきオムニバス形式で記録していきます。第1回は、スタック更新時関連の話題が多めです。第1回と言いつつ、続くかはわかりませんが…

※本記事は対象の読者層として、AWSの基本的なリソース名等の用語を理解している初学者の方を想定しております。

テンプレートとスタックの違いがわかりません


最初の最初に引っかかった疑問です。CloudFormation関連のドキュメントを読んでいると、「テンプレート」「スタック」という二つの用語がたびたび出てきます。いずれも作成したいリソースの情報を持ったものだろうということまではわかるのですが、いまいち違いが判らない時期がありました。

テンプレートは、リソースの情報が記載されたJSON形式、あるいはYAML形式のファイルのことです。喩えるならば、EC2やS3といったリソースの設計図にあたるものになります。要はただのテキストファイルですので、テキストエディタさえあればお手元のPC上で作成可能です。単一のテンプレートに、複数のリソースの記述をすることもできます(例:EC2の起動テンプレート + EC2のAutoScaling Group)。このテンプレートファイルをCloudFormationに読み込ませることでスタックを作成することができ、そのスタックがAWS上にリソースを作成します。リソースを作成したあとは、テンプレートファイルを削除してしまっても作成されたリソースに影響を及ぼすことはありません。

スタックは、AWSのCloudFormation上に存在するリソースで、先述のようにCloudFormationにテンプレートファイルを読み込ませることで作成されます。喩えるならば、EC2やS3といったリソースの施工から維持管理まで担当する管理人のようなものになります。この管理人(スタック)が、設計図(テンプレート)を見ながら実際のリソースを作成している、とイメージすればわかりやすいかと。

スタックはリソースと紐づいているため、スタックを更新/削除すればリソースも更新/削除されます。たとえばEC2のインスタンスタイプなど、リソースのパラメータを変更する必要が出てきた場合は、該当箇所の記述を書き換えたテンプレートファイルを読み込ませることでスタックが更新され、実リソースにもその変更内容が反映されます。また、スタックを削除すれば、そのスタックが管理していたリソースもすべて削除されます。管理人が修正された設計書を受け取ればそれに従ってリソースは作り変えられるし、管理人がいなくなれば管理対象のリソースも取り壊されるというわけです。なお、逆にリソース側の設定をコンソール等から変更した場合でも、スタック側で保持している設定が書き換わることはありません。そのため、スタックの内容と実際のリソース設定に矛盾があるというケースは往々に発生します。ちょっとした変更をしたい場合に、きちんとスタックから更新するのか、リソース設定を直にいじるかの運用方法は事前に決めておいたほうがよいでしょう。

スタックを削除したのにリソースが削除されません


上で述べたように、CloudFormationの基本的な挙動としては、スタックを消せばリソースも削除されるはずです。ところが、あるスタックを削除してもリソースそものも、自分の場合はAutoScalingGroupは削除されず、元気に動いているということがありました。おかしいなと思いつつ、そのときは結局コンソールのEC2の画面から直接AutoScalingGroupを削除することになったのですが、後から調べてみると、スタック作成時にテンプレートに記載してあったDeletionPolicyの項目が原因であることがわかりました。

DeletionPolicyは、スタック削除時のリソースの挙動に関するフィールドです。このフィールドにRetainという値を指定すると、通常時の挙動と異なり、スタックを削除しても紐づいていたリソースは削除されなくなります。本番環境の最重要リソースなど、誤操作による削除をなんとしてでも避けたいリソース等で有効化するのが想定される使い方です。ただ、スタックが削除されることで宙ぶらりんなリソースができることになるので、特にリソースをCloudFormationで厳格に管理している場合はあまりむやみに有効化するものではない、と個人的には思います。

YAML

AWSTemplateFormatVersion: '2010-09-09'
Resources:
  myS3Bucket:
    Type: AWS::S3::Bucket
    DeletionPolicy: Retain

参考:AWSドキュメント「DeletionPolicy 属性」 https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-attribute-deletionpolicy.html

起動テンプレートのスタックを更新したのに、新しいインスタンスが古い起動テンプレートで起動します


EC2の起動テンプレートとAutoScalingGroupの両方を管理しているスタックを更新したときの話です。

起動テンプレートは、EC2起動時の設定項目をあらかじめ設定し置いたものです。手動でEC2インスタンスを起動した場合、ボリュームのサイズや使用するAMI、インスタンスタイプなど、細かに設定する画面が出てきて、それらすべてを入力してようやく起動することができます。起動テンプレートを一度作成しておけば、逐一入力することなく、同じ設定のインスタンスを一瞬で起動できるようになる、というわけです。Webサーバなど、まったく同じ設定のサーバを水平方向にスケールしたい場合、AWSではAutoScalingを用いるのが一般的ですが、AutoScalingGroupではこの起動テンプレートを指定することが必須になってきます。

今回は、インスタンスのCPU使用率がひっ迫していたため、起動テンプレート内のインスタンスタイプをより高性能なものに変更しました。ところが、AutoScalingGroupからインスタンスの入れ替えを実施しても、CPUのひっ迫は改善せず。新規に起動したインスタンスのインスタンスタイプを見てみると、従来と同じ低性能なもののままになっていました。起動テンプレートの中身は高性能なものにちゃんと書き換えたはずなのに、なぜ?

原因は、AutoScalingGroupのLaunchTemplateフィールド内にあるVersionフィールドの記述方法にありました。起動テンプレートにはバージョン管理機能があり、今回のようにスタックで起動設定を更新した場合、古い設定を完全に上書きするのではなく、新しいバージョンとして保存します。起動テンプレートIDはどのバージョンでも同じ値であるため、起動テンプレート使用時にはどのバージョンを使うか指定してやる必要があります。というわけで、AutoScalingGroupのVersionフィールドには使いたいバージョンを記載することになるのですが、ここで困ったことが一つ。Versionフィールドで特定のバージョンを指定していると、起動テンプレートの新バージョンができた際に反映されないという問題です。たとえば起動テンプレートを更新し、バージョン2が作成されたとします。このとき、AutoScalingでは旧バージョンの1をVersionで指定しているはずなので、この値を2に書き換えてやる必要が出てくるわけです。

理由がわかればなんてことはないもので、Version: 2と記述を変えてやることでインスタンスは高性能なインスタンスタイプで起動するようになりました。しかしここで別の問題が。起動テンプレート更新のたびに毎回このバージョン書き換え作業をやるとなると、大変スマートではない。なんのためのIaCなのか。

解決策として、Versionに!GetAtt myLaunchTemplate.LatestVersionNumberと記述するというものがあります。GetAtt関数は、実際のリソースの設定値を読み取り、使用することができます。今回はmyLaunchTemplateという論理IDを持つ起動テンプレートから、LatestVersionNumber、つまり最新のバージョンを取得ます。この記述により、AutoScalingによる起動時にその最新バージョンで起動するようになります。スマートですね。

YAML

---
Resources:
  myLaunchTemplate:
    Type: AWS::EC2::LaunchTemplate
    Properties:
      LaunchTemplateName: !Sub ${AWS::StackName}-launch-template
      LaunchTemplateData:
        ImageId: ami-02354e95b3example
        InstanceType: t3.micro

~~~~~~~~~~中略~~~~~~~~~~

  myASG:
    Type: AWS::AutoScaling::AutoScalingGroup
    Properties:
      LaunchTemplate:
        LaunchTemplateId: !Ref myLaunchTemplate
        Version: !GetAtt myLaunchTemplate.LatestVersionNumber
  ...

参考:AWSドキュメント「AWS CloudFormation スタックを起動設定から起動テンプレートに移行する」 https://docs.aws.amazon.com/ja_jp/autoscaling/ec2/userguide/migrate-launch-configurations-with-cloudformation.html

AutoScalingGroupのスタックを更新したのに、変更内容が反映されません


先ほどと同じく、EC2の起動テンプレートとAutoScalingGroupの両方を管理しているスタックを更新したときの話です。

AutoScalingGroupの主な役割は、同一の起動テンプレートから複数台のサーバを起動し、その台数を増減することです。CloudFormationテンプレートに記載する項目は、使用する起動テンプレート、起動する台数(最大と最小の台数を設定し、幅を持たせられる)、インスタンスを起動するサブネットなどになります。

今回は、起動テンプレート内のインスタンスタイプを高性能なものに変更しました(先ほどのリベンジです)。起動テンプレートのバージョンも最新版を参照するようにしっかり設定し、あとはスタックを更新すればAutoScalingGroupの管理するインスタンスが自動的に入れ替わり、更新が完了する……という算段だったのですが、うまくいきませんでした。終了するはずだった古いインスタンスはけろっとして起動し続けていますし、性能の上がったインスタンスが新しく起動してくる気配もありません。

原因はUpdatePolicyフィールドを記載していないことでした。UpdatePolicyフィールドは、CloudFormationテンプレートのAutoScalingGroupの記述のうち、起動テンプレートやサブネットなどの部分に変更があった際のAutoScalingGroupの挙動を定義してくれます。挙動の種類は3つ、新しいインスタンスがすべて起動してから古いインスタンスをすべて終了するReplacingUpdate、最低起動台数を維持しつつ、インスタンスの起動と削除を並行して実施するRollingUpdate、AutoScalingGroupでイベントがスケジュールされている場合に選択できるScheduledActionです。今回は下記のようにRollingUpdateを選択しました。スタックを更新すると、インスタンスが1台起動して、1台終了して、また1台起動して……というのを繰り返して、最終的にすべてが新しいインスタンスに入れ替わりました。とってもスムーズ。

YAML

---
Resources:
  myASG:
    Type: AWS::AutoScaling::AutoScalingGroup
    Properties:
      LaunchTemplate:
        LaunchTemplateId: !Ref myLaunchTemplate
        Version: !GetAtt myLaunchTemplate.LatestVersionNumber
    UpdatePolicy:
      AutoScalingRollingUpdate:
        MaxBatchSize: 1
        MinInstancesInService: 0
...

参考:AWSドキュメント「UpdatePolicy属性」 https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-attribute-updatepolicy.html

テンプレート内のParameterフィールドでDefault値を変更したのですが、スタック更新時に反映されません


テンプレートには作成したいリソースの内容を記入するResourcesフィールドのほかに、設定値を変数として切り出すParametersフィールドを設定することができます。プログラミングにおいて、よく使用され、かつ変更される値を変数化しておくと、修正時に変数定義の一か所だけ修正すれば済むので楽、という話がありますが、それと同様の効果があります。Parametersフィールドには、各パラメータのパラメータ名、データの型などを設定します。CloudFormation でスタック作成時にパラメータの実値を記入するページが表示されるので記入します。記入した値はResourcesフィールド内の!Ref {パラメータ名}と書かれた部分に流し込まれ、リソースが作成されるといった流れです。

CloudFormation のスタック作成時に値を記入、と書いたのですが、実はパラメータの項目としてDefaultの値を設定しておくことで、記入画面に自動で値がフィルされるようになります。予め変数の中身の値までテンプレートに記入しておくことで、実際の作成時の記入ミスを防げる。これぞIaC。

しかしながら、ちょっとした問題点があります。スタックの更新時は、Defaultの値を書き換えてもスタック作成画面のパラメータ記入欄に自動で反映されないのです。これについてはAWSサポートに問い合わせたところ、「Defaultフィールドはあくまでスタックの"作成"時のための機能であり、"更新"時には対応していない」とのことでした。つまり、更新時はDefaultを設定していないとき同様に値を直接記入してやる必要があるということです。残念。テンプレート内のDefaultを書き換えたからといって、そのまま更新するとそのパラメータ部分は変更されないまま更新が走ってしまいますので、気を付けましょう。

参考:AWSドキュメント「パラメータ」 https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/parameters-section-structure.html

おわりに


CloudFormationはなかなか奥が深い世界で、便利な機能や設定項目が掘れば掘るほど出てきます。かたや、それらを設定していないとせっかくのIaCの恩恵を十分に受けられていないということも多いです。これからも気づきがあるたびに、こういった形で残していくようにしたいと思います。

筆者のその他の記事


執筆者プロフィール:三好 伊織
2022年4月、IT未経験からSHIFTに新卒入社。当初はテスト領域への配属を考えていたが、何がどう転んだか技術領域のITソリューション部に所属。現在はKubernetesを用いたコンテナ基盤運用の案件に従事。趣味はジャズの演奏と鑑賞、特技は道端に生えるキノコの鑑定(ただし食べるのは苦手)。

お問合せはお気軽に
https://service.shiftinc.jp/contact/

SHIFTについて(コーポレートサイト)
https://www.shiftinc.jp/

SHIFTのサービスについて(サービスサイト)
https://service.shiftinc.jp/

SHIFTの導入事例
https://service.shiftinc.jp/case/

お役立ち資料はこちら
https://service.shiftinc.jp/resources/

SHIFTの採用情報はこちら

PHOTO:UnsplashPhilipp Katzenberger