본 글은 Web, Application, Data Layer로 나눈 3-Tier Architecture를 AWS 리소스로 구성하는 내용이다.
Terraform을 통해 구성하였으며, 전체 코드는 깃허브에 업로드 해두었다.
글을 나눈 기준은 AWS에서 VPC 대시보드에 제공하는 서비스와 EC2 대시보드에서 제공하는 서비스이다.
VPC (Virtual Private Cloud)
사용자가 AWS 리소스를 관리할 논리적으로 격리된 네트워크 공간
방(Subnet)도 만들고 가구(EC2, RDS 등)도 들일 집 역할을 하는 것이 바로 VPC이다.
RFC 1918 range |
Example | |
10.0.0.0 ~ 10.255.255.255 (10/8 prefix) |
10.0.0.0/16 | |
172.16.0.0 ~ 172.31.255.255 (172.16/12 prefix) |
172.31.0.0/16 | |
192.168.0.0 ~ 192.168.255.255 (192.168/16 prefix) |
192.168.0.0/20 |
RFC 1918 규약에 따라 위와 같은 private IP address 대역을 권장한다. AWS에서 VPC의 CIDR 로 허용하는 Block의 크기는 /16 ~ /28 이다.
vpc.tf
resource "aws_vpc" "vpc" {
cidr_block = "10.0.0.0/16"
tags = var.tags
}
위와 같이 10.0.0.0/16 대역에 vpc를 만들어 주었다. 세부 설정은 블로그 코드엔 생략하였기 때문에 전체 코드는 깃허브에 올려두었다.
Subnet
VPC를 다시 CIDR 범위로 나눈 공간을 Subnet이라고 한다.
VPC가 Subnet의 저장소라면 Subnet은 Instance의 저장소로 볼 수 있다.
Public Subnet은 인터넷 접근이 가능한 Subnet, Private Subnet은 인터넷 접근이 불가능한 Subnet으로 구분된다.
Region & Availiability Zone
Region은 현재 사용자가 사용할 수 있는 지역을 뜻하며 대한민국 서울의 Region은 ap-northeast-2이다. Availiability Zone은 하나의 데이터 센터를 의미 하며, Region은 2개 이상의 Availiability Zone으로 구성되있다. 서울의 Availiability Zone은 ap-northeast-2a, ap-northeast-2b, ap-northeast-2c, ap-northeast-2d로 현재 총 4개가 있다. 하나의 인스턴스에서 장애가 발생할 경우, 다른 AZ가 요청을 처리할 수 있도록 2개 이상의 AZ에 Subnet을 생성한다.
subnet.tf
data "aws_availability_zones" "availability_zones" {
state = "available"
exclude_names = ["ap-northeast-2b", "ap-northeast-2d"]
}
resource "aws_subnet" "public_subnets" {
count = 2
cidr_block = replace("10.0.x.0/24", "x", count.index)
vpc_id = aws_vpc.vpc.id
availability_zone = data.aws_availability_zones.availability_zones.names[count.index]
map_public_ip_on_launch = true
tags = var.tags
}
resource "aws_subnet" "private_subnets_application" {
count = 2
cidr_block = replace("10.0.x.0/24", "x", 10 + count.index)
vpc_id = aws_vpc.vpc.id
availability_zone = data.aws_availability_zones.availability_zones.names[count.index]
map_public_ip_on_launch = false
tags = var.tags
}
resource "aws_subnet" "private_subnets_data" {
count = 2
cidr_block = replace("10.0.x.0/24", "x", 20 + count.index)
vpc_id = aws_vpc.vpc.id
availability_zone = data.aws_availability_zones.availability_zones.names[count.index]
map_public_ip_on_launch = false
tags = var.tags
}
Subnet은 3-Tier로 web(public), application(private), data(private)으로 구성했다. 각 서브넷은 2개의 가용 영역으로 구성했으며, public subnet만 public ip를 할당받도록 설정(map_public_ip_on_launch)했다. 또한 본 글은 프리티어 기준이기 때문에 프리티어 AMI에서 지원하지 않는 availiabitiy zone 를 제외해주었다.
IGW (Internet Gateway)
Public Subnet의 Instance가 외부 인터넷과 통신할 수 있도록 VPC에 연결하는 통로
VPC는 기본적으로 격리된 네트워크 환경이기 때문에 인터넷을 사용하기 위해선 통로를 만들어주어야 한다. Internet Gateway가 이 통로 역할을 하게 된다.
Internet Gateway가 연결된 Subnet을 Public Subnet, 연결되지 않은 Subnet을 Private Subnet으로 나눈다.
internet_gateway.tf
resource "aws_internet_gateway" "internet_gateway" {
vpc_id = aws_vpc.vpc.id
tags = var.tags
}
NAT Gateway
Private Subnet에서 외부의 인터넷과 통신하기 위한 통로
외부에서 접근하면 안되지만, 외부로 접근이 가능해야 하는 application instance가 있는 Private Subnet에 주로 사용된다.
인터넷 통신이 가능해야 하기 때문에 Public Subnet에 생성하며, 라우팅 테이블을 통해 Private Subnet과 연결해준다.
Private Subnet에서 외부 인터넷으로 요청이 나갈 때 고정 IP를 사용하기 때문에, Elastic IP를 통해 고정 IP를 발급받아야 한다.
Elastic IP
EC2와 같은 인스턴스를 생성하면 고정된 IP가 아닌 유동적인 IP를 할당받게 되어, 인스턴스를 다시 실행할 때마다 IP가 변경된다. 이를 위해 고정적인 Public IP 주소를 할당해주는 서비스가 Elastic IP이다.
nat_gateway.tf
resource "aws_eip" "nat_eip" {
vpc = true
}
resource "aws_nat_gateway" "nat_gateway" {
allocation_id = aws_eip.nat_eip.id
subnet_id = aws_subnet.public_subnets.*.id[0]
tags = var.tags
}
Route Table
Subnet 또는 Gateway의 네트워크 트래픽의 경로를 설정하는 테이블
라우팅 테이블에서 각 Subnet에 Internet gateway와 NAT gateway를 연결시킨다.
라우팅 테이블을 명시적으로 설정해주지 않으면 기본 라우팅 테이블을 연결해준다.
기본 라우팅 테이블은 Target은 VPC CIDR, Destination은 local로 설정되어 있으며 수정이 가능하다.
route_table.tf
# public route table
resource "aws_route_table" "public_route_table" {
vpc_id = aws_vpc.vpc.id
tags = var.tags
}
resource "aws_route" "public_route" {
route_table_id = aws_route_table.public_route_table.id
destination_cidr_block = var.entire_cidr_block
gateway_id = aws_internet_gateway.internet_gateway.id
}
resource "aws_route_table_association" "public_route_table_associations" {
count = var.public_subnet_count
subnet_id = aws_subnet.public_subnets.*.id[count.index]
route_table_id = aws_route_table.public_route_table.id
}
# private route table
resource "aws_route_table" "private_route_table" {
vpc_id = aws_vpc.vpc.id
tags = var.tags
}
resource "aws_route" "private_route" {
route_table_id = aws_route_table.private_route_table.id
destination_cidr_block = var.entire_cidr_block
nat_gateway_id = aws_nat_gateway.nat_gateway.id
}
resource "aws_route_table_association" "private_route_table_associations" {
count = var.private_subnet_application_count
subnet_id = aws_subnet.private_subnets_application.*.id[count.index]
route_table_id = aws_route_table.private_route_table.id
}
Network ACL (Network Access Control List)
Network ACL은 Subnet의 인바운드(ingress), 아웃바운드(egress) 트래픽을 허용 혹은 거부할 수 있는 서비스이다.
Security Group
Security Group과 Network ACL은 비슷한 역할을 하게 되는데, 차이점은 이 글에 서술해두었다.
Security Group이 Instance의 방화벽 역할이라면, Network ACL은 Subnet의 방화벽 역할을 한다.
2중 보안을 위해 Security Group과 Network ACL을 같이 사용하는 아키텍처도 있고, 오히려 설정이 꼬이기 때문에 둘 중 하나만 사용하는 아키텍처도 많이 볼 수 있다.
Ephemeral Port
휘발성 port, 임시 port, Dynamic port 라고도 한다. 위 그림과 같이 TCP 연결에서 웹 서버는 80번 port를 통하여 연결하지만, client는 임시 포트 번호를 통해 연결하게 된다. 각 리소스 혹은 OS가 사용하는 port range는 해당 문서 참조
Security Group은 stateful하기 때문에 Ephemeral port를 고려하지 않아도 되지만, Network ACL은 stateless 하기 때문에 Ephemeral Range도 고려해서 구성해주어야 한다.
network_acl.tf
resource "aws_network_acl" "public_network_acl" {
vpc_id = aws_vpc.vpc.id
subnet_ids = aws_subnet.public_subnets.*.id
tags = var.tags
}
resource "aws_network_acl" "private_network_acl" {
vpc_id = aws_vpc.vpc.id
subnet_ids = aws_subnet.private_subnets_application.*.id
tags = var.tags
}
# public subnet rules
resource "aws_network_acl_rule" "public_network_acl_ingress_http" {
network_acl_id = aws_network_acl.public_network_acl.id
rule_number = 100
rule_action = "allow"
egress = false
protocol = "tcp"
from_port = 80
to_port = 80
cidr_block = var.entire_cidr_block
}
resource "aws_network_acl_rule" "public_network_acl_egress_http" {
network_acl_id = aws_network_acl.public_network_acl.id
rule_number = 100
rule_action = "allow"
egress = true
protocol = "tcp"
from_port = 80
to_port = 80
cidr_block = var.entire_cidr_block
}
resource "aws_network_acl_rule" "public_network_acl_ingress_ssh" {
network_acl_id = aws_network_acl.public_network_acl.id
rule_number = 120
rule_action = "allow"
egress = false
protocol = "tcp"
from_port = 22
to_port = 22
cidr_block = var.access_ip
}
resource "aws_network_acl_rule" "public_network_acl_egress_ssh" {
network_acl_id = aws_network_acl.public_network_acl.id
rule_number = 120
rule_action = "allow"
egress = true
protocol = "tcp"
from_port = 22
to_port = 22
cidr_block = aws_vpc.vpc.cidr_block
}
resource "aws_network_acl_rule" "public_network_acl_ingress_ephemeral" {
network_acl_id = aws_network_acl.public_network_acl.id
rule_number = 140
rule_action = "allow"
egress = false
protocol = "tcp"
from_port = 1024
to_port = 65535
cidr_block = var.entire_cidr_block
}
resource "aws_network_acl_rule" "public_network_acl_egress_ephemeral" {
network_acl_id = aws_network_acl.public_network_acl.id
rule_number = 140
rule_action = "allow"
egress = true
protocol = "tcp"
from_port = 1024
to_port = 65535
cidr_block = var.entire_cidr_block
}
# private subnet rules
resource "aws_network_acl_rule" "private_network_acl_ingress_vpc" {
network_acl_id = aws_network_acl.private_network_acl.id
rule_number = 100
rule_action = "allow"
egress = false
protocol = -1
from_port = 0
to_port = 0
cidr_block = aws_vpc.vpc.cidr_block
}
resource "aws_network_acl_rule" "private_network_acl_egress_vpc" {
network_acl_id = aws_network_acl.private_network_acl.id
rule_number = 100
rule_action = "allow"
egress = true
protocol = -1
from_port = 0
to_port = 0
cidr_block = aws_vpc.vpc.cidr_block
}
resource "aws_network_acl_rule" "private_network_acl_ingress_nat" {
network_acl_id = aws_network_acl.private_network_acl.id
rule_number = 110
rule_action = "allow"
egress = false
protocol = "tcp"
from_port = 1024
to_port = 65535
cidr_block = var.entire_cidr_block
}
resource "aws_network_acl_rule" "private_network_acl_egress_http" {
network_acl_id = aws_network_acl.private_network_acl.id
rule_number = 120
rule_action = "allow"
egress = true
protocol = "tcp"
from_port = 80
to_port = 80
cidr_block = var.entire_cidr_block
}
참조
https://velog.io/@ghldjfldj/AWS-VPC란-무엇인가
https://medium.com/harrythegreat/aws-가장쉽게-vpc-개념잡기-71eef95a7098
https://yoo11052.tistory.com/m/170
https://kimjingo.tistory.com/m/180
https://aws-hyoh.tistory.com/entry/VPC-쉽게-이해하기-1
https://terraform101.inflearn.devopsart.dev/cont/vpc-practice/vpc-practice-with-nat/
https://developer.ibm.com/articles/secure-vpc-access-with-a-bastion-host-and-terraform/
https://www.44bits.io/ko/post/understanding_aws_vpc
https://aws.plainenglish.io/terraform-aws-three-tier-architecture-design-d2ed61d7ec4a
https://blog.outsider.ne.kr/1301
https://registry.terraform.io/providers/hashicorp/aws/latest/docs
'Devops > AWS' 카테고리의 다른 글
[AWS] ALB - Application Load Balancer (0) | 2023.01.13 |
---|---|
[AWS] Security Group (0) | 2023.01.10 |
[AWS] IAM - Identity and Access Management (0) | 2023.01.09 |