내 VPC에 속한 서버는 어떻게 서로를 알고 패킷을 주고받을까?
이번 포스트에서는 2015년 re:Invent에서 발표한 Another Day, Another Billion Packets 내용을 토대로 AWS VPC 네트워크의 숨겨진 원리에 대해서 알아보고자 한다.
AWS는 클라우드 서비스를 제공해 주는 회사이다. 클라우드는 온프레미스와 다르게 태생적으로 공유를 원칙으로 한다. 여기서 공유란 여러 사용자 간에 공통된 리소스를 사용함을 의미한다. 예컨대, 클라우드에서 서버를 제공한다고 해보자. 클라우드에서 서버는 물리 서버가 아닌 VM이라는 가상머신이다. 가상머신은 물리 서버 위에서 운영된다. 사용자 10명이 각각 서버를 하나씩 요청하면, 물리 서버 위에 10개의 가상 머신이 생성된다. 즉, 하나의 물리 서버를 공유하고 있는 것이다.
물리 서버는 AWS에서 정한 IP를 가지고 있다. 그 위에 고객이 생성한 서버도 네트워크 통신을 위해 IP가 필요하다. IP는 네트워크에서 서버를 고유하게 식별해 주기 때문에 반드시 필요하다. 만약 AWS가 서버를 생성하면서 고유할 사설 네트워크 대역에서 IP를 할당해 준다면 어떨까?
예컨대, AWS가 사설 네트워크 대역으로 10.0.0.0/8 대역을 사용한다고 해보자. AWS는 10.0.0.0/32부터 10.255.255.255/32까지 총 16,777,216개를 사용할 수 있다. 네트워크 장비까지 고려하면 이보다 더 적은 수의 서버만 띄울 수 있다. 그러면 AWS는 전 세계적으로 약 1,600만 개 서버만 지원할 수 있다는 제한에 부딪힌다. 고객사가 계속 늘어나고, AWS를 사용하는 서비스 추세가 가파른 시점에서 1,600만 개는 턱없이 부족하다.
또한, 사설 네트워크가 겹치는 경우도 발생할 수 있다. AWS를 도입하고자 하는 고객사가 사설 네트워크를 10.0.0.0/8과 겹치도록 설정한다면, 라우팅에 문제가 발생한다. 기존에 10.0.0.0/8 대역을 온프레미스로 지정했던 라우팅 규칙 때문에, AWS로 라우팅 할 수 있는 방법이 없다.
위 두 가지 문제를 해결하기 위해서 AWS는 소프트웨어로 정의된 가상 네트워크를 제공한다.
이것이 바로 AWS VPC이다. VPC에 대한 소개는 다음의 글을 통해 확인할 수 있다.
사용자는 VPC를 원하는 대로 만들 수 있다. VPC의 사설 네트워크 대역도 정해진 규칙 내에서 원하는 대로 지정할 수 있다. 심지어는 같은 네트워크 대역을 가진 VPC를 여러 개 만드는 것도 가능하다. 예컨대, 10.0.0.0/16 대역을 가진 VPC-A와 VPC-B 대역을 만들 수 있다. 두 VPC는 같은 사용자 계정에 속하지만 엄연히 다른 네트워크이다.
이 경우 VPC-A의 10.0.0.10 서버는 VPC-A의 10.0.0.11과 통신할 수 있다. 하지만, VPC-B의 10.0.0.11과는 통신이 불가능하다. 설령 두 서버가 모두 같은 물리 서버에 존재한다고 해도 동일하다.
그러면 AWS는 어떻게 같은 IP를 가진 두 서버를 구분할 수 있을까?
AWS는 Mapping Service를 통해 VPC 네트워킹 문제를 해결했다.
Mapping Service는 고객의 VPC 라우팅 및 IP와 실제 물리 서버 간 매핑정보를 관리하는 서비스이다. Mapping Service는 어떤 고객의 가상머신이 어느 물리 서버에 올라가 있는지 알고 있다. AWS는 가상 머신 간 통신에서 중간에 패킷을 가로채서 Mapping Service에 매핑 정보를 요청한다. 가상 머신이 어느 물리서버에 존재하는지 알아야 패킷을 전달할 수 있기 때문이다.
같은 서브넷에서 존재하는 경우부터 Mapping Service가 어떻게 동작하는지 확인해 보자. 같은 서브넷이라는 의미는 같은 라우터를 사용하고 있고, 스위치를 통해 통신을 할 수 있다는 뜻이다. 즉, 네트워크 레이어 중 L2 레이어에 해당한다.
보편적인 네트워크에서 L2 스위치를 통해 통신이 이루어지는 원리를 살펴보면 다음과 같다.
1. 먼저 10.0.0.2 서버의 라우팅 테이블에 10.0.0.3에 매핑되는 넥스트 홉을 찾는다. 같은 서브넷이면 라우터를 통하지 않고 직접 접속이 가능하다. 따라서 10.0.0.3에 해당하는 MAC 주소만 알아내면 된다. 10.0.0.2 서버는 통신하고자 하는 10.0.0.3 서버의 MAC주소를 알기 위해 ARP 패킷을 보낸다. ARP는 같은 네트워크 안에 있는 서버의 물리적인 MAC주소를 알아내기 위한 질의 요청이다.
2. 스위치는 ARP 패킷에 적힌 10.0.0.3의 MAC주소를 자신이 가지고 있는지 확인한다. 만약 없다면, ARP 패킷을 자신에게 붙어있는 모든 포트에 전송한다.(브로드캐스트)
3. 10.0.0.3 서버는 ARP 패킷에 적힌 IP가 자신과 같음을 확인하고 자신의 MAC 주소를 적어서 10.0.0.2 서버에 응답을 전송한다. 다른 포트에 연결된 서버는 자신과 다른 IP이므로 요청을 무시한다.
4. 스위치는 10.0.0.3 서버에 대한 MAC 정보를 기억한 후에 패킷을 10.0.0.2로 전송한다.
5. 10.0.0.2 서버는 실제 요청에 10.0.0.3에 대한 MAC 정보를 넣어서 보낸다. 그러면 비로소 10.0.0.3 서버와 통신할 수 있다.
이처럼 스위치를 통한 L2 레이어 통신은 AWS VPC 네트워크에서 다음과 같이 변경된다. 그림에서는 VPC 구분을 위해 색상을 통해서 표시했다. 빨간색 테두리가 적힌 10.0.0.2와 10.0.0.3이 같은 VPC에 속한 가상머신이다.
시나리오: 빨간색 VPC의 10.0.0.2에서 빨간색 VPC의 10.0.0.3을 호출
1. 물리 서버 192.168.0.3에 위치한 빨간색 VPC의 10.0.0.2는 10.0.0.3의 MAC 주소를 모르기 때문에 ARP 요청을 보낸다.
2. 물리 서버는 ARP 패킷을 가로채서 Mapping Service에 빨간색 VPC에 속한 10.0.0.3 서버의 MAC주소와 해당 서버가 올라가 있는 물리 서버의 주소를 문의한다.
3. Mapping Service는 대상 서버가 올라간 물리 서버 주소인 192.168.0.4와 대상 서버의 MAC 주소를 반환한다.
4. 물리 서버는 요청한 APR 패킷에 응답을 채워서 다시 10.0.0.2에 응답한다.
5. MAC 주소를 알아낸 10.0.0.2 서버는 이제 MAC 주소를 채워서 요청을 전송한다. 이때, L2 레이어 통신을 위한 MAC 주소와 대상 IP 주소를 함께 적는다.
6. AWS는 먼저 요청을 한 서버가 속한 VPC 정보를 패킷에 추가한다. 이를 VPC Encapsulation이라고 한다. 그리고, 물리 서버 간 통신을 위해 물리 서버 기준으로 출발지 주소와 목적지 주소를 VPC 정보 위에 추가한다. 패킷은 네트워크를 타고 192.168.0.4로 도달한다.
7. 물리 서버 192.168.0.4는 요청이 유효한지 확인하기 위해 Mapping Service에 질의한다. 질의 내용은 "빨간색 VPC에 속한 10.0.0.2이 물리 서버 192.168.0.3에 존재하는지"이다.
8. Mapping Service는 자신이 가진 정보를 통해 해당 질의 내용이 유효한지 확인한다.
9. 유효한 요청임을 확인하면, 물리서버는 VPC 정보가 적힌 헤더까지 벗겨낸 후, 실제 요청을 10.0.0.3 서버로 전달한다.
위와 같은 다소 복잡한 과정을 거쳐서 같은 서브넷 간 통신이 이루어진다. Mapping Service는 VPC와 가상머신, 그리고 물리 서버의 정보를 모두 가지고 있기 때문에 VPC가 서로 섞여있더라도 올바르게 패킷을 검증할 수 있다. 다음 중 하나라도 어긋나면 Mapping Service는 패킷을 무시하고 경고를 준다.
대상 서버의 VPC에 속한 서버가 요청한 물리 서버에 존재해야 한다. 출발지 물리 서버 위에 같은 VPC에 속한 서버가 없다면, 이는 중간에 요청 패킷이 잘못되었음을 의미한다. 같은 서브넷에 있으려면 반드시 같은 VPC에 속해야 한다.
대상 서버의 VPC에 속하고, 요청한 서버의 IP와 동일한 서버가 출발지 물리 서버에 존재해야 한다.
그러면 서브넷이 다를 때는 어떨까? 서브넷이 다르다는 의미는 네트워크 대역이 다름을 의미한다. 비록 같은 VPC 내부이지만 서브넷 간 대역이 다르므로 통신 시에 라우터를 통해야 한다. 따라서 L3 레이어에서 라우팅이 이루어져야 한다.
이번에도 보편적인 네트워크 상황을 확인해 보자. 일반적인 네트워크에서는 아래와 같은 방식으로 서로 다른 서브넷 간 통신이 이루어진다.
1. 먼저 라우팅 테이블에 목적지 대역에 매핑되는 넥스트 홉이 있는지 찾는다. 10.0.1.3은 10.0.0.2와 다른 서브넷이라고 가정했기 때문에 넥스트 홉은 디폴트 게이트웨이이자 라우터인 10.0.0.1이 된다. 10.0.0.2 서버는 10.0.0.1 게이트웨이의 MAC 주소를 알아내기 위해 ARP 패킷을 전송한다. 스위치는 위에서 설명했던 방식처럼 ARP 패킷을 전체로 브로드캐스트 하여 10.0.0.1의 위치를 찾는다.
2. 10.0.0.1 게이트웨이는 자신을 찾는 ARP 패킷에 응답하여 자신의 위치를 전달한다.
3. 10.0.0.2 서버는 10.0.0.1 게이트웨이의 MAC주소를 적어서 패킷을 전송한다. 패킷을 전달받은 라우터는 L2 헤더를 제거하고, 목적지 IP에 매핑되는 넥스트 홉을 라우팅 테이블에서 조회한다.
4. 목적지 IP 대역에 매핑되는 넥스트 홉은 목적지 서버가 속한 서브넷의 게이트웨이인 10.0.1.1이다. 두 라우터는 같은 네트워크 상에 존재하기 때문에, 10.0.0.1 게이트웨이는 자신의 MAC주소를 L2 출발지로, 10.0.1.1의 MAC주소를 L2 목적지로 적어 패킷을 전송한다.
5. 10.0.1.1 게이트웨이는 패킷을 받아 L2 헤더를 제거한다. 10.0.1.1 게이트웨이는 자신의 라우팅 테이블에서 목적지인 10.0.1.3의 넥스트 홉을 찾는다. 10.0.1.1과 10.0.1.3은 같은 서브넷이므로 직접 통신한다. 따라서 10.0.1.1 게이트웨이는 10.0.1.3의 MAC주소를 찾아 L2 헤더를 구성한다. 자신의 MAC주소를 출발지로, 10.0.1.3의 MAC주소를 목적지로 설정한다. 패킷은 10.0.1.3으로 전달된다.
서로 다른 서브넷 간 통신하는 경우는 앞서 설명했던 L2와 다르게 라우터 간 통신이 발생한다. AWS 서브넷도 이와 유사하게 동작한다. AWS VPC 서브넷을 구성하면 첫 4개 IP와 마지막 IP는 할당이 불가하다. 그 이유는 AWS에서 네트워크 통신에 해당 IP들을 사용하기 때문이다. 이 중 두 번째 IP가 VPC 라우터에 할당된다. 예컨대, 10.0.0.0/24 대역을 가진 서브넷이라면 VPC 라우터의 IP는 10.0.0.1이 된다.
이제 AWS에서 어떻게 L3 레이어 통신이 이루어지는지 알아보자.
시나리오: 빨간색 VPC의 10.0.0.2에서 빨간색 VPC의 10.0.1.3을 호출
1. 물리 서버 192.168.0.3에 위치한 빨간색 VPC의 10.0.0.2는 다른 서브넷과 통신하기 위해 게이트웨이 주소인 10.0.0.1에 대한 ARP 요청을 보낸다.
2. 물리 서버는 ARP 패킷을 가로채서 Mapping Service에 빨간색 VPC에 속한 10.0.0.1 게이트웨이의 MAC주소를 문의한다.
3. Mapping Service는 10.0.0.1 게이트웨이의 MAC 주소를 반환한다.
4. 물리 서버는 요청한 APR 패킷에 응답을 채워서 다시 10.0.0.2에 응답한다.
5. 10.0.0.1 게이트웨이의 MAC 주소를 알아낸 10.0.0.2 서버는 MAC 주소를 채워서 요청을 전송한다.
6. 물리 서버는 요청을 가로채서 Mapping Service에 빨간색 VPC에 속한 10.0.1.3 서버의 물리 서버 주소와 MAC 주소를 요청한다.
7. Mapping Service는 빨간색 VPC에 속한 10.0.1.3 서버가 올라간 물리 서버 주소를 반환한다.
8. AWS는 먼저 요청을 한 10.0.0.2 서버가 속한 VPC 정보를 패킷에 추가한다. 그리고, 물리 서버 간 통신을 위해 물리 서버 기준으로 출발지 주소와 목적지 주소를 VPC 정보 위에 추가한다. 패킷은 네트워크를 타고 192.168.0.4로 도달한다.
9. 물리 서버 192.168.0.4는 요청이 유효한지 확인하기 위해 Mapping Service에 질의한다. 질의 내용은 "빨간색 VPC에 속한 10.0.0.2이 물리 서버 192.168.0.3에 존재하는지"이다.
10. Mapping Service는 자신이 가진 정보를 통해 해당 질의 내용이 유효한지 확인한다.
11. 유효한 요청임을 확인하면, 물리서버는 VPC 정보가 적힌 헤더까지 벗겨낸 후, 실제 요청을 10.0.1.3 서버로 전달한다. 이때 L2 헤더에 적힌 출발지는 10.0.1.3에 속한 게이트웨이인 10.0.1.1의 위치로 변경한다. 당연히 L2의 목적지는 10.0.1.3 서버의 위치이다.
전반적인 구조는 L2 레이어 통신과 유사하다. L3 레이어에서는 게이트웨이 주소와 목적지 서버의 물리 서버 주소를 알아내기 위해 Mapping Service에 2번 요청을 보낸다. 그리고 물리서버에서 목적지 서버로 패킷을 전달할 때, 목적지 서버가 속한 게이트웨이 주소로 L2 출발지 MAC주소를 변경한다. 이는 출발지와 목적지 서버가 같은 서브넷이 아니기 때문이다. 실제로 10.0.0.1 게이트웨이에서는 10.0.1.3으로 패킷을 전달할 수 없다. 10.0.1.1 게이트웨이로 패킷을 전달해야 10.0.1.3 서버로 패킷을 안전하게 전달할 수 있다. 이를 위해 AWS는 중간에서 헤더를 바꿔주는 작업을 해주는 것이다.
지금까지 AWS VPC 내부적으로 L2, L3 레이어에서 어떻게 통신이 이루어지는지 알아보았다. EC2 서버를 올리고 아무런 요청이 없는 상태에서 ARP 패킷을 확인해 보면 아무것도 잡히지 않는다. 이는 AWS에서는 ARP를 통해 MAC주소를 요청하지만, 실제로는 ARP Flooding이 아닌 Mapping Service를 사용하기 때문이다. Mapping Service 덕분에 우리는 안전하게 우리의 VPC에 있는 서버끼리 통신할 수 있다.
아쉽게도 2015년 자료를 기반으로 만들었기 때문에 지금은 많이 바뀌었을지도 모른다. 하지만 기본적인 콘셉트 자체는 바뀌지 않았으리라 생각한다. 만약 새로운 방식으로 바뀌었다면, AWS가 조만간 또 놀라운 가상 네트워크의 예술을 보여주리라 믿는다.
https://www.youtube.com/watch?v=3qln2u1Vr2E&t=2126s&ab_channel=AmazonWebServices