‘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는 스택을 삭제하면 생성한 모든 리소스가 자동으로 삭제된다는 점에서 장점이 있습니다.

[참고자료]