AWS Transit Gateway (TGW) 란?
AWS Transit Gateway는 클라우드 네트워킹 서비스로, 다수의 가상 프라이빗 클라우드(VPC)와 온 프레미스 네트워크를 연결하여 중앙에서 효율적으로 관리할 수 있도록 도와줍니다. TGW는 이러한 네트워크 간의 트래픽 라우팅을 담당하며, 중앙 집중식 출구 트래픽 관리를 통해 보안을 강화하고 비용을 절감하는 데에 도움을 줍니다.
AWS Transit Gateway 기능
1) 중앙 집중식 연결
VPC 및 VPN 연결을 단일 게이트웨이에 연결하여 네트워크 구성을 단순화합니다. 이를 통해 각 VPC 또는 VPN 연결에 대해 개별적으로 피어링 연결을 설정할 필요가 없습니다.
2) 라우팅
라우팅 테이블을 사용하여 네트워크 트래픽을 라우팅합니다. 라우팅 테이블은 트래픽이 어디에서 오는지와 어디로 가는지에 따라 트래픽을 어떻게 라우팅할지 결정합니다. 또한, 세분화된 네트워크 규칙을 적용할 수 있습니다.
3) 스케일링
수천 개의 VPC 및 VPN 연결을 지원하므로, 대규모 네트워크 환경에서도 확장성을 유지할 수 있습니다.
4) 고가용성
여러 가용 영역에 걸쳐 설계되어 있어, 하나의 가용 영역에서 문제가 발생하더라도 다른 가용 영역에서 계속해서 서비스를 제공하며, 자동 장애 복구 기능을 갖추고 있어 신속한 복구가 가능합니다.
5) 보안
네트워크 트래픽을 중앙에서 관리하므로, 네트워크 트래픽을 보다 효과적으로 모니터링하고, 필요한 보안 정책을 적용할 수 있습니다.
중앙 집중식 Egress 트래픽 관리를 통한 네트워크 비용 절감과 보안 강화
각 VPC에 대해 별도의 NAT 게이트웨이를 사용하는(See Figure 1) 대신, 하나의 NAT 게이트웨이를 여러 VPC와 연결된 TGW와 공유하여 비용을 절감(See Figure 2)할 수 있습니다.
A. 보안 강화
1) AWS Network Firewall
네트워크 방화벽 및 침입 탐지 및 방지 서비스로, 출구 트래픽의 중앙 검사 지점으로 사용됩니다. 이 방화벽은 특정 도메인에만 트래픽을 허용하는 상태를 유지하는 규칙을 지원합니다.
2) Amazon Route 53 Resolver DNS Firewall
특정 도메인 이름으로의 출구 트래픽을 제한할 수 있습니다. 주로 데이터의 무단 외부 유출을 방지하기 위해 적용됩니다.
DNS Firewall 규칙에서는 특정 도메인에 대한 액세스를 허용하거나 거부하는 도메인 목록을 적용할 수 있습니다. 이러한 도메인 목록에는 악의적 활동이나 기타 잠재적 위협과 연관된 도메인 네임이 포함될 수 있습니다.
이러한 방식을 사용하면, 모든 네트워크 통신이 시작되기 전에 DNS 요청이 Route 53 Resolver로 전송되어 IP 주소가 허용되거나 거부됩니다. 인터넷으로 향하는 트래픽은 TGW로 라우팅됩니다.
B. 비용 절감
1) NAT 게이트웨이의 비용 문제
AWS NAT GW는 VPC의 Private Subnet에서 인터넷에 연결하는 데 필요한 서비스로, 스케일링이 될 수록 비용이 상당히 누적될 수 있습니다.
예를 들어, 하나의 NAT GW는 한국 리전 기준으로 $0.059/시간이며, 이는 3개의 가용 영역(AZ)에 배포될 경우 시간당 $0.177/VPC로 누적됩니다. 이를 개발, 테스트, 프로덕션의 3단계에 걸쳐 적용한다고 하면, 시간당 $0.531/애플리케이션의 비용이 발생하며, 이는 100개의 애플리케이션이 구동 될 경우 시간당 $53.1로 누적됩니다. 이 경우, 연간 네트워크 비용이 $465,156에 이릅니다.
2) TGW 활용 비용 절감
Transit Gateway를 활용하여 Egress 중앙화를 구성할 경우, TGW에 연결하는 VPC 수에 의해 비용이 청구되며, AZ나 서브넷의 수는 비용 산정에 포함되지 않습니다. 이 경우, NAT GW 비용이 $0.059/NAT/시간 * 3 AZs * VPCs에서 $0.059/NAT/시간 * 3 AZs + $0.05/TGW 첨부/시간 * (VPCs + 1)로 감소하게 됩니다. 이렇게 하면 예를 들어 300개의 VPC에 대해 연간 $374,424.3을 절약할 수 있습니다.
Parity Equation
두 방식의 비용이 동일하게 발생하는 VPC의 수를 찾기 위해 다음과 같은 방정식을 풀 수 있습니다:
NAT GW 만으로 구성된 방식과 TGW로 구성된 방식의 비용(Seoul 리전 기준)을 통해 Parity Point를 도출하고자 할 때,
만약 AZ가 3개라면, 위와 같은 Parity가 도출 될 수 있습니다.
따라서, 2개 이상의 VPC가 있다면 TGW를 통한 Egress 중앙화에 의한 네트워크 비용 감축이 발생함을 알 수 있습니다.
Use Case
- AWS Transit Gateway를 이용한 Egress VPC 패턴 구현을 위한 Python CDK 스택
1. 사전 준비
- AWS CLI를 설치하고 IAM 사용자나 AWS STS 보안 토큰을 사용하여 인증합니다.
- Python과 pip를 설치합니다. Python 3.6 이상 버전이 필요합니다.
- AWS CDK를 설치합니다. Python에서는 `pip install aws-cdk` 명령어를 사용하여 설치할 수 있습니다.
2. 환경 설정 및 CDK 프로젝트 생성
- 새로운 CDK 프로젝트를 생성합니다. 예를 들어, `cdk init app --language python` 명령어를 사용할 수 있습니다.
- 프로젝트 폴더에서 의존성 패키지들을 설치합니다.
3. CDK 애플리케이션 코드 작성
- egress VPC와 private VPC를 포함하는 CDK 스택을 Python으로 작성합니다. 전체 코드는 아래와 같습니다.
import aws_cdk as core
import aws_cdk.aws_ec2 as ec2
import aws_cdk.aws_iam as iam
class EgressVpcTgDemoStack(core.Stack):
def __init__(self, scope: core.Construct, id: str, props=None):
super().__init__(scope, id, props)
# set-up egress and private VPCs
egress_vpc = ec2.Vpc(self, 'EgressVPC',
cidr="10.0.1.0/26",
subnet_configuration=[
ec2.SubnetConfiguration(
cidr_mask=28,
name='PublicEgressVPCSubNet',
subnet_type=ec2.SubnetType.PUBLIC),
ec2.SubnetConfiguration(
cidr_mask=28,
name='PrivateEgressVPCSubNet',
subnet_type=ec2.SubnetType.PRIVATE)
])
private_vpc = ec2.Vpc(self, 'PrivateVPC',
cidr="10.0.2.0/26",
max_azs=1,
enable_dns_hostnames=True,
enable_dns_support=True,
subnet_configuration=[
ec2.SubnetConfiguration(
cidr_mask=28,
name='IsolatedSubnetPrivateVPC',
subnet_type=ec2.SubnetType.ISOLATED)
])
# Security Group for EC2 instances in isolated subnet accessed via Systems Manager
ssm_private_sg = ec2.SecurityGroup(self, 'SSMPrivateSecurityGroup',
vpc=private_vpc,
security_group_name='DemoEC2InstanceSecurityGroup',
description='Demo EC2 Instance Security Group',
allow_all_outbound=True)
# Create Transit Gateway
transit_gateway = ec2.CfnTransitGateway(self, 'TransitGateway',
description="Transit Gateway",
vpn_ecmp_support='enable',
default_route_table_association='disable',
default_route_table_propagation='disable',
tags=[core.CfnTag(key='Name', value="Transit Gateway")])
# Attach VPCs to gateway
transit_gateway_attachment_egress = ec2.CfnTransitGatewayAttachment(
self, 'TransitGatewayAttachmentEgress',
transit_gateway_id=transit_gateway.ref,
vpc_id=egress_vpc.vpc_id,
subnet_ids=[subnet.subnet_id for subnet in egress_vpc.private_subnets],
tags=[core.CfnTag(key='Name', value="TG-Egress-VPC-PrivateSubNet-Attachment")]
)
transit_gateway_attachment_egress.add_depends_on(transit_gateway)
transit_gateway_attachment_private = ec2.CfnTransitGatewayAttachment(
self, 'TransitGatewayAttachmentPrivate',
transit_gateway_id=transit_gateway.ref,
vpc_id=private_vpc.vpc_id,
subnet_ids=[subnet.subnet_id for subnet in private_vpc.isolated_subnets],
tags=[core.CfnTag(key='Name', value="TG-Private-VPC-PrivateSubNet-Attachment")]
)
transit_gateway_attachment_private.add_depends_on(transit_gateway)
# Add routes
for subnet in egress_vpc.public_subnets:
ec2.CfnRoute(self, f'{subnet.node.unique_id}',
route_table_id=subnet.route_table.route_table_id,
destination_cidr_block=private_vpc.vpc_cidr_block,
transit_gateway_id=transit_gateway.ref
).add_depends_on(transit_gateway_attachment_egress)
for subnet in private_vpc.isolated_subnets:
ec2.CfnRoute(self, f'{subnet.node.unique_id}',
route_table_id=subnet.route_table.route_table_id,
destination_cidr_block="0.0.0.0/0",
transit_gateway_id=transit_gateway.ref
).add_depends_on(transit_gateway_attachment_private)
# Getting latest Amazon Linux AMI
latest_linux_ami = ec2.AmazonLinuxImage(generation=ec2.AmazonLinuxGeneration.AMAZON_LINUX_2,
edition=ec2.AmazonLinuxEdition.STANDARD,
virtualization=ec2.AmazonLinuxVirt.HVM,
storage=ec2.AmazonLinuxStorage.GENERAL_PURPOSE)
# SSM agent Role
ssm_role = iam.Role(self, 'SSMRole',
assumed_by=iam.ServicePrincipal('ec2.amazonaws.com'),
managed_policies=[
iam.ManagedPolicy.from_aws_managed_policy_name('AmazonSSMManagedInstanceCore'),
iam.ManagedPolicy.from_aws_managed_policy_name('CloudWatchAgentServerPolicy')
],
inline_policies={
'ssmS3policy': iam.PolicyDocument(
statements=[
iam.PolicyStatement(
effect=iam.Effect.ALLOW,
actions=['s3:GetObject'],
resources=[
f'arn:aws:s3:::aws-ssm-{self.region}/*',
f'arn:aws:s3:::aws-windows-downloads-{self.region}/*',
f'arn:aws:s3:::amazon-ssm-{self.region}/*',
f'arn:aws:s3:::amazon-ssm-packages-{self.region}/*',
f'arn:aws:s3:::{self.region}-birdwatcher-prod/*',
f'arn:aws:s3:::patch-baseline-snapshot-{self.region}/*'
])
])
})
# Launch instance in private VPC subnet
demo_instance = ec2.CfnInstance(self, "DemoInstance",
subnet_id=private_vpc.isolated_subnets[0].subnet_id,
image_id=latest_linux_ami.get_image(self).image_id,
instance_type="t2.nano",
iam_instance_profile=iam.CfnInstanceProfile(
self, "DemoEC2InstanceProfile",
roles=[ssm_role.role_name]
).ref,
tags=[core.CfnTag(key='Name', value="Demo instance")],
security_group_ids=[ssm_private_sg.security_group_id])
4. 스택 배포
- Python CDK 애플리케이션 코드를 완성하고, 로컬 환경에서 이를 테스트합니다.
- `cdk synth` 명령어를 실행하여 CloudFormation 템플릿을 생성하고 검토합니다.
- `cdk deploy` 명령어를 사용하여 스택을 AWS 계정에 배포합니다. 배포 과정은 터미널에서 진행 상황을 관찰할 수 있습니다.
이렇게 Python을 사용한 AWS CDK를 통해 복잡한 인프라를 효율적으로 구성하고 관리할 수 있으며, 코드의 양도 상당히 줄일 수 있습니다.
'AWS Ambassador' 카테고리의 다른 글
6. AWS Opensearch를 활용한 Zero ETL RAG 활용 (0) | 2025.03.05 |
---|---|
5. 환각을 최소화하는 최신 정보 반영 LLM RAG 애플리케이션 (0) | 2025.03.05 |
4. 온프레미스와 클라우드의 최적 조합: 하이브리드 클라우드 전략 (0) | 2025.03.05 |
3. AWS 서버리스의 Latency 문제 분석과 개선을 위한 Caching 전략 (0) | 2025.03.05 |
1. Auto Scaling 원리 (0) | 2025.03.05 |