이 기사의 배경으로는, 직원들에게 AWS 사용자 계정을 발급한 결과 여러 VPC와 NAT 게이트웨이가 난립하게 된 상황이 있습니다.
이 기사의 주요 내용과 직접적인 관련은 없지만, 관심 있는 분들은 아래 기사를 참조해 주세요.
2024/12/04 업데이트
AWS announces access to VPC resources over AWS PrivateLink
AWS PrivateLink로 NLB를 거치지 않고 VPC 내 리소스에 접근할 수 있게 되었습니다.
자세한 내용은 위 공식 기사를 참고하시기 바랍니다.
저희 회사에서도 원래 NLB를 사용했지만, 이를 계기로 NLB를 사용하지 않는 방법으로 전환하였습니다.
배경
각 프로젝트마다 별도의 VPC가 있습니다.
인터넷 연결 방법으로 NAT 게이트웨이를 사용해야 하지만, VPC 수가 늘어나면서 비용이 병목 현상이 발생했습니다.
따라서 아래 그림과 같이 여러 VPC를 트랜짓 게이트웨이로 연결하고 인터넷 이그레스를 위해 NAT 게이트웨이를 통합하는 방안을 고려했습니다;
중앙 집중식 IPv4 송신을 위한 NAT 게이트웨이 사용https://docs.aws.amazon.com/ko_kr/whitepapers/latest/building-scalable-secure-multi-vpc-network-infrastructure/using-nat-gateway-for-centralized-egress.html
하지만 저희 환경에서는 트랜짓 게이트웨이가 VPC 간에 연결할 수 없다는 사실을 발견했습니다.
원인
저희의 잘못이기는 하지만, 각 프로젝트에 대해 IAM 사용자에게만 비용을 지불하고 특정 IP 주소 밴딩을 설정하지 않았습니다.
그 결과 많은 VPC에 중복되는 CIDR이 있었고, 각 VPC를 전송 게이트웨이에 연결할 수 없었습니다.
(또는 더 정확하게는 연결할 수는 있었지만 Transit Gateway 경로 테이블에 지정된 리턴 경로가 여러 VPC에 중복되어 있었습니다.)
동일한 CIDR를 사용하는 Amazon VPC를 연결할 수 있나요?
https://aws.amazon.com/ko/transit-gateway/faqs/?nc1=h_ls
AWS Transit Gateway는 동일한 CIDR를 사용하는 Amazon VPC 간의 라우팅을 지원하지 않습니다. 이미 연결된 Amazon VPC와 동일한 CIDR가 있는 새로운 Amazon VPC를 연결하면 AWS Transit Gateway는 새로운 Amazon VPC 경로를 AWS Transit Gateway 라우팅 테이블에 전파하지 않습니다.
아래와 같이 프라이빗 NAT 게이트웨이를 사용하여 겹치지 않는 IP 주소로 변환하는 방법을 생각해 보았습니다.
하지만 이 방법은 각 VPC에서 사설 NAT 게이트웨이를 사용하게 되므로 비용 절감 목표를 달성할 수 없습니다.
중첩되는 네트워크 간에 통신 사용
https://docs.aws.amazon.com/ko_kr/vpc/latest/userguide/nat-gateway-scenarios.html#private-nat-overlapping-networks
방법
다행히도 각 프로젝트에서 NAT 게이트웨이의 주된 용도는 HTTP/HTTPS를 통한 인터넷 통신이었습니다.
따라서 이 요구 사항을 충족하기 위한 구성으로 AWS PrivateLink와 프록시 서버를 사용하여 NAT 게이트웨이를 통해 인터넷 출구를 집계했습니다.
AWS PrivateLink를 사용하면 VPC 간 IP 주소 범위가 겹쳐도 통신이 가능합니다.
구성은 다음과 같습니다.
이 구성도에서는 통신 출발지 VPC가 4개만 표시되어 있지만, 실제로는 더 많은 VPC가 존재합니다.
또한, NAT Gateway 또는 Proxy Instance를 리전 내 각 AZ에 배치함으로써 더 높은 가용성을 가진 운영이 가능합니다.
2024/12/04 업데이트
참고로, NLB가 필요했던 시기의 구성도를 남겨둡니다.
이전에는 PrivateLink에서 온 트래픽을 NLB가 받아 타겟으로 라우팅했습니다.
최신 업데이트에서는 PrivateLink에서 온 트래픽을 Resource Gateway가 받아 Resource Configuration에서 지정된 리소스로 라우팅할 수 있게 된 것 같습니다.
NLB가 사용되었던 이전의 구성도
관리자 단계
구성을 위한 준비 과정은 다음과 같습니다.
- VPC를 생성하고 각 AZ에 3개의 서브넷을 만듭니다.
- 서브넷 A: NAT 게이트웨이용
- 서브넷 B: 프록시 서버용
- 서브넷 C: NLB용
- 서브넷 A에 대해 다음을 설정합니다.
- '0.0.0.0.0/0 → IGW' 경로를 생성합니다.
- NAT 게이트웨이 생성
- 서브넷 B에 대해 다음을 설정합니다.
- “0.0.0.0/0 → NAT 게이트웨이” 경로 생성
- EC2 인스턴스 시작
- EC2 인스턴스에 Squid를 설치하고 프록시 서버로 구성합니다.
- 타겟 그룹을 생성하고 이를 프록시 서버로 등록합니다.
- 서브넷 C에서 다음을 구성합니다.
- NLB를 생성하고 서브넷 C를 지정합니다.
- VPC 엔드포인트 서비스를 생성하고 생성된 NLB를 지정합니다.
엔드포인트 서비스는 미리 통신 중인 AWS 계정을 허용합니다.
보유한 도메인이 있는 경우 엔드포인트 서비스에 프라이빗 DNS를 등록할 수 있습니다.
모든 사용자(프로젝트)에게 배포하기 위한 구성 절차를 가능한 한 동일하게 만들고 싶었기 때문에 프라이빗 DNS를 설정했습니다.
모든 사용자(프로젝트)에 대해 가능한 한 일관된 구성 프로세스를 만들고 싶었기 때문에 비공개 DNS를 설정했습니다.
(프라이빗 DNS가 없는 경우 각 프로젝트는 각 VPC에서 생성된 VPC 엔드포인트의 IP 주소를 확인해야 합니다.)
사용자 측 절차
먼저 VPC 생성부터 시작하겠습니다.
VPC 생성
- 아래 URL에서 VPC, 서브넷 등을 생성한다.
https://us-east-1.console.aws.amazon.com/vpcconsole/home?region=us-east-1#CreateVpc:createMode=vpcWithResources - 아래와 같은 설정으로 생성한다. ( 밑줄 친 항목 외에는 기본값으로 설정해 두었습니다.)
- 생성할 리소스: VPC 등
- 이제 서브넷 등도 동시에 생성해 줍니다.
- 이름표 자동 생성: ☑️ 자동 생성 ‘Proxy’
- VPC나 서브넷의 이름을 자동으로 생성해 줍니다. 나중에 변경 가능합니다.
- IPv4 CIDR 블록: 10.0.0.0.0/16
- VPC의 사설 IP 주소 범위입니다. 나중에 변경할 수 없습니다.
- 인터넷에 접속할 때 사용하는 공인 IP 주소와 무관합니다.
- 가용 영역(AZ) 수: 2
- 서브넷을 배치하는 AWS 데이터센터의 개수로 인식하고 있습니다.
- 퍼블릭 서브넷 수: 2
- 인터넷에 접속할 수 있는 서브넷의 개수입니다.
- 프라이빗 서브넷 수: 2
- 인터넷에 접속할 수 없는 서브넷의 개수입니다.
- NAT 게이트웨이 ($): 1개의 AZ에서
- NAT 게이트웨이를 사용할 경우, 모든 AZ에 배치할지 여부를 설정합니다.
- NAT 게이트웨이 개수마다 비용이 발생하므로 일단 하나의 AZ에 생성합니다.
- ( ($) 표시가 요금이 부과됨을 의미함?)
- VPC 엔드포인트: S3 게이트웨이
- VPC에서 S3에 직접 접속할 수 있도록 하는 것 같습니다.
- 추가하는 것 자체는 무료이고, 요금 절감에 기여하는 것 같아서 그냥 추가해 두었습니다.
- 생성할 리소스: VPC 등
이미지
프록시 서버 준비
익숙하기 때문에 이 절차는 AWS CLI 명령어로 진행합니다.
- 아래 AWS CLI 명령어를 사용하여 EC2 인스턴스에 세션 매니저로 연결하기 위한 인스턴스 프로필을 생성합니다.
# Create IAM role
aws iam create-role \
--role-name SSMRole \
--assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}'
# Attach IAM policy for SSM
aws iam attach-role-policy \
--role-name SSMRole \
--policy-arn arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
# Create instance profile
aws iam create-instance-profile \
--instance-profile-name SSMRole
# Add IAM role to instance profile
aws iam add-role-to-instance-profile \
--instance-profile-name SSMRole \
--role-name SSMRole
- 아래 AWS CLI 명령어를 사용하여 Resource Gateway의 트래픽을 허용하는 규칙을 가진 Proxy 인스턴스용 보안 그룹을 생성합니다.
※ 아웃바운드 규칙은 기본적으로 생성되므로 설정할 필요가 없습니다.
Source | Protocol | Port |
10.0.0.0/16 (VPC CIDR) | All | All |
Destination | Protocol | Port |
0.0.0.0/0 | All | All |
# Get VPC ID
VPC_ID=$(aws ec2 describe-vpcs \
--filters "Name=tag:Name,Values=Proxy-vpc" \
--query "Vpcs[].VpcId" \
--output text)
# Create a security group for proxy instance
SECURITY_GROUP_ID=$(aws ec2 create-security-group \
--group-name Proxy-sg \
--description "Security group for proxy instance" \
--vpc-id $VPC_ID \
--query 'GroupId' \
--output text)
# Add an inbound rule to allow all traffic from VPC
aws ec2 authorize-security-group-ingress \
--group-id $SECURITY_GROUP_ID \
--protocol -1 \
--cidr 10.0.0.0/16
- 아래 AWS CLI 명령어를 사용하여, 이 보안 그룹을 가진 EC2 인스턴스를 하나의 프라이빗 서브넷에 시작합니다.
# Get Amazon Linux 2 AMI ID
AMI_ID=$(aws ec2 describe-images \
--owners amazon \
--filters "Name=name,Values=amzn2-ami-hvm-2.0.*-x86_64-gp2" \
--query 'sort_by(Images, &CreationDate)[-1].ImageId' \
--output text)
# Get Subnet ID for running proxy instance
SUBNET_ID=$(aws ec2 describe-subnets \
--filters "Name=tag:Name,Values=Proxy-subnet-private1-*" \
--query "Subnets[].SubnetId" \
--output text)
# Run EC2 instance for proxy
aws ec2 run-instances \
--image-id $AMI_ID \
--count 1 \
--instance-type t2.micro \
--iam-instance-profile Name=SSMRole \
--security-group-ids $SECURITY_GROUP_ID \
--subnet-id $SUBNET_ID \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=ProxyInstance}]'
- 아래 AWS CLI 명령어를 사용하여 세션 매니저를 통해 EC2 인스턴스에 연결합니다.
# Get EC2 instance ID
EC2_ID=$(aws ec2 describe-instances \
--filters "Name=tag:Name,Values=ProxyInstance" \
--query "Reservations[].Instances[].InstanceId" \
--output text)
# Start connect by Session Manager
aws ssm start-session --target $EC2_ID
- 아래 명령어를 실행하여 Proxy 서버를 구성합니다.
여기에서는 최소한의 설정을 사용하지만, 적절히 사용 사례에 맞게 커스터마이징하십시오.
## Bellow command is at Session Manager.
# Ready for operating proxy
sh-4.2$ sudo yum -y install squid
sh-4.2$ sudo systemctl start squid
sh-4.2$ sudo systemctl enable squid
sh-4.2$ sudo lsof -i:3128
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
squid 3551 squid 11u IPv6 33657 0t0 TCP *:squid (LISTEN)
Resource Gateway 생성
Resource Gateway 생성이 완료되면 지정한 서브넷에 ENI가 생성됩니다.
PrivateLink를 통해 들어오는 트래픽은 이 ENI에서 나오는 이미지로 생각하시면 됩니다.
- 아래 AWS CLI 명령어를 사용하여 Resource Gateway에서 오는 트래픽을 허용하는 규칙을 가진 Resource Gateway용 보안 그룹을 생성합니다.
※ 아웃바운드 규칙은 기본적으로 생성되므로 설정할 필요가 없습니다.
Source | Protocol | Port |
0.0.0.0/0 | All | All |
Destination | Protocol | Port |
0.0.0.0/0 | All | All |
# Get VPC ID
VPC_ID=$(aws ec2 describe-vpcs \
--filters "Name=tag:Name,Values=Proxy-vpc" \
--query "Vpcs[].VpcId" \
--output text)
# Create a security group for resource gateway
SECURITY_GROUP_ID=$(aws ec2 create-security-group \
--group-name ResourceGateway-sg \
--description "Security group for resource gateway" \
--vpc-id $VPC_ID \
--query 'GroupId' \
--output text)
# Add an inbound rule to allow all traffic
aws ec2 authorize-security-group-ingress \
--group-id $SECURITY_GROUP_ID \
--protocol -1 \
--cidr 0.0.0.0/0
- 아래 URL에서 Resource Gateway를 생성합니다.
https://us-east-1.console.aws.amazon.com/vpcconsole/home?region=us-east-1#CreateResourceGateway - 아래 설정으로 생성합니다.
- Resource gateway name: proxy-rgw
- IP address type: IPv4
- VPC: Proxy-vpc
- ☑️ us-east-1a: Proxy-subnet-private1-us-east-1a
- ☑️ us-east-1c: Proxy-subnet-private2-us-east-1c
- 이 서브넷에 ENI가 생성됩니다.
- Security groups: ResourceGateway-sg
- 생성된 ENI에 설정되는 보안 그룹입니다.
- PrivateLink에서 리소스로의 트래픽이 허용되어야 합니다.
이미지
Resource Configuration 생성
여기에서 PrivateLink를 통해 들어오는 트래픽의 라우팅 대상을 지정합니다.
지정된 Resource Gateway를 통과하는 특정 포트 번호의 트래픽이 지정된 리소스로 라우팅됩니다.
- 아래 URL에서 Resource Configuration을 생성합니다.
https://us-east-1.console.aws.amazon.com/vpcconsole/home?region=us-east-1#CreateResourceConfig: - 아래 설정으로 생성합니다
- Name: Proxy-conf
- Configuration type: ◉ Resource
- Type: Single
- Protocol: TCP
- Resource gateway: proxy-rgw
- Resource type: ◉ DNS resource
- Domain name: proxy.awsexample.com
- Proxy Instance의 IP 주소로 해석되는 도메인 이름을 지정했습니다.
- 추후 설명하겠지만, 여기를 도메인 이름으로 지정하면 프라이빗 DNS가 활성화되는지 확인하려 했지만, 그렇게 동작하지는 않았습니다.
- IP 주소로 지정하는 것도 가능합니다.
- IP address type: IPv4
- Port ranges:
- Lower bound: 1
- Upper bound: 65535
- Allow association with shareable service networks: ◉ Allow
- 아마도 Amazon VPC Lattice의 "서비스 네트워크"에서 이 Configuration을 연계할 수 있는지 여부를 의미하는 것 같습니다.
이번에는 "서비스 네트워크"를 사용하지 않으므로 기본 설정을 유지하겠습니다.
- 아마도 Amazon VPC Lattice의 "서비스 네트워크"에서 이 Configuration을 연계할 수 있는지 여부를 의미하는 것 같습니다.
이미지
추후 설명하겠지만, NLB를 사용한 VPC 엔드포인트 서비스와는 달리, 도메인 소유를 확인하는 항목 등을 찾을 수 없었고, 위 설정으로 VPC 엔드포인트의 프라이빗 DNS 이름이 설정되지 않았습니다.
또한, 라우팅 대상 리소스 지정에 도메인 이름을 사용할 수는 있지만, 이를 위해서는 해당 도메인 이름이 리소스의 프라이빗 IP 주소를 가리켜야 하는 상황입니다.
공용으로 사용하고 있는 도메인 이름을 프라이빗 DNS 이름으로 사용하는 방법은 현재로서는 없는 것 같습니다.
사용자 측 절차
각 프로젝트에서는 VPC 위에 VPC 엔드포인트를 생성한 후 Proxy를 설정하도록 하였습니다.
VPC 엔드포인트 생성
- 아래 URL에서 VPC 엔드포인트를 생성합니다.
https://us-east-1.console.aws.amazon.com/vpcconsole/home?region=us-east-1#CreateVpcEndpoint: - 다음 설정으로 생성합니다.
- Name tag - optional: Proxy-vpce
- Type: Resources - New
- Resource configurations: proxy-conf
- VPC: <프로젝트 VPC>
- DNS name: ☑️ Enable DNS name
- Subnets:
- ☑️ us-east-1a: <프로젝트 서브넷 AZ-a>
- ☑️ us-east-1c: <프로젝트 서브넷 AZ-c>
- Security groups: <보안 그룹 ID>
- 통신 출발지에서의 트래픽을 허용하는 규칙이 필요합니다.
- 당사에서는 VPC CIDR에서의 모든 통신을 허용하도록 안내하고 있습니다.
이미지
Proxy 설정
생성된 VPC 엔드포인트를 확인해보면 프라이빗 DNS 이름이 활성화된 것처럼 보였지만, 도메인 이름은 표시되지 않았습니다.
애초에 활성화가 가능한지 여부도 명확하지 않아, 일단은 각 프로젝트에서 프라이빗 IP 주소를 확인하고 설정하도록 안내하였습니다.
- 아래 URL을 통해 생성된 VPC 엔드포인트의 ID를 기반으로 프라이빗 IP 주소를 확인할 수 있습니다.
https://us-east-1.console.aws.amazon.com/ec2/home?region=us-east-1#NIC:description=:VPC%20Endpoint%20Interface;v=3;$case=tags:false%5C,client:false;$regex=tags:false%5C,client:false - 각 통신 출발지 서버에 Proxy 설정을 진행해 주시면 됩니다.
현재로서는 이 구성으로 잘 작동하고 있습니다.
다만, 고유한 도메인 이름이 할당되면 절차 배포가 더 쉬워질 것이므로 프라이빗 DNS 이름을 사용할 수 있다면 매우 유용할 것 같습니다.
혹시 방법이 있다면, "문의하기"를 통해 알려주시면 감사하겠습니다.
또한, Proxy로 처리할 수 없는 통신이 앞으로 발생할 가능성도 있다고 생각합니다.
그 경우를 대비해 각 프로젝트에 대해 IP 주소 사용 범위를 미리 정해두려고 합니다.