- 公開日
- 最終更新日
【CloudFormation 】ALBとEC2構成で作る「Hello World!」 ~CloudFormation編~
この記事を共有する
目次
はじめに
こんにちは、サービスGの羽生です。
突然ですが、CloudFormationというサービスをご存じでしょうか?
CloudFormationとは、AWSが提供しているIaC(Infrastructure as Code)サービスです。
有名なIaCツールとしてはオープンソースのTerraformがありますが、CloudFormationはAWSのサービスとして利用できるという特徴があります。
前回の記事では、Terraformで「Hello World!」を表示させる環境を構築しましたが、
新たにCloudFormationに触れる機会があったので、今回は CloudFormation を使って、「Hello World!」 を表示できる環境を作っていきたいと思います!
※併せて前回の記事もぜひご覧ください! 【Terraform】Terraformで作るAWSインフラ!ALBとEC2構成で「Hello World!」表示 persol-serverworks.co.jp
こんな方におすすめ
- CloudFormationを使ったインフラ構築に興味がある方
- Terraformは触ったことあるけど、CloudFormationは触ったことない方
- とりあえず何でもいいからIaCでインフラを構築したい!な方
前提
事前に以下の準備が整っていることをご確認ください。
- AWSマネジメントコンソールにアクセス可能であること
- CloudFormationのスタックを操作できる権限があること
- その他構築リソース(ALBやEC2等)を操作できる権限があること
概要
構築
以下の環境を構築します。(前回の記事と同じです)
- Amazon VPC(以下、VPC):パブリック+プライベートサブネット
- Amazon EC2(以下、EC2):プライベートサブネットに2台、Webサーバー(Apache)を起動
- Application Load Balancer(以下、ALB):パブリックサブネットに配置、EC2 をターゲットにHTTPを転送
- NAT ゲートウェイ:プライベートサブネットからの外部通信用
- インターネットゲートウェイ:ALBのインターネットアクセス
動作確認
構築後、以下の動作確認を行います。(前回の記事と同じです)
- Webブラウザからのアクセス:ブラウザからALB のDNS名にアクセスし、「Hello World! <Hostname>」 が表示されること
- AWS Systems Manager(以下、SSM)によるEC2への接続:管理コンソールから接続可能なこと
- EC2から外部への通信:EC2へ接続後、外部へ通信が可能であること
構成図
- 実線:HTTPによるアクセス経路(Hello World!表示)
- 点線:SSMによるEC2への接続経路(EC2→SSM)

手順
1. CloudFormationのテンプレートファイルを作成
ローカル環境でyamlファイルを作成し、以下の内容を記述して保存します。
ファイル名はなんでも構いません。
ここを押すと展開します
AWSTemplateFormatVersion: '2010-09-09'
Description: 'ALB + EC2 Hello World Stack with VPC, NAT Gateway, and SSM'
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: "Environment Configuration"
Parameters:
- Environment
- Label:
default: "Network Configuration"
Parameters:
- VpcCIDR
- PublicSubnet1CIDR
- PublicSubnet2CIDR
- PrivateSubnet1CIDR
- PrivateSubnet2CIDR
- Label:
default: "EC2 Configuration"
Parameters:
- LatestAmiId
- InstanceType
ParameterLabels:
Environment:
default: "Environment Name"
VpcCIDR:
default: "VPC CIDR"
PublicSubnet1CIDR:
default: "Public Subnet 1 CIDR (AZ-1a)"
PublicSubnet2CIDR:
default: "Public Subnet 2 CIDR (AZ-1c)"
PrivateSubnet1CIDR:
default: "Private Subnet 1 CIDR (AZ-1a)"
PrivateSubnet2CIDR:
default: "Private Subnet 2 CIDR (AZ-1c)"
LatestAmiId:
default: "Amazon Linux 2023 AMI ID"
InstanceType:
default: "EC2 Instance Type"
Parameters:
Environment:
Type: String
Default: cloudformation-dev
Description: Environment name prefix for all resources
AllowedPattern: ^[a-zA-Z0-9-]+$
ConstraintDescription: Must contain only alphanumeric characters and hyphens
VpcCIDR:
Type: String
Default: 10.0.0.0/16
Description: CIDR block for VPC
AllowedPattern: '^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$'
ConstraintDescription: Must be a valid CIDR block (e.g., 10.0.0.0/16)
PublicSubnet1CIDR:
Type: String
Default: 10.0.1.0/24
Description: CIDR block for Public Subnet 1 in AZ-1a
AllowedPattern: '^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$'
ConstraintDescription: Must be a valid CIDR block (e.g., 10.0.1.0/24)
PublicSubnet2CIDR:
Type: String
Default: 10.0.2.0/24
Description: CIDR block for Public Subnet 2 in AZ-1c
AllowedPattern: '^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$'
ConstraintDescription: Must be a valid CIDR block (e.g., 10.0.2.0/24)
PrivateSubnet1CIDR:
Type: String
Default: 10.0.10.0/24
Description: CIDR block for Private Subnet 1 in AZ-1a
AllowedPattern: '^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$'
ConstraintDescription: Must be a valid CIDR block (e.g., 10.0.10.0/24)
PrivateSubnet2CIDR:
Type: String
Default: 10.0.20.0/24
Description: CIDR block for Private Subnet 2 in AZ-1c
AllowedPattern: '^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$'
ConstraintDescription: Must be a valid CIDR block (e.g., 10.0.20.0/24)
LatestAmiId:
Type: AWS::SSM::Parameter::Value
Default: /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64
Description: Amazon Linux 2023 latest AMI ID
InstanceType:
Type: String
Default: t2.micro
AllowedValues:
- t2.micro
- t2.small
- t2.medium
Description: EC2 instance type for web servers (t2.micro is Free Tier eligible)
Resources:
# VPC
MainVPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VpcCIDR
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: !Sub '${Environment}-vpc'
# InternetGateway
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Sub '${Environment}-igw'
AttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref MainVPC
InternetGatewayId: !Ref InternetGateway
# Public Subnet
PublicSubnetAZ1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref MainVPC
CidrBlock: !Ref PublicSubnet1CIDR
AvailabilityZone: ap-northeast-1a
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub '${Environment}-public-subnet-ap-northeast-1a'
PublicSubnetAZ2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref MainVPC
CidrBlock: !Ref PublicSubnet2CIDR
AvailabilityZone: ap-northeast-1c
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub '${Environment}-public-subnet-ap-northeast-1c'
# Private Subnet
PrivateSubnetAZ1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref MainVPC
CidrBlock: !Ref PrivateSubnet1CIDR
AvailabilityZone: ap-northeast-1a
Tags:
- Key: Name
Value: !Sub '${Environment}-private-subnet-ap-northeast-1a'
PrivateSubnetAZ2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref MainVPC
CidrBlock: !Ref PrivateSubnet2CIDR
AvailabilityZone: ap-northeast-1c
Tags:
- Key: Name
Value: !Sub '${Environment}-private-subnet-ap-northeast-1c'
# Regional NATGateway
RegionalNATGateway:
Type: AWS::EC2::NatGateway
Properties:
AvailabilityMode: regional
VpcId: !Ref MainVPC
Tags:
- Key: Name
Value: !Sub '${Environment}-natgw'
# Public Route Table
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref MainVPC
Tags:
- Key: Name
Value: !Sub '${Environment}-public-rt'
PublicRoute:
Type: AWS::EC2::Route
DependsOn: AttachGateway
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
PublicSubnetRouteTableAssociationAZ1:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnetAZ1
RouteTableId: !Ref PublicRouteTable
PublicSubnetRouteTableAssociationAZ2:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnetAZ2
RouteTableId: !Ref PublicRouteTable
# Private Route Table
PrivateRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref MainVPC
Tags:
- Key: Name
Value: !Sub '${Environment}-private-rt'
PrivateRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PrivateRouteTable
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref RegionalNATGateway
PrivateSubnetRouteTableAssociationAZ1:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnetAZ1
RouteTableId: !Ref PrivateRouteTable
PrivateSubnetRouteTableAssociationAZ2:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnetAZ2
RouteTableId: !Ref PrivateRouteTable
# Security Group for ALB
ALBSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub '${Environment}-alb-sg'
GroupDescription: Security group for ALB
VpcId: !Ref MainVPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
SecurityGroupEgress:
- IpProtocol: -1
CidrIp: 0.0.0.0/0
Tags:
- Key: Name
Value: !Sub '${Environment}-alb-sg'
# Security Group for EC2
WebSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub '${Environment}-web-sg'
GroupDescription: Security group for Web servers
VpcId: !Ref MainVPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
SourceSecurityGroupId: !Ref ALBSecurityGroup
SecurityGroupEgress:
- IpProtocol: -1
CidrIp: 0.0.0.0/0
Tags:
- Key: Name
Value: !Sub '${Environment}-web-sg'
# IAM Role for SSM
SSMRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub '${Environment}-ssmrole'
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: !Sub '${Environment}-ssmrole'
SSMInstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
InstanceProfileName: !Sub '${Environment}-ssmprofile'
Roles:
- !Ref SSMRole
# EC2 Instance 1
WebInstance1:
Type: AWS::EC2::Instance
Properties:
ImageId: !Ref LatestAmiId
InstanceType: !Ref InstanceType
SubnetId: !Ref PrivateSubnetAZ1
SecurityGroupIds:
- !Ref WebSecurityGroup
IamInstanceProfile: !Ref SSMInstanceProfile
UserData:
Fn::Base64: !Sub |
#!/bin/bash
yum update -y
yum install -y httpd
systemctl enable httpd
systemctl start httpd
HOSTNAME=$(hostname)
echo "Hello World! Hostname: $HOSTNAME" > /var/www/html/index.html
Tags:
- Key: Name
Value: !Sub '${Environment}-web-ec2-1'
# EC2 Instance 2
WebInstance2:
Type: AWS::EC2::Instance
Properties:
ImageId: !Ref LatestAmiId
InstanceType: !Ref InstanceType
SubnetId: !Ref PrivateSubnetAZ2
SecurityGroupIds:
- !Ref WebSecurityGroup
IamInstanceProfile: !Ref SSMInstanceProfile
UserData:
Fn::Base64: !Sub |
#!/bin/bash
yum update -y
yum install -y httpd
systemctl enable httpd
systemctl start httpd
HOSTNAME=$(hostname)
echo "Hello World! Hostname: $HOSTNAME" > /var/www/html/index.html
Tags:
- Key: Name
Value: !Sub '${Environment}-web-ec2-2'
# Application Load Balancer
ApplicationLoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Name: !Sub '${Environment}-alb'
Type: application
Scheme: internet-facing
SecurityGroups:
- !Ref ALBSecurityGroup
Subnets:
- !Ref PublicSubnetAZ1
- !Ref PublicSubnetAZ2
Tags:
- Key: Name
Value: !Sub '${Environment}-alb'
# Target Group
TargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Name: !Sub '${Environment}-tg'
Port: 80
Protocol: HTTP
TargetType: instance
VpcId: !Ref MainVPC
HealthCheckPath: /
HealthCheckIntervalSeconds: 30
HealthCheckTimeoutSeconds: 5
HealthyThresholdCount: 3
UnhealthyThresholdCount: 3
Targets:
- Id: !Ref WebInstance1
Port: 80
- Id: !Ref WebInstance2
Port: 80
Tags:
- Key: Name
Value: !Sub '${Environment}-tg'
# HTTP Listener
HTTPListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
LoadBalancerArn: !Ref ApplicationLoadBalancer
Port: 80
Protocol: HTTP
DefaultActions:
- Type: forward
TargetGroupArn: !Ref TargetGroup
Outputs:
ALBDNSName:
Description: DNS name of the Application Load Balancer
Value: !GetAtt ApplicationLoadBalancer.DNSName
Export:
Name: !Sub '${AWS::StackName}-ALB-DNS'
VPCId:
Description: VPC ID
Value: !Ref MainVPC
Export:
Name: !Sub '${AWS::StackName}-VPC-ID'
2. テンプレートのアップロードと設定
2-1.以下の手順でyamlファイルをアップロードします。
- AWS管理コンソールにログインし、サービスメニューから「CloudFormation」を選択します
- 「スタックの作成」→「新しいリソースを使用(標準)」を選択します
- 設定は画像のように選択し、「ファイルの選択」から先ほど作成した「.yaml」ファイルを選択します。ファイルのアップロードを確認後、「次へ」を選択
- ※本手順でテンプレートをアップロードすると、S3バケットが自動作成されます。既存バケットを使用する場合は、「Amazon S3 URL」を選択しバケットを指定してください。

- ※本手順でテンプレートをアップロードすると、S3バケットが自動作成されます。既存バケットを使用する場合は、「Amazon S3 URL」を選択しバケットを指定してください。
2-2.スタックの詳細設定を行います。
- スタック名を入力します。ご自由に命名してください。(例:TechBlog-20260313)
- パラメータは下表の通り、デフォルト値のままでOKです。そのまま「次へ」を選択します。
- ※デフォルトから変更する場合は、CIDRブロック等の重複に注意し適切な値を設定してください。
| パラメーター名 | デフォルト値 | 説明 |
|---|---|---|
| Environment Name | cloudformation-dev | 環境識別子 |
| VPC CIDR | 10.0.0.0/16 | VPCのCIDR |
| Public Subnet 1 CIDR(AZ-1a) | 10.0.1.0/24 | パブリックサブネット1のCIDR |
| Public Subnet 2 CIDR(AZ-1c) | 10.0.2.0/24 | パブリックサブネット2のCIDR |
| Private Subnet 1 CIDR(AZ-1a) | 10.0.10.0/24 | プライベートサブネット1のCIDR |
| Private Subnet 2 CIDR(AZ-1c) | 10.0.20.0/24 | プライベートサブネット2のCIDR |
| Amazon Linux 2023 AMI ID | /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64 | SSMパラメータストアに公開されているAmazon Linux 2023のAMI ID |
| EC2 Instance Type | t2.micro | Webサーバーのインスタンスタイプ |
2-3.スタックオプションの設定を行います。
- 追加のオプション設定はせず、画面下部までスクロールし、「AWS CloudFormationによってIAMリソースがカスタム名で作成される場合があることを承認します。」にチェックを入れて「次へ」を選択します。

3. スタックのデプロイ
- 設定内容が一覧で表示されるため、問題なければ「送信」を選択します。
- ※「送信」を選択すると、リソース構築が開始されます。誤クリックにご注意ください。

- ※「送信」を選択すると、リソース構築が開始されます。誤クリックにご注意ください。
4. デプロイ完了の確認
- スタックステータスが「CREATE_COMPLETE」になるまで待機しましょう(約5分)。
- ・デプロイ進行中の画面 (ステータスが「CREATE_IN_PROGRESS」)
- ・デプロイ完了の画面 (ステータスが「CREATE_COMPLETE」)

- ・デプロイ進行中の画面 (ステータスが「CREATE_IN_PROGRESS」)
動作確認
デプロイが完了したので、動作確認をしていきましょう!
1. Webページの表示
- 対象スタックの「出力」タブを確認し、「ALBDNSName」キーに表示されている値を選択します。
- ブラウザの別タブが開き「Hello World!Hostname:<EC2 プライベートDNS名>」が表示されたらOKです!何度かリロードしてプライべートDNS名が切り替わることも確認してみましょう!

2. EC2インスタンスへの接続確認
- AWS管理コンソールに戻り、サービスメニューから「EC2」を選択します
- 「インスタンス」から今回デプロイしたインスタンスにチェックをいれ、「接続」を選択します。
- 接続画面が表示されるので、「SSM Session Manager」タブに切り替えて「接続」を選択します。
- コマンドラインの画面が表示されれば接続完了です!

3. EC2インスタンスから外部への通信確認
- インスタンスへの接続が確立できたら、以下コマンドを実行してインターネットへ接続可能であることを確認します。
ping google.com -c 10
以下のような結果であればOKです!

これまで確認した以下の結果がすべて想定通りであれば完了です!スタックのデプロイと動作確認まで問題ないことが確認できましたね!
- Webページの表示
- インスタンスへの接続確認
- インスタンスから外部への通信確認
スタックの削除
構築したリソースは利用料金が発生する可能性がありますので、動作確認がすべて完了したらスタックを削除しましょう。
1. スタックの削除実行
以下の手順でスタックを削除していきます。
- AWS管理コンソールに戻り、サービスメニューから「CloudFormation」を選択します
- スタック一覧画面から、今回扱ったスタックにチェックを入れ、「スタックを削除」を選択します。
- 確認画面が表示されるので、スタック名を入力し「スタックを削除」を選択します。
- スタック削除が開始されますので、完了まで待機しましょう(約5分)。

2. スタックの削除確認
待機したらスタック削除を確認していきましょう。
- 以下のように「DELETE_COMPLETE」と表示されていれば削除完了です。
- スタックが削除されると一覧に表示されなくなりますが、「ステータスのフィルター」がデフォルトで「アクティブ」になっているためです。
これを「削除済み」に変更してみましょう。
- 以下のように対象のスタックが削除済みリストに表示されていますので、こちらから確認可能です!
- ※スタックを選択すると、過去のデプロイや削除の履歴も確認することができます。

- ※スタックを選択すると、過去のデプロイや削除の履歴も確認することができます。
3. S3バケットの確認
スタックは削除してしまいましたが、テンプレートであるyamlファイルはちゃんと残っていますのでご安心ください。 いつでも再利用できるぜ!ということでついでにこちらも確認してみましょう。
- AWS管理コンソールのサービスメニューから「S3」を選択します。
- 「cf-templates-<ランダム文字列>-ap-northeast-1」というバケットがあるはずなので、バケット名を選択します。
- オブジェクトの中を確認し、テンプレートファイルが存在していることを確認しましょう。
バケット内に残存しているので、再度スタック作成時に同じようにyamlテンプレートをアップロードする必要はなく、オブジェクトURLを設定することでテンプレートを指定することができます!

スタックのデプロイから動作確認、スタック削除まで終了したので作業はこれにて完了です!
最後に
今回はCloudFormationを使って、簡単なWeb構成を構築しました。 冒頭にTerraformを引き合いに出しましたが、個人的な比較を簡単にまとめます。
| 項目 | CloudFormation | Terraform |
|---|---|---|
| 提供元 | AWS公式 | HashiCorp社 |
| 対応クラウド | AWS専用 | マルチクラウド対応(AWS、Azure、GCP等) |
| 記法 | JSON / YAML | HCL(HashiCorp Configuration Language) |
| インストール | 不要 | 必要 |
| 料金 | 無料(デプロイしたリソース料金のみ) | 一部無料 ※ |
| 状態管理 | AWS側で管理 | tfstateファイルで管理 |
| モジュール化 | ネストスタック、スタックセット | モジュール機能充実(公式レジストリ/コミュニティ多数) |
| ロールバック | あり(更新失敗時に自動ロールバック) | 自動ロールバックなし。コードを前バージョンに戻し再適用 |
※CLIが無料で利用可能。企業向けの有料プランあり。
両者それぞれの強みと弱みがあるので、プロジェクトの要件や環境に応じて適切なIaCツールを選択できるといいですね!
参考文献
本ブログでのYAMLテンプレートは下記公式ドキュメントを参考に記述していますが、あくまで一例です。
情報量がかなり多いですがぜひご確認ください。
・CloudFormation テンプレート形式
・AWS リソースおよびプロパティタイプのリファレンス
この記事は私が書きました
S.Hanyu
記事一覧サッカー観戦(プレミア、ラ・リーガ)とコーヒーが好きです。 Terraformが好きでよく触っています。