はじめに
「コンソールで手動構築できた構成を、コードで再現したい」——それを実現するのが AWS CloudFormation です。
この記事では、template.yaml 1ファイルにリソース構成を定義し、コマンド1本でEC2 Tomcat環境を一括構築するハンズオンを紹介します。コンソール版(AWSコンソール版ハンズオン)と全く同じ構成を、CloudFormationで自動化します。
ローカル環境(VSCode)
├── template.yaml(CloudFormationテンプレート)
└── AWS CLI コマンド
↓ スタック作成(コマンド1本)
AWS環境
├── IAMロール(SSM接続用) ← template.yamlで定義
├── セキュリティグループ(SSH:22・Tomcat:8080) ← template.yamlで定義
└── EC2インスタンス(t2.micro / AL2023 + Java17 + Tomcat10)← template.yamlで定義このハンズオンで体験できること:
template.yamlへのリソース定義(IAMロール・セキュリティグループ・EC2インスタンス)!Ref/!Sub/!GetAttを使ったリソース間参照aws cloudformation create-stackコマンド1本での全リソース一括デプロイaws cloudformation delete-stackコマンド1本での全リソース一括削除
このハンズオンの特徴:
- コンソール版では20〜30分かかった手動作業が、コマンド1本(約5〜10分)で完了する
delete-stack1本で依存関係を考慮した順番で全リソースを自動削除できる
-->
CloudFormation vs コンソール:どれだけ違うか
| 比較項目 | CloudFormation | コンソール(手動) |
|---|---|---|
| IAMロール作成 | --parameters で値を渡すだけ | 画面でポチポチ(5クリック以上) |
| セキュリティグループ作成 | 同上 | 画面でポチポチ |
| EC2起動(UserData含む) | 同上 | 画面でポチポチ |
| 全リソースのデプロイ | コマンド1本(5〜10分) | 複数画面を行き来(20〜30分) |
| リソース削除 | 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-tomcat-web/)直下に配置します。
⚠️ コピー前に確認:
Default: '123.456.78.901/32'の箇所はダミーIPです。このまま使うとスタック作成は成功しますが、SSHもブラウザもアクセスできません。--parametersでIPを上書きするか(推奨)、Defaultを自分のIPに書き換えてから使ってください。
AWSTemplateFormatVersion: '2010-09-09'
Description: 'EC2 + Tomcat Application Server Hands-on (Phase 1-2)'
Parameters:
KeyName:
Type: String
Default: 'my-ec2-tomcat-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 Tomcat access (CIDR format e.g. 203.0.113.1/32)
Resources:
EC2Role:
Type: AWS::IAM::Role
Properties:
RoleName: 'my-ec2-tomcat-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-tomcat-role'
# Instance Profile: wrapper that attaches the IAM role to EC2
EC2InstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
InstanceProfileName: 'my-ec2-tomcat-profile'
Roles:
- !Ref EC2Role
# Security Group: allow SSH(22) and Tomcat(8080) from MyIP only
EC2SecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: 'my EC2 Tomcat hands-on SG'
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: !Ref MyIP
Description: SSH access from my IP
- IpProtocol: tcp
FromPort: 8080
ToPort: 8080
CidrIp: !Ref MyIP
Description: Tomcat HTTP access from my IP
Tags:
- Key: Name
Value: 'my-ec2-tomcat-sg'
# EC2 Instance: Amazon Linux 2023 + Java 17 + Tomcat 10 (installed via UserData)
EC2Instance:
Type: AWS::EC2::Instance
Properties:
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
dnf update -y
dnf install -y java-17-amazon-corretto
useradd -m -d /opt/tomcat -U -s /bin/false tomcat
TOMCAT_VERSION="10.1.25"
cd /tmp
wget -q "https://archive.apache.org/dist/tomcat/tomcat-10/v${TOMCAT_VERSION}/bin/apache-tomcat-${TOMCAT_VERSION}.tar.gz" \
-O apache-tomcat.tar.gz
tar xzf apache-tomcat.tar.gz
mv "apache-tomcat-${TOMCAT_VERSION}" /opt/tomcat/latest
chown -R tomcat:tomcat /opt/tomcat
chmod -R u+x /opt/tomcat/latest/bin
cat > /etc/systemd/system/tomcat.service << 'SVCEOF'
[Unit]
Description=Apache Tomcat Web Application Container
After=network.target
[Service]
Type=forking
User=tomcat
Group=tomcat
Environment="JAVA_HOME=/usr/lib/jvm/java-17-amazon-corretto"
Environment="CATALINA_HOME=/opt/tomcat/latest"
Environment="CATALINA_BASE=/opt/tomcat/latest"
Environment="CATALINA_PID=/opt/tomcat/latest/temp/tomcat.pid"
Environment="JAVA_OPTS=-Xms256m -Xmx512m"
ExecStart=/opt/tomcat/latest/bin/startup.sh
ExecStop=/opt/tomcat/latest/bin/shutdown.sh
Restart=on-failure
[Install]
WantedBy=multi-user.target
SVCEOF
systemctl daemon-reload
systemctl enable tomcat
systemctl start tomcat
cat > /opt/tomcat/latest/webapps/ROOT/index.jsp << 'JSPEOF'
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>Tomcat on EC2</title></head>
<body>
<h1>Hello from Tomcat on Amazon Linux 2023!</h1>
<p>Java Version: <%= System.getProperty("java.version") %></p>
<p>Tomcat Version: <%= application.getServerInfo() %></p>
</body>
</html>
JSPEOF
chown tomcat:tomcat /opt/tomcat/latest/webapps/ROOT/index.jsp
Tags:
- Key: Name
Value: 'my-ec2-tomcat-instance'
Outputs:
InstanceId:
Description: EC2 Instance ID
Value: !Ref EC2Instance
PublicIP:
Description: Public IP Address
Value: !GetAtt EC2Instance.PublicIp
TomcatURL:
Description: Tomcat URL (open in browser)
Value: !Sub 'http://${EC2Instance.PublicIp}:8080'
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}:8080' → http://x.x.x.x:8080 |
!GetAtt リソース.属性 | リソースの属性値を取得する | !GetAtt EC2Instance.PublicIp → IPアドレス |
Apache版との主な違い(template.yaml):
| 項目 | Apache版 | Tomcat版(今回) |
|---|---|---|
| セキュリティグループ | SSH(22) + HTTP(80) | SSH(22) + TCP(8080) |
| UserData | httpd のインストール | Java17 + Tomcat10 のインストール |
| Outputs | WebsiteURL(port 80) | TomcatURL(port 8080) |
構築されるリソースと論理ID:
| リソース | template.yaml上の論理ID |
|---|---|
| IAMロール | EC2Role |
| インスタンスプロファイル | EC2InstanceProfile |
| セキュリティグループ | EC2SecurityGroup |
| EC2インスタンス | EC2Instance |
作業順序
⓪ 自分のIPアドレスを確認する(重要)
↓
① キーペアを作成する(コンソールで実施)
↓
② プロジェクトフォルダに移動する
↓
③ スタックを作成する(aws cloudformation create-stack)
↓
④ 作成状況・Outputsを確認する
↓
⑤ 動作確認(ブラウザ・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-tomcat-key |
| キーペアのタイプ | RSA |
| プライベートキーファイル形式 | .pem |
ダウンロードされた my-ec2-tomcat-key.pem を保存します。
C:\Users\ユーザー名\.ssh\my-ec2-tomcat-key.pem前回のキーペアの流用について: Apacheハンズオンで作成した
my-ec2-apache-keyを流用する場合、この手順はスキップし、後続コマンドのKeyNameの値をmy-ec2-apache-keyに変更します。
② プロジェクトフォルダに移動する
VSCodeのターミナル(CMD)を開き、プロジェクトフォルダに移動します。
cd C:\my-aws\aws-learning-projects\ec2-tomcat-webtemplate.yaml があることを確認します。
dir template.yaml③ スタックの作成
以下のコマンドを実行します。203.0.113.1 は⓪で確認した自分のIPアドレスに置き換えてください。
aws cloudformation create-stack ^
--stack-name my-ec2-tomcat-stack ^
--template-body file://template.yaml ^
--region ap-northeast-1 ^
--capabilities CAPABILITY_NAMED_IAM ^
--parameters ^
ParameterKey=KeyName,ParameterValue=my-ec2-tomcat-key ^
ParameterKey=MyIP,ParameterValue=203.0.113.1/32各オプションの説明:
| オプション | 意味 |
|---|---|
--stack-name my-ec2-tomcat-stack | スタック名(英字始まり) |
--template-body file://template.yaml | 使用するテンプレートファイル |
--region ap-northeast-1 | デプロイ先リージョン(東京) |
--capabilities CAPABILITY_NAMED_IAM | 名前付きIAMロール作成の明示的な許可 |
--parameters ... | テンプレートのParametersに渡す値 |
^について: Windowsのコマンドプロンプトで長いコマンドを複数行に分けるための改行エスケープ文字です。1行で書いても動作します。
template.yamlのコメントは英語のみ: 日本語コメントが含まれているとWindows上のAWS CLIがエンコードエラーを起こすため、コメントはすべて英語で記述しています。
成功すると以下のようなStackIdが表示されます。
{
"StackId": "arn:aws:cloudformation:ap-northeast-1:123456789012:stack/my-ec2-tomcat-stack/xxxxx"
}④ 作成完了・Outputsの確認
スタック作成は完了まで約3〜5分かかります。ただし、Tomcatのインストール(UserData)はさらに5〜10分かかるため、スタック完了後もすぐにブラウザでアクセスできないことがあります。
作成状況の確認
aws cloudformation describe-stacks ^
--stack-name my-ec2-tomcat-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-tomcat-stack ^
--query "Stacks[0].Outputs" ^
--output table以下のような出力が表示されます。
------------------------------------------------------------------------------------------
| DescribeStacks |
+----------------+-----------------------------------------------------------------------+
| OutputKey | OutputValue |
+----------------+-----------------------------------------------------------------------+
| InstanceId | i-0123456789abcdef0 |
| PublicIP | x.x.x.x |
| TomcatURL | http://x.x.x.x:8080 |
| SSHCommand | ssh -i C:\Users\...\.ssh\my-ec2-tomcat-key.pem ec2-user@x.x.x.x |
+----------------+-----------------------------------------------------------------------+控えておく情報: PublicIP と TomcatURL
⑤ 動作確認
ブラウザで確認
Outputsに表示された TomcatURL(http://x.x.x.x:8080)をブラウザで開きます。
Hello from Tomcat on Amazon Linux 2023!
Java Version: 17.x.x
Tomcat Version: Apache Tomcat/10.1.25が表示されれば成功です。
表示されない場合: UserDataのインストール完了まで5〜10分かかります。スタックが
CREATE_COMPLETEになった後も少し待ってからアクセスしてください。
SSHで接続して確認
Outputsに表示された SSHCommand を実行します(パスは実際のパスに修正します)。
ssh -i C:\Users\ユーザー名\.ssh\my-ec2-tomcat-key.pem ec2-user@(PublicIP)初回接続時の確認には yes を入力します。
# Javaのバージョン確認
java -version
# Tomcatサービスの状態確認(active (running) と表示されれば正常)
sudo systemctl status tomcat
# TomcatのログでERRORがないか確認
sudo tail -50 /opt/tomcat/latest/logs/catalina.out
# UserDataの実行ログ確認(インストール状況を詳細に確認できる)
sudo cat /var/log/cloud-init-output.log
# 8080番ポートのListen確認
sudo ss -tlnp | grep :8080
exit【接続できない場合】真のIPアドレスを確認する
curl で確認したIPを設定したが接続できない場合、実際の接続元IPが異なる可能性があります。
セキュリティグループIDを取得:
aws cloudformation describe-stack-resources ^
--stack-name my-ec2-tomcat-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-tomcat-stack ^
--region ap-northeast-1削除の進行状況を確認します(完了まで約3〜5分)。
aws cloudformation describe-stacks ^
--stack-name my-ec2-tomcat-stack ^
--query "Stacks[0].StackStatus" ^
--output text| ステータス | 意味 |
|---|---|
DELETE_IN_PROGRESS | 削除中(しばらく待つ) |
DELETE_FAILED | 削除失敗(トラブルシューティングを参照) |
削除完了の確認(以下のエラーが表示されれば削除完了)。
aws cloudformation describe-stacks ^
--stack-name my-ec2-tomcat-stack ^
--region ap-northeast-1An error occurred (ValidationError) when calling the DescribeStacks operation:
Stack with id my-ec2-tomcat-stack does not existこのメッセージが表示されれば削除完了です。キーペアはCloudFormationで管理していないため、手動で削除します。
EC2 → キーペア → my-ec2-tomcat-key を選択 → 「アクション」→「削除」
ローカルの .pem ファイルも削除します。
コンソール版との比較
コンソール版でIAMロール・セキュリティグループ・EC2を手動作成した手順が、CloudFormationではすべて template.yaml に定義されています。
| CFnの記述 | コンソールでやること |
|---|---|
EC2Role(IAMロール定義) | IAM → ロールを作成(AmazonSSMManagedInstanceCore付与) |
EC2SecurityGroup(SGルール定義) | EC2 → セキュリティグループを作成(SSH/TCP8080各ルール追加) |
EC2Instance(UserData含む) | EC2 → インスタンスを起動(UserData貼り付け・キーペア選択) |
--parameters MyIP=... | セキュリティグループのインバウンドルールに自分のIPを入力 |
delete-stack | インスタンス終了 → SG削除 → IAMロール削除(順番が重要) |
コンソール版で詰まりやすいポイント:
UserDataのTomcatインストールが5〜10分かかるため、コンソール版では「インスタンスが動いているのにアクセスできない」状態が続きます。
CloudFormationでも待ち時間は同じですが、デプロイ自体はコマンド1本で完了します。
トラブルシューティング
| 症状 | 原因 | 対処 |
|---|---|---|
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環境の既知の問題) |
| ブラウザで8080に接続できない | UserDataのインストールが未完了 | スタック完了後5〜10分待つ。SSHで sudo cat /var/log/cloud-init-output.log を確認 |
| Tomcatは起動しているがJSPが動かない | index.jspがTomcat起動後に配置されなかった | sudo systemctl restart tomcat でTomcatを再起動する |
| SSH接続タイムアウト | セキュリティグループのIP設定誤り | SGのインバウンドルールでSSH(22)のIPを確認 |
DELETE_FAILED になる | 手動でリソースを変更したためCFnが管理できない | コンソールで該当リソースを確認して手動削除後、delete-stack を再実行 |
ValidationError: Template format error | template.yamlのインデントが崩れている | YAMLはインデントが厳格。スペース2つ単位でインデントを確認する |
まとめ
今回のハンズオンで実現できたこと:
| 確認項目 | 内容 |
|---|---|
| IaCの体験 | template.yaml 1ファイルにIAMロール・SG・EC2をコードで定義 |
| 一括デプロイ | create-stack コマンド1本でコンソール版と同じ構成を5〜10分で構築 |
| Outputs活用 | デプロイ後にIPアドレス・TomcatURL・SSHコマンドを describe-stacks で自動取得 |
| 一括削除 | delete-stack 1本でリソースの依存関係を自動解決して全削除 |
CloudFormationのメリットを実感できたポイント
- コンソール版では20〜30分かかった作業がコマンド1本(5〜10分)で完了した
delete-stackで依存関係を気にせず全リソースを一括削除できたtemplate.yamlをGitで管理することで、環境構成の変更履歴が残せる
コンソール版と比較してみる
CloudFormationが裏で何をやっているか、同じ構成をAWSコンソールのみで構築する手順をまとめました。コンソール版ではキーペア・IAMロール・セキュリティグループ・EC2を依存関係順に手動で作成する必要があり、各リソースの役割と繋がりが視覚的に理解できます。
EC2サーバ構築ハンズオン:AWSコンソールでTomcatを構築する手順
関連記事
-->
コメント