🔄 NAT (Network Address Translation)
NAT가 필요한 이유
프라이빗 서브넷의 인스턴스가 인터넷에 접근해야 하지만(소프트웨어 업데이트 등), 인터넷에서 직접 접근당하면 안 되는 경우 NAT를 사용
┌────────────────────────────────────────┐
│ 인터넷 │
└──────────────┬─────────────────────────┘
│
▼
┌────────────────────────────────────────┐
│ 인터넷 게이트웨이 (IGW) │
└──────────────┬─────────────────────────┘
│
▼
┌────────────────────────────────────────┐
│ 퍼블릭 서브넷 (10.0.1.0/24) │
│ │
│ ┌──────────────────────┐ │
│ │ NAT Gateway │ │
│ │ - 공인 IP 보유 │ │
│ └──────────┬───────────┘ │
└────────────────┼────────────────────────┘
│
▼
┌────────────────────────────────────────┐
│ 프라이빗 서브넷 (10.0.2.0/24) │
│ │
│ EC2 인스턴스들 │
│ - 사설 IP만 보유 │
│ - NAT를 통해 아웃바운드만 가능 │
└────────────────────────────────────────┘
NAT Gateway
AWS 관리형 NAT 서비스로, 고가용성과 높은 대역폭을 제공
NAT Gateway 생성
# 1. Elastic IP 할당
EIP_ALLOC_ID=$(aws ec2 allocate-address \
--domain vpc \
--query 'AllocationId' \
--output text)
# 2. NAT Gateway 생성
NAT_GW_ID=$(aws ec2 create-nat-gateway \
--subnet-id subnet-12345678 \ # 퍼블릭 서브넷
--allocation-id $EIP_ALLOC_ID \
--tag-specifications 'ResourceType=natgateway,Tags=[{Key=Name,Value=MyNATGateway}]' \
--query 'NatGateway.NatGatewayId' \
--output text)
# 3. NAT Gateway 상태 확인 (available까지 대기)
aws ec2 describe-nat-gateways \
--nat-gateway-ids $NAT_GW_ID
# 4. 프라이빗 서브넷 라우팅 테이블에 라우트 추가
aws ec2 create-route \
--route-table-id rtb-private123 \
--destination-cidr-block 0.0.0.0/0 \
--nat-gateway-id $NAT_GW_ID
Python으로 NAT Gateway 구성
import boto3
import time
class NATGatewayManager:
def __init__(self, region='us-east-1'):
self.ec2 = boto3.client('ec2', region_name=region)
def create_nat_gateway(self, public_subnet_id, name='MyNATGateway'):
"""NAT Gateway 생성 및 설정"""
# 1. Elastic IP 할당
print("📍 Elastic IP 할당 중...")
eip = self.ec2.allocate_address(Domain='vpc')
allocation_id = eip['AllocationId']
public_ip = eip['PublicIp']
print(f"✅ Elastic IP 할당 완료: {public_ip}")
# 2. NAT Gateway 생성
print("\n🔄 NAT Gateway 생성 중...")
response = self.ec2.create_nat_gateway(
SubnetId=public_subnet_id,
AllocationId=allocation_id,
TagSpecifications=[{
'ResourceType': 'natgateway',
'Tags': [{'Key': 'Name', 'Value': name}]
}]
)
nat_gateway_id = response['NatGateway']['NatGatewayId']
print(f"NAT Gateway ID: {nat_gateway_id}")
# 3. Available 상태까지 대기
print("\n⏳ NAT Gateway 활성화 대기 중...")
while True:
status = self.ec2.describe_nat_gateways(
NatGatewayIds=[nat_gateway_id]
)
state = status['NatGateways'][0]['State']
print(f"현재 상태: {state}")
if state == 'available':
print("✅ NAT Gateway 활성화 완료!")
break
elif state == 'failed':
print("❌ NAT Gateway 생성 실패")
return None
time.sleep(10)
return {
'nat_gateway_id': nat_gateway_id,
'allocation_id': allocation_id,
'public_ip': public_ip
}
def configure_private_route_table(self, route_table_id, nat_gateway_id):
"""프라이빗 라우팅 테이블 구성"""
print(f"\n🛣️ 라우팅 테이블 {route_table_id} 구성 중...")
self.ec2.create_route(
RouteTableId=route_table_id,
DestinationCidrBlock='0.0.0.0/0',
NatGatewayId=nat_gateway_id
)
print("✅ NAT Gateway 라우트 추가 완료")
def setup_high_availability_nat(self, public_subnets, private_route_tables):
"""고가용성 NAT Gateway 구성 (AZ별)"""
nat_gateways = []
for i, (public_subnet, private_rt) in enumerate(
zip(public_subnets, private_route_tables), 1
):
print(f"\n{'='*50}")
print(f"NAT Gateway {i} 구성 시작")
print(f"{'='*50}")
nat_gw = self.create_nat_gateway(
public_subnet,
name=f'NAT-Gateway-{i}'
)
if nat_gw:
self.configure_private_route_table(
private_rt,
nat_gw['nat_gateway_id']
)
nat_gateways.append(nat_gw)
return nat_gateways
# 사용 예제
if __name__ == '__main__':
manager = NATGatewayManager()
# 단일 AZ NAT Gateway
nat_gw = manager.create_nat_gateway('subnet-public123')
manager.configure_private_route_table('rtb-private123', nat_gw['nat_gateway_id'])
# 다중 AZ NAT Gateway (고가용성)
public_subnets = ['subnet-pub-1a', 'subnet-pub-1b']
private_rts = ['rtb-private-1a', 'rtb-private-1b']
nat_gateways = manager.setup_high_availability_nat(
public_subnets,
private_rts
)
NAT Gateway vs NAT Instance
특성 | NAT Gateway | NAT Instance |
가용성 | AZ 내 고가용성 | 단일 장애점 |
대역폭 | 최대 100 Gbps | 인스턴스 타입에 따름 |
유지보수 | AWS 관리 | 사용자 관리 |
비용 | $0.045/시간 + 데이터 | EC2 비용 |
보안 그룹 | 불가 | 가능 |
Bastion 호스트 | 불가 | 가능 |
권장 사용 | 프로덕션 | 개발/테스트 |
NAT Instance 구성 (비용 절감용)
# NAT Instance 설정
# 1. Amazon Linux AMI로 EC2 시작
# 2. 소스/대상 확인 비활성화
aws ec2 modify-instance-attribute \
--instance-id i-1234567890abcdef0 \
--no-source-dest-check
# 3. 인스턴스에서 IP 포워딩 활성화 (SSH 접속 후)
sudo sysctl -w net.ipv4.ip_forward=1
sudo /sbin/iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
# 4. 재부팅 시에도 유지되도록 설정
echo "net.ipv4.ip_forward = 1" | sudo tee -a /etc/sysctl.conf
sudo service iptables save
🔐 VPN 연결
AWS Site-to-Site VPN
온프레미스 네트워크와 AWS VPC를 안전하게 연결
┌────────────────────────────────────┐ │ 온프레미스 데이터센터 │ │ │ │ ┌──────────────────────┐ │ │ │ Customer Gateway │ │ │ │ (물리적 또는 SW) │ │ │ └──────────┬───────────┘ │ └─────────────┼────────────────────────┘ │ IPsec Tunnel │ (암호화된 연결) │ ┌─────────────┼────────────────────────┐ │ AWS VPC ▼ │ │ ┌──────────────────────┐ │ │ │ Virtual Private │ │ │ │ Gateway (VGW) │ │ │ └──────────┬───────────┘ │ │ │ │ │ ┌──────────▼───────────┐ │ │ │ 프라이빗 서브넷 │ │ │ │ - EC2, RDS 등 │ │ │ └──────────────────────┘ │ └────────────────────────────────────┘
Site-to-Site VPN 구성
# 1. Virtual Private Gateway 생성
VGW_ID=$(aws ec2 create-vpn-gateway \
--type ipsec.1 \
--amazon-side-asn 64512 \
--tag-specifications 'ResourceType=vpn-gateway,Tags=[{Key=Name,Value=MyVGW}]' \
--query 'VpnGateway.VpnGatewayId' \
--output text)
# 2. VPC에 연결
aws ec2 attach-vpn-gateway \
--vpn-gateway-id $VGW_ID \
--vpc-id vpc-12345678
# 3. Customer Gateway 생성
CGW_ID=$(aws ec2 create-customer-gateway \
--type ipsec.1 \
--public-ip 203.0.113.10 \ # 온프레미스 공인 IP
--bgp-asn 65000 \
--tag-specifications 'ResourceType=customer-gateway,Tags=[{Key=Name,Value=MyCGW}]' \
--query 'CustomerGateway.CustomerGatewayId' \
--output text)
# 4. VPN Connection 생성
VPN_CONN_ID=$(aws ec2 create-vpn-connection \
--type ipsec.1 \
--customer-gateway-id $CGW_ID \
--vpn-gateway-id $VGW_ID \
--options TunnelOptions='[{TunnelInsideCidr=169.254.10.0/30},{TunnelInsideCidr=169.254.11.0/30}]' \
--tag-specifications 'ResourceType=vpn-connection,Tags=[{Key=Name,Value=MyVPN}]' \
--query 'VpnConnection.VpnConnectionId' \
--output text)
# 5. VPN 구성 다운로드
aws ec2 describe-vpn-connections \
--vpn-connection-ids $VPN_CONN_ID \
--query 'VpnConnections[0].CustomerGatewayConfiguration' \
--output text > vpn-config.txt
# 6. 라우트 전파 활성화
aws ec2 enable-vgw-route-propagation \
--route-table-id rtb-12345678 \
--gateway-id $VGW_ID
AWS Client VPN
개별 사용자가 VPC에 안전하게 접근 가능하게 한다
import boto3
class ClientVPNManager:
def __init__(self):
self.ec2 = boto3.client('ec2')
self.acm = boto3.client('acm')
def create_client_vpn_endpoint(
self,
server_cert_arn,
client_cert_arn,
vpc_id,
subnet_ids,
client_cidr='10.100.0.0/16'
):
"""Client VPN Endpoint 생성"""
response = self.ec2.create_client_vpn_endpoint(
ClientCidrBlock=client_cidr,
ServerCertificateArn=server_cert_arn,
AuthenticationOptions=[{
'Type': 'certificate-authentication',
'MutualAuthentication': {
'ClientRootCertificateChainArn': client_cert_arn
}
}],
ConnectionLogOptions={
'Enabled': True,
'CloudwatchLogGroup': '/aws/clientvpn',
'CloudwatchLogStream': 'connection-log'
},
VpcId=vpc_id,
SecurityGroupIds=[], # 보안 그룹 추가
TagSpecifications=[{
'ResourceType': 'client-vpn-endpoint',
'Tags': [{'Key': 'Name', 'Value': 'My-Client-VPN'}]
}]
)
endpoint_id = response['ClientVpnEndpointId']
# 서브넷 연결
for subnet_id in subnet_ids:
self.ec2.associate_client_vpn_target_network(
ClientVpnEndpointId=endpoint_id,
SubnetId=subnet_id
)
# 권한 부여 규칙
self.ec2.authorize_client_vpn_ingress(
ClientVpnEndpointId=endpoint_id,
TargetNetworkCidr='0.0.0.0/0',
AuthorizeAllGroups=True
)
return endpoint_id
# 사용 예제
vpn_manager = ClientVPNManager()
endpoint_id = vpn_manager.create_client_vpn_endpoint(
server_cert_arn='arn:aws:acm:region:account:certificate/xxx',
client_cert_arn='arn:aws:acm:region:account:certificate/yyy',
vpc_id='vpc-12345678',
subnet_ids=['subnet-123', 'subnet-456']
)
🔗 VPC Peering
두 VPC 간의 프라이빗 네트워크 연결을 생성합니다.
VPC Peering 개념
VPC A (10.0.0.0/16) VPC B (10.1.0.0/16)
┌──────────────────┐ ┌──────────────────┐
│ │ │ │
│ EC2 Instances │◄──────►│ EC2 Instances │
│ │ Peer │ │
│ 10.0.1.5 │Connection 10.1.1.10 │
│ │ │ │
└──────────────────┘ └──────────────────┘
특징:
- 프라이빗 IP로 통신
- 리전 간 가능
- 전이적 피어링 불가 (A-B, B-C ≠ A-C)
- CIDR 중복 불가
VPC Peering 구성
# 1. Peering Connection 생성
PEER_CONN_ID=$(aws ec2 create-vpc-peering-connection \
--vpc-id vpc-11111111 \ # Requester VPC
--peer-vpc-id vpc-22222222 \ # Accepter VPC
--peer-region us-west-2 \ # 다른 리전인 경우
--tag-specifications 'ResourceType=vpc-peering-connection,Tags=[{Key=Name,Value=VPC-A-to-B}]' \
--query 'VpcPeeringConnection.VpcPeeringConnectionId' \
--output text)
# 2. Peering 수락 (Accepter VPC 계정에서)
aws ec2 accept-vpc-peering-connection \
--vpc-peering-connection-id $PEER_CONN_ID \
--region us-west-2
# 3. Requester VPC 라우팅 테이블 업데이트
aws ec2 create-route \
--route-table-id rtb-requester \
--destination-cidr-block 10.1.0.0/16 \
--vpc-peering-connection-id $PEER_CONN_ID
# 4. Accepter VPC 라우팅 테이블 업데이트
aws ec2 create-route \
--route-table-id rtb-accepter \
--destination-cidr-block 10.0.0.0/16 \
--vpc-peering-connection-id $PEER_CONN_ID \
--region us-west-2
VPC Peering 자동화
class VPCPeeringManager:
def __init__(self, region='us-east-1'):
self.ec2 = boto3.client('ec2', region_name=region)
def create_peering(self, vpc_a_id, vpc_b_id, vpc_b_region=None):
"""VPC Peering Connection 생성"""
params = {
'VpcId': vpc_a_id,
'PeerVpcId': vpc_b_id,
'TagSpecifications': [{
'ResourceType': 'vpc-peering-connection',
'Tags': [{'Key': 'Name', 'Value': f'{vpc_a_id}-to-{vpc_b_id}'}]
}]
}
if vpc_b_region:
params['PeerRegion'] = vpc_b_region
response = self.ec2.create_vpc_peering_connection(**params)
pcx_id = response['VpcPeeringConnection']['VpcPeeringConnectionId']
print(f"✅ Peering Connection 생성: {pcx_id}")
# 상태 확인
waiter = self.ec2.get_waiter('vpc_peering_connection_exists')
waiter.wait(VpcPeeringConnectionIds=[pcx_id])
return pcx_id
def accept_peering(self, pcx_id, region=None):
"""Peering Connection 수락"""
ec2_accepter = boto3.client('ec2', region_name=region or 'us-east-1')
ec2_accepter.accept_vpc_peering_connection(
VpcPeeringConnectionId=pcx_id
)
print(f"✅ Peering Connection 수락: {pcx_id}")
def update_route_tables(self, pcx_id, route_table_id, destination_cidr):
"""라우팅 테이블 업데이트"""
self.ec2.create_route(
RouteTableId=route_table_id,
DestinationCidrBlock=destination_cidr,
VpcPeeringConnectionId=pcx_id
)
print(f"✅ 라우트 추가: {destination_cidr} → {pcx_id}")
def setup_bidirectional_peering(
self,
vpc_a_id,
vpc_b_id,
vpc_a_rt,
vpc_b_rt,
vpc_a_cidr,
vpc_b_cidr
):
"""양방향 Peering 설정"""
# Peering 생성 및 수락
pcx_id = self.create_peering(vpc_a_id, vpc_b_id)
self.accept_peering(pcx_id)
# 양방향 라우트 추가
self.update_route_tables(vpc_a_rt, vpc_b_cidr, pcx_id)
self.update_route_tables(vpc_b_rt, vpc_a_cidr, pcx_id)
print("\n🎉 양방향 Peering 설정 완료!")
return pcx_id
# 사용 예제
peering_mgr = VPCPeeringManager()
peering_mgr.setup_bidirectional_peering(
vpc_a_id='vpc-11111111',
vpc_b_id='vpc-22222222',
vpc_a_rt='rtb-aaa',
vpc_b_rt='rtb-bbb',
vpc_a_cidr='10.0.0.0/16',
vpc_b_cidr='10.1.0.0/16'
)
🌐 Transit Gateway
여러 VPC와 온프레미스 네트워크를 중앙 집중식으로 연결
Transit Gateway vs VPC Peering
VPC Peering (Mesh 토폴로지):
VPC-A ←→ VPC-B
↕ ↕
VPC-C ←→ VPC-D
문제: N개 VPC 연결 시 N(N-1)/2개의 Peering 필요
Transit Gateway (Hub-Spoke 토폴로지):
VPC-A
↓
VPC-C → [TGW] ← VPC-B
↑
VPC-D
장점: N개 VPC 연결 시 N개의 Attachment만 필요
Transit Gateway 구성
# 1. Transit Gateway 생성
TGW_ID=$(aws ec2 create-transit-gateway \
--description "Central Transit Gateway" \
--options AmazonSideAsn=64512,AutoAcceptSharedAttachments=enable,DefaultRouteTableAssociation=enable,DefaultRouteTablePropagation=enable \
--tag-specifications 'ResourceType=transit-gateway,Tags=[{Key=Name,Value=Central-TGW}]' \
--query 'TransitGateway.TransitGatewayId' \
--output text)
# 2. VPC Attachment 생성
TGW_ATTACH=$(aws ec2 create-transit-gateway-vpc-attachment \
--transit-gateway-id $TGW_ID \
--vpc-id vpc-12345678 \
--subnet-ids subnet-123 subnet-456 \
--tag-specifications 'ResourceType=transit-gateway-attachment,Tags=[{Key=Name,Value=VPC-A-Attachment}]' \
--query 'TransitGatewayVpcAttachment.TransitGatewayAttachmentId' \
--output text)
# 3. VPC 라우팅 테이블 업데이트
aws ec2 create-route \
--route-table-id rtb-12345678 \
--destination-cidr-block 10.0.0.0/8 \
--transit-gateway-id $TGW_ID
# 4. Transit Gateway 라우팅 테이블 조회
aws ec2 describe-transit-gateway-route-tables \
--filters "Name=transit-gateway-id,Values=$TGW_ID"
📍 VPC Endpoints
AWS 서비스에 프라이빗하게 접근할 수 있게 한다
Interface Endpoint vs Gateway Endpoint
특성 | Interface Endpoint | Gateway Endpoint |
기술 | PrivateLink (ENI) | 라우팅 테이블 |
지원 서비스 | 대부분의 AWS 서비스 | S3, DynamoDB만 |
비용 | $0.01/시간 + 데이터 | 무료 |
AZ | AZ별 배치 가능 | VPC 전체 |
보안 그룹 | 적용 가능 | 불가 |
Gateway Endpoint 생성 (S3, DynamoDB)
# S3 Gateway Endpoint 생성
aws ec2 create-vpc-endpoint \
--vpc-id vpc-12345678 \
--service-name com.amazonaws.us-east-1.s3 \
--route-table-ids rtb-123 rtb-456 \
--policy-document '{
"Statement": [{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:*",
"Resource": "*"
}]
}'
# DynamoDB Gateway Endpoint 생성
aws ec2 create-vpc-endpoint \
--vpc-id vpc-12345678 \
--service-name com.amazonaws.us-east-1.dynamodb \
--route-table-ids rtb-123 rtb-456
Interface Endpoint 생성
class VPCEndpointManager:
def __init__(self, region='us-east-1'):
self.ec2 = boto3.client('ec2', region_name=region)
self.region = region
def create_interface_endpoint(
self,
service_name,
vpc_id,
subnet_ids,
security_group_ids,
endpoint_name
):
"""Interface Endpoint 생성"""
full_service_name = f'com.amazonaws.{self.region}.{service_name}'
response = self.ec2.create_vpc_endpoint(
VpcEndpointType='Interface',
ServiceName=full_service_name,
VpcId=vpc_id,
SubnetIds=subnet_ids,
SecurityGroupIds=security_group_ids,
PrivateDnsEnabled=True,
TagSpecifications=[{
'ResourceType': 'vpc-endpoint',
'Tags': [{'Key': 'Name', 'Value': endpoint_name}]
}]
)
endpoint_id = response['VpcEndpoint']['VpcEndpointId']
print(f"✅ Interface Endpoint 생성: {endpoint_id}")
print(f" 서비스: {full_service_name}")
return endpoint_id
def create_gateway_endpoint(
self,
service_name,
vpc_id,
route_table_ids,
endpoint_name
):
"""Gateway Endpoint 생성"""
full_service_name = f'com.amazonaws.{self.region}.{service_name}'
response = self.ec2.create_vpc_endpoint(
VpcEndpointType='Gateway',
ServiceName=full_service_name,
VpcId=vpc_id,
RouteTableIds=route_table_ids,
TagSpecifications=[{
'ResourceType': 'vpc-endpoint',
'Tags': [{'Key': 'Name', 'Value': endpoint_name}]
}]
)
endpoint_id = response['VpcEndpoint']['VpcEndpointId']
print(f"✅ Gateway Endpoint 생성: {endpoint_id}")
return endpoint_id
def setup_common_endpoints(
self,
vpc_id,
private_subnet_ids,
route_table_ids,
security_group_id
):
"""일반적으로 필요한 Endpoints 생성"""
endpoints = {}
# Gateway Endpoints (무료)
print("🌐 Gateway Endpoints 생성 중...")
endpoints['s3'] = self.create_gateway_endpoint(
's3', vpc_id, route_table_ids, 'S3-Endpoint'
)
def setup_common_endpoints(
self,
vpc_id,
private_subnet_ids,
route_table_ids,
security_group_id
):
"""일반적으로 필요한 Endpoints 생성"""
endpoints = {}
# Gateway Endpoints (무료)
print("🌐 Gateway Endpoints 생성 중...")
endpoints['s3'] = self.create_gateway_endpoint(
's3', vpc_id, route_table_ids, 'S3-Endpoint'
)
endpoints['dynamodb'] = self.create_gateway_endpoint(
'dynamodb', vpc_id, route_table_ids, 'DynamoDB-Endpoint'
)
# Interface Endpoints (유료)
print("\n🔌 Interface Endpoints 생성 중...")
interface_services = [
'ec2',
'ssm',
'ssmmessages',
'ec2messages',
'logs',
'secretsmanager'
]
for service in interface_services:
try:
endpoint_id = self.create_interface_endpoint(
service,
vpc_id,
private_subnet_ids,
[security_group_id],
f'{service.upper()}-Endpoint'
)
endpoints[service] = endpoint_id
except Exception as e:
print(f"⚠️ {service} Endpoint 생성 실패: {e}")
print(f"\n✅ 총 {len(endpoints)}개 Endpoint 생성 완료")
return endpoints사용 예제
### VPC Endpoint 활용 예제
```python
# VPC Endpoint를 통한 S3 접근
import boto3
# Endpoint를 통해 S3 접근 (인터넷 게이트웨이 불필요)
s3 = boto3.client('s3')
# 프라이빗 서브넷에서 실행해도 작동
response = s3.list_buckets()
# VPC Endpoint 정책으로 특정 버킷만 접근 허용 가능
endpoint_policy = {
"Statement": [{
"Effect": "Allow",
"Principal": "*",
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": [
"arn:aws:s3:::my-company-bucket/*"
]
}]
}
🛡️ Network Access Control List (NACL)
NACL vs Security Group
특성 | NACL | Security Group |
작동 레벨 | 서브넷 레벨 | 인스턴스 레벨 (ENI) |
상태 | Stateless | Stateful |
규칙 | 허용 & 거부 | 허용만 |
규칙 평가 | 번호 순서대로 | 모든 규칙 평가 |
적용 | 자동 (서브넷) | 명시적 연결 |
기본 동작 | 모두 허용 | 모두 거부 |
NACL 구성
# 1. NACL 생성
NACL_ID=$(aws ec2 create-network-acl \
--vpc-id vpc-12345678 \
--tag-specifications 'ResourceType=network-acl,Tags=[{Key=Name,Value=Custom-NACL}]' \
--query 'NetworkAcl.NetworkAclId' \
--output text)
# 2. Inbound 규칙 추가
# 규칙 100: HTTP 허용
aws ec2 create-network-acl-entry \
--network-acl-id $NACL_ID \
--rule-number 100 \
--protocol tcp \
--port-range From=80,To=80 \
--cidr-block 0.0.0.0/0 \
--rule-action allow \
--ingress
# 규칙 110: HTTPS 허용
aws ec2 create-network-acl-entry \
--network-acl-id $NACL_ID \
--rule-number 110 \
--protocol tcp \
--port-range From=443,To=443 \
--cidr-block 0.0.0.0/0 \
--rule-action allow \
--ingress
# 규칙 120: SSH 허용 (특정 IP만)
aws ec2 create-network-acl-entry \
--network-acl-id $NACL_ID \
--rule-number 120 \
--protocol tcp \
--port-range From=22,To=22 \
--cidr-block 203.0.113.0/24 \
--rule-action allow \
--ingress
# 규칙 130: Ephemeral Ports 허용
aws ec2 create-network-acl-entry \
--network-acl-id $NACL_ID \
--rule-number 130 \
--protocol tcp \
--port-range From=1024,To=65535 \
--cidr-block 0.0.0.0/0 \
--rule-action allow \
--ingress
# 3. Outbound 규칙 추가
aws ec2 create-network-acl-entry \
--network-acl-id $NACL_ID \
--rule-number 100 \
--protocol -1 \
--cidr-block 0.0.0.0/0 \
--rule-action allow \
--egress
# 4. 서브넷 연결
aws ec2 replace-network-acl-association \
--association-id aclassoc-12345678 \
--network-acl-id $NACL_ID
NACL 설계 모범 사례
class NACLManager:
def __init__(self):
self.ec2 = boto3.client('ec2')
def create_web_tier_nacl(self, vpc_id, subnet_id):
"""웹 계층용 NACL 생성"""
# NACL 생성
nacl = self.ec2.create_network_acl(
VpcId=vpc_id,
TagSpecifications=[{
'ResourceType': 'network-acl',
'Tags': [{'Key': 'Name', 'Value': 'Web-Tier-NACL'}]
}]
)
nacl_id = nacl['NetworkAcl']['NetworkAclId']
# Inbound 규칙
inbound_rules = [
(100, 80, 80, '0.0.0.0/0', 'HTTP from Internet'),
(110, 443, 443, '0.0.0.0/0', 'HTTPS from Internet'),
(120, 22, 22, '10.0.0.0/16', 'SSH from VPC'),
(130, 1024, 65535, '0.0.0.0/0', 'Return traffic')
]
for rule_num, from_port, to_port, cidr, desc in inbound_rules:
self.ec2.create_network_acl_entry(
NetworkAclId=nacl_id,
RuleNumber=rule_num,
Protocol='6', # TCP
PortRange={'From': from_port, 'To': to_port},
CidrBlock=cidr,
RuleAction='allow',
Ingress=True
)
print(f"✅ Inbound 규칙 {rule_num}: {desc}")
# Outbound 규칙
outbound_rules = [
(100, 80, 80, '0.0.0.0/0', 'HTTP to Internet'),
(110, 443, 443, '0.0.0.0/0', 'HTTPS to Internet'),
(120, 3306, 3306, '10.0.2.0/24', 'MySQL to DB tier'),
(130, 1024, 65535, '0.0.0.0/0', 'Return traffic')
]
for rule_num, from_port, to_port, cidr, desc in outbound_rules:
self.ec2.create_network_acl_entry(
NetworkAclId=nacl_id,
RuleNumber=rule_num,
Protocol='6',
PortRange={'From': from_port, 'To': to_port},
CidrBlock=cidr,
RuleAction='allow',
Egress=True
)
print(f"✅ Outbound 규칙 {rule_num}: {desc}")
# 서브넷 연결
associations = self.ec2.describe_network_acls(
Filters=[
{'Name': 'association.subnet-id', 'Values': [subnet_id]}
]
)
for assoc in associations['NetworkAcls'][0]['Associations']:
if assoc['SubnetId'] == subnet_id:
self.ec2.replace_network_acl_association(
AssociationId=assoc['NetworkAclAssociationId'],
NetworkAclId=nacl_id
)
break
print(f"\n✅ NACL 생성 및 연결 완료: {nacl_id}")
return nacl_id
def create_database_tier_nacl(self, vpc_id, subnet_id, app_tier_cidr):
"""데이터베이스 계층용 NACL 생성"""
nacl = self.ec2.create_network_acl(
VpcId=vpc_id,
TagSpecifications=[{
'ResourceType': 'network-acl',
'Tags': [{'Key': 'Name', 'Value': 'DB-Tier-NACL'}]
}]
)
nacl_id = nacl['NetworkAcl']['NetworkAclId']
# Inbound: 애플리케이션 계층에서만 접근 허용
self.ec2.create_network_acl_entry(
NetworkAclId=nacl_id,
RuleNumber=100,
Protocol='6',
PortRange={'From': 3306, 'To': 3306},
CidrBlock=app_tier_cidr,
RuleAction='allow',
Ingress=True
)
# Ephemeral ports
self.ec2.create_network_acl_entry(
NetworkAclId=nacl_id,
RuleNumber=110,
Protocol='6',
PortRange={'From': 1024, 'To': 65535},
CidrBlock=app_tier_cidr,
RuleAction='allow',
Ingress=True
)
# Outbound: 애플리케이션 계층으로만 응답
self.ec2.create_network_acl_entry(
NetworkAclId=nacl_id,
RuleNumber=100,
Protocol='6',
PortRange={'From': 1024, 'To': 65535},
CidrBlock=app_tier_cidr,
RuleAction='allow',
Egress=True
)
print(f"✅ DB NACL 생성 완료: {nacl_id}")
return nacl_id
# 사용 예제
nacl_mgr = NACLManager()
web_nacl = nacl_mgr.create_web_tier_nacl('vpc-12345678', 'subnet-web123')
db_nacl = nacl_mgr.create_database_tier_nacl(
'vpc-12345678',
'subnet-db123',
'10.0.2.0/24' # 애플리케이션 계층 CIDR
)
🎓 Part 2
- Q: NAT Gateway와 NAT Instance의 주요 차이점은?
- A: NAT Gateway는 AWS 관리형(고가용성), NAT Instance는 사용자 관리형(단일 장애점)
- Q: VPC Peering에서 전이적 피어링이 불가능한 이유는?
- A: 보안과 비용 관리를 위해 명시적 연결만 허용
- Q: Transit Gateway를 사용해야 하는 경우는?
- A: 3개 이상의 VPC 연결, 온프레미스 연결, 중앙 집중식 관리가 필요한 경우
- Q: Gateway Endpoint와 Interface Endpoint의 비용 차이는?
- A: Gateway Endpoint는 무료, Interface Endpoint는 시간당 + 데이터 처리 비용
- Q: NACL과 Security Group 중 먼저 평가되는 것은?
- A: NACL (서브넷 레벨이 인스턴스 레벨보다 먼저)
📋 체크리스트
NAT 구성
고가용성을 위한 AZ별 NAT Gateway 배치
NAT Gateway용 Elastic IP 할당
프라이빗 서브넷 라우팅 테이블 업데이트
비용 최적화 검토 (NAT Instance 고려)
VPN 연결
VPN 타입 결정 (Site-to-Site vs Client VPN)
Virtual Private Gateway 생성
Customer Gateway 구성
VPN 터널 이중화
라우트 전파 활성화
VPC Peering
CIDR 블록 중복 확인
양방향 라우팅 설정
Security Group 규칙 업데이트
리전 간 레이턴시 고려
VPC Endpoints
S3, DynamoDB용 Gateway Endpoint 생성
필수 서비스용 Interface Endpoint 생성
Endpoint 보안 그룹 구성
Private DNS 활성화
NACL
계층별 NACL 설계
Ephemeral Ports 허용
규칙 번호 체계 정의
Stateless 특성 이해
Share article