CloudFormationでインフラを安全に、保守しやすく、再利用しやすく管理するための実践ガイドです。
目次
- スタック設計
- テンプレートの書き方
- パラメータと設定値の管理
- シークレット管理
- データ保護と削除対策
- 変更管理とデプロイ
- 再利用性(ネスト・モジュール・StackSets)
- セキュリティとIAM
- 検証・テスト・ポリシー適用
- ドリフト管理と運用
- 命名・タグ付け
- CI/CD
- よくあるアンチパターン
- チェックリスト
1. スタック設計
ライフサイクルと所有者でスタックを分割する
変更頻度と担当チームが異なるリソースを、同じスタックに入れないようにします。
| レイヤー | 例 | 変更頻度 |
|---|---|---|
| ネットワーク | VPC、サブネット、ルートテーブル、NAT Gateway | 低 |
| セキュリティ / 共通基盤 | IAMロール、KMSキー、共通セキュリティグループ | 低〜中 |
| データ | RDS、DynamoDB、S3(データ保管用) | 低 |
| アプリケーション | ECS、Lambda、ALB、API Gateway | 高 |
- ステートフルなリソース(DB、S3など)とステートレスなリソースを分ける。 アプリのデプロイでデータ層のリソースが誤って置き換えられる事故を防げます。
- 1スタックが大きくなりすぎないようにする。リソース数の上限(1スタック500リソース)に近づくのは、分割すべきサインです。
- スタックが小さすぎてもスタック間の依存関係が複雑になるので、「一緒にデプロイ・削除されるもの」を1単位にするのが目安です。
スタック間の連携方法を選ぶ
| 方法 | 特徴 | 向いているケース |
|---|---|---|
Outputs + Export / Fn::ImportValue | 強い結合。参照されている間はExport元を変更・削除できない | 変更がほぼない基盤値(VPC IDなど) |
| SSM Parameter Store 経由 | 疎結合。値の更新が自由 | 変更の可能性がある値、チームをまたぐ連携 |
| ネストスタックのパラメータ受け渡し | 親子間で完結 | 1つのアプリを構成する部品同士 |
注意:
Exportは一度他のスタックから参照されると、値の変更や削除ができなくなります。気軽に多用すると、リファクタリング時に身動きが取れなくなります。
2. テンプレートの書き方
形式
- YAMLを推奨。コメントが書け、可読性が高く、短縮形の組み込み関数(
!Ref,!Sub,!GetAtt)が使えます。 AWSTemplateFormatVersion: "2010-09-09"とDescriptionを必ず記述します。
基本構成例
AWSTemplateFormatVersion: "2010-09-09"
Description: "Application stack for my-app"
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label: { default: "Environment" }
Parameters: [Environment]
Parameters:
Environment:
Type: String
AllowedValues: [dev, stg, prod]
Description: "Deployment environment"
Conditions:
IsProd: !Equals [!Ref Environment, prod]
Mappings:
EnvConfig:
dev: { InstanceClass: db.t4g.micro }
stg: { InstanceClass: db.t4g.medium }
prod: { InstanceClass: db.r6g.large }
Resources:
# ...
Outputs:
# ...書き方のポイント
!Subを!Joinより優先する。 読みやすく、ミスが減ります。# 良い例 BucketName: !Sub "${AWS::StackName}-${Environment}-logs" # 読みにくい例 BucketName: !Join ["-", [!Ref AWS::StackName, !Ref Environment, "logs"]]- ハードコードを避け、擬似パラメータを使う。
AWS::Region、AWS::AccountId、AWS::Partition、AWS::StackNameなど。# ARNのパーティションもハードコードしない(GovCloud・中国リージョン対応) Resource: !Sub "arn:${AWS::Partition}:s3:::${MyBucket}/*" - AMI IDをハードコードしない。 SSMの公開パラメータを参照します。
Parameters: LatestAmiId: Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id> Default: /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64 - 条件分岐(
Conditions)は最小限に。 環境差分が大きいなら、テンプレートを分けるかパラメータで吸収する方が保守しやすいことが多いです。 - 繰り返しの記述には
AWS::LanguageExtensionsトランスフォームのFn::ForEachが使えます。ただし可読性とのバランスを考えます。 DependsOnは、暗黙的な依存(!Ref/!GetAtt)で表現できない場合にのみ使います。
3. パラメータと設定値の管理
- パラメータには制約を付ける。
AllowedValues、AllowedPattern、MinLength、MaxLength、ConstraintDescriptionで不正な入力を防ぎます。 - AWS固有のパラメータ型を使う。
AWS::EC2::VPC::Id、AWS::EC2::Subnet::Id、List<AWS::EC2::SecurityGroup::Id>などを使うと、デプロイ前に存在チェックが行われます。 - 環境ごとの値はパラメータファイルで管理する。
params/ dev.json stg.json prod.json - パラメータ数が多くなりすぎたら、
Mappingsや SSM Parameter Store への移行を検討します。 - 機密値をパラメータで受け取る場合は
NoEcho: trueを付けます(ただし後述の動的参照の方が推奨)。
4. シークレット管理
- パスワードやAPIキーをテンプレート・パラメータファイルに直接書かない。
- 動的参照(Dynamic References)を使う。
# Secrets Manager MasterUserPassword: !Sub "{{resolve:secretsmanager:${DbSecret}:SecretString:password}}" # SSM Parameter Store(SecureString) ApiKey: "{{resolve:ssm-secure:/my-app/prod/api-key}}" - RDSなどは、
ManageMasterUserPassword: trueを使ってパスワード管理をサービス側(Secrets Manager)に任せるのが最も安全です。 NoEchoは値をマスクするだけで、テンプレートのMetadataやOutputsに書くと漏れます。シークレットをOutputsに出力しないでください。
5. データ保護と削除対策
DeletionPolicy / UpdateReplacePolicy
ステートフルなリソースには必ず設定します。
Database:
Type: AWS::RDS::DBInstance
DeletionPolicy: Snapshot # スタック削除時にスナップショットを取得
UpdateReplacePolicy: Snapshot # 置換更新時にもスナップショットを取得
Properties:
DeletionProtection: true # サービス側の削除保護も併用
# ...
DataBucket:
Type: AWS::S3::Bucket
DeletionPolicy: Retain
UpdateReplacePolicy: Retain| ポリシー | 動作 |
|---|---|
Delete | リソースを削除(多くのリソースのデフォルト) |
Retain | リソースを残す |
RetainExceptOnCreate | 作成失敗時のロールバックでは削除し、それ以外は残す(ゴミリソースが残りにくい) |
Snapshot | スナップショット取得後に削除(RDS、EBS、ElastiCacheなど対応リソースのみ) |
重要:
UpdateReplacePolicyを忘れがちです。プロパティ変更で置換(Replacement)が発生すると、DeletionPolicyだけでは古いリソースが削除されてしまいます。
その他の保護機能
- スタックの削除保護(Termination Protection) を本番スタックで有効にする。
- スタックポリシー で、特定リソースの更新・置換を禁止する。
{ "Statement": [ { "Effect": "Allow", "Action": "Update:*", "Principal": "*", "Resource": "*" }, { "Effect": "Deny", "Action": ["Update:Replace", "Update:Delete"], "Principal": "*", "Resource": "LogicalResourceId/Database" } ] } - 物理名(
BucketName、TableNameなど)の固定はなるべく避ける。 名前を固定すると、置換が必要な変更が「名前の重複」で失敗します。固定が必要な場合は、置換が起きる変更に注意します。
6. 変更管理とデプロイ
変更セット(Change Sets)を必ず使う
- 本番では
update-stackを直接実行せず、変更セットを作成 → 内容確認 → 実行 の流れにします。 - 特に
Replacement: Trueのリソースがないかを必ず確認します。 aws cloudformation deployを使う場合も、--no-execute-changesetで事前確認できます。
aws cloudformation create-change-set \
--stack-name my-app-prod \
--change-set-name release-001 \
--template-body file://template.yaml \
--parameters file://params/prod.json
aws cloudformation describe-change-set \
--stack-name my-app-prod \
--change-set-name release-001ロールバック設定
- ロールバックトリガー:CloudWatchアラームを指定し、デプロイ後の監視期間中にアラームが発火したら自動ロールバックします。
- 開発環境では
--disable-rollbackや「失敗時に保持」オプションを使うと、失敗したリソースをその場で調査できます(本番では使わない)。
コンソールでの手動変更を禁止する
- CloudFormationが管理するリソースを、コンソールやCLIで直接変更しないでください。ドリフトの原因になり、次回デプロイで予期しない上書きや失敗が起きます。
- 緊急対応で手動変更した場合は、必ずテンプレートに反映します。
既存リソースの取り込み・移動
- 手動で作ったリソースは リソースインポート でスタックに取り込めます(
DeletionPolicyの指定が必須)。 - 既存環境からテンプレートを作るには IaCジェネレーター が使えます。
- スタック間でリソースを移すときは、
Retainで切り離してから別スタックにインポートするか、スタックリファクタリング機能を使います。
7. 再利用性(ネスト・モジュール・StackSets)
| 仕組み | 用途 |
|---|---|
| ネストスタック | 1つのアプリを部品(ネットワーク、ALB等)に分け、親スタックからまとめてデプロイ |
| CloudFormation モジュール | 組織標準のリソース構成(例:暗号化済みS3バケット)をレジストリに登録し、リソースのように使う |
| StackSets | 複数アカウント・複数リージョンへ同じスタックを展開(AWS Organizations と連携) |
| AWS SAM / CDK | サーバーレスや複雑な構成の記述量を減らす高レベルの抽象化 |
- ネストスタックは深くしすぎない(2〜3階層まで)。デバッグが難しくなります。
- StackSets では、サービスマネージド権限 + 自動デプロイ を使うと、新規アカウントにも自動で展開されます。同時実行数・失敗許容数を設定して段階的に展開します。
- 共通テンプレートはバージョン管理し、変更時の影響範囲を把握できるようにします。
8. セキュリティとIAM
- CloudFormation サービスロールを使う。 デプロイ実行者に広い権限を与えず、
--role-arnで指定したロールにリソース操作権限を持たせます。実行者にはcloudformation:*とiam:PassRoleのみ付与する構成が理想です。 - 最小権限の原則。 テンプレート内のIAMポリシーで
"Action": "*"や"Resource": "*"を避けます。 - IAMリソースを含むテンプレートでは
CAPABILITY_IAM/CAPABILITY_NAMED_IAMが必要です。これは「IAMを変更する」ことの明示的な承認なので、レビューの対象にします。 - 暗号化をデフォルトにする。 S3、EBS、RDS、SNS、SQS などで暗号化を有効にし、必要に応じてKMSカスタマーマネージドキーを使います。
- S3バケットには
PublicAccessBlockConfigurationを必ず設定します。 - CloudTrail で CloudFormation API の呼び出しを記録します。
9. 検証・テスト・ポリシー適用
| ツール | 目的 |
|---|---|
aws cloudformation validate-template | 構文チェック(最低限) |
| cfn-lint | リソース仕様に基づく詳細な静的解析。必須で導入したい |
| cfn-guard | 組織のルール(ポリシー・アズ・コード)で検証 |
| cfn-nag / Checkov | セキュリティ観点の静的解析 |
| taskcat | 複数リージョンへの実デプロイテスト |
| CloudFormation Hooks | デプロイ時にサーバー側でルールを強制(違反をブロック/警告) |
pip install cfn-lint
cfn-lint templates/**/*.yamlcfn-guard ルールの例:
# すべてのS3バケットでバージョニングを必須にする
AWS::S3::Bucket {
Properties.VersioningConfiguration.Status == "Enabled"
}
- これらをpre-commitフックとCIの両方に組み込みます。
- Hooks はクライアント側のチェックを回避されても効くため、組織全体のガードレールとして有効です。
10. ドリフト管理と運用
- ドリフト検出を定期的に実行する。 AWS Config のマネージドルール(
cloudformation-stack-drift-detection-check)で自動化できます。 - ドリフトが見つかったら、「テンプレートに反映する」か「デプロイで元に戻す」かを判断し、放置しません。
- スタックイベントを EventBridge に連携し、失敗(
UPDATE_ROLLBACK_COMPLETE、UPDATE_ROLLBACK_FAILEDなど)を通知します。 UPDATE_ROLLBACK_FAILEDになった場合は、原因を解消してからcontinue-update-rollbackを実行します(必要に応じて特定リソースをスキップ)。- サービスクォータ(スタック数、Export数、リソース数など)を把握しておきます。
11. 命名・タグ付け
命名規則
- スタック名:
<システム名>-<環境>-<レイヤー>(例:myapp-prod-network) - 論理ID:役割がわかるPascalCase(例:
PublicSubnet1、AppSecurityGroup) - 論理IDは安易に変えない。 論理IDの変更は「削除して新規作成」として扱われます。
タグ付け
- スタックレベルのタグは、対応するリソースに自動で伝播します。
- 最低限のタグを統一します。
| タグキー | 例 |
|---|---|
Environment | prod |
Project / System | myapp |
Owner | platform-team |
CostCenter | 1234 |
ManagedBy | CloudFormation |
aws:cloudformation:stack-nameなどのタグは自動付与されるため、手動で付ける必要はありません。
12. CI/CD
推奨パイプライン
[Pull Request]
├─ cfn-lint
├─ cfn-guard / セキュリティスキャン
└─ 変更セット作成(差分をPRにコメント)
↓ レビュー・承認
[main マージ]
├─ dev へデプロイ
├─ 自動テスト
├─ stg へデプロイ
└─ 手動承認 → prod へ変更セット実行
- テンプレートとパラメータファイルは Gitで管理 し、Gitを唯一の正(Single Source of Truth)にします。
- Git同期(Git sync) を使うと、リポジトリの変更をCloudFormationが直接検知してデプロイできます。
- CodePipeline、GitHub Actions(
aws-actions/aws-cloudformation-github-deploy)などでデプロイを自動化します。 - CIからのデプロイはOIDC連携のIAMロールを使い、長期アクセスキーを使わないようにします。
- 本番デプロイには承認ステップを入れます。
推奨ディレクトリ構成例
.
├── templates/
│ ├── network.yaml
│ ├── security.yaml
│ ├── database.yaml
│ └── app.yaml
├── params/
│ ├── dev/
│ ├── stg/
│ └── prod/
├── rules/ # cfn-guard ルール
├── .cfnlintrc
└── .github/workflows/
13. よくあるアンチパターン
| アンチパターン | 問題点 | 対策 |
|---|---|---|
| 全リソースを1スタックに詰め込む | 変更の影響範囲が大きく、上限にも達しやすい | ライフサイクルで分割 |
| 変更セットを見ずに本番更新 | 意図しないリソース置換でデータ消失 | 変更セットのレビューを必須化 |
DeletionPolicy 未設定のDB | スタック削除・置換でデータ消失 | Snapshot / Retain を設定 |
| パスワードをパラメータファイルに記載 | Gitに機密情報が残る | 動的参照・Secrets Manager |
| コンソールで手動変更 | ドリフト、次回デプロイで上書き | 変更はすべてテンプレート経由 |
Export の多用 | 相互依存で変更・削除できなくなる | SSM Parameter Storeで疎結合に |
| ARN・アカウントID・リージョンのハードコード | 他環境で再利用できない | 擬似パラメータを使用 |
| 物理名の固定 | 置換が必要な更新が失敗する | 自動生成名を使う |
| 管理者権限でデプロイ | 誤操作・侵害時の影響が大きい | サービスロール+最小権限 |
14. チェックリスト
デプロイ前に確認する項目です。
テンプレート
- cfn-lint でエラー・警告がない
- ポリシーチェック(cfn-guard など)を通過している
- リージョン・アカウントID・ARNのハードコードがない
- パラメータに適切な型と制約が付いている
データ保護
- ステートフルなリソースに
DeletionPolicyとUpdateReplacePolicyを設定した - 本番スタックで削除保護を有効にした
- 重要リソースにスタックポリシーを設定した
セキュリティ
- シークレットは動的参照で取得している
- IAMポリシーが最小権限になっている
- 暗号化を有効にした
- サービスロールでデプロイしている
デプロイ
- 変更セットで差分を確認し、
Replacementを把握した - ロールバックトリガーを設定した(本番)
- 必須タグを付与した
運用
- ドリフト検出を定期実行している
- スタックの失敗イベントを通知している