CloudFormationでEC2インスタンスをデプロイする
はじめに
前回は、CloudFormationのパラメータとOutputs機能を使って、テンプレートの再利用性を高めました。今回は、いよいよS3バケットよりも複雑なリソースであるEC2インスタンスをデプロイしてみましょう。EC2インスタンスをデプロイするには、VPC、サブネット、セキュリティグループといった複数のリソースが必要になります。
実際のWebサーバーとして動作するEC2インスタンスを作成し、CloudFormationでのインフラ構築の全体像を理解しましょう。
前提条件
- AWSアカウントが作成済みであること
- EC2キーペアが作成済みであること(SSH接続用)
- CloudFormationの基本概念を理解していること
ステップ1:テンプレートの作成
今回は、実際に動作するWebサーバーを構築するテンプレートを作成します。ファイル名は ec2-webserver-template.yaml
としましょう。
AWSTemplateFormatVersion: "2010-09-09"
Description: "A template to create a secure EC2 instance running a web server with proper network setup."
Parameters:
EnvironmentName:
Type: String
Default: dev
AllowedValues:
- dev
- staging
- prod
Description: "Environment name for resource tagging"
InstanceType:
Type: String
Default: t2.micro
AllowedValues:
- t2.micro
- t2.small
- t2.medium
- t3.micro
- t3.small
- t3.medium
Description: "EC2 instance type"
ConstraintDescription: "Must be a valid EC2 instance type"
KeyPairName:
Type: AWS::EC2::KeyPair::KeyName
Description: "Name of an existing EC2 KeyPair for SSH access"
ConstraintDescription: "Must be the name of an existing EC2 KeyPair"
SSHLocation:
Type: String
Default: "0.0.0.0/0"
Description: "The IP address range that can SSH to the EC2 instances"
AllowedPattern: "^([0-9]{1,3}\\.){3}[0-9]{1,3}/[0-9]{1,2}$"
ConstraintDescription: "Must be a valid IP CIDR range of the form x.x.x.x/x"
LatestAmiId:
Type: "AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>"
Default: "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2"
Description: "The latest AMI ID for Amazon Linux 2"
Resources:
# VPC Configuration
WebServerVPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: !Sub "${EnvironmentName}-WebServer-VPC"
- Key: Environment
Value: !Ref EnvironmentName
# Public Subnet
PublicSubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref WebServerVPC
CidrBlock: 10.0.1.0/24
AvailabilityZone: !Select [0, !GetAZs ""]
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub "${EnvironmentName}-Public-Subnet"
- Key: Environment
Value: !Ref EnvironmentName
# Internet Gateway
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Sub "${EnvironmentName}-IGW"
- Key: Environment
Value: !Ref EnvironmentName
# Attach Internet Gateway to VPC
AttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref WebServerVPC
InternetGatewayId: !Ref InternetGateway
# Public Route Table
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref WebServerVPC
Tags:
- Key: Name
Value: !Sub "${EnvironmentName}-Public-RT"
- Key: Environment
Value: !Ref EnvironmentName
# Public Route
PublicRoute:
Type: AWS::EC2::Route
DependsOn: AttachGateway
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
# Associate Public Subnet with Route Table
PublicSubnetRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet
RouteTableId: !Ref PublicRouteTable
# Security Group for Web Server
WebServerSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub "${EnvironmentName}-WebServer-SG"
GroupDescription: "Security group for web server - allows HTTP and SSH access"
VpcId: !Ref WebServerVPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
Description: "HTTP access from anywhere"
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
Description: "HTTPS access from anywhere"
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: !Ref SSHLocation
Description: "SSH access from specified IP range"
SecurityGroupEgress:
- IpProtocol: -1
CidrIp: 0.0.0.0/0
Description: "All outbound traffic"
Tags:
- Key: Name
Value: !Sub "${EnvironmentName}-WebServer-SG"
- Key: Environment
Value: !Ref EnvironmentName
# IAM Role for EC2 Instance
EC2InstanceRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${EnvironmentName}-EC2-WebServer-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: Environment
Value: !Ref EnvironmentName
# Instance Profile
EC2InstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
InstanceProfileName: !Sub "${EnvironmentName}-EC2-WebServer-Profile"
Roles:
- !Ref EC2InstanceRole
# EC2 Instance
WebServerInstance:
Type: AWS::EC2::Instance
Properties:
InstanceType: !Ref InstanceType
ImageId: !Ref LatestAmiId
KeyName: !Ref KeyPairName
IamInstanceProfile: !Ref EC2InstanceProfile
SubnetId: !Ref PublicSubnet
SecurityGroupIds:
- !Ref WebServerSecurityGroup
UserData:
Fn::Base64: !Sub |
#!/bin/bash
yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd
# Create a simple HTML page
cat > /var/www/html/index.html << 'EOF'
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CloudFormation Web Server</title>
<style>
body {
font-family: Arial, sans-serif;
text-align: center;
margin: 50px;
background-color: #f0f8ff;
}
.container {
max-width: 600px;
margin: 0 auto;
padding: 20px;
border-radius: 10px;
background-color: white;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
</style>
</head>
<body>
<div class="container">
<h1>🎉 CloudFormation Web Server</h1>
<p>Congratulations! Your EC2 instance is running successfully.</p>
<p><strong>Environment:</strong> ${EnvironmentName}</p>
<p><strong>Instance ID:</strong> <span id="instance-id">Loading...</span></p>
<p><strong>Region:</strong> ${AWS::Region}</p>
<hr>
<p><em>This server was deployed using AWS CloudFormation</em></p>
</div>
<script>
// Get instance metadata
fetch('http://169.254.169.254/latest/meta-data/instance-id')
.then(response => response.text())
.then(data => {
document.getElementById('instance-id').textContent = data;
})
.catch(error => {
document.getElementById('instance-id').textContent = 'Unable to fetch';
});
</script>
</body>
</html>
EOF
# Set proper permissions
chown apache:apache /var/www/html/index.html
chmod 644 /var/www/html/index.html
# Send success signal to CloudFormation
/opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource WebServerInstance --region ${AWS::Region}
CreationPolicy:
ResourceSignal:
Count: 1
Timeout: PT10M
Tags:
- Key: Name
Value: !Sub "${EnvironmentName}-WebServer"
- Key: Environment
Value: !Ref EnvironmentName
Outputs:
InstanceId:
Description: "The Instance ID of the EC2 instance"
Value: !Ref WebServerInstance
Export:
Name: !Sub "${AWS::StackName}-InstanceId"
PublicIp:
Description: "Public IP address of the EC2 instance"
Value: !GetAtt WebServerInstance.PublicIp
Export:
Name: !Sub "${AWS::StackName}-PublicIp"
WebsiteURL:
Description: "URL of the web server"
Value: !Sub "http://${WebServerInstance.PublicIp}"
Export:
Name: !Sub "${AWS::StackName}-WebsiteURL"
SSHCommand:
Description: "SSH command to connect to the instance"
Value: !Sub "ssh -i your-key.pem ec2-user@${WebServerInstance.PublicIp}"
VPCId:
Description: "VPC ID of the created network"
Value: !Ref WebServerVPC
Export:
Name: !Sub "${AWS::StackName}-VPCId"
SubnetId:
Description: "Subnet ID where the instance is deployed"
Value: !Ref PublicSubnet
Export:
Name: !Sub "${AWS::StackName}-SubnetId"
SecurityGroupId:
Description: "Security Group ID attached to the instance"
Value: !Ref WebServerSecurityGroup
Export:
Name: !Sub "${AWS::StackName}-SecurityGroupId"
ステップ2:テンプレートの詳細解説
このテンプレートでは、セキュリティとベストプラクティスを考慮した完全なWebサーバー環境を構築しています。
パラメータセクション
新しく追加されたパラメータ
-
EnvironmentName
: 環境の識別とタグ付けのため -
KeyPairName
: SSH接続用のキーペア(必須) -
SSHLocation
: SSH接続を許可するIPアドレス範囲(セキュリティ向上)
改善されたパラメータ
-
InstanceType
: 使用可能なインスタンスタイプを制限 - 各パラメータに適切な制約と説明を追加
ネットワークリソース
VPC設定
- 全リソースに適切なタグを付与
- DNS解決とホスト名の有効化
サブネット設定
-
MapPublicIpOnLaunch: true
でパブリックIP自動割り当て - アベイラビリティーゾーンを自動選択
セキュリティ設定
セキュリティグループ
- HTTPS(443番ポート)も追加
- SSH接続元IPを制限可能
- 各ルールに説明を追加
IAMロール
- EC2インスタンス用のIAMロールを追加
- Session Manager経由でのアクセスが可能
- セキュアな管理を実現
EC2インスタンス設定
User Data
- Apache Webサーバーの自動インストール・設定
- カスタムHTMLページの作成
- インスタンスメタデータを表示する動的コンテンツ
- CloudFormationシグナルによる起動完了通知
Creation Policy
- インスタンスの正常起動を確認
- タイムアウト設定で無限待機を防止
ステップ3:スタックのデプロイ
CloudFormationコンソールでのデプロイ
-
前準備:キーペアの作成
- EC2コンソールでキーペアを作成
- キーペア名をメモしておく
-
スタックの作成
- CloudFormationコンソールで「スタックの作成」をクリック
-
ec2-webserver-template.yaml
ファイルをアップロード - スタック名(例:
MyWebServerStack
)を入力
-
パラメータの設定
-
KeyPairName
: 作成済みのキーペア名を選択 -
SSHLocation
: 必要に応じてSSH接続元IPを制限(例:123.456.789.0/32
) - その他はデフォルトでOK
-
-
デプロイ実行
- 「AWS CloudFormationによってIAMリソースが作成される場合があることを承認します」にチェック
- 「スタックの作成」をクリック
AWS CLIでのデプロイ(推奨)
# パラメータファイルの作成
cat > parameters.json << 'EOF'
[
{
"ParameterKey": "EnvironmentName",
"ParameterValue": "dev"
},
{
"ParameterKey": "InstanceType",
"ParameterValue": "t2.micro"
},
{
"ParameterKey": "KeyPairName",
"ParameterValue": "your-key-pair-name"
},
{
"ParameterKey": "SSHLocation",
"ParameterValue": "0.0.0.0/0"
}
]
EOF
# スタックのデプロイ
aws cloudformation create-stack \
--stack-name MyWebServerStack \
--template-body file://ec2-webserver-template.yaml \
--parameters file://parameters.json \
--capabilities CAPABILITY_NAMED_IAM
ステップ4:デプロイ結果の確認
基本的な確認
-
CloudFormationコンソールでの確認
- スタックのステータスが
CREATE_COMPLETE
になることを確認 - 「出力」タブでWebサイトのURLを確認
- スタックのステータスが
-
Webサイトへのアクセス
- 出力されたWebsiteURLにブラウザでアクセス
- カスタムHTMLページが表示されることを確認
-
SSH接続のテスト
ssh -i your-key.pem ec2-user@<PublicIP>
高度な確認
# スタック情報の取得
aws cloudformation describe-stacks --stack-name MyWebServerStack
# Webサイトのレスポンス確認
curl -I http://<PublicIP>
# インスタンスの状態確認
aws ec2 describe-instances --instance-ids <InstanceId>
セキュリティベストプラクティス
1. SSH接続の制限
# 特定のIPアドレスからのみSSH接続を許可
SSHLocation:
Type: String
Default: "203.0.113.0/32" # あなたのパブリックIPに変更
2. Session Managerの活用
# SSHの代わりにSession Managerを使用
aws ssm start-session --target <InstanceId>
3. セキュリティグループの最小権限化
- 必要なポートのみを開放
- 信頼できるIPアドレスからのアクセスのみ許可
トラブルシューティング
よくある問題と解決法
1. キーペアが見つからないエラー
Parameter validation failed: Parameter value 'non-existent-key' does not exist
- 解決策:EC2コンソールでキーペアの存在を確認
2. インスタンス起動の失敗
CREATE_FAILED - Received 0 SUCCESS signal(s) out of 1
- 解決策:User Dataスクリプトのログを確認
-
/var/log/cloud-init-output.log
でエラーを調査
3. Webサイトにアクセスできない
- セキュリティグループの設定を確認
- インスタンスのステータスチェックを確認
- Apache の起動状態を確認
# SSH接続後の確認コマンド
sudo systemctl status httpd
sudo journalctl -u httpd
リソースのクリーンアップ
スタックの削除
# CloudFormation経由での削除(推奨)
aws cloudformation delete-stack --stack-name MyWebServerStack
# 削除の確認
aws cloudformation describe-stacks --stack-name MyWebServerStack
手動削除が必要な場合
- Elastic IPアドレス(アタッチされている場合)
- 追加で作成したセキュリティグループ
- カスタムで作成したキーペア
まとめ
今回は、EC2インスタンスという複数の依存関係を持つリソースを、CloudFormationで安全にデプロイしました。この実践を通じて、以下の重要な概念を学びました:
学習のポイント
- 複合リソースの管理:VPC、サブネット、セキュリティグループなど、関連するリソースを一括管理
- セキュリティの考慮:IAMロール、セキュリティグループ、SSH接続制限などの実装
- 自動化の活用:User Dataによるソフトウェアの自動インストール・設定
-
依存関係の管理:
DependsOn
や!Ref
を使った適切なリソース順序制御 - 監視と検証:Creation Policyによる起動確認
次のステップ
- Auto Scaling:負荷に応じたインスタンスの自動スケーリング
- Load Balancer:複数インスタンスでの負荷分散
- データベース統合:RDSとの連携
- CI/CDパイプライン:自動デプロイの実装
次回は、テンプレートの更新と変更セットの活用方法について解説します。既存のインフラを安全に更新する手法を学びましょう。お楽しみに!