EC2サーバ構築をIaC化:CloudFormationでEC2 Tomcatを自動デプロイする手順【コンソール版との比較付き】

AWS Basic
スポンサーリンク
スポンサーリンク

はじめに

「コンソールで手動構築できた構成を、コードで再現したい」——それを実現するのが 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-stack 1本で依存関係を考慮した順番で全リソースを自動削除できる

-->

スポンサーリンク

CloudFormation vs コンソール:どれだけ違うか

比較項目CloudFormationコンソール(手動)
IAMロール作成--parameters で値を渡すだけ画面でポチポチ(5クリック以上)
セキュリティグループ作成同上画面でポチポチ
EC2起動(UserData含む)同上画面でポチポチ
全リソースのデプロイコマンド1本(5〜10分)複数画面を行き来(20〜30分)
リソース削除delete-stack 1本依存関係順に個別削除(4ステップ)
再現性高い(同じ構成を何度でも再現可能)低い(手動ミスのリスクがある)
バージョン管理Gitで管理可能不可(過去の設定を記録できない)

スポンサーリンク

キーワード解説

用語意味
CloudFormationAWSが提供するIaC(Infrastructure as Code)サービス。YAMLテンプレートでリソースをコード化する
スタックCloudFormationが管理するリソースのまとまり。作成・削除・更新をまとめて操作できる
!RefCloudFormation組み込み関数。パラメータの値やリソースのIDを参照する
!SubCloudFormation組み込み関数。文字列内の ${変数} を展開する
!GetAttCloudFormation組み込み関数。リソースの属性値(ARNなど)を取得する
CAPABILITY_NAMED_IAM名前付きIAMリソースを作成する場合に必要な明示的な許可フラグ
UserDataEC2の初回起動時のみ自動実行されるシェルスクリプト

前提条件

ツール確認コマンド最低バージョン目安
AWS CLI v2aws --version2.x
Gitgit --version2.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 MyIP203.0.113.1/32
!Ref リソースIDリソースのIDを参照する!Ref EC2SecurityGroupsg-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)
UserDatahttpd のインストールJava17 + Tomcat10 のインストール
OutputsWebsiteURL(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: ルーター管理画面で確認(推奨)

  1. ブラウザで以下のURLにアクセスします
    http://192.168.0.1  または  http://192.168.1.1
  2. 「ネットワークマップ」または「インターネット」項目を開く
  3. **「インターネット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-web

template.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     |
+----------------+-----------------------------------------------------------------------+

控えておく情報: PublicIPTomcatURL


⑤ 動作確認

ブラウザで確認

Outputsに表示された TomcatURLhttp://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 text

SSH全開放(一時的・テスト用):

aws ec2 authorize-security-group-ingress ^
  --group-id sg-XXXXXXXXX ^
  --protocol tcp ^
  --port 22 ^
  --cidr 0.0.0.0/0 ^
  --region ap-northeast-1

SSH接続して真の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-1
An 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 decodedtemplate.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 errortemplate.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を構築する手順


関連記事

-->

コメント