서브넷
Subnet
IP 네트워크를 논리적으로 분할하여 보안, 관리, 성능을 최적화하는 기술. CIDR 표기법으로 네트워크 범위를 정의하며, 클라우드 VPC 설계의 핵심 개념.
Subnet
IP 네트워크를 논리적으로 분할하여 보안, 관리, 성능을 최적화하는 기술. CIDR 표기법으로 네트워크 범위를 정의하며, 클라우드 VPC 설계의 핵심 개념.
서브넷(Subnet, Subnetwork)은 큰 IP 네트워크를 작은 논리적 단위로 분할하는 기술입니다. 마치 큰 건물을 층별로 나누어 관리하듯, 네트워크를 서브넷으로 나누면 트래픽 관리, 보안 정책 적용, IP 주소 효율화가 가능합니다. 1985년 RFC 950에서 처음 정의되었으며, 현대 클라우드 인프라의 핵심 개념입니다.
서브넷은 CIDR(Classless Inter-Domain Routing) 표기법으로 표현됩니다. 예를 들어 10.0.1.0/24는 10.0.1.0 ~ 10.0.1.255 범위의 256개 IP를 포함하며, 실제 사용 가능한 호스트는 254개입니다(네트워크 주소와 브로드캐스트 주소 제외). /24에서 숫자는 네트워크 부분의 비트 수를 의미하며, 숫자가 클수록 서브넷 크기가 작아집니다.
클라우드 환경에서 서브넷은 VPC(Virtual Private Cloud) 설계의 기본 단위입니다. AWS, GCP, Azure 모두 서브넷을 통해 리소스를 격리하고 네트워크 정책을 적용합니다. 예를 들어 AWS에서 VPC를 10.0.0.0/16으로 생성하면, 이를 10.0.1.0/24(Public), 10.0.2.0/24(Private), 10.0.3.0/24(Database) 등으로 나누어 계층적 보안을 구현합니다.
서브넷 설계는 확장성과 보안의 균형이 핵심입니다. 너무 작은 서브넷(/28, 16개 IP)은 리소스 확장에 제약이 있고, 너무 큰 서브넷(/16, 65,536개 IP)은 브로드캐스트 도메인이 커져 성능 저하를 유발할 수 있습니다. 실무에서는 /24(254개 호스트)를 기본 단위로 하고, 데이터베이스나 관리용 서브넷은 /27(30개 호스트)이나 /28(14개 호스트)로 구성하는 것이 일반적입니다.
| CIDR | 서브넷 마스크 | 총 IP 수 | 사용 가능 호스트 | 용도 |
|---|---|---|---|---|
| /16 | 255.255.0.0 | 65,536 | 65,534 | VPC 전체 범위 |
| /20 | 255.255.240.0 | 4,096 | 4,094 | 대규모 서브넷 |
| /24 | 255.255.255.0 | 256 | 254 | 표준 서브넷 (가장 일반적) |
| /27 | 255.255.255.224 | 32 | 30 | 소규모 서브넷 |
| /28 | 255.255.255.240 | 16 | 14 | 최소 단위 (AWS 최소) |
클라우드 환경에서 서브넷은 인터넷 접근 방식에 따라 Public Subnet과 Private Subnet으로 구분됩니다. 이 구분은 보안 아키텍처의 근간이며, 올바른 설계 없이는 보안 사고로 이어질 수 있습니다.
인터넷 게이트웨이(IGW)와 연결되어 외부에서 직접 접근 가능. 웹 서버, 로드 밸런서, Bastion Host(점프 서버) 배치. 라우팅 테이블에 0.0.0.0/0 → IGW 경로 설정됨.
NAT Gateway를 통해서만 아웃바운드 가능. 애플리케이션 서버, 데이터베이스, 내부 서비스 배치. 인바운드는 Public Subnet의 리소스를 통해서만 가능. 0.0.0.0/0 → NAT Gateway 경로 설정.
# AWS VPC 및 서브넷 구성 (Terraform)
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16" # 65,536개 IP
enable_dns_hostnames = true
enable_dns_support = true
tags = { Name = "production-vpc" }
}
# Public Subnet (웹 서버, 로드 밸런서용)
resource "aws_subnet" "public_a" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24" # 254개 호스트
availability_zone = "ap-northeast-2a"
map_public_ip_on_launch = true # 자동 Public IP 할당
tags = { Name = "public-subnet-a", Tier = "public" }
}
resource "aws_subnet" "public_b" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.2.0/24"
availability_zone = "ap-northeast-2b"
map_public_ip_on_launch = true
tags = { Name = "public-subnet-b", Tier = "public" }
}
# Private Subnet (애플리케이션 서버용)
resource "aws_subnet" "private_app_a" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.10.0/24"
availability_zone = "ap-northeast-2a"
tags = { Name = "private-app-subnet-a", Tier = "private" }
}
# Private Subnet (데이터베이스용 - 더 작은 크기)
resource "aws_subnet" "private_db_a" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.20.0/27" # 30개 호스트 (DB는 적게 필요)
availability_zone = "ap-northeast-2a"
tags = { Name = "private-db-subnet-a", Tier = "database" }
}
# Internet Gateway (Public Subnet용)
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = { Name = "main-igw" }
}
# NAT Gateway (Private Subnet의 아웃바운드용)
resource "aws_nat_gateway" "main" {
allocation_id = aws_eip.nat.id
subnet_id = aws_subnet.public_a.id
tags = { Name = "main-nat-gw" }
}
# Public Subnet 라우팅 테이블
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
tags = { Name = "public-rt" }
}
# Private Subnet 라우팅 테이블
resource "aws_route_table" "private" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.main.id
}
tags = { Name = "private-rt" }
}
import ipaddress
# CIDR 블록 분석
network = ipaddress.ip_network("10.0.0.0/24")
print(f"네트워크 주소: {network.network_address}") # 10.0.0.0
print(f"브로드캐스트: {network.broadcast_address}") # 10.0.0.255
print(f"서브넷 마스크: {network.netmask}") # 255.255.255.0
print(f"총 호스트 수: {network.num_addresses}") # 256
print(f"사용 가능: {network.num_addresses - 2}") # 254
# 사용 가능한 호스트 IP 나열
for ip in list(network.hosts())[:5]:
print(f"호스트 IP: {ip}")
# VPC를 서브넷으로 분할
vpc = ipaddress.ip_network("10.0.0.0/16")
subnets = list(vpc.subnets(new_prefix=24)) # /16을 /24로 분할
print(f"\n/16 VPC를 /24로 분할하면 {len(subnets)}개 서브넷")
print(f"첫 번째 서브넷: {subnets[0]}") # 10.0.0.0/24
print(f"마지막 서브넷: {subnets[-1]}") # 10.0.255.0/24
# IP가 서브넷에 속하는지 확인
def check_ip_in_subnet(ip: str, subnet: str) -> bool:
return ipaddress.ip_address(ip) in ipaddress.ip_network(subnet)
print(f"\n10.0.1.50은 10.0.1.0/24에 속함: {check_ip_in_subnet('10.0.1.50', '10.0.1.0/24')}")
print(f"10.0.2.50은 10.0.1.0/24에 속함: {check_ip_in_subnet('10.0.2.50', '10.0.1.0/24')}")
# AWS VPC 서브넷 설계 예시
def design_vpc_subnets(vpc_cidr: str) -> dict:
"""VPC CIDR를 받아 표준 3계층 서브넷 구조 반환"""
vpc = ipaddress.ip_network(vpc_cidr)
subnets_24 = list(vpc.subnets(new_prefix=24))
return {
"vpc": str(vpc),
"public_a": str(subnets_24[0]), # 10.0.0.0/24
"public_b": str(subnets_24[1]), # 10.0.1.0/24
"private_app_a": str(subnets_24[10]), # 10.0.10.0/24
"private_app_b": str(subnets_24[11]), # 10.0.11.0/24
"private_db_a": str(subnets_24[20]), # 10.0.20.0/24
"private_db_b": str(subnets_24[21]), # 10.0.21.0/24
}
design = design_vpc_subnets("10.0.0.0/16")
for name, cidr in design.items():
print(f"{name}: {cidr}")
# AWS CLI로 서브넷 조회 및 생성
# VPC 내 모든 서브넷 조회
aws ec2 describe-subnets \
--filters "Name=vpc-id,Values=vpc-12345678" \
--query "Subnets[*].{ID:SubnetId,CIDR:CidrBlock,AZ:AvailabilityZone,Public:MapPublicIpOnLaunch}" \
--output table
# Public Subnet 생성
aws ec2 create-subnet \
--vpc-id vpc-12345678 \
--cidr-block 10.0.1.0/24 \
--availability-zone ap-northeast-2a \
--tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=public-subnet-a}]'
# Private Subnet 생성
aws ec2 create-subnet \
--vpc-id vpc-12345678 \
--cidr-block 10.0.10.0/24 \
--availability-zone ap-northeast-2a \
--tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=private-subnet-a}]'
# 서브넷에 라우팅 테이블 연결
aws ec2 associate-route-table \
--subnet-id subnet-11111111 \
--route-table-id rtb-22222222
# 서브넷의 사용 가능한 IP 수 확인
aws ec2 describe-subnets \
--subnet-ids subnet-11111111 \
--query "Subnets[0].AvailableIpAddressCount"
# CIDR 계산 (ipcalc 사용)
ipcalc 10.0.0.0/24
# Network: 10.0.0.0/24
# Netmask: 255.255.255.0
# Broadcast: 10.0.0.255
# HostMin: 10.0.0.1
# HostMax: 10.0.0.254
# Hosts/Net: 254
# 서브넷 간 충돌 확인
ipcalc -c 10.0.1.0/24 10.0.2.0/24 # OK (충돌 없음)
ipcalc -c 10.0.1.0/24 10.0.1.128/25 # OVERLAP (충돌)
"VPC CIDR는 10.0.0.0/16으로 잡고, AZ별로 서브넷을 나눕시다. Public은 10.0.1.0/24, 10.0.2.0/24, Private App은 10.0.10.0/24, 10.0.11.0/24, DB는 /27로 작게 10.0.20.0/27, 10.0.20.32/27 이렇게요. 나중에 EKS 노드 그룹 확장을 고려하면 Private은 /20으로 크게 잡는 것도 고려해볼 만합니다."
"현재 모든 리소스가 Public Subnet에 있는데, 이건 심각한 보안 이슈입니다. RDS와 내부 서비스는 반드시 Private Subnet으로 옮기고, NAT Gateway 통해서만 외부 통신하도록 변경해야 합니다. Security Group만으로는 방어 계층이 부족해요."
"서브넷 IP가 거의 다 찼습니다. /24라서 254개인데 지금 240개 사용 중이에요. 새 서브넷 10.0.12.0/24를 만들어서 오토스케일링 그룹에 추가하고, 라우팅 테이블 연결해야 합니다. 다음부터는 처음부터 /20으로 여유있게 설계하는 게 좋겠습니다."
/28(14개 호스트)로 설계했다가 서비스 확장 시 IP 부족. AWS EKS는 노드당 IP를 많이 소모하므로 최소 /24, 권장 /20. 서브넷은 나중에 확장이 어려우니 처음부터 여유있게!
10.0.0.0/16 VPC에 10.0.0.0/24와 10.0.0.128/25를 모두 생성하면 중복 오류. 서브넷 CIDR는 VPC 내에서 겹치면 안 됩니다. 설계 전 ipcalc나 스프레드시트로 미리 검증하세요.
실수로 Private Subnet 라우팅 테이블에 Internet Gateway를 연결하면 Public Subnet이 되어버림. 라우팅 테이블 변경 시 반드시 서브넷 용도 재확인 필요.
VPC는 /16, 일반 서브넷은 /24, 확장 예상 서브넷은 /20, 소규모 전용은 /27 사용. AZ당 최소 2개 서브넷(Public/Private) 구성. CIDR는 10.x.0.0/16, 172.16-31.0.0/16, 192.168.0.0/16 중 선택.