課題
AWS上で構築したVPC、EC2、RDS、Route53などのリソースを、CloudFormationを使ってInfrastructure as Code(IaC)化しました。
学習のため、構成やテンプレート内容をまとめます。
構成
セキュリティグループ
| セキュリティグループ名 | 許可ポート | 許可元SG / 許可元IP | 用途・説明 |
|---|---|---|---|
| HttpHttpsSg | 80, 443 | 0.0.0.0/0 | WebサーバへのHTTP/HTTPS公開 |
| BastionSg | 22 | 0.0.0.0/0(要制限推奨) | BastionへのSSH接続許可 |
| WebserverToRdsSg | 5432 | HttpHttpsSg | WebサーバからRDS(PostgreSQL)への接続許可 |
| BastionToRdsSg | 5432 | BastionSg | BastionからRDS(PostgreSQL)への接続許可 |
| BackendApiSg | 4000 | HttpHttpsSg | WebサーバからAPIサーバ(Express)への接続許可 |
Infrastructure as Code(IaC)
CloudFormationサンプル全文を見る
※ 今回はサンプルのため、VPC、EC2、サブネットグループ、RDS、Route53 など、すべてのリソースを1つのファイルにまとめて記述しています
main.yml
AWSTemplateFormatVersion: '2010-09-09'
Description: CloudFormation Template for VPC, Subnets, EC2 (Bastion + Web), RDS, Route53
Parameters:
VpcCidr:
Type: String
Default: 10.0.0.0/16
Description: CIDR block for the VPC
PublicSubnetCidr:
Type: String
Default: 10.0.0.0/20
Description: CIDR block for the public subnet
# Description: EC2インスタンスで使用するAMI IDを指定
LatestAmiId:
Type: String
Description: The AMI ID to use for EC2 instances
# Description: EC2インスタンスへSSH接続するためのキーペア名を指定
# このパラメータは、CloudFormation スタック作成時に**ユーザーが入力する「EC2キーペア名」**を受け取る
KeyPairName:
Type: String
Description: EC2 KeyPair Name for SSH access
main.yml(続き)
Resources:
# VPC:-----------------------------------------------------------------------------------
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VpcCidr
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: vpc-ts-study-record
# Internet Gateway:-----------------------------------------------------------------------------------
IGW:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: igw-ts-study-record
# AttachIGW:-----------------------------------------------------------------------------------
AttachIGW:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref VPC
InternetGatewayId: !Ref IGW
# Subnet:-----------------------------------------------------------------------------------
PublicSubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: !Ref PublicSubnetCidr
# インスタンスを起動するときに自動でパブリックIPを割り当てる
MapPublicIpOnLaunch: true
AvailabilityZone: ap-northeast-1a
Tags:
- Key: Name
Value: public-subnet-ts-study-record
# Private Subnet 1 (ap-northeast-1a)
# MapPublicIpOnLaunch: false
# プライベートサブネット内は、作成されるインスタンスに自動的にパブリックIPを割り当てない
PrivateSubnetA:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.64.0/20
AvailabilityZone: ap-northeast-1a
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: private-subnet-ts-study-record
# Private Subnet 2 (ap-northeast-1c)
PrivateSubnetC:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.80.0/20
AvailabilityZone: ap-northeast-1c
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: private-subnet-02-ts-study-record
# Subnet Group:-----------------------------------------------------------------------------------
# RDS Subnet Group(プライベートサブネットはアベイラビリティゾーンを変えて2つにする)
RDSSubnetGroup:
Type: AWS::RDS::DBSubnetGroup
Properties:
DBSubnetGroupDescription: Subnet group for private RDS
SubnetIds:
- !Ref PrivateSubnetA
- !Ref PrivateSubnetC
Tags:
- Key: Name
Value: private-rds-subnet-group-ts-study-record
main.yml(続き)
# Route Table:-----------------------------------------------------------------------------------
# Public Route Table
PublicRouteTable:
# VPC 内で使うPublicルートテーブルを作成する
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: rt-public-ts-study-record
# Default Route to IGW
PublicDefaultRoute:
Type: AWS::EC2::Route
DependsOn: AttachIGW # 作成順序の制御
Properties:
RouteTableId: !Ref PublicRouteTable # PublicRouteTableにルートを設定
# 「この宛先へのトラフィックはどこに送るか」を定義。主にアウトバウンド(VPC → インターネット)用
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref IGW
# Public Subnet にルートテーブルを関連付け
PublicSubnetRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet
RouteTableId: !Ref PublicRouteTable
# Private Route Table
# VPC 内で使うPrivateルートテーブルを作成する(必要に応じて NAT 経由でインターネットへのアクセスも管理)
PrivateRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: rt-private-ts-study-record
# Private Subnet A にルートテーブルを関連付け
PrivateSubnetARouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnetA
RouteTableId: !Ref PrivateRouteTable
# Private Subnet C にルートテーブルを関連付け
PrivateSubnetCRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnetC
RouteTableId: !Ref PrivateRouteTable
main.yml(続き)
# Security Group:-----------------------------------------------------------------------------------
# 通信プロトコルまたはポート番号、送信元/送信先IPアドレス範囲などに基づいて、インスタンスへのトラフィックを制御
HttpHttpsSg:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Allow HTTP and HTTPS access
VpcId: !Ref VPC
Tags:
- Key: Name
Value: http-https-sg-ts-study-record
# インバウンド
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80 # HTTP ポート
ToPort: 80
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 443 # HTTPS ポート
ToPort: 443
CidrIp: 0.0.0.0/0
# SSHアクセス
BastionSg:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: SSH access for Bastion
VpcId: !Ref VPC
Tags:
- Key: Name
Value: bastion-sg-ts-study-record
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 0.0.0.0/0 # 必要に応じて制限# 10.x.x.xx/32 のように特定IPアドレスからのアクセスに制限できる
SecurityGroupEgress:
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 0.0.0.0/0
WebserverToRdsSg:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Allow DB access from WebServer only
VpcId: !Ref VPC
Tags:
- Key: Name
Value: webserver-to-rds-sg-ts-study-record
SecurityGroupIngress:
# prisma → RDS(WebServer → RDS (PostgreSQL))
- IpProtocol: tcp
FromPort: 5432
ToPort: 5432
SourceSecurityGroupId: !Ref HttpHttpsSg
BastionToRdsSg:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Allow DB access from Bastion only
VpcId: !Ref VPC
Tags:
- Key: Name
Value: bastion-to-rds-sg-ts-study-record
SecurityGroupIngress:
# ローカル環境でのマイグレーション時(Bastion → RDS (PostgreSQL) )
- IpProtocol: tcp
FromPort: 5432
ToPort: 5432
SourceSecurityGroupId: !Ref BastionSg
BackendApiSg:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Allow API access from WebServer only
VpcId: !Ref VPC
Tags:
- Key: Name
Value: backend-api-sg-ts-study-record
SecurityGroupIngress:
# WebServer → Backend API(Express / ポート 4000)
- IpProtocol: tcp
FromPort: 4000
ToPort: 4000
SourceSecurityGroupId: !Ref HttpHttpsSg
main.yml(続き)
# インスタンス:-----------------------------------------------------------------------------------
WebServerEC2:
Type: AWS::EC2::Instance
Properties:
InstanceType: t3.small
ImageId: !Ref 'LatestAmiId'
SubnetId: !Ref PublicSubnet
KeyName: !Ref KeyPairName
SecurityGroupIds:
- !Ref HttpHttpsSg
UserData:
Fn::Base64: |
#!/bin/bash
yum update -y
amazon-linux-extras install -y nginx1
systemctl enable nginx
systemctl start nginx
mkdir -p /home/ec2-user/ts-study-record-aws/frontend/dist
echo "<h1>Hello from Nginx WebServer</h1>" > /home/ec2-user/ts-study-record-aws/frontend/dist/index.html
Tags:
- Key: Name
Value: ec2-web-server-ts-study-record
# 🔐 SSHアクセス手順
# 1. AWS マネジメントコンソール → EC2 → キーペア → 「キーペアを作成」
# 2. 例: ts-study-bastion-key という名前を作成
# 3. 作成時に .pem ファイルがローカルPCに自動的にダウンロードされる(再取得不可)
# 4. 下記テンプレートの "EC2 インスタンス作成時"に、そのキーペア名を `KeyName` に指定してSSH接続を行う
BastionEC2:
Type: AWS::EC2::Instance
Properties:
InstanceType: t3.micro
ImageId: !Ref 'LatestAmiId' # Amazon Linux 2023 の AMI をパラメータで指定
SubnetId: !Ref PublicSubnet
SecurityGroupIds:
- !Ref BastionSg # SSH 専用 SG
# コンソールでキーペアを作成し、ここに作成したキーペア名を指定
KeyName: !Ref KeyPairName
Tags:
- Key: Name
Value: ec2-bastion-ts-study-record
Db:
Type: AWS::RDS::DBInstance
Properties:
DBInstanceIdentifier: db-ts-study-record
AllocatedStorage: 20 # ストレージ容量(GB)
DBInstanceClass: db.t4g.micro
Engine: postgres
MasterUsername: admin
# MasterUserPassword は AWSマネジメントコンソールからSecrets Manager に記載し、参照する
MasterUserPassword: '{{resolve:secretsmanager:rds-master-password:SecretString:password}}'
VPCSecurityGroups:
- !Ref WebserverToRdsSg
- !Ref BastionToRdsSg
DBSubnetGroupName: !Ref RDSSubnetGroup
# MultiAZ: true の場合、スタンバイは自動で異なる AZ に配置される
# サブネットグループ(DBSubnetGroupName)で複数AZのサブネットを指定していれば、RDSが自動でAZを選択
MultiAZ: true
PubliclyAccessible: false
Port: 5432
Tags:
- Key: Name
Value: rds-ts-study-record
# AvailabilityZone: ap-northeast-1c
main.yml(続き)
# Route53:-----------------------------------------------------------------------------------
# Route53 Hosted Zone
# 独自ドメイン用のパブリックホストゾーンを作成
MyHostedZone:
Type: AWS::Route53::HostedZone
Properties:
Name: your-domain.com # ← 実際のドメイン名に置き換え
HostedZoneConfig:
Comment: Hosted Zone for sample environment
# Aレコード(Address Record) / DNSで「ドメイン名」を「IPv4アドレス」に対応付ける
WebServerARecord:
Type: AWS::Route53::RecordSet
Properties:
HostedZoneId: !Ref MyHostedZone
Name: www.your-domain.com # ← サブドメイン名に置き換え
Type: A
TTL: 300
ResourceRecords:
- !GetAtt WebServerEC2.PublicIp
# Aレコード(ルートドメイン用)
RootARecord:
Type: AWS::Route53::RecordSet
Properties:
HostedZoneId: !Ref MyHostedZone
Name: your-domain.com # ← 実際のドメイン名に置き換え
Type: A
TTL: 300
ResourceRecords:
- !GetAtt WebServerEC2.PublicIp
# Aレコード(Bastionサーバー用)
BastionARecord:
Type: AWS::Route53::RecordSet
Properties:
HostedZoneId: !Ref MyHostedZone
Name: bastion.your-domain.com # ← サブドメイン名に置き換え
Type: A
TTL: 300
ResourceRecords:
- !GetAtt BastionEC2.PublicIp
# CNAMEレコード (Canonical Name Record)
# 特定のドメイン名(例: db.home.example.com)を、
# RDSなどのデータベースのエンドポイント(例: db-xxxx.ap-northeast-1.rds.amazonaws.com)に紐付けるDNSレコード
# CNAMEレコード(DBエンドポイント用)
DBCnameRecord:
Type: AWS::Route53::RecordSet
Properties:
HostedZoneId: !Ref MyHostedZone
Name: db.home.your-domain.com # ← サブドメイン名に置き換え
Type: CNAME
TTL: 300
ResourceRecords:
- !GetAtt Db.Endpoint.Address
# homeサブドメイン用のパブリックホストゾーン
HomeHostedZone:
Type: AWS::Route53::HostedZone
Properties:
Name: home.your-domain.com # ← 実際のサブドメインに置き換え
HostedZoneConfig:
Comment: Hosted Zone for home subdomain
# db.home用のCNAMEレコード
DbHomeCnameRecord:
Type: AWS::Route53::RecordSet
Properties:
HostedZoneId: !Ref HomeHostedZone
Name: db.home.your-domain.com # ← 実際のFQDNに置き換え
Type: CNAME
TTL: 300
ResourceRecords:
- db-xxxxxxx.ap-northeast-1.rds.amazonaws.com # ← RDSのエンドポイントに置き換え
# Outputs:-----------------------------------------------------------------------------------
Outputs:
# WebサーバのパブリックIP
WebServerPublicIP:
Description: Public IP of WebServer
Value: !GetAtt WebServerEC2.PublicIp
# BastionのパブリックIP
BastionPublicIP:
Description: Public IP of Bastion Host
Value: !GetAtt BastionEC2.PublicIp
# RDSエンドポイント(接続用)
DbEndpoint:
Description: RDS endpoint address
Value: !GetAtt Db.Endpoint.Address
# RDSポート
DbPort:
Description: RDS port
Value: !GetAtt Db.Endpoint.Port
# Route53 Hosted Zone ID
HostedZoneID:
Description: Route53 Hosted Zone ID
Value: !Ref MyHostedZone
# Elastic IPを使う場合の出力例(必要に応じて)
# 本を見る限りElastic IPは使っていないようなのでコメントアウト
# WebServerElasticIP:
# Description: Elastic IP of WebServer
# Value: !Ref WebServerElasticIP
終わりに
全体像を見失いがちなので、「どこに何を繋げるか」「どのポートを使うか」などを図解して考えることが大事だと感じました。
もし間違いや改善点があれば、ご指摘いただけると幸いです。
参考
ドキュメント
https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/introduction.html