Create CloudFormation Stack with VPC, Subnet, ALB, EC2, etc...

Create CloudFormation Stack with VPC, Subnet, ALB, EC2, etc...

avatar

Phong Nguyen

2023.12.22

Bài Lab này sẽ cung cấp CloudFormation Template tạo 1 VPC with 1 private và 1 public subnet trong mỗi AZ. Mỗi public subnet sẽ chứa 1 EC2 và được kết nối đến Application Load Balancer. VPC sẽ có Internet Gateway, Route Table.

Lưu ý

  • AWS Region: US East (N. Virginia) us-east-1

Architecture

Images

CloudFormation Template

VPC-ALB-EC2-Stack.yaml
AWSTemplateFormatVersion: 2010-09-09
Description: Create VPC, ALB, EC2 Stack
Parameters:
  KeyName:
    Description: Key Pair for EC2
    Type: 'AWS::EC2::KeyPair::KeyName'

Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
      - Key: Name
        Value: d-vpc-DVA-001

  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
      - Key: Name
        Value: d-igw-DVA-001

  AttachGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGateway

  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.0.1.0/24
      AvailabilityZone: !Select [0, !GetAZs '']
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: d-vpc-DVA-001-subnet-public1

  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.0.2.0/24
      MapPublicIpOnLaunch: false
      AvailabilityZone: !Select [0, !GetAZs '']
      Tags:
        - Key: Name
          Value: d-vpc-DVA-001-subnet-private1

  PublicSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.0.3.0/24
      AvailabilityZone: !Select [1, !GetAZs '']
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: d-vpc-DVA-001-subnet-public2

  PrivateSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.0.4.0/24
      AvailabilityZone: !Select [1, !GetAZs '']
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: d-vpc-DVA-001-subnet-private2

  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: d-vpc-DVA-001-rtb-public

  PublicRoute:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  PublicSubnetRouteTableAssociation1:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet1
      RouteTableId: !Ref PublicRouteTable

  PublicSubnetRouteTableAssociation2:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet2
      RouteTableId: !Ref PublicRouteTable

  PrivateRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
      - Key: Name
        Value: d-vpc-DVA-001-rtb-private

  PrivateSubnetRouteTableAssociation1:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnet1
      RouteTableId: !Ref PrivateRouteTable

  PrivateSubnetRouteTableAssociation2:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnet2
      RouteTableId: !Ref PrivateRouteTable

  #EC2 Instances
  EC2Instance1:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: ami-079db87dc4c10ac91
      InstanceType: t2.micro
      Tags:
        - Key: Name
          Value: d-ec2-DVA-web-server-01
      SecurityGroupIds:
        - !Ref EC2SecurityGroup
      SubnetId: !Ref PublicSubnet1
      KeyName: !Ref KeyName
      UserData:
        Fn::Base64: !Sub |
          #!/bin/bash
          yum update -y
          yum install -y httpd
          systemctl start httpd
          systemctl enable httpd
          echo "<h1>Hello from $(hostname -f) in AZ A</h1>" > /var/www/html/index.html

  EC2Instance2:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: ami-079db87dc4c10ac91
      InstanceType: t2.micro
      Tags:
        - Key: Name
          Value: d-ec2-DVA-web-server-02
      SecurityGroupIds:
        - !Ref EC2SecurityGroup
      SubnetId: !Ref PublicSubnet2
      KeyName: !Ref KeyName
      UserData:
        Fn::Base64: !Sub |
          #!/bin/bash
          yum update -y
          yum install -y httpd
          systemctl start httpd
          systemctl enable httpd
          echo "<h1>Hello from $(hostname -f) in AZ B</h1>" > /var/www/html/index.html

  # EC2 and ALB Security Groups
  ELBSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: ELB Security Group
      GroupName: d-sg-DVA-ALB
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: d-sg-DVA-ALB
      SecurityGroupIngress:
      - IpProtocol: tcp
        FromPort: 80
        ToPort: 80
        CidrIp: 0.0.0.0/0

  EC2SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Allow access from ALB
      GroupName: d-sg-DVA-web-server
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: d-sg-DVA-web-server
      SecurityGroupIngress:
      - IpProtocol: tcp
        FromPort: 80
        ToPort: 80
        SourceSecurityGroupId:
          Fn::GetAtt:
          - ELBSecurityGroup
          - GroupId
      - IpProtocol: tcp
        FromPort: 22
        ToPort: 22
        CidrIp: 0.0.0.0/0

  # Target Group, Listener and Application  Load Balancer
  EC2TargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      HealthCheckIntervalSeconds: 30
      HealthCheckProtocol: HTTP
      HealthCheckTimeoutSeconds: 15
      HealthyThresholdCount: 2
      Matcher:
        HttpCode: '200'
      Name: d-tg-DVA-web-server
      Port: 80
      Protocol: HTTP
      TargetGroupAttributes:
      - Key: deregistration_delay.timeout_seconds
        Value: '20'
      Targets:
      - Id: !Ref EC2Instance1
        Port: 80
      - Id: !Ref EC2Instance2
        Port: 80
      UnhealthyThresholdCount: 3
      VpcId: !Ref VPC

  ALBListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
        - Type: forward
          TargetGroupArn: !Ref EC2TargetGroup
      LoadBalancerArn: !Ref ApplicationLoadBalancer
      Port: 80
      Protocol: HTTP

  ApplicationLoadBalancer:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Name: d-alb-DVA-web-server-alb
      Scheme: internet-facing
      Subnets:
      - !Ref PublicSubnet1
      - !Ref PublicSubnet2
      SecurityGroups:
        - !GetAtt ELBSecurityGroup.GroupId

Mô tả các resource được tạo

  • VPC

    • VPC CIDR: 10.0.0.0/16
    • EnableDnsSupport: true
    • EnableDnsHostnames: true
  • Subnets:

    • Public Subnet 1 - in AZ a with CIDR 10.0.1.0/24.
    • Public Subnet 2 - in AZ b with CIDR10.0.3.0/24.
    • Private Subnet 1 - in AZ a with CIDR 10.0.2.0/24.
    • Private Subnet 2 - in AZ b with CIDR 10.0.4.0/24
  • Internet Gateway: Attatch to VPC

  • Public Route Tables

    • Route to Internet via Internet Gateway
    • Routes traffic trong mạng 10.0.0.0/16
    • Associate with 2 public subnet
  • Private Route Tables

    • Routes traffic chỉ trong mạng 10.0.0.0/16
    • Associate with 2 private subnet
  • EC2 Instances

    • 2 EC2 Instances in public subnet with user data to install Apache web server.
    • The Instances have a reference to EC2 Security group and KeyPair.
  • Security Groups:

    • EC2 Security Group: Port 22 for SSH and Port 80 open to the Load Balancer.
    • ELB Security Group: Port 80 open to everyone.
  • TargetGroup

  • Application Load Balancer

Deploying CloudFormation Template

  1. Copy nội dung Template phía trên và lưu lại với tên VPC-ALB-EC2-Stack.yaml hoặc tải file Tại đây
  2. Tại CloudFormation Console -> Chọn Menu Stack -> Create stack Images
  3. Upload file VPC-ALB-EC2-Stack.yaml và nhấn Next Images
  4. Nhập tên Stack: VPC-ALB-EC2-Stack-{random-characters} (Vis Stack name không được trùng trong cùng 1 Region). Chọn KeyName từ Pulldown (Key-pair mà bạn đã tạo trước đó) Images
  5. Next cho đến khi hoàn thành việc tạo Stack Images Chúng ta sẽ đợi cho đến khi Resource được tạo xong.

Nếu Stack tạo bị lỗi thì xem thử tại Tab Event xem thông báo lỗi gì để fix template rồi update lại Template nhé. Những lỗi thường gặp phải như là không tồn tại AMI ID ở Region XYZ. Lý do mỗi Region thì có AMI ID khác nhau, Guidline này mình hướng dẫn các bạn chạy ở Region us-east-1 nên dùng ImageId: ami-079db87dc4c10ac91. Nếu các bạn chạy ở Region khác thì cần update giá trị ImageId.

Sau khi đợi 1 lúc thì Stack đã tạo thành công! Images

Verify kết quả

  1. Copy DNS của Application Load Balancer Images
  2. Access từ Browser Images Images

Clean up

  1. Delete CloudFormation Template Images
  2. Đợi cho đến khi Template được xóa hoàn toàn.

Challenge

Để tuân theo best practice thì 2 EC2 của chúng ta cần đặt ở Private subnet. Thay đổi cloudformation template để xây dựng kiến trúc như bên dưới. Images

  1. Đặt 2 EC2 ở private subnet
  2. Thêm NAT Gateway, đặt tại public subnet. (Chúng ta cần NAT Gateway để khi EC2 start sẽ chạy script cài đặt web apache trong user-data)
  3. Thêm 1 route cho Private Route Table: cho phép đi ra internet thông qua NAT Gateway
  4. Controll việc NAT Gateway phải được tạo xong thì mới tạo EC2. (Vì EC2 không đi ra internet dc sẽ ko cài đặt web apache được)

Trong trường Stack tạo thành công, nhưng TargetGroup health check vẫn fail thì khả năng web apache chưa được cài thành công trên EC2. Bạn cần tạo thủ công hoặc thay đổi template để thêm 1 EC2 đóng vai trò là bastion host, đặt tại public subnet.

  • ssh vào bastion host
  • Tạo KeyPair để ssh vào web server
    • Đứng ở bastion host ssh vào webserver tham khảo Tại đây
  • Confirm apache server đã running hay chưa: systemctl status httpd.service
  • Nếu apache server chưa được cài đặt thì tiến hành chạy thủ công.
    yum update -y
    yum install -y httpd.x86_64
    systemctl start httpd.service
    systemctl enable httpd.service
    echo “Hello World from $(hostname -f)> /var/www/html/index.html