CloudFormationの基本と実践的なスタック管理

AWS CloudFormationは、AWSが提供するInfrastructure as Code(IaC)サービスだ。Terraformなど他のIaCツールを使ったことがある人にとっては、「AWS専用のIaCツール」という理解で概ね正しい。ただし、CloudFormation固有の概念や設計思想を理解しておくと、既存プロジェクトへの参画や運用がスムーズになる。

この記事では、IaC経験者を対象に、CloudFormationの基本概念からスタック管理の実務、ベストプラクティスまでを解説する。

CloudFormation固有の概念

テンプレート・スタック・リソースの関係

CloudFormationの世界は、3つの概念で構成される。

cfn-basic-structure.png

テンプレートはYAMLまたはJSONで記述された設計図だ。どのようなAWSリソースを、どのような設定で作成するかを宣言的に記述する。

スタックは、テンプレートから作成されたリソースの集合体だ。テンプレートをCloudFormationにデプロイすると、スタックが作成される。スタックは単なるリソースの入れ物ではなく、ライフサイクル管理の単位でもある。スタックを削除すれば、そこに含まれるリソースも(設定次第で)一括削除される。

リソースは、実際に作成されるAWSの構成要素(EC2インスタンス、S3バケット、IAMロールなど)を指す。

この関係性はTerraformの「設定ファイル → State → 実リソース」と似ているが、決定的な違いがある。

状態管理:tfstateがない世界

Terraformを使っていると、terraform.tfstateの管理に神経を使う。S3+DynamoDBでリモートステート管理を設定し、ロックを考慮し、ステートの破損に怯える経験は誰にでもあるだろう。

CloudFormationにはこの悩みがない。状態はAWS側で自動管理される

具体的には、CloudFormationサービス自体がスタックの状態を保持している。どのリソースがどのスタックに属しているか、最後にデプロイされたテンプレートの内容は何か、といった情報はすべてAWS側で管理される。ユーザーがステートファイルを意識する必要はない。

これは大きなメリットだが、トレードオフもある。

観点 CloudFormation Terraform
状態管理 AWS自動管理(楽) ユーザー管理(柔軟)
状態の可視性 コンソール/APIで確認 JSONファイルで直接確認可
状態の移行 難しい(後述のStack Refactoring) terraform state mvで柔軟
マルチクラウド 不可 可能

組み込みの依存関係解決

CloudFormationは、リソース間の依存関係を自動的に解決する。例えば、EC2インスタンスがセキュリティグループを参照している場合、セキュリティグループが先に作成される。

Resources:
  MySecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: My SG

  MyInstance:
    Type: AWS::EC2::Instance
    Properties:
      SecurityGroupIds:
        - !Ref MySecurityGroup  # この参照から依存関係を自動推論

明示的に依存関係を指定したい場合はDependsOn属性を使う。ただし、!Ref!GetAttによる参照があれば、ほとんどの場合は自動推論で十分だ。

組み込み関数

CloudFormationには便利な組み込み関数(Intrinsic Functions)がある。よく使うものを紹介する。

!Ref - リソースやパラメータを参照する。リソースの場合、返される値はリソースタイプによって異なる(例:EC2インスタンスならインスタンスID)。

!GetAtt - リソースの属性を取得する。!GetAtt MyInstance.PublicIpのように使う。

!Sub - 文字列内で変数を展開する。!Sub "arn:aws:s3:::${BucketName}/*"のように使う。

!Join - 文字列を結合する。!Join ["-", [!Ref Env, "app", "bucket"]]

!If - 条件分岐。Conditionsセクションと組み合わせて使う。

!ImportValue - 他のスタックからエクスポートされた値をインポートする(後述)。

スタック管理の実務

スタック管理はCloudFormation運用の核心部分だ。ここを理解しているかどうかで、運用の安定性が大きく変わる。

スタック分割戦略

cfn-stack-division.png

「どのリソースをどのスタックにまとめるか」は重要な設計判断だ。AWSの公式ベストプラクティスでは、ライフサイクルオーナーシップの2軸で考えることを推奨している。

ライフサイクル:変更頻度が同じリソースをまとめる。VPCのようなインフラ基盤は頻繁に変更しないが、アプリケーション設定は頻繁に変わる。これらを同じスタックに入れると、アプリ設定を変えるたびにVPCに影響が及ぶリスクを負うことになる。

オーナーシップ:管理責任が同じリソースをまとめる。インフラチームが管理するネットワーク層と、アプリチームが管理するコンピューティング層は、別スタックにした方がチーム間の独立性が保たれる。

典型的な分割パターンとしては以下がある。

ただし、分割しすぎると管理が複雑になる。最初は1つのスタックから始めて、成長に応じて分割していくアプローチも有効だ。

Change Sets:更新前のプレビュー

スタックを更新する前に、何が変わるのかを確認できるのがChange Setsだ。Terraformのterraform planに相当する機能と考えて良い。

Change Setを作成すると、以下の情報が得られる。

特に重要なのは「置換」の検出だ。一部のリソースは、特定のプロパティを変更すると「更新」ではなく「削除→再作成」になる。例えば、EC2インスタンスのAMI IDを変更すると、インスタンスは置換される。これはダウンタイムやデータ損失につながる可能性があるため、Change Setで事前に確認することが重要だ。

# Change Setの作成
aws cloudformation create-change-set \
  --stack-name my-stack \
  --template-body file://template.yaml \
  --change-set-name my-changes

# Change Setの確認
aws cloudformation describe-change-set \
  --stack-name my-stack \
  --change-set-name my-changes

# 問題なければ実行
aws cloudformation execute-change-set \
  --stack-name my-stack \
  --change-set-name my-changes

従来のChange Setsは「前回デプロイしたテンプレート」と「新しいテンプレート」の2者比較だった。つまり、誰かがコンソールから手動変更していても、Change Setには反映されない。ドリフトを知りたければ、別途detect-stack-driftを実行する必要があった。

2025年に導入されたDrift-aware Change Setsは、「新テンプレート」「前回テンプレート」「実際のインフラ状態」の3者を比較する。これにより、手動変更が入った環境でも、より正確な変更プレビューが可能になった。

更新の挙動とロールバック

CloudFormationの更新は、デフォルトで自動ロールバックが有効になっている。更新中にエラーが発生すると、自動的に前の状態に戻る。

これはTerraformとの大きな違いだ。Terraformは更新に失敗すると、中途半端な状態で止まることがある。CloudFormationは「全部成功するか、全部戻すか」のトランザクション的な挙動をする(ただし完全なトランザクションではない)。

更新の挙動には3つのポリシーがある。

Update(更新):リソースのプロパティをその場で変更する。ダウンタイムなし、または最小限。

Replace(置換):新しいリソースを作成してから古いリソースを削除する。プロパティによっては置換が必須になる。

Delete(削除):テンプレートからリソース定義を削除した場合。

ロールバックが失敗することもある。例えば、S3バケットにオブジェクトが入っている状態でバケットを削除しようとすると、ロールバックに失敗してスタックがROLLBACK_FAILED状態になる。この場合は手動での対処が必要だ。

スタック間連携

cfn-stack-integration.png

複数のスタックを連携させる方法は主に3つある。

1. Outputs/Exports

あるスタックの出力値を、別のスタックで参照する最もシンプルな方法。

# ネットワークスタック
Outputs:
  VpcId:
    Value: !Ref MyVPC
    Export:
      Name: shared-vpc-id  # エクスポート名

# アプリケーションスタック
Resources:
  MyInstance:
    Type: AWS::EC2::Instance
    Properties:
      SubnetId: !ImportValue shared-vpc-id  # インポート

注意点として、エクスポートされた値を参照しているスタックがある限り、元のスタックを削除できない。依存関係が暗黙的に生まれるため、スタックの削除順序を意識する必要がある。

2. Nested Stacks

スタックの中にスタックを入れ子にする方法。共通パターンをモジュール化するのに適している。

Resources:
  NetworkStack:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: https://s3.amazonaws.com/mybucket/network.yaml
      Parameters:
        Environment: !Ref Environment

親スタックを更新すると、変更があった子スタックも自動的に更新される。テンプレートの再利用性が高まるが、デバッグが複雑になる面もある。

3. StackSets

複数のAWSアカウント・リージョンに同じスタックをデプロイする機能。組織全体のガバナンス(セキュリティ設定、監査設定など)を一元管理するのに使う。

2025年にはデプロイ順序の制御が強化され、DependsOnパラメータで最大10個の依存関係を指定できるようになった。基盤スタックが完了してからアプリケーションスタックをデプロイする、といった制御が可能だ。

ドリフト検出と対処

**ドリフト(Drift)**とは、CloudFormationで管理しているリソースが、テンプレートの定義と異なる状態になっていることを指す。典型的には、誰かがコンソールから手動で設定を変更した場合に発生する。

# ドリフト検出の開始
aws cloudformation detect-stack-drift --stack-name my-stack

# ドリフト検出結果の確認
aws cloudformation describe-stack-drift-detection-status \
  --stack-drift-detection-id <detection-id>

# 各リソースのドリフト詳細
aws cloudformation describe-stack-resource-drifts \
  --stack-name my-stack

ドリフトが検出された場合の対処法は主に2つ。

  1. テンプレートを実態に合わせる:手動変更が正しい場合。テンプレートを修正して、現在の状態を正として採用する。

  2. 実態をテンプレートに合わせる:手動変更が誤りの場合。スタックを更新して、テンプレートの定義に戻す。

2025年の改善で、Drift-aware Change Setsが導入された。これにより、「テンプレートの変更」「前回デプロイ時の状態」「現在の実態」の3者を比較した上でChange Setを生成できるようになった。ドリフトがある環境でも、より安全に更新できる。

削除時の保護

スタックやリソースの誤削除を防ぐ仕組みがいくつかある。

DeletionPolicy

リソースレベルで削除時の挙動を制御する。

Resources:
  MyDatabase:
    Type: AWS::RDS::DBInstance
    DeletionPolicy: Retain  # スタック削除時もリソースは残す
    Properties:
      # ...

  MyBucket:
    Type: AWS::S3::Bucket
    DeletionPolicy: Snapshot  # 削除前にスナップショットを作成(対応リソースのみ)

本番環境のデータベースやS3バケットにはRetainを設定しておくのが安全だ。

Stack Policy

スタックレベルで、特定リソースの更新を防ぐ。

{
  "Statement": [
    {
      "Effect": "Deny",
      "Action": "Update:Replace",
      "Principal": "*",
      "Resource": "LogicalResourceId/ProductionDatabase"
    },
    {
      "Effect": "Allow",
      "Action": "Update:*",
      "Principal": "*",
      "Resource": "*"
    }
  ]
}

この例では、ProductionDatabaseの置換(Replace)操作を禁止している。誤ってデータベースが再作成されることを防げる。

Termination Protection

スタック自体の削除を防ぐフラグ。有効にすると、明示的に無効化しない限りスタックを削除できない。

aws cloudformation update-termination-protection \
  --stack-name my-stack \
  --enable-termination-protection

本番スタックには必ず設定しておくべきだ。

ベストプラクティス

テンプレート設計

パラメータで環境差分を吸収する

Parameters:
  Environment:
    Type: String
    AllowedValues: [dev, stg, prod]

  InstanceType:
    Type: String
    Default: t3.micro
    AllowedValues: [t3.micro, t3.small, t3.medium]

Mappingsで環境ごとの値を管理する

Mappings:
  EnvConfig:
    dev:
      InstanceType: t3.micro
      MinCapacity: 1
    prod:
      InstanceType: t3.medium
      MinCapacity: 3

Resources:
  MyInstance:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: !FindInMap [EnvConfig, !Ref Environment, InstanceType]

Conditionsで条件分岐する

Conditions:
  IsProduction: !Equals [!Ref Environment, prod]

Resources:
  ProductionOnlyResource:
    Type: AWS::SNS::Topic
    Condition: IsProduction  # prod環境のみ作成

機密情報の扱い

テンプレートにパスワードやAPIキーを直接書いてはいけない。**動的参照(Dynamic References)**を使う。

Resources:
  MyDatabase:
    Type: AWS::RDS::DBInstance
    Properties:
      MasterUsername: '{{resolve:secretsmanager:MySecret:SecretString:username}}'
      MasterUserPassword: '{{resolve:secretsmanager:MySecret:SecretString:password}}'

この構文で、AWS Secrets ManagerやSystems Manager Parameter Storeから値を動的に取得できる。テンプレートには参照先だけが記述され、実際の値は含まれない。

検証ツール

cfn-lint:テンプレートの静的解析ツール。構文エラー、ベストプラクティス違反、非推奨プロパティなどを検出する。

pip install cfn-lint
cfn-lint template.yaml

TaskCat:複数リージョンでテンプレートを実際にデプロイしてテストするツール。CI/CDパイプラインに組み込んで使う。

CI/CD統合

CloudFormationテンプレートもコードとして管理し、CI/CDパイプラインでデプロイするのが現代的なプラクティスだ。

一般的なフローは以下の通り。

  1. テンプレートをGitリポジトリで管理
  2. プルリクエスト時にcfn-lintで検証
  3. マージ後、Change Setを自動作成
  4. 承認フローを経てChange Setを実行

AWS CodePipelineを使えば、CloudFormationとの統合が容易だ。GitHub ActionsやGitLab CIからaws cloudformationコマンドを叩く方法もある。

他ツールとの比較

Terraform vs CloudFormation

観点 CloudFormation Terraform
対応クラウド AWSのみ マルチクラウド
言語 YAML/JSON HCL
状態管理 AWS自動管理 ユーザー管理(リモートステート設定が必要)
ロールバック 自動 なし(手動で対処)
新サービス対応 即座(AWS純正) 遅延あり(プロバイダ更新待ち)
リソース上限 500/スタック 制限なし
モジュール Nested Stacks Terraform Modules(より柔軟)
プランニング Change Sets terraform plan
インポート あり(制限あり) terraform import(柔軟)
コミュニティ AWS公式 巨大なOSSコミュニティ

CloudFormationが適している場面

Terraformが適している場面

どちらが優れているというより、要件と組織の状況で選ぶべきツールは変わる。「既存プロジェクトがCloudFormationだから」というのも立派な選択理由だ。

AWS CDK

AWS CDK(Cloud Development Kit)は、TypeScript、Python、Javaなどのプログラミング言語でインフラを定義できるツールだ。内部的にはCloudFormationテンプレートを生成する。

// CDKの例(TypeScript)
const bucket = new s3.Bucket(this, 'MyBucket', {
  versioned: true,
  encryption: s3.BucketEncryption.S3_MANAGED,
});

CDKのメリットは以下の通り。

CloudFormationを直接書くか、CDKを使うかは好みと要件による。CDKを使う場合でも、CloudFormationの概念理解は必要だ。デバッグ時には生成されたCloudFormationテンプレートを読むことになるし、スタック管理の考え方は同じだからだ。

まとめ

CloudFormationは、AWSに特化したIaCツールとして、状態管理の自動化や自動ロールバックなどの特徴を持つ。Terraformとは設計思想が異なる部分があり、それぞれの特性を理解した上で使い分けることが重要だ。

スタック管理においては、分割戦略、Change Setsによる事前確認、ドリフト検出、削除保護といった機能を適切に使いこなすことで、安全な運用が可能になる。2025年には Drift-aware Change SetsやStack Refactoringなど、実務上の課題を解決する機能が追加されており、継続的に進化している。

IaC経験者であれば、CloudFormation固有の概念さえ押さえれば、すぐに実践で使えるようになるはずだ。

参考

抽出された概念