はじめに
「コンソールで手動構築できた構成を、コードで再現したい」——それを実現するのが AWS CloudFormation です。
この記事では、template.yaml 1ファイルにリソース構成を定義し、コマンド1本でEC2 + Apache環境を一括構築するハンズオンを紹介します。コンソール版(AWSコンソール版ハンズオン)と全く同じ構成を、CloudFormationで自動化します。
ローカル環境(VSCode)
├── template.yaml(CloudFormationテンプレート)
└── AWS CLI コマンド
↓ スタック作成(コマンド1本)
AWS環境
├── IAMロール(SSM接続用) ← template.yamlで定義
├── セキュリティグループ(SSH22・HTTP80)← template.yamlで定義
└── EC2インスタンス(t2.micro / Amazon Linux 2023 + Apache)← template.yamlで定義このハンズオンで体験できること:
template.yamlへのリソース定義(IAMロール・セキュリティグループ・EC2インスタンス)!Ref/!Sub/!GetAttを使ったリソース間参照aws cloudformation create-stackコマンド1本での全リソース一括デプロイaws cloudformation delete-stackコマンド1本での全リソース一括削除
このハンズオンの特徴:
- コンソール版では15〜20分かかった手動作業が、コマンド1本(約5分)で完了する
delete-stack1本で依存関係を考慮した順番で全リソースを自動削除できる
-->
CloudFormation vs コンソール:どれだけ違うか
| 比較項目 | CloudFormation | コンソール(手動) |
|---|---|---|
| IAMロール作成 | --parameters で値を渡すだけ | 画面でポチポチ(5クリック以上) |
| セキュリティグループ作成 | 同上 | 画面でポチポチ |
| EC2起動(UserData含む) | 同上 | 画面でポチポチ |
| 全リソースのデプロイ | コマンド1本(3〜5分) | 複数画面を行き来(15〜20分) |
| リソース削除 | delete-stack 1本 | 依存関係順に個別削除(4ステップ) |
| 再現性 | 高い(同じ構成を何度でも再現可能) | 低い(手動ミスのリスクがある) |
| バージョン管理 | Gitで管理可能 | 不可(過去の設定を記録できない) |
キーワード解説
| 用語 | 意味 |
|---|---|
| CloudFormation | AWSが提供するIaC(Infrastructure as Code)サービス。YAMLテンプレートでリソースをコード化する |
| スタック | CloudFormationが管理するリソースのまとまり。作成・削除・更新をまとめて操作できる |
!Ref | CloudFormation組み込み関数。パラメータの値やリソースのIDを参照する |
!Sub | CloudFormation組み込み関数。文字列内の ${変数} を展開する |
!GetAtt | CloudFormation組み込み関数。リソースの属性値(ARNなど)を取得する |
| CAPABILITY_NAMED_IAM | 名前付きIAMリソースを作成する場合に必要な明示的な許可フラグ |
| UserData | EC2の初回起動時のみ自動実行されるシェルスクリプト |
前提条件
| ツール | 確認コマンド | 最低バージョン目安 |
|---|---|---|
| AWS CLI v2 | aws --version | 2.x |
| Git | git --version | 2.x |
| VSCode | - | - |
AWS認証確認:
aws sts get-caller-identityアカウントIDが表示されれば認証設定済みです。
template.yaml の全文
以下が今回使用する template.yaml の全文です。プロジェクトフォルダ(ec2-apache-web/)直下に配置します。
⚠️ コピー前に確認:
Default: '123.456.78.901/32'の箇所はダミーIPです。このまま使うとスタック作成は成功しますが、SSHもWebもアクセスできません。--parametersでIPを上書きするか(推奨)、Defaultを自分のIPに書き換えてから使ってください。
AWSTemplateFormatVersion: '2010-09-09'
Description: 'EC2 + Apache Web Server Hands-on (Phase 1-1)'
Parameters:
KeyName:
Type: String
Default: 'my-ec2-apache-key'
Description: Name of an existing EC2 KeyPair
MyIP:
Type: String
Default: '123.456.78.901/32'
Description: Your IP address to allow SSH and HTTP access (CIDR format e.g. 203.0.113.1/32)
Resources:
EC2Role:
Type: AWS::IAM::Role
Properties:
RoleName: 'my-ec2-apache-role'
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: ec2.amazonaws.com
Action: 'sts:AssumeRole'
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
Tags:
- Key: Name
Value: 'my-ec2-apache-role'
# Instance Profile: wrapper that attaches the IAM role to EC2
EC2InstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
InstanceProfileName: 'my-ec2-apache-profile'
Roles:
- !Ref EC2Role
# Security Group: allow SSH(22) and HTTP(80) from MyIP only
EC2SecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: 'my EC2 Apache hands-on SG'
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: !Ref MyIP
Description: SSH access from my IP
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: !Ref MyIP
Description: HTTP access from my IP
Tags:
- Key: Name
Value: 'my-ec2-apache-sg'
# EC2 Instance: Amazon Linux 2023 + Apache (installed via UserData)
EC2Instance:
Type: AWS::EC2::Instance
Properties:
# Automatically fetches the latest Amazon Linux 2023 AMI ID from SSM Parameter Store
ImageId: '{{resolve:ssm:/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64}}'
InstanceType: t2.micro
KeyName: !Ref KeyName
SecurityGroupIds:
- !Ref EC2SecurityGroup
IamInstanceProfile: !Ref EC2InstanceProfile
# UserData: shell script executed automatically on first boot
UserData:
Fn::Base64: |
#!/bin/bash
yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd
echo "<h1>Hello from Amazon Linux 2023!</h1>" > /var/www/html/index.html
echo "<p>Instance ID: $(ec2-metadata --instance-id | cut -d ' ' -f 2)</p>" >> /var/www/html/index.html
echo "<p>Availability Zone: $(ec2-metadata --availability-zone | cut -d ' ' -f 2)</p>" >> /var/www/html/index.html
Tags:
- Key: Name
Value: 'my-ec2-apache-instance'
Outputs:
InstanceId:
Description: EC2 Instance ID
Value: !Ref EC2Instance
PublicIP:
Description: Public IP Address
Value: !GetAtt EC2Instance.PublicIp
WebsiteURL:
Description: Website URL (open in browser)
Value: !Sub 'http://${EC2Instance.PublicIp}'
SSHCommand:
Description: SSH command (update the .pem path as needed)
Value: !Sub 'ssh -i C:\Users\username\.ssh\${KeyName}.pem ec2-user@${EC2Instance.PublicIp}'!Ref と !Sub の使い分け:
| 関数 | 意味 | 例 |
|---|---|---|
!Ref パラメータ名 | パラメータの値を参照する | !Ref MyIP → 203.0.113.1/32 |
!Ref リソースID | リソースのIDを参照する | !Ref EC2SecurityGroup → sg-xxxxx |
!Sub '文字列' | 文字列内の ${変数} を展開する | !Sub 'http://${EC2Instance.PublicIp}' → http://x.x.x.x |
!GetAtt リソース.属性 | リソースの属性値を取得する | !GetAtt EC2Instance.PublicIp → IPアドレス |
構築されるリソースと論理ID:
| リソース | template.yaml上の論理ID |
|---|---|
| IAMロール | EC2Role |
| インスタンスプロファイル | EC2InstanceProfile |
| セキュリティグループ | EC2SecurityGroup |
| EC2インスタンス | EC2Instance |
作業順序
⓪ 自分のIPアドレスを確認する(重要)
↓
① キーペアを作成する(コンソールで実施)
↓
② プロジェクトフォルダに移動する
↓
③ スタックを作成する(aws cloudformation create-stack)
↓
④ 作成状況・Outputsを確認する
↓
⑤ 動作確認(Web・SSH)
↓
⑥ スタックを削除する(aws cloudformation delete-stack)【重要】⓪ 自分のIPアドレスを確認する
セキュリティグループで自分のIPのみを許可するために、正確なIPアドレスを事前に確認します。
注意:
curl https://checkip.amazonaws.comで表示されるIPと、EC2への実際の接続元IPが一致しない場合があります(ISPやプロキシの経路の違いによる)。
方法A: ルーター管理画面で確認(推奨)
- ブラウザで以下のURLにアクセスします
http://192.168.0.1 または http://192.168.1.1 - 「ネットワークマップ」または「インターネット」項目を開く
- **「インターネットIPアドレス」または「WAN IPアドレス」**の値を控えます
方法B: コマンドで確認
curl https://checkip.amazonaws.com控えておく情報: 自分のIPアドレス(例: 203.0.113.1)
① キーペアの作成(コンソールで実施)
キーペアのみコンソールで作成します。CloudFormationではキーペアのダウンロードが行えないためです。
AWSコンソール → EC2 → キーペア → 「キーペアを作成」
| 設定項目 | 値 |
|---|---|
| 名前 | my-ec2-apache-key |
| キーペアのタイプ | RSA |
| プライベートキーファイル形式 | .pem |
ダウンロードされた my-ec2-apache-key.pem を保存します。
C:\Users\ユーザー名\.ssh\my-ec2-apache-key.pem② プロジェクトフォルダに移動する
VSCodeのターミナル(CMD)を開き、プロジェクトフォルダに移動します。
cd C:\my-aws\aws-learning-projects\ec2-apache-webtemplate.yaml があることを確認します。
dir template.yaml③ スタックの作成
以下のコマンドを実行します。203.0.113.1 は⓪で確認した自分のIPアドレスに置き換えてください。
aws cloudformation create-stack ^
--stack-name my-ec2-apache-stack ^
--template-body file://template.yaml ^
--region ap-northeast-1 ^
--capabilities CAPABILITY_NAMED_IAM ^
--parameters ^
ParameterKey=KeyName,ParameterValue=my-ec2-apache-key ^
ParameterKey=MyIP,ParameterValue=203.0.113.1/32各オプションの説明:
| オプション | 意味 |
|---|---|
--stack-name my-ec2-apache-stack | スタック名(任意の名前) |
--template-body file://template.yaml | 使用するテンプレートファイル |
--region ap-northeast-1 | デプロイ先リージョン(東京) |
--capabilities CAPABILITY_NAMED_IAM | 名前付きIAMロールを作成するための明示的な許可 |
--parameters ... | テンプレートのParametersに渡す値 |
^について: Windowsのコマンドプロンプトで長いコマンドを複数行に分けるための改行エスケープ文字です。1行で書いても動作します。
成功すると以下のようなStackIdが表示されます。
{
"StackId": "arn:aws:cloudformation:ap-northeast-1:123456789012:stack/my-ec2-apache-stack/xxxxx"
}④ 作成完了・Outputsの確認
スタック作成は完了まで約3〜5分かかります。
作成状況の確認
aws cloudformation describe-stacks ^
--stack-name my-ec2-apache-stack ^
--query "Stacks[0].StackStatus" ^
--output text| ステータス | 意味 |
|---|---|
CREATE_IN_PROGRESS | 作成中(しばらく待つ) |
CREATE_COMPLETE | 作成完了 |
CREATE_FAILED | 作成失敗(トラブルシューティングを参照) |
ROLLBACK_COMPLETE | 失敗してロールバック完了(リソースは作成されていない) |
Outputs(接続情報)の確認
CREATE_COMPLETE になったら以下を実行します。
aws cloudformation describe-stacks ^
--stack-name my-ec2-apache-stack ^
--query "Stacks[0].Outputs" ^
--output table以下のような出力が表示されます。
------------------------------------------------------------------------------------
| DescribeStacks |
+----------------+-----------------------------------------------------------------+
| OutputKey | OutputValue |
+----------------+-----------------------------------------------------------------+
| InstanceId | i-0123456789abcdef0 |
| PublicIP | x.x.x.x |
| WebsiteURL | http://x.x.x.x |
| SSHCommand | ssh -i C:\Users\...\.ssh\my-ec2-apache-key.pem ec2-user@x.x.x.x |
+----------------+-----------------------------------------------------------------+控えておく情報: PublicIP と WebsiteURL
⑤ 動作確認
Webブラウザで確認
Outputsに表示された WebsiteURL(http://x.x.x.x)をブラウザで開きます。
Hello from Amazon Linux 2023!
Instance ID: i-0123456789abcdef0
Availability Zone: ap-northeast-1aが表示されれば成功です。
表示されない場合: UserDataのApacheインストールが完了するまで2〜3分かかります。少し待ってからリロードしてください。
SSHで接続して確認
Outputsに表示された SSHCommand のコマンドを実行します(パスは実際のパスに修正します)。
ssh -i C:\Users\ユーザー名\.ssh\my-ec2-apache-key.pem ec2-user@(PublicIP)初回接続時の確認には yes を入力します。
# Apacheの動作状態を確認(active (running) と表示されれば正常)
sudo systemctl status httpd
# ApacheのHTMLファイルを確認
cat /var/www/html/index.html
# EC2から切断
exit【接続できない場合】真のIPアドレスを確認する
curl で確認したIPを設定したが接続できない場合、実際の接続元IPが異なる可能性があります。
セキュリティグループIDを取得:
aws cloudformation describe-stack-resources ^
--stack-name my-ec2-apache-stack ^
--query "StackResources[?ResourceType=='AWS::EC2::SecurityGroup'].PhysicalResourceId" ^
--output textSSH全開放(一時的・テスト用):
aws ec2 authorize-security-group-ingress ^
--group-id sg-XXXXXXXXX ^
--protocol tcp ^
--port 22 ^
--cidr 0.0.0.0/0 ^
--region ap-northeast-1SSH接続して真のIPを確認します。
echo $SSH_CLIENT
# 出力例: 203.0.113.1 17508 22
# 先頭の値が真の接続元IP
exit確認後、全開放を削除して真のIPで制限し直します。
rem 全開放を削除
aws ec2 revoke-security-group-ingress ^
--group-id sg-XXXXXXXXX ^
--protocol tcp ^
--port 22 ^
--cidr 0.0.0.0/0 ^
--region ap-northeast-1
rem 真のIPで再制限
aws ec2 authorize-security-group-ingress ^
--group-id sg-XXXXXXXXX ^
--protocol tcp ^
--port 22 ^
--cidr 真のIP/32 ^
--region ap-northeast-1⑥ スタックの削除
課金を止めるために、ハンズオン完了後は必ず削除してください。
CloudFormationはスタック削除で全リソースを一括削除できます。手動でリソースを1つ1つ削除する必要はありません。
aws cloudformation delete-stack ^
--stack-name my-ec2-apache-stack ^
--region ap-northeast-1削除の進行状況を確認します(完了まで約3〜5分)。
aws cloudformation describe-stacks ^
--stack-name my-ec2-apache-stack ^
--query "Stacks[0].StackStatus" ^
--output text| ステータス | 意味 |
|---|---|
DELETE_IN_PROGRESS | 削除中(しばらく待つ) |
DELETE_FAILED | 削除失敗(トラブルシューティングを参照) |
削除完了の確認(以下のエラーが表示されれば削除完了)。
aws cloudformation describe-stacks ^
--stack-name my-ec2-apache-stack ^
--region ap-northeast-1An error occurred (ValidationError) when calling the DescribeStacks operation:
Stack with id my-ec2-apache-stack does not existこのメッセージが表示されれば削除完了です。キーペアはCloudFormationで管理していないため、手動で削除します。
EC2 → キーペア → my-ec2-apache-key を選択 → 「アクション」→「削除」
ローカルの .pem ファイルも削除します。
コンソール版との比較
コンソール版でIAMロール・セキュリティグループ・EC2を手動作成した手順が、CloudFormationではすべて template.yaml に定義されています。
| SAM/CFnの記述 | コンソールでやること |
|---|---|
EC2Role(IAMロール定義) | IAM → ロールを作成(AmazonSSMManagedInstanceCore付与) |
EC2SecurityGroup(SGルール定義) | EC2 → セキュリティグループを作成(SSH/HTTP各ルール追加) |
EC2Instance(UserData含む) | EC2 → インスタンスを起動(UserData貼り付け・キーペア選択) |
--parameters MyIP=... | セキュリティグループのインバウンドルールに自分のIPを入力 |
delete-stack | インスタンス終了 → SG削除 → IAMロール削除(順番が重要) |
コンソール版で詰まりやすいポイント:
セキュリティグループ削除時に「使用中」エラーが出るのは、EC2インスタンスが「終了済み」になっていないためです。
CloudFormationなら削除順序を自動で解決してくれるので、このエラーは発生しません。
トラブルシューティング
| 症状 | 原因 | 対処 |
|---|---|---|
CREATE_FAILED になる | キーペアが存在しない | コンソールでキーペアを作成し、--parameters KeyName=... の値を確認 |
CREATE_FAILED になる | CAPABILITY_NAMED_IAM が不足 | コマンドに --capabilities CAPABILITY_NAMED_IAM が含まれているか確認 |
stackName failed to satisfy regular expression pattern | スタック名が数字始まり | CloudFormationのスタック名は英字で始まる必要がある |
Unable to load paramfile, text contents could not be decoded | template.yaml に日本語が含まれている | template.yaml のコメント・説明文をすべて英語で記述する(Windows環境の既知の問題) |
| SSH接続タイムアウト | セキュリティグループのIP設定誤り | 「真のIPアドレスを確認する」手順を実行 |
| Webページが表示されない(起動直後) | ApacheのUserDataが未完了 | 2〜3分待ってからリロード |
DELETE_FAILED になる | 手動でリソースを変更したためCFnが管理できない | コンソールで該当リソースを確認して手動削除後、delete-stack を再実行 |
ValidationError: Template format error | template.yamlのインデントが崩れている | YAMLはインデントが厳格。スペース2つ単位でインデントを確認する |
まとめ
今回のハンズオンで実現できたこと:
| 確認項目 | 内容 |
|---|---|
| IaCの体験 | template.yaml 1ファイルにIAMロール・SG・EC2をコードで定義 |
| 一括デプロイ | create-stack コマンド1本でコンソール版と同じ構成を5分で構築 |
| Outputs活用 | デプロイ後にIPアドレス・SSHコマンドを describe-stacks で自動取得 |
| 一括削除 | delete-stack 1本でリソースの依存関係を自動解決して全削除 |
CloudFormationのメリットを実感できたポイント
- コンソール版では15〜20分かかった作業がコマンド1本(5分)で完了した
delete-stackで依存関係を気にせず全リソースを一括削除できたtemplate.yamlをGitで管理することで、環境構成の変更履歴が残せる
コンソール版と比較してみる
CloudFormationが裏で何をやっているか、同じ構成をAWSコンソールのみで構築する手順をまとめました。コンソール版ではキーペア・IAMロール・セキュリティグループ・EC2を依存関係順に手動で作成する必要があり、各リソースの役割と繋がりが視覚的に理解できます。
関連記事
-->
コメント