두 VPC 연결하기: VPC 피어링 설정과 라우트 테이블 업데이트 완전 가이드

같은 계정, 같은 리전에 VPC가 두 개 있는데 서로 프라이빗 IP로 통신이 안 된다 — 이건 AWS를 처음 쓰는 팀이든 오래 쓴 팀이든 한 번씩은 마주치는 상황이다. VPC 피어링(VPC Peering) 자체는 단순하지만, 라우트 테이블과 보안 그룹까지 세 군데를 동시에 맞춰야 트래픽이 흐른다. 하나라도 빠지면 핑이 안 되고, 어디가 문제인지 찾는 데 시간을 쓰게 된다.

TL;DR — VPC 피어링 핵심 요약

단계작업 주체핵심 포인트
1. 피어링 연결 요청Requester VPC상대 VPC ID 지정, 같은 계정이면 자동 수락 가능
2. 피어링 연결 수락Accepter VPC수락 전까지 트래픽 불가
3. 라우트 테이블 업데이트양쪽 VPC 모두상대 CIDR → pcx-xxxxxx, 양방향 필수
4. 보안 그룹 업데이트양쪽 인스턴스상대 CIDR 또는 SG ID 허용
5. 통신 검증운영자VPC Flow Logs 또는 ping/curl 확인

VPC 피어링이 동작하는 방식

VPC 피어링은 두 VPC 사이에 논리적인 1:1 네트워크 경로를 만든다. 인터넷 게이트웨이, NAT, VPN, 별도의 게이트웨이 장비가 전혀 개입하지 않는다. AWS 내부 네트워크를 통해 직접 라우팅되므로 트래픽이 공인 인터넷을 경유하지 않는다.

중요한 제약이 하나 있다. 피어링은 전이적(transitive) 라우팅을 지원하지 않는다. VPC-A ↔ VPC-B, VPC-B ↔ VPC-C가 각각 피어링되어 있어도 VPC-A에서 VPC-C로 VPC-B를 경유해 통신할 수 없다. 세 VPC가 모두 통신해야 한다면 각각 직접 피어링해야 한다.

또한 두 VPC의 CIDR 블록이 겹치면 피어링 자체가 생성되지 않는다. 설계 단계에서 CIDR 충돌을 반드시 확인해야 한다.

graph LR subgraph VPC_A ["VPC-A (10.0.0.0/16)"] A1["EC2 Instance 10.0.1.10"] RT_A["Route Table A 10.1.0.0/16 → pcx"] end subgraph VPC_B ["VPC-B (10.1.0.0/16)"] B1["EC2 Instance 10.1.1.10"] RT_B["Route Table B 10.0.0.0/16 → pcx"] end PCX["Peering Connection pcx-xxxxxxxxx status: active"] A1 --> RT_A RT_A --> PCX PCX --> RT_B RT_B --> B1
  1. VPC-A (10.0.0.0/16)VPC-B (10.1.0.0/16)는 서로 다른 CIDR을 사용한다.
  2. 피어링 연결(pcx-xxxxxx)이 두 VPC 사이의 논리적 경로 역할을 한다.
  3. 각 VPC의 라우트 테이블에 상대 CIDR을 피어링 연결로 향하도록 항목을 추가해야 한다.
  4. 보안 그룹은 라우트 테이블과 독립적으로 동작하므로 별도로 인바운드 규칙을 열어야 한다.

사전 준비: CIDR 충돌 확인

피어링을 만들기 전에 두 VPC의 CIDR 블록이 겹치지 않는지 확인한다. 겹치는 경우 피어링 요청 자체가 실패한다.

# VPC 목록과 CIDR 확인
aws ec2 describe-vpcs \
  --query 'Vpcs[*].{VpcId:VpcId,CidrBlock:CidrBlock,Name:Tags[?Key==`Name`]|[0].Value}' \
  --output table \
  --region us-east-1

출력에서 두 VPC의 CidrBlock 값이 겹치지 않는지 눈으로 확인한다. 예를 들어 VPC-A가 10.0.0.0/16, VPC-B가 10.1.0.0/16이면 문제없다.

VPC 피어링 설정: 단계별 가이드

1단계: 피어링 연결 요청 생성

Requester VPC(VPC-A)에서 Accepter VPC(VPC-B)로 피어링 요청을 보낸다. 같은 계정, 같은 리전이므로 --peer-owner-id--peer-region은 생략해도 되지만, 명시하면 실수를 줄일 수 있다.

aws ec2 create-vpc-peering-connection \
  --vpc-id vpc-0a1b2c3d4e5f00001 \
  --peer-vpc-id vpc-0a1b2c3d4e5f00002 \
  --region us-east-1

명령 실행 후 출력에서 VpcPeeringConnectionId 값을 기록해 둔다. 이후 단계에서 계속 사용한다. 형식은 pcx-xxxxxxxxxxxxxxxxx다.

# 피어링 연결 ID 확인
aws ec2 describe-vpc-peering-connections \
  --filters Name=status-code,Values=pending-acceptance \
  --query 'VpcPeeringConnections[*].{PeeringId:VpcPeeringConnectionId,Requester:RequesterVpcInfo.VpcId,Accepter:AccepterVpcInfo.VpcId,Status:Status.Code}' \
  --output table \
  --region us-east-1

2단계: 피어링 연결 수락

같은 계정 내 피어링이라도 수락 단계를 거쳐야 한다. 수락하지 않으면 상태가 pending-acceptance로 남고 트래픽은 흐르지 않는다.

aws ec2 accept-vpc-peering-connection \
  --vpc-peering-connection-id pcx-xxxxxxxxxxxxxxxxx \
  --region us-east-1

수락 후 상태가 active로 바뀌는지 확인한다.

aws ec2 describe-vpc-peering-connections \
  --vpc-peering-connection-ids pcx-xxxxxxxxxxxxxxxxx \
  --query 'VpcPeeringConnections[0].Status' \
  --output json \
  --region us-east-1

"Code": "active"가 나오면 피어링 연결 자체는 완료된 것이다. 하지만 아직 트래픽은 흐르지 않는다 — 라우트 테이블이 비어 있기 때문이다.

3단계: 라우트 테이블 업데이트 — 양방향 필수

피어링이 active 상태여도 라우트 테이블에 항목이 없으면 패킷이 어디로 가야 할지 모른다. VPC-A의 라우트 테이블에는 VPC-B의 CIDR을, VPC-B의 라우트 테이블에는 VPC-A의 CIDR을 추가해야 한다. 한쪽만 하면 단방향 통신도 안 된다.

먼저 각 VPC에 연결된 라우트 테이블 ID를 확인한다.

# VPC-A의 라우트 테이블 확인
aws ec2 describe-route-tables \
  --filters Name=vpc-id,Values=vpc-0a1b2c3d4e5f00001 \
  --query 'RouteTables[*].{RouteTableId:RouteTableId,Main:Associations[0].Main}' \
  --output table \
  --region us-east-1

# VPC-B의 라우트 테이블 확인
aws ec2 describe-route-tables \
  --filters Name=vpc-id,Values=vpc-0a1b2c3d4e5f00002 \
  --query 'RouteTables[*].{RouteTableId:RouteTableId,Main:Associations[0].Main}' \
  --output table \
  --region us-east-1

서브넷마다 별도의 라우트 테이블이 연결되어 있다면, 통신에 참여하는 서브넷의 라우트 테이블 모두에 항목을 추가해야 한다. 메인 라우트 테이블만 업데이트하면 커스텀 라우트 테이블이 연결된 서브넷은 빠진다.

# VPC-A의 라우트 테이블에 VPC-B CIDR 추가
# (VPC-A에서 VPC-B 방향)
aws ec2 create-route \
  --route-table-id rtb-0a1b2c3d4e5f00001 \
  --destination-cidr-block 10.1.0.0/16 \
  --vpc-peering-connection-id pcx-xxxxxxxxxxxxxxxxx \
  --region us-east-1

# VPC-B의 라우트 테이블에 VPC-A CIDR 추가
# (VPC-B에서 VPC-A 방향)
aws ec2 create-route \
  --route-table-id rtb-0a1b2c3d4e5f00002 \
  --destination-cidr-block 10.0.0.0/16 \
  --vpc-peering-connection-id pcx-xxxxxxxxxxxxxxxxx \
  --region us-east-1

라우트가 제대로 들어갔는지 확인한다.

aws ec2 describe-route-tables \
  --route-table-ids rtb-0a1b2c3d4e5f00001 \
  --query 'RouteTables[0].Routes[?VpcPeeringConnectionId!=`null`]' \
  --output json \
  --region us-east-1

4단계: 보안 그룹 인바운드 규칙 추가

라우트 테이블이 완벽해도 보안 그룹이 막으면 통신이 안 된다. 보안 그룹은 라우트 레이어와 완전히 독립적으로 동작한다. VPC-A의 인스턴스가 VPC-B 인스턴스로 접근하려면, VPC-B 인스턴스의 보안 그룹에 VPC-A CIDR에서 오는 트래픽을 허용하는 인바운드 규칙이 있어야 한다.

# VPC-B 인스턴스 보안 그룹에 VPC-A CIDR 허용 (예: TCP 8080)
aws ec2 authorize-security-group-ingress \
  --group-id sg-0a1b2c3d4e5f00002 \
  --protocol tcp \
  --port 8080 \
  --cidr 10.0.0.0/16 \
  --region us-east-1

# VPC-A 인스턴스 보안 그룹에 VPC-B CIDR 허용 (응답 트래픽은 상태 추적으로 자동 허용되지만,
# VPC-B에서 VPC-A로 먼저 연결을 시작해야 한다면 이쪽도 열어야 함)
aws ec2 authorize-security-group-ingress \
  --group-id sg-0a1b2c3d4e5f00001 \
  --protocol tcp \
  --port 8080 \
  --cidr 10.1.0.0/16 \
  --region us-east-1

보안 그룹은 상태 추적(stateful) 방화벽이다. 인바운드를 허용하면 해당 연결의 응답 트래픽은 아웃바운드 규칙 없이도 자동으로 허용된다. 하지만 양쪽 모두 먼저 연결을 시작해야 하는 구조라면 양쪽 모두 인바운드 규칙을 열어야 한다.

sequenceDiagram participant OPS as 운영자 participant VPC_A as VPC-A participant AWS as AWS API participant VPC_B as VPC-B OPS->>AWS: create-vpc-peering-connection AWS-->>OPS: pcx-xxx (pending-acceptance) OPS->>AWS: accept-vpc-peering-connection AWS-->>OPS: status: active OPS->>VPC_A: create-route (→ 10.1.0.0/16 via pcx) OPS->>VPC_B: create-route (→ 10.0.0.0/16 via pcx) OPS->>VPC_B: authorize-security-group-ingress (from 10.0.0.0/16) OPS->>VPC_A: authorize-security-group-ingress (from 10.1.0.0/16) VPC_A->>VPC_B: 프라이빗 IP 통신 성공
  1. 피어링 연결 요청 → 수락 순서로 진행되며, 수락 전에는 라우트 추가가 의미 없다.
  2. 라우트 테이블은 VPC-A와 VPC-B 양쪽 모두 업데이트해야 한다.
  3. 보안 그룹은 라우트와 독립적으로 평가된다 — 마지막 관문이다.

VPC 피어링 설정에 필요한 IAM 권한

피어링 설정 작업을 자동화하거나 별도 역할로 실행한다면 최소 권한 원칙에 따라 아래 권한이 필요하다.

🔽 IAM 정책 예시 (클릭하여 펼치기)
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "VpcPeeringSetup",
      "Effect": "Allow",
      "Action": [
        "ec2:CreateVpcPeeringConnection",
        "ec2:AcceptVpcPeeringConnection",
        "ec2:DescribeVpcPeeringConnections",
        "ec2:CreateRoute",
        "ec2:DescribeRouteTables",
        "ec2:DescribeVpcs",
        "ec2:AuthorizeSecurityGroupIngress",
        "ec2:DescribeSecurityGroups"
      ],
      "Resource": "*"
    }
  ]
}

ec2:CreateRouteec2:DescribeRouteTables는 리소스 수준 제한을 지원하는 정도가 다르다. AWS Service Authorization Reference에서 각 액션의 리소스 수준 지원 여부를 확인하고 적용 가능한 경우 ARN으로 범위를 좁히는 것이 좋다.

실제 운영에서 자주 겪는 함정: 라우트는 됐는데 핑이 안 될 때

피어링 active, 라우트 테이블 양쪽 다 추가, 그런데 핑이 안 된다. 이 상황에서 대부분 보안 그룹을 의심하는데, 실제로는 ICMP를 허용하지 않은 경우가 많다. TCP 8080은 열었지만 ICMP echo는 별도로 열어야 한다.

또 다른 패턴은 서브넷에 커스텀 라우트 테이블이 연결되어 있는데 메인 라우트 테이블만 업데이트한 경우다. 콘솔에서 VPC 라우트 테이블 목록을 보면 메인 라우트 테이블이 눈에 잘 띄어서 거기만 수정하고 끝냈다고 생각하기 쉽다. 실제 서브넷이 어떤 라우트 테이블을 쓰는지는 서브넷 상세 페이지에서 확인해야 한다.

# 특정 서브넷에 연결된 라우트 테이블 확인
aws ec2 describe-route-tables \
  --filters Name=association.subnet-id,Values=subnet-0a1b2c3d4e5f00001 \
  --query 'RouteTables[*].{RouteTableId:RouteTableId,Routes:Routes}' \
  --output json \
  --region us-east-1

이 명령으로 아무것도 안 나오면 해당 서브넷은 메인 라우트 테이블을 사용하고 있는 것이다. 결과가 나오면 그 라우트 테이블에 피어링 라우트가 있는지 확인한다.

라우트 테이블은 VPC 단위가 아니라 서브넷 단위로 적용된다. VPC 전체에 라우트를 추가했다고 생각했지만 실제로는 메인 라우트 테이블 하나만 바꾼 것일 수 있다.

VPC Flow Logs로 트래픽 흐름 검증

핑이나 curl로 확인이 어려운 환경이라면 VPC Flow Logs를 활성화해서 실제 패킷이 어디서 막히는지 확인한다. ACCEPT/REJECT 필드를 보면 보안 그룹이나 네트워크 ACL 중 어디서 드롭되는지 구분할 수 있다.

# VPC-A에 Flow Logs 활성화 (CloudWatch Logs로 전송)
aws ec2 create-flow-logs \
  --resource-type VPC \
  --resource-ids vpc-0a1b2c3d4e5f00001 \
  --traffic-type ALL \
  --log-destination-type cloud-watch-logs \
  --log-group-name /aws/vpc/flowlogs \
  --deliver-logs-permission-arn arn:aws:iam::123456789012:role/FlowLogsRole \
  --region us-east-1

Flow Logs에서 REJECT가 찍히면 보안 그룹 또는 네트워크 ACL 문제다. 로그에 아무것도 안 찍히면 라우트 테이블 문제일 가능성이 높다 — 패킷이 아예 해당 인터페이스에 도달하지 못한 것이다.

네트워크 ACL은 기본적으로 모든 트래픽을 허용하지만, 커스텀 ACL을 적용했다면 인바운드와 아웃바운드 모두 명시적으로 허용해야 한다. 네트워크 ACL은 상태 비추적(stateless)이기 때문이다.

전체 설정 흐름 요약

graph TD S1["CIDR 충돌 확인"] --> S2{"충돌 없음?"} S2 -- "No" --> E1["CIDR 재설계 필요"] S2 -- "Yes" --> S3["피어링 연결 요청 생성"] S3 --> S4["피어링 연결 수락"] S4 --> S5["VPC-A 라우트 테이블 업데이트 (→ VPC-B CIDR via pcx)"] S5 --> S6["VPC-B 라우트 테이블 업데이트 (→ VPC-A CIDR via pcx)"] S6 --> S7["보안 그룹 인바운드 규칙 추가"] S7 --> S8{"네트워크 ACL 커스텀 여부?"} S8 -- "Yes" --> S9["ACL 인바운드/아웃바운드 확인"] S8 -- "No" --> S10["통신 검증"] S9 --> S10 S10 --> S11["완료"]
  1. CIDR 충돌 여부를 먼저 확인한다 — 겹치면 피어링 생성 자체가 실패한다.
  2. 피어링 요청 후 반드시 수락 단계를 거쳐야 active 상태가 된다.
  3. 라우트 테이블은 통신에 참여하는 모든 서브넷의 라우트 테이블을 양방향으로 업데이트한다.
  4. 보안 그룹 인바운드 규칙을 추가한다 — 라우트와 독립적으로 평가된다.
  5. 네트워크 ACL이 커스텀으로 설정되어 있다면 인바운드/아웃바운드 모두 확인한다.

VPC 피어링 설정 완료 후 다음 단계

VPC 피어링은 1:1 연결이다. 연결해야 할 VPC가 늘어날수록 피어링 수가 기하급수적으로 증가한다. VPC가 3개 이상이고 허브-앤-스포크 구조가 필요하다면 AWS Transit Gateway를 검토할 시점이다. Transit Gateway는 전이적 라우팅을 지원하므로 VPC 수가 많아질수록 관리 복잡도가 낮아진다.

피어링 설정 후 DNS 해석이 필요하다면 피어링 연결의 DNS 해석 옵션을 활성화해야 한다. 기본값은 비활성화 상태다.

# 피어링 연결의 DNS 해석 활성화 (Requester 측)
aws ec2 modify-vpc-peering-connection-options \
  --vpc-peering-connection-id pcx-xxxxxxxxxxxxxxxxx \
  --requester-peering-connection-options AllowDnsResolutionFromRemoteVpc=true \
  --region us-east-1

# Accepter 측
aws ec2 modify-vpc-peering-connection-options \
  --vpc-peering-connection-id pcx-xxxxxxxxxxxxxxxxx \
  --accepter-peering-connection-options AllowDnsResolutionFromRemoteVpc=true \
  --region us-east-1

공식 문서: Amazon VPC Peering Guide

용어 정리

용어설명
VPC 피어링 (VPC Peering)두 VPC 사이에 프라이빗 네트워크 경로를 만드는 AWS 기능. 인터넷을 경유하지 않는다.
피어링 연결 ID (pcx-)피어링 연결을 식별하는 고유 ID. 라우트 테이블의 타겟으로 사용된다.
전이적 라우팅 (Transitive Routing)A→B→C처럼 중간 VPC를 경유하는 라우팅. VPC 피어링은 지원하지 않는다.
네트워크 ACL서브넷 수준의 상태 비추적 방화벽. 인바운드/아웃바운드 규칙을 모두 명시해야 한다.
VPC Flow LogsVPC 내 네트워크 인터페이스를 통과하는 IP 트래픽 정보를 캡처하는 기능.

Related Posts

댓글

이 블로그의 인기 게시물

EC2 SSH 연결 타임아웃 완전 해결 가이드: Security Group 인바운드 규칙부터 라우팅까지

EC2 SSH 연결 시간 초과: 확인해야 할 보안 그룹(Security Group) 규칙

IAM User vs IAM Role 차이점 완전 정리 — EC2에서 S3 접근 시 무엇을 써야 하는가