AWS Cloudformation으로 인프라 구축하기#2 – Server 구성하기

‘AWS Cloudformation으로 인프라 구축하기#1 – Network 구성하기’에 이어서 이번 글에서는 이전에 생성한 네트워크를 바탕으로 Server를 구축하여 3tier를 구축해보겠습니다.


3tier Architecture

3tier Architecture는 한 서버에 3가지 로직을 구현한 것이 아닌, 3계층(Presentation 계층, Application 계층, Data 계층)으로 나누어 논리적 또는 물리적 장치에서 구축하는 것입니다. 3개의 계층을 사용하면 수평 확장성, 성능 및 가용성이 향상될 수 있습니다. 각 계층이 다른 계층에 영향을 주지 않고 변경되거나 재배치 될 수 있기 때문에 애플리케이션을 지속적으로 발전시키기 더 쉽습니다.


이번 글에서 Cloudformation 템플릿에서 빌드되는 내용은 아래와 같습니다.
– EC2 인스턴스 5개
– RDS 1개
– 외부 ALB, 내부 ALB 각 1대
– EC2 인스턴스와 RDS에 적용할 보안그룹 4개 (Bastion, WEB, WAS, DB)


[Cloudformation Template] – Parameters

Bespin-network 스택에서 VPC, Subnet 정보를 가져오기 위해서 파라미터로 지정합니다. 파라미터 섹션에는 서버 접속 키, AMI 이미지, RDS 설정 정보를 사용자 정의 값으로 설정했습니다.

Parameters:
  NetworkStackName:
    Description: >-
      Name of an active CloudFormation stack that contains the networking
      resources, such as the VPC and subnet that will be used in this stack.
    Type: String
    MinLength: 1
    MaxLength: 128
    AllowedPattern: '^[a-zA-Z][-a-zA-Z0-9]*$'
    Default: Bespin-network
 
  KeyName:
    Description: Name of an existing EC2 KeyPair to enable SSH access to the instance
    Type: AWS::EC2::KeyPair::KeyName
    Default: 'bespin-key'
 
  AMI:
    Type: AWS::EC2::Image::Id
    Default: ami-0fd0765afb77bcca7
   
  DBInstanceClass:
    Default: db.m5.large
    Description: DB instance class
    Type: String
    AllowedValues:
      - db.m5.large
      - db.m5.xlarge
     
  DBUsername:
    Description: Username for DB Access
    Type: String
    MinLength: 1
    MaxLength: 64
    AllowedPattern: '^[a-zA-Z][-a-zA-Z0-9]*$'
   
  DBPassword:
    NoEcho: true
    Description: Password for DB Access
    Type: String
    MinLength: 8
    MaxLength: 40
    AllowedPattern: '^[a-zA-Z][-a-zA-Z0-9]*$'

[Cloudformation Template] – Resources

EC2 인스턴스 생성 시 AMI 이미지, 서버 접속 키 정보를 파라미터 값에서 참조합니다. 내장 함수 Fn::ImprotValue 사용하여 네트워크를 구성한 스택에서 내보낸 출력의 값을 반환합니다. 내장 함수 !Sub를 사용하여 입력문자열의 변수를 지정한 값으로 변경합니다.

Resources:
###########
# Instance
###########
  BastionHostInstance:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: t2.micro
      ImageId: !Ref AMI
      SubnetId:
        Fn::ImportValue:
          !Sub ${NetworkStackName}-PublicSubnet1      
      KeyName: !Ref KeyName
      SecurityGroupIds:
        - !Ref BastionHostSecurityGroup
      Tags:
        - Key: "Name"
          Value: "Bespin-BastionHost"
 
  WebInstance2a:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: t2.micro
      ImageId: !Ref AMI
      SubnetId:
        Fn::ImportValue:
          !Sub ${NetworkStackName}-PrivateSubnet1
      KeyName: !Ref KeyName
      SecurityGroupIds:
        - !Ref WebSecurityGroup
      Tags:
        - Key: "Name"
          Value: "Bespin-WebServer"
         
  WebInstance2c:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: t2.micro
      ImageId: !Ref AMI
      SubnetId:
        Fn::ImportValue:
          !Sub ${NetworkStackName}-PrivateSubnet2
      KeyName: !Ref KeyName
      SecurityGroupIds:
        - !Ref WebSecurityGroup
      Tags:
        - Key: "Name"
          Value: "Bespin-WebServer"      
 
  WasInstance2a:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: t2.micro
      ImageId: !Ref AMI
      SubnetId:
        Fn::ImportValue:
          !Sub ${NetworkStackName}-PrivateSubnet3
      KeyName: !Ref KeyName
      SecurityGroupIds:
        - !Ref WasSecurityGroup
      Tags:
        - Key: "Name"
          Value: "Bespin-WasServer"
         
  WasInstance2c:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: t2.micro
      ImageId: !Ref AMI
      SubnetId:
        Fn::ImportValue:
          !Sub ${NetworkStackName}-PrivateSubnet4
      KeyName: !Ref KeyName
      SecurityGroupIds:
        - !Ref WasSecurityGroup
      Tags:
        - Key: "Name"
          Value: "Bespin-WasServer"

ALB 로드밸런서를 생성합니다. ALB는 External, Internal 하나씩 생성합니다.

###########
# Load Balancer
###########
 
  WEBALB:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Scheme : internet-facing
      Subnets:
        - Fn::ImportValue:
            !Sub ${NetworkStackName}-PublicSubnet1  
        - Fn::ImportValue:
            !Sub ${NetworkStackName}-PublicSubnet2
      SecurityGroups:
        - !Ref ELBEXSecurityGroup
  WEBALBListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
        - Type: forward
          TargetGroupArn: !Ref WEBTargetGroup
      LoadBalancerArn: !Ref WEBALB
      Port: 80
      Protocol: HTTP
       
  WEBTargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      HealthCheckIntervalSeconds: 30
      HealthCheckTimeoutSeconds: 5
      HealthyThresholdCount: 5
      Matcher:
        HttpCode : '200'
      Port: 80
      Protocol: HTTP
      UnhealthyThresholdCount: 2
      VpcId:
        Fn::ImportValue:
          !Sub ${NetworkStackName}-VPC
      TargetGroupAttributes:
        - Key: deregistration_delay.timeout_seconds
          Value: '300'
      Targets:
        - Id: !Ref WebInstance2a
          Port: 80  
        - Id: !Ref WebInstance2c
          Port: 80
      Tags:
        - Key: Name
          Value: WEBTargetGroup
        - Key: Port
          Value: 80
     
  WASALB:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Scheme : internal
      Subnets:
        - Fn::ImportValue:
            !Sub ${NetworkStackName}-PrivateSubnet1  
        - Fn::ImportValue:
            !Sub ${NetworkStackName}-PrivateSubnet2
      SecurityGroups:
        - !Ref ELBINSecurityGroup
  WASALBListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
        - Type: forward
          TargetGroupArn: !Ref WASTargetGroup
      LoadBalancerArn: !Ref WASALB
      Port: 8080
      Protocol: HTTP
       
  WASTargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      HealthCheckIntervalSeconds: 30
      HealthCheckTimeoutSeconds: 5
      HealthyThresholdCount: 5
      Matcher:
        HttpCode : '200'
      Port: 8080
      Protocol: HTTP
      UnhealthyThresholdCount: 2
      VpcId:
        Fn::ImportValue:
          !Sub ${NetworkStackName}-VPC
      TargetGroupAttributes:
        - Key: deregistration_delay.timeout_seconds
          Value: '300'
      Targets:
        - Id: !Ref WasInstance2a
          Port: 8080
        - Id: !Ref WasInstance2c
          Port: 8080
      Tags:
        - Key: Name
          Value: WASTargetGroup
        - Key: Port
          Value: 8080

각 EC2 인스턴스와 RDS에 연결할 Security group을 생성합니다.

###########
# Security Group
###########
  BastionHostSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: BastionHost
      GroupDescription: Enable access to BastionHost
      VpcId:
        Fn::ImportValue:
          !Sub ${NetworkStackName}-VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: 0.0.0.0/0
      Tags:
        - Key: "Name"
          Value: "Bespin-BastionHost-SG"
 
  WebSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: Web
      GroupDescription: Enable access to Web
      VpcId:
        Fn::ImportValue:
          !Sub ${NetworkStackName}-VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          SourceSecurityGroupId: !Ref ELBEXSecurityGroup
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          SourceSecurityGroupId: !Ref BastionHostSecurityGroup
      Tags:
        - Key: "Name"
          Value: "Bespin-WEB-SG"
         
  WasSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: Was
      GroupDescription: Enable access to Was
      VpcId:
        Fn::ImportValue:
          !Sub ${NetworkStackName}-VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 8080
          ToPort: 8080
          SourceSecurityGroupId: !Ref ELBINSecurityGroup
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          SourceSecurityGroupId: !Ref BastionHostSecurityGroup
      Tags:
        - Key: "Name"
          Value: "Bespin-WAS-SG"
         
  ELBEXSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: ELB-EX
      GroupDescription: Enable access to ELB-EX
      VpcId:
        Fn::ImportValue:
          !Sub ${NetworkStackName}-VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0

RDS를 생성합니다. RDS의 인스턴스 type, DB 관리자 계정 및 비밀번호는 파라미터로 값을 받겠습니다. 템플릿에는 DB 인스턴스 1대만 구축하였습니다.

###########
# RDS
###########
  SubnetGroup:
    Type: AWS::RDS::DBSubnetGroup
    Properties:
      DBSubnetGroupDescription: SubnetGroup for MySQL RDS
      DBSubnetGroupName: Bespin-SubnetGroup
      SubnetIds:
        - Fn::ImportValue:
            !Sub ${NetworkStackName}-PrivateSubnet5
        - Fn::ImportValue:
            !Sub ${NetworkStackName}-PrivateSubnet6
 
  RDS:
    Type: AWS::RDS::DBInstance
    Properties:
      DBInstanceIdentifier: Bespin-Mysql
      DBName: Bepsindb
      DBInstanceClass: !Ref DBInstanceClass
      Engine: MySQL
      EngineVersion: 8.0.29
      MasterUsername: !Ref DBUsername
      MasterUserPassword: !Ref DBPassword
      AllocatedStorage: 5
      DBSubnetGroupName: !Ref SubnetGroup
      VPCSecurityGroups:
        - !Ref DBSecurityGroup

[AWS Cloudformation 스택 생성 결과]

AWS Cloudformation 스택 생성이 완료되었습니다.


[AWS 종속성 오류 문제]

AWS Console에서 리소스를 제거하게 된다면 종속성 오류 문제가 발생할 수 있습니다. AWS Cloudformation는 스택을 삭제하면 생성한 모든 리소스가 자동으로 삭제된다는 점에서 장점이 있습니다.


[참고자료]

Leave a Comment