1. はじめに
1-1 ご挨拶
初めまして、井村と申します。
AWSやCloudFormationは、久しぶりに触れる機会があり、そのたびに調査しています。
他のことにも時間を使いたいため、Application Load Balancer(以下ALB)とEC2を構築するCloudFormationを備忘録として残します。
1-2 構築と検証の流れ
CloudFormationを用いてALB、EC2を構築します。
HTTP接続を確認した後、秘密鍵と自己署名のサーバ証明書を作成し、HTTPS接続が可能であることを確認します。
2. 構成図
CloudFormationを利用して作成するシステム構成図は以下の通りです。
3. 構築
以下のCloudFormationテンプレートを利用します。
web-server.yaml
AWSTemplateFormatVersion: 2010-09-09
Description: This is a template for creating a load balancer and server.
Parameters:
System:
Type: String
Description: The system name for the resources.
Default: timura
Env:
Type: String
Description: The environment for the resources (e.g., dev, prod).
Default: dev
AmiId:
Type: AWS::SSM::Parameter::Value<String>
Default: /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64
Description: The AMI ID
InstanceType:
Type: String
Description: The instance type for the EC2 instances.
Default: t3.nano
AllowedValues:
- t3.micro
- t3.nano
- t2.micro
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: System Information
Parameters:
- System
- Env
- Label:
default: Instance Information
Parameters:
- AmiId
ParameterLabels:
System:
default: System Name
Env:
default: Environment
AmiId:
default: AMI ID
Resources:
# Create a VPC
VPC:
Type: AWS::EC2::VPC
Description: Create a VPC
Properties:
CidrBlock: 10.0.0.0/16
EnableDnsHostnames: true
EnableDnsSupport: true
InstanceTenancy: default
Tags:
- Key: Name
Value: !Sub ${System}-${Env}-vpc
# Create public subnets
PubSnet01:
Type: AWS::EC2::Subnet
Description: Create a public subnet01
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.0.0/24
AvailabilityZone: !Select [0, !GetAZs ""]
Tags:
- Key: Name
Value: !Sub ${System}-${Env}-snet-pub01
PubSnet02:
Type: AWS::EC2::Subnet
Description: Create a public subnet02
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.1.0/24
AvailabilityZone: !Select [1, !GetAZs ""]
Tags:
- Key: Name
Value: !Sub ${System}-${Env}-snet-pub02
# Create private subnets
PriSnet01:
Type: AWS::EC2::Subnet
Description: Create a private subnet01
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.10.0/24
AvailabilityZone: !Select [0, !GetAZs ""]
Tags:
- Key: Name
Value: !Sub ${System}-${Env}-snet-pri01
PriSnet02:
Type: AWS::EC2::Subnet
Description: Create a private subnet02
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.11.0/24
AvailabilityZone: !Select [1, !GetAZs ""]
Tags:
- Key: Name
Value: !Sub ${System}-${Env}-snet-pri02
# Create an Internet Gateway
InternetGateway:
Type: AWS::EC2::InternetGateway
Description: Create an Internet Gateway
Properties:
Tags:
- Key: Name
Value: !Sub ${System}-${Env}-igw
# Attach the Internet Gateway to the VPC
AttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Description: Attach the Internet Gateway to the VPC
Properties:
VpcId: !Ref VPC
InternetGatewayId: !Ref InternetGateway
# Create a Elastic IP for the Nat Gateway
NatEIP:
Type: AWS::EC2::EIP
Description: Create an Elastic IP for the Nat Gateway
Properties:
Domain: vpc
Tags:
- Key: Name
Value: !Sub ${System}-${Env}-eip-natgw
# Create an Nat Gateway
NatGateway:
Type: AWS::EC2::NatGateway
Description: Create a Nat Gateway
Properties:
AllocationId: !GetAtt NatEIP.AllocationId
SubnetId: !Ref PubSnet01
Tags:
- Key: Name
Value: !Sub ${System}-${Env}-natgw
# Create route tables for the public subnets
PubRouteTable01:
Type: AWS::EC2::RouteTable
Description: Create a route table for the public subnet01
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${System}-${Env}-rt-pub01
PubRouteTable02:
Type: AWS::EC2::RouteTable
Description: Create a route table for the public subnet02
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${System}-${Env}-rt-pub02
# Create route tables for the private subnets
PriRouteTable01:
Type: AWS::EC2::RouteTable
Description: Create a route table for the private subnet01
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${System}-${Env}-rt-pri01
PriRouteTable02:
Type: AWS::EC2::RouteTable
Description: Create a route table for the private subnet02
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${System}-${Env}-rt-pri02
# Create routes for the public subnets
PubRouteTable01Route:
Type: AWS::EC2::Route
Description: Create a route for the public subnet01
DependsOn: AttachGateway
Properties:
RouteTableId: !Ref PubRouteTable01
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
PubRouteTable02Route:
Type: AWS::EC2::Route
Description: Create a route for the public subnet02
DependsOn: AttachGateway
Properties:
RouteTableId: !Ref PubRouteTable02
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
# Create routes for the private subnets
PriRouteTable01Route:
Type: AWS::EC2::Route
Description: Create a route for the private subnet01
Properties:
RouteTableId: !Ref PriRouteTable01
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref NatGateway
PriRouteTable02Route:
Type: AWS::EC2::Route
Description: Create a route for the private subnet02
Properties:
RouteTableId: !Ref PriRouteTable02
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref NatGateway
# Associate the public subnets with the public route tables
PubSnet01RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Description: Associate the public subnet01 with the public route table
Properties:
SubnetId: !Ref PubSnet01
RouteTableId: !Ref PubRouteTable01
PubSnet02RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Description: Associate the public subnet02 with the public route table
Properties:
SubnetId: !Ref PubSnet02
RouteTableId: !Ref PubRouteTable02
# Associate the private subnets with the private route tables
PriSnet01RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Description: Associate the private subnet01 with the private route table
Properties:
SubnetId: !Ref PriSnet01
RouteTableId: !Ref PriRouteTable01
PriSnet02RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Description: Associate the private subnet02 with the private route table
Properties:
SubnetId: !Ref PriSnet02
RouteTableId: !Ref PriRouteTable02
# Create nacls for the public subnets
PubNacl01:
Type: AWS::EC2::NetworkAcl
Description: Create a nacl for the public subnet01
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${System}-${Env}-nacl-pub01
PubNacl02:
Type: AWS::EC2::NetworkAcl
Description: Create a nacl for the public subnet02
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${System}-${Env}-nacl-pub02
# Create nacls for the private subnets
PriNacl01:
Type: AWS::EC2::NetworkAcl
Description: Create a nacl for the private subnet01
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${System}-${Env}-nacl-pri01
PriNacl02:
Type: AWS::EC2::NetworkAcl
Description: Create a nacl for the private subnet02
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${System}-${Env}-nacl-pri02
# Associate the public nacls with the public subnets
PubNacl01Association:
Type: AWS::EC2::SubnetNetworkAclAssociation
Description: Associate the nacl for the public subnet01
Properties:
NetworkAclId: !Ref PubNacl01
SubnetId: !Ref PubSnet01
PubNacl02Association:
Type: AWS::EC2::SubnetNetworkAclAssociation
Description: Associate the nacl for the public subnet02
Properties:
NetworkAclId: !Ref PubNacl02
SubnetId: !Ref PubSnet02
# Associate the private nacls with the private subnets
PriNacl01Association:
Type: AWS::EC2::SubnetNetworkAclAssociation
Description: Associate the nacl for the private subnet01
Properties:
NetworkAclId: !Ref PriNacl01
SubnetId: !Ref PriSnet01
PriNacl02Association:
Type: AWS::EC2::SubnetNetworkAclAssociation
Description: Associate the nacl for the private subnet02
Properties:
NetworkAclId: !Ref PriNacl02
SubnetId: !Ref PriSnet02
# Create Netwrork ACL rules for the public subnets
PubNacl01InboundRule:
Type: AWS::EC2::NetworkAclEntry
Description: Create an inbound rule for the public subnet01
Properties:
Egress: false
NetworkAclId: !Ref PubNacl01
RuleNumber: 1000
Protocol: -1
RuleAction: allow
CidrBlock: 0.0.0.0/0
PubNacl01OutboundRule:
Type: AWS::EC2::NetworkAclEntry
Description: Create an outbound rule for the public subnet01
Properties:
Egress: true
NetworkAclId: !Ref PubNacl01
RuleNumber: 1000
Protocol: -1
RuleAction: allow
CidrBlock: 0.0.0.0/0
PubNacl02InboundRule:
Type: AWS::EC2::NetworkAclEntry
Description: Create an inbound rule for the public subnet02
Properties:
Egress: false
NetworkAclId: !Ref PubNacl02
RuleNumber: 1000
Protocol: -1
RuleAction: allow
CidrBlock: 0.0.0.0/0
PubNacl02OutboundRule:
Type: AWS::EC2::NetworkAclEntry
Description: Create an outbound rule for the public subnet02
Properties:
Egress: true
NetworkAclId: !Ref PubNacl02
RuleNumber: 1000
Protocol: -1
RuleAction: allow
CidrBlock: 0.0.0.0/0
# Create Netwrork ACL rules for the private subnets
PriNacl01InboundRule:
Type: AWS::EC2::NetworkAclEntry
Description: Create an inbound rule for the private subnet01
Properties:
Egress: false
NetworkAclId: !Ref PriNacl01
RuleNumber: 1000
Protocol: -1
RuleAction: allow
CidrBlock: 0.0.0.0/0
PriNacl01OutboundRule:
Type: AWS::EC2::NetworkAclEntry
Description: Create an outbound rule for the private subnet01
Properties:
Egress: true
NetworkAclId: !Ref PriNacl01
RuleNumber: 1000
Protocol: -1
RuleAction: allow
CidrBlock: 0.0.0.0/0
PriNacl02InboundRule:
Type: AWS::EC2::NetworkAclEntry
Description: Create an inbound rule for the private subnet02
Properties:
Egress: false
NetworkAclId: !Ref PriNacl02
RuleNumber: 1000
Protocol: -1
RuleAction: allow
CidrBlock: 0.0.0.0/0
PriNacl02OutboundRule:
Type: AWS::EC2::NetworkAclEntry
Description: Create an outbound rule for the private subnet02
Properties:
Egress: true
NetworkAclId: !Ref PriNacl02
RuleNumber: 1000
Protocol: -1
RuleAction: allow
CidrBlock: 0.0.0.0/0
# Create security groups for the public subnets
PubSg01:
Type: AWS::EC2::SecurityGroup
Description: Create a security group for the public subnet01
Properties:
GroupDescription: Security group for the public subnet01
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${System}-${Env}-sg-pub01
PubSg02:
Type: AWS::EC2::SecurityGroup
Description: Create a security group for the public subnet02
Properties:
GroupDescription: Security group for the public subnet02
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${System}-${Env}-sg-pub02
# Create security groups for the private subnets
PriSg01:
Type: AWS::EC2::SecurityGroup
Description: Create a security group for the private subnet01
Properties:
GroupDescription: Security group for the private subnet01
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
SourceSecurityGroupId: !Ref PubSg01
Tags:
- Key: Name
Value: !Sub ${System}-${Env}-sg-pri01
PriSg02:
Type: AWS::EC2::SecurityGroup
Description: Create a security group for the private subnet02
Properties:
GroupDescription: Security group for the private subnet02
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
SourceSecurityGroupId: !Ref PubSg02
Tags:
- Key: Name
Value: !Sub ${System}-${Env}-sg-pri02
# Create an SSMRole for the instances
SSMRole:
Type: AWS::IAM::Role
Description: Create an SSM role for the instances
Properties:
RoleName: !Sub ${System}-${Env}-ssm-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: !Sub ${System}-${Env}-ssm-role
# Create an instance profile for the SSM role
SSMInstanceProfile:
Type: AWS::IAM::InstanceProfile
Description: Create an instance profile for the SSM role
Properties:
Roles:
- !Ref SSMRole
InstanceProfileName: !Sub ${System}-${Env}-ssm-role
# Create an EC2 instance
EC2:
Type: AWS::EC2::Instance
Description: Create a web server instance
Properties:
InstanceType: !Ref InstanceType
ImageId: !Ref AmiId
SubnetId: !Ref PriSnet01
SecurityGroupIds:
- !Ref PriSg01
IamInstanceProfile: !Ref SSMInstanceProfile
UserData:
Fn::Base64: !Sub |
#!/bin/bash
timedatectl set-timezone Asia/Tokyo
hostnamectl set-hostname ${System}-${Env}-ec2-web
dnf update -y
dnf install -y httpd
dnf install -y openssl
systemctl start httpd
systemctl enable httpd
echo "This is $(hostname)" > /var/www/html/index.html
Tags:
- Key: Name
Value: !Sub ${System}-${Env}-ec2-web
# Create an Application Load Balancer
ALB:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Description: Create an Application Load Balancer
Properties:
Name: !Sub ${System}-${Env}-alb
Subnets:
- !Ref PubSnet01
- !Ref PubSnet02
SecurityGroups:
- !Ref PubSg01
- !Ref PubSg02
Scheme: internet-facing
Tags:
- Key: Name
Value: !Sub ${System}-${Env}-alb
# Create a target group for the ALB
ALBTargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Description: Create a target group for the ALB
Properties:
Name: !Sub ${System}-${Env}-tg-web
Port: 80
Protocol: HTTP
VpcId: !Ref VPC
TargetType: instance
Targets:
- Id: !Ref EC2
Tags:
- Key: Name
Value: !Sub ${System}-${Env}-tg-web
# Create a listener for the ALB
ALBListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Description: Create a listener for the ALB
Properties:
LoadBalancerArn: !Ref ALB
Port: 80
Protocol: HTTP
DefaultActions:
- Type: forward
TargetGroupArn: !Ref ALBTargetGroup
Outputs:
ALBDnsName:
Description: The DNS name of the Application Load Balancer
Value: !GetAtt ALB.DNSName
以下補足になります。
- Metadataセクション:「AWS::CloudFormation::Interface」はCloudFormationコンソールでのパラメータの表示方法をカスタマイズします。beforeがMetadataなし、afterがMetadataありのコンソール画面になります。パラメータ欄がカテゴライズできます。
before
after
- 組み込み関数 1 : サブネットのアベイラビリティゾーンを指定する記述です。
AvailabilityZone: !Select [0, !GetAZs ""]:
!GetAZs は、指定されたリージョンのすべてのアベイラビリティゾーンのリストを返します。空の文字列("")を指定すると、スタックが作成されている現在のリージョンのアベイラビリティゾーンが返されます。
[
"ap-northeast-1a",
"ap-northeast-1c",
"ap-northeast-1d"
]
!Select関数は、リストから特定のインデックスの値を選択します。この場合、インデックス0を指定しているため、最初のアベイラビリティゾーンが選択されます。
よって"ap-northeast-1a"が選定されます。
- 組み込み関数 2 : 指定されたリソースの論理ID(この場合はPubSnet01)を参照し、そのリソースのIDを返します。
SubnetId: !Ref PubSnet01
公式ドキュメントより戻り値は参照されるエンティティのタイプによって異なるとのこと。例えば、 AWS::EC2::EIP リソースは IP アドレスを返し、 AWS::EC2::Instance はインスタンス ID を返します。
- 組み込み関数 3 : ALBという論理IDを持つリソースのDNSName属性を取得しています。
Value: !GetAtt ALB.DNSName
上記はアプリケーションロードバランサー(ALB)のDNS名を取得することを意味します。
4. 確認
CloudFormationの出力値をブラウザのURL欄に貼り付けると、以下のように表示されます。
HTTP接続できることが確認できました。
次にEC2へログイン後、以下コマンドを実行し秘密鍵と自己署名のサーバ証明書を作成します。
# 作業ディレクトリへ移動
cd /etc/pki/tls/private/;pwd
# 秘密鍵およびサーバ証明書の作成
# x509が自己署名証明書、nodesが秘密鍵を暗号化しない(パスフレーズを設定しない)オプション
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout server.key -out server.crt
### 以下は opensslコマンドの補足です。
### Common NameはALBのドメイン名になります。以下は東京リージョンの例です。
Country Name (2 letter code) [XX]:JP
State or Province Name (full name) []:Tokyo
Locality Name (eg, city) [Default City]:Shinjuku
Organization Name (eg, company) [Default Company Ltd]:Example Corp
Organizational Unit Name (eg, section) []:Example Dept
Common Name (eg, your name or your servers hostname) []:timura-dev-alb-1224505215.ap-northeast-1.elb.amazonaws.com
Email Address []:timura@example.com
### ここまで補足
# ファイル存在の確認
ls -l
# 秘密鍵ファイルの中身を取得
sudo cat server.key
# サーバ証明書ファイルの中身を取得
sudo cat server.crt
上記ファイルの中身をALBに設定しHTTPS接続出来るよう設定します。
作成したALBを選択し、[リスナーの追加] をクリックします。
プロトコルは[HTTPS]を選択、ターゲットグループはCloudFormationで作成されたものを選択します。
証明書の取得先は[証明書をインポート]を選択します。
証明書のプライベートキーには [sudo cat server.key] の内容を、証明書本文には [sudo cat server.crt] の内容を貼り付けてください。
[送信]をクリックすると設定が更新されます。
それではプロトコルをhttpsに指定し、ALBのDNS名をブラウザのURL欄に貼り付けます。
無事にHTTPS接続が確認できました。