1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【CloudFormation】AWS上で構築したリソースを、CloudFormationでInfrastructure as Code(IaC)化してみた

Last updated at Posted at 2025-10-31

課題

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

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?