CloudFormationデプロイ失敗を防ぐ〜見落としがちなリスクと対策〜

はじめに
西藤です。
クラウドインフラの構築や変更をコードで表現・管理する手法をInfrastructure as Code (IaC)と呼びます。
その中でもAWSにおいては、AWSネイティブなツールとしてAWS CloudFormationがあります。
本ブログの読者の多くの方も規模の大小はあれど、何らかの形でCloudFormationの利用経験があるのではないかと思います。
では、その変更内容のレビューはどうしていますか?
テンプレートのファイルを編集して、そのコードをチームでレビューし、問題なければデプロイするという流れでしょうか。
果たして、コード内容を見るレビューだけで十分でしょうか?
今回は、CloudFormationを使用したインフラ変更時のレビューについて、掘り下げます。
記事の前提
このブログ記事では次のような観点を掘り下げていきます。
- IaCコードレビューの課題
- CloudFormation「変更セット」の活用と課題
- cfn-lintによる静的解析
- 自動化による品質向上
- taskcatを使った実環境テスト
これらを通じて、CloudFormationを使用したインフラ変更におけるより良いプラクティスを考察したいと思います。
また、サンプルとして次のような既存インフラがあって、それに変更を加えていくシナリオを考えてみましょう。
- VPC内にプライベートサブネット2つ、パブリックサブネット2つ
- EC2インスタンス1つがパブリックサブネットに設置されておりApacheのウェブサーバーが稼働している
- インターネットからEC2インスタンスのIPアドレスへアクセスできる

テンプレートのコードは次のような形です。
AWSTemplateFormatVersion: '2010-09-09'
Description: 'VPC with public and private subnet, and EC2 instance for web hosting'
Resources:
# VPC
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
EnableDnsHostnames: true
EnableDnsSupport: true
Tags:
- Key: Name
Value: WebApp-VPC
# Internet Gateway
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: WebApp-IGW
AttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref VPC
InternetGatewayId: !Ref InternetGateway
# Public Subnet
PublicSubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.1.0/24
AvailabilityZone: ap-northeast-1a
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: Public-Subnet
# Private Subnet
PrivateSubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.2.0/24
AvailabilityZone: ap-northeast-1a
Tags:
- Key: Name
Value: Private-Subnet
# Route Tables
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: Public-Route-Table
PrivateRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: Private-Route-Table
# Routes
PublicRoute:
Type: AWS::EC2::Route
DependsOn: AttachGateway
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
# Route Table Associations
PublicSubnetRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet
RouteTableId: !Ref PublicRouteTable
PrivateSubnetRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnet
RouteTableId: !Ref PrivateRouteTable
# Security Group for Web Server
WebServerSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for web server
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
Description: HTTP access
Tags:
- Key: Name
Value: WebServer-SG
# EC2 Instance
WebServerInstance:
Type: AWS::EC2::Instance
Properties:
ImageId: ami-0f95ad36d6d54ceba # Amazon Linux 2023 AMI (ap-northeast-1)
InstanceType: t3.small
SubnetId: !Ref PublicSubnet
SecurityGroupIds:
- !Ref WebServerSecurityGroup
UserData:
Fn::Base64: |
#!/bin/bash
dnf update -y
dnf install -y httpd
systemctl start httpd
systemctl enable httpd
echo "<h1>Hello from Web Server</h1>" > /var/www/html/index.html
Tags:
- Key: Name
Value: WebServer
Outputs:
VPCId:
Description: VPC ID
Value: !Ref VPC
WebServerPublicIP:
Description: Public IP address of the web server
Value: !GetAtt WebServerInstance.PublicIp
WebServerURL:
Description: URL of the web server
Value: !Sub "http://${WebServerInstance.PublicIp}"
AMIのIDを動的に取得していないですとか、単一のアベイラビリティーゾーン(AZ)でサブネットを構築してしまっているなどの課題はありますが、あくまでサンプルとしてご覧ください。
では、この既存インフラに変更を加えるとき、安全に行うにはどのようにすればいいか確認していきましょう。
1. IaCコードレビューの課題
さて、CloudFormationで管理されているインフラの変更をレビューするときに具体的にどのようなステップを踏むでしょうか?
たとえば、次のような流れが考えられます。
- 担当者がテンプレートファイルを編集
- その編集内容をGitリポジトリにPushし、コード管理システム上にてPull Requestを作成
- レビュアーがPull Requestの差分を確認
- 問題がなければPull Requestを承認・Merge(修正が必要な際は担当者が指摘をして、修正が確認できたら承認・Merge)
- 承認されたコードでデプロイ
図に表すと次のとおりです。
コードベースがあるとき、Pull Requestの差分を確認してコードレビューを行うのは一般的です。
しかし、CloudFormationのコードのレビューでわかるのは「コード上の変更点」だけです。
もちろん、たとえば
- 「今取り組んでいるタスクってEC2インスタンスのサイズ変更することじゃなかったっけ?これだと台数の変更になっている」
という点をレビュアーが指摘してくれる可能性はあるので一定の効果はあります。
しかし、「どのようなインフラ変更が起きるのか」をコードの変更点から推測することになります。
これだと、「人の目でコードの変更内容を見た分には問題なさそうだったが、実際にデプロイするときにエラーになってしまう。想定していなかったインフラの変更が入る」
ということが起きるリスクが残ります。
そういったリスクに対応するために、次に紹介するような仕組みを加えて、インフラ変更時の安全性を高めていきましょう。
2. CloudFormation「変更セット」の活用
コードレビューでは見えないインフラ差分を事前に可視化する方法として、CloudFormationには「変更セット」という仕組みがあります。
これはCloudFormationのテンプレートデプロイ時にどのようなインフラ変更が行われるかを、実際のインフラと比較した上で表示してくれる機能です。
これをどのように活用できるか確認してみましょう。
たとえば、冒頭のインフラ例に対して、
- EC2インスタンスのインスタンスタイプを
t3.micro
からt3.small
に変更する - AMIが古くなっているので最新のものに差し替える
というケースを考えてみましょう。
YAMLコード内においては
sample-infrastructure.yaml
# <略>
# EC2 Instance
WebServerInstance:
Type: AWS::EC2::Instance
Properties:
ImageId: ami-0f95ad36d6d54ceba # Amazon Linux 2023 AMI (ap-northeast-1)
InstanceType: t3.micro
SubnetId: !Ref PublicSubnet
SecurityGroupIds:
- !Ref WebServerSecurityGroup
UserData:
Fn::Base64: |
#!/bin/bash
dnf update -y
dnf install -y httpd
systemctl start httpd
systemctl enable httpd
echo "<h1>Hello from Web Server</h1>" > /var/www/html/index.html
Tags:
- Key: Name
Value: WebServer
# <略>
のような記述になっていたとします。
そこからInstanceType: t3.micro
と書かれている記述を InstanceType: t3.small
に更新したコードに変更します。
sample-infrastructure-v2.yaml:
# <略>
# EC2 Instance
WebServerInstance:
Type: AWS::EC2::Instance
Properties:
ImageId: ami-07faa35bbd2230d90 # Amazon Linux 2023 AMI (ap-northeast-1)
InstanceType: t3.small
SubnetId: !Ref PublicSubnet
SecurityGroupIds:
- !Ref WebServerSecurityGroup
UserData:
Fn::Base64: |
#!/bin/bash
dnf update -y
dnf install -y httpd
systemctl start httpd
systemctl enable httpd
echo "<h1>Hello from Web Server</h1>" > /var/www/html/index.html
Tags:
- Key: Name
Value: WebServer
# <略>
というコードになります。diffを見ると次のような形です。
% diff sample-infrastructure.yaml sample-infrastructure-v2.yaml
114,115c114,115
< ImageId: ami-0f95ad36d6d54ceba # Amazon Linux 2023 AMI (ap-northeast-1)
< InstanceType: t3.micro
---
> ImageId: ami-07faa35bbd2230d90 # Amazon Linux 2023 AMI (ap-northeast-1)
> InstanceType: t3.small
このコードを使って、既存のCloudFormation Stackに対して、変更セット作成を進めます。

修正版のYAMLファイルをアップロードして手順を進める

「送信」を押して、変更セットの作成開始
画面のメモ表示にもあるとおり「変更セット」の作成を行うこと自体は既存のStackに影響を与えません。「送信」を押した後の画面で実際のインフラ変更を行うかを判断することができるので、安心して手順を進めましょう。

変更セットの作成が完了すると、既存のインフラにどのような変更が見込まれるかの表示画面に変わります。

上記の例を見ると、EC2インスタンスにReplaceAndDelete
が赤字で表示されています。
今回の例で、AMIのID変更を行おうとしたことが理由ではあるのですが、これはリソースの削除を伴う形での置き換えが行われることを表しています。
たとえば、EC2インスタンスで言うと、EC2インスタンス内におけるデータ(ログ・アプリケーションデータ・アプリケーション自身)の保全を行なっていないと、このインフラ変更が原因でそれらのデータが失われることを意味します。
関係者が、EC2のインフラ変更時の挙動をあらかじめ理解していれば、コードだけを見たレビュー時点でもこれに気がつけるかもしれませんが、人力では限界があります。
このような「変更セット」を使って、仕組み的にインフラ変更時のリスクを検出し、その上でインフラ変更の意思決定することを基本動作とできると良いでしょう。
フロー図に表すと次のような具合です。
3. 「変更セット」で検出できない事象
さて、「変更セット」を使うことでインフラ変更時の挙動を確認できました。
しかし、残念ながらこれだけでは網羅しきれないシチュエーションがあります。
次の例を見てください。
sample-infrastructure-v3.yaml:
# <略>
# EC2 Instance
WebServerInstance:
Type: AWS::EC2::Instance
Properties:
ImageId: ami-0f95ad36d6d54ceba # Amazon Linux 2023 AMI (ap-northeast-1)
InstanceType: t3.smal # t3.smallではなく、t3.smal
SubnetId: !Ref PublicSubnet
# <略>
インスタンスタイプとして t3.small
を指定しようとしたところスペルミスで t3.smal
と記載してしまっているケースを考えます。
この状態で「変更セット」を作成し実行するとどうなるでしょうか。
作成画面は次のようになりました。
「変更セット」自体はエラーなく作成できてしまいました。

変更箇所の詳細を見ても、smal
(※smallではない)になると表示されていますが、この時点ではエラーになりません。

さらにこのまま実行するとどうなるでしょうか。試してみます。
実行自体は開始できました。

しかし、最終的に The following supplied instance types do not exist: [t3.smal]
(t3.smalというインスタンスタイプは存在しない)というエラーとともに失敗し、Rollbackとなりました。

つまり、インスタンスタイプという個別の属性の内訳自体はCloudFormationの「変更セット」機能では捕捉してくれないのです。
Pull Requestレビューと「変更セット」の確認を経ても、デプロイ時にエラーとなるリスクは残るというわけです。
では、どう対策できるのか?
次に紹介する仕組みを使って、そういったデプロイエラーのリスクを軽減していきましょう。
4. cfn-lintによる静的解析
cfn-lintは https://github.com/aws-cloudformation/cfn-lint にて公開されているCloudFormationのlinterです。
CloudFormationのテンプレートコードを精査して、エラーを発生させる可能性のある構文エラーがないかを検査できます。
このツールを使ってどのような活用ができるかを紹介します。
コマンドラインとして利用
cfn-lintはCLIツールとして提供されており、テンプレートファイルに対してチェックを行うコマンドが使えます。
上記のような t3.smal
と書いてしまっているYAMLファイルに対してチェックコマンドを実行すると次のような画面になります。
% cfn-lint sample-infrastructure-v3.yaml
# <略>
E3030 't3.smal' is not one of ['a1.2xlarge', 'a1.4xlarge', 'a1.large', 'a1.medium', 'a1.metal', 'a1.xlarge', 'c1.medium', # <略>
t3.smal
が有効なインスタンスタイプではないということを警告しています。(末尾を省略してしまっているのですが、実際にはすべてのインスタンスタイプが列挙されていて、そのいずれにも合致していない。という旨の表示になっていました。)
このように、cfn-lintを併用することで、CloudFormationの機能だけでは検出できないエラーも事前に防止できます。
IDEの拡張機能として利用
また、VS CodeのようなIDEの拡張機能としても提供されています。インストールした上で該当のコード箇所を見てみますと次のような表示になります。

編集中のコード上に波線表示で、エラーが指摘されています。
これを使えば、導入のハードルも低く、視覚的にもわかりやすい形で誤りに気がつくことができますね。
5. 自動化による品質向上
さて、上記にはCloudFormationの「変更セット」機能とcfn-lintの例を挙げました。
それぞれの機能を組み合わせていくことで、万全なチェックを行えますが、欠かさずにそれらの機能を使わないといけません。
往々にして、こういったチェックの仕組みから漏れてしまった時に、トラブルというのは発生するものです。
人の意識に頼らず、自動でチェックが働く仕組みが重要です。
ここでは、これらの仕組みを自動的に使うようにする設定を紹介します。
5.1 CloudFormation Git同期機能
CloudFormationにGit同期機能というものがあります。
https://aws.amazon.com/jp/about-aws/whats-new/2023/11/aws-cloudformation-git-management-stacks/
従来はテンプレートファイルがgit管理されていても、それをAWSアカウント内にデプロイする際には専用のコマンドラインやAPIを組み合わせた仕組みを作って、デプロイする必要がありました。
2023年にリリースされたこの機能により、各Gitソリューションと連携してGitリポジトリ内のコード変更と直接連動してインフラ変更を進めることができるようになっています。
そして、さらに
のように、Pull Request上のコメントの形で通知できるようになりました。
AWS CodeConnectionsでの接続設定、CloudFormationでのGit同期の有効化設定・・・など必要な設定はいくつかあるのですが、そういった設定のハンズオンはここでは割愛して、どのようなことが実現できるかを紹介します。
前提として、GitHubリポジトリと連携済みのCloudFormation Stackがあります。

この状態で、CloudFormationテンプレートを変更し、GitリポジトリにPushします。(変更内容は最初の例と同じEC2インスタンスをt3.micro
からt3.small
へ変更します)
すると、Pushしてからしばらくして自動的にPull Request上にコメントが追加されました。
今回の場合は1つのリソースにて更新があったという旨のコメントがついています。

コメント内のClick here to view change details
をクリックすると変更セットの内訳が表示されます。

リソースごとの情報を一覧で表示する仕組みなので仕方ないのですが、横に長い表示になるため、左右にスクロールして確認します。

重要なところで言うと、そのリソースが削除を伴わず変更されるのかなどがわかるAttributeChangeType
などを見ると良いでしょう。
(この例だと、Modify
とあり、リソースの削除は発生しないということがわかります。)
変更セットの内容が自動的にPull Requestに投稿されるため、コード差分だけでなく実際のインフラ変更内容もPull Request上で確認できます。これによりレビューの精度と安全性が高まります。
そして、レビュー完了後、Pull RequestをMergeすることでデプロイも自動的に行われるので、細かい手間も削減されます。
フロー図に表すと次のような形です。
自動化される部分も増えて、合理的になっています。
5.2 Git Hookでcfn-lintを自動実行
cfn-lintは
CLIとして提供されているので、ファイルの更新を行った後に必要に応じて手動でコマンドを実行してチェックするのは手間です。
git hookを仕込みcfn-lintコマンドを呼び出すようにすることで、エラーを内包したコードがcommitされないように防ぐことが可能です。
以下にはそのスクリプトの例を紹介します。
.git/hooks/pre-commit:
#!/bin/bash
# CloudFormation Lint Pre-commit Hook
# このフックは、コミット前にCloudFormationテンプレートをcfn-lintで検証します
echo "Running cfn-lint on CloudFormation templates..."
# コミット対象のファイルを取得
files=$(git diff --cached --name-only --diff-filter=ACM | while read file; do
# ファイル拡張子をチェック(yaml, yml, json)
if [[ "$file" =~ \.(yaml|yml|json)$ ]]; then
# ファイルの内容にAWSTemplateFormatVersionが含まれているかチェック
if grep -q "AWSTemplateFormatVersion" "$file" 2>/dev/null; then
echo "$file"
fi
fi
done)
if [ -z "$files" ]; then
echo "No CloudFormation template files to check."
exit 0
fi
# cfn-lintが利用可能かチェック
if ! command -v cfn-lint &> /dev/null; then
echo "Error: cfn-lint is not installed or not in PATH"
echo "Please install cfn-lint: brew install cfn-lint"
exit 1
fi
# 各ファイルに対してcfn-lintを実行
exit_code=0
for file in $files; do
echo "Checking $file..."
# cfn-lintを実行
if ! cfn-lint "$file" -i W ; then
echo "❌ cfn-lint failed for $file"
exit_code=1
else
echo "✅ $file passed cfn-lint validation"
fi
done
if [ $exit_code -ne 0 ]; then
echo ""
echo "❌ Pre-commit hook failed!"
echo "Please fix the CloudFormation template errors before committing."
echo "You can run 'cfn-lint <filename>' manually to see detailed errors."
else
echo ""
echo "✅ All CloudFormation templates passed validation!"
fi
exit $exit_code
内容としては
- pre-commitとして、git commitが行われようとした時点でスクリプト呼び出し
- gitの変更内容にCloudFormationテンプレートファイルが含まれているか確認
- cfn-lintコマンドが利用可能か確認(できない場合はセットアップを促す)
- cfn-lintコマンドが利用可能だった場合は、コマンド実行しエラーがあれば、そこでコミット失敗となるようにする(Warningは判定から除外)
- エラーがなければ、git commitを許可する
という内容です。
この設定を入れた上で、誤ったコードを含んだ状態でgit commitしようとした時の表示は次のとおりです。
% git commit -m "smal"
Running cfn-lint on CloudFormation templates...
Checking sample-infrastructure.yaml...
E3030 't3.smal' is not one of ['a1.2xlarge', 'a1.4xlarge', 'a1.large', 'a1.medium', 'a1.metal', 'a1.xlarge', 'c1.medium'
# <略>
sample-infrastructure.yaml:114:7
❌ cfn-lint failed for sample-infrastructure.yaml
❌ Pre-commit hook failed!
Please fix the CloudFormation template errors before committing.
You can run 'cfn-lint <filename>' manually to see detailed errors.
インスタンスタイプとして、t3.smal
と入れた内容でgit commitしようとしたら、エラーを検出し、git commitに失敗します。
これにより、コード編集のたびにcfn-lintを叩く必要がなく、git commitしようとした段階で自動的にエラーが含まれるのを防いでくれます。
6. taskcatによる実環境テスト
ここまでCloudFormationの「変更セット」機能とcfn-lintの活用方法を紹介しました。
この組み合わせで、デプロイ時のエラーになるリスクはかなり軽減できると思いますが、CloudFormation「変更セット」のドキュメントには次のような気になる記述があります。
変更セットでは、CloudFormation によるスタックの更新が正常に行われるかどうかはわかりません。例えば、アカウントクォータを超過する、更新をサポートしていないリソースを更新しようとしている、リソースの変更に必要な許可が足りていないなど、スタックの更新が失敗する原因になる可能性があるものを、変更セットでは確認しません。更新が失敗した場合、CloudFormation では元の状態にリソースをロールバックするように試みます。
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-changesets.html
あれ、一見するとここまでの内容と矛盾しそうです。
「変更セット」やcfn-lintは、そもそもコードの記述内容が原因でエラーとなることを防ぐ仕組みだと言えます。ただし、それだけではアカウントクオータの制約は検出できないわけです。
結局、デプロイしようとしている変更に対して、そのリソース以外の要因で失敗する可能性は残る。実際にAWSアカウント内にデプロイしてみないとわからない。というエラーのリスクは残るわけでそこがポイントとなります。
そこで検討したいのがtaskcatです。
これはCloudFormationテンプレートを使って、実際に1回デプロイを行い、デプロイが完遂できるかどうかを確認することで、そのテンプレートがエラーなくデプロイできるものになっているかをチェックできるツールです。
(チェックが終わったらデプロイ済みのリソースの削除も自動的におこわれます。)
実際にデプロイを行うことで上記のようなアカウントクオータの制約などのような「実際にデプロイしてはじめて気が付くポイント」を洗い出すことができます。
デプロイ時のParameter設定もファイルに定義する形で設定できるので、さまざまなシチュエーションを試し、「このテンプレートをAWS環境に実際にデプロイしてもエラーなく実行できるのか」を確認できるので、より安心してデプロイできるようにする仕組みとして使えます。
詳細は末尾の参考資料欄を見ていただくほか、以前、このツールを使った仕組みについて書いた記事もあるのでそちらをご参考いただければと思います。

まとめ
以上、CloudFormationにおけるインフラ変更を安全に実行するための方法を紹介しました。内容が長くなってしまったので、改めてまとめると次のとおりです。
- CloudFormationで管理されているインフラ変更にはテンプレートのコード変更だけでなく「変更セット」を使った確認を行うことで、コードレビューだけでは予防できないデプロイ時のエラーを防ぐ
- 「変更セット」を使った確認だけではカバーされないエラーがあるので、cfn-lintで補う
- 「変更セット」もcfn-lintも自動化された仕組みを作ることでもれなくチェックが行われる体制を構築するべき
- taskcatを使ってCloudFormationテンプレートのデプロイが完遂できるかを事前に確認することで、デプロイ時のエラーのリスクをさらに減らす
こういった工夫を加えながら、CloudFormationでのインフラ管理をより安全なものにしたい人にとっての参考になれば幸いです。
今回はデプロイ時のエラー回避に焦点を当てたため、セキュリティリスクの静的解析やベストプラクティスとの比較は対象外としました。
冒頭のサンプルコードもセキュリティ的な観点からするとまだ多くの課題が残ったコードになっているはずです。
これらの点については、別の機会に掘り下げたいと思います。
参考資料
- CloudFormation変更セットのドキュメントページ
- cfn-lintのGitHubリポジトリページ
- CloudFormation Git同期機能・機能リリース時の発表
- CloudFormation Git同期機能のドキュメントページ
- cfn-lintの紹介ブログ記事(pre-commitのアプローチの参考にした)
- taskcatの公式ページ
- taskcatのGitHubリポジトリページ
- taskcatを使って実践例を紹介している過去記事
- CloudFormationのベストプラクティス紹介ページ