AWS에서는 어떤 웹 서비스를 구현할 때 3 계층 구조 (3 Tier Architecture) 를 따르도록 권장하고 있습니다. 기본적으로 사용자에게 노출되는 웹 서버, API, 그리고 데이터베이스 순서대로 계층을 구분하고, 각 계층은 인접한 계층으로만 엑세스가 가능하도록 제한함으로써 보안을 강화하는 구조입니다.

서비스의 규모가 점차 커지게 되면 여러 계정 혹은 VPC를 이용하여 각 서비스의 모듈을 별도로 개발하고 이런 모듈들을 모아서 하나의 큰 서비스를 구현할 수 있습니다. 그리고 각각의 서비스가 서브 도메인들로 나뉘어 있다고 생각해봅시다. 그러면 로그인 상태 검증을 위해서 sso.example.com 같은 도메인을 호출하게 될 것입니다. 혹은 다른 계정에서 API 도메인 api.example.com을 호출할 수도 있습니다. 만약 이 도메인들이 퍼블릭 도메인이라면 요청은 인터넷을 통해서 전달될 것입니다.

물론, 사내 서비스의 경우에는 별도의 프라이빗 도메인을 추가하여 처리할 수 있을 것입니다. 그럼에도 불구하고 계정이 다른 경우에 프라이빗 도메인을 공유하기 위해서 여러 절차를 거쳐야만 합니다. 이번 글에서는 실제로 OpsNow에서 코드 리포지토리인 Bitbucket 서버에 대한 퍼블릭 접근을 최소화했는지 살펴보도록 하겠습니다.

프라이빗 도메인 사용하기

AWS의 Route 53 서비스에 접속하면 호스팅 영역 생성 버튼을 찾을 수 있습니다.

호스팅 영역은 example.com을 등록하면 *.example.com에 대한 레코드등록이 가능합니다.

사용하고자 하는 프라이빗 도메인을 입력하여 호스팅 영역을 등록합니다.

호스팅 영역을 적용하고자 하는 VPC를 추가해야 합니다.

생성이 완료되면 위에서 선택한 VPC 내의 모든 인스턴스에서 nslookup을 통해 DNS에 정보가 잘 반영된 것을 확인할 수 있습니다.

nslookup -q=NS example.com
Server: 10.200.0.2
Address: 10.200.0.2#53

Non-authoritative answer:
example.com nameserver = ns-1024.awsdns-00.org.
example.com nameserver = ns-1536.awsdns-00.co.uk.
example.com nameserver = ns-512.awsdns-00.net.
example.com nameserver = ns-0.awsdns-00.com.
Code language: Bash (bash)

단일 계정에서는 이처럼 매우 간단하게 설정할 수 있습니다. 이제 서브 도메인 레코드를 호스팅 영역에 등록해서 사용하시면 됩니다.


그렇다면, 계정 A와 VPC 피어링을 맺은 다른 계정 B의 VPC에서도 프라이빗 도메인을 함께 이용하고 싶습니다. 어떻게 해야 할까요? 두 가지 방법이 있습니다.

  • Route 53의 프라이빗 호스팅 영역에 다른 계정의 VPC 등록하기
  • Route 53 Resolver를 이용하여 다른 VPC의 DNS 서버만 이용하기

결론부터 말씀드리자면, OpsNow에서는 후자를 선택했습니다. Split-View DNS라는 방식을 적용하기 위해서 퍼블릭과 프라이빗 도메인을 모두 사용 가능한 구조를 갖춘 것입니다. 각각을 어떻게 설정하는지 알아보고 그 이유를 살펴보도록 하겠습니다.

프라이빗 호스팅 영역에 다른 계정의 VPC 등록

특이하게도, 계정 A에서 생성한 프라이빗 호스팅 영역에 계정 B의 VPC를 등록하기 위해서는 반드시 CLI를 이용해야 합니다. 콘솔상에서는 작업을 처리할 수 없기 때문에 꽤나 번거로운 편입니다. 관련하여 자세한 내용은 AWS의 문서를 참고하시기 바랍니다.

가장 먼저, 계정 A의 프라이빗 호스팅 영역에 다른 VPC가 추가될 수 있도록 권한을 제공해야 합니다. 계정 A에서 다음과 같은 명령을 수행합니다.

aws route53 create-vpc-association-authorization \
--hosted-zone-id <hosted_zone_id> \
--vpc VPCRegion=<vpc_region>,VPCId=<vpc_id>
{
"HostedZoneId": "***",
"VPC": {
"VPCRegion": "ap-northeast-2",
"VPCId": "***"
}
}
Code language: PHP (php)

여기서 hosted_zone_id는 계정 A의 것을, vpc_region과 vpc_id는 계정 B의 것을 입력합니다. 계정 B의 VPC에서 계정 A에 대한 연결 권한이 설정되었으므로, 이제 계정 B로 넘어가서 연결 절차를 진행합니다.

aws route53 associate-vpc-with-hosted-zone \
--hosted-zone-id <hosted_zone_id> \
--vpc VPCRegion=<vpc_region>,VPCId=<vpc_id>
{
"ChangeInfo": {
"Id": "/change/***",
"Status": "PENDING",
"SubmittedAt": "2020-12-05T07:57:59.379000+00:00",
"Comment": ""
}
}
Code language: PHP (php)

이제 잠시 기다리면 연결이 진행되고 연결이 완료된 계정 B의 VPC에서도 프라이빗 도메인을 사용할 수 있게 됩니다. 실제로 NS 레코드를 찾을 수 있게 되었습니다.

nslookup -q=NS example.com
Server: 127.0.0.53
Address: 127.0.0.53#53

Non-authoritative answer:
example.com nameserver = ns-1024.awsdns-00.org.
example.com nameserver = ns-1536.awsdns-00.co.uk.
example.com nameserver = ns-512.awsdns-00.net.
example.com nameserver = ns-0.awsdns-00.com.
Code language: PHP (php)

위 과정을 되돌리기 위해서는 disassociate-vpc-from-hosted-zone 명령과 delete-vpc-association-authorization 명령을 같은 옵션, 역순으로 진행하시면 됩니다.

이와 같이 프라이빗 도메인을 VPC 사이에 연결하는 것에는 치명적인 단점이 존재합니다. 이미 다양한 서비스를 example.com에 대해서 구축해놨는데, 갑자기 프라이빗 호스팅 영역이 생성되면 이를 우선적으로 조회하고 퍼블릭 DNS에는 연결되지 않아서 프라이빗 호스팅 영역에 레코드가 존재하지 않는다면 아무 곳으로도 연결이 되지 않습니다.

예를 들어, test.example.com을 퍼블릭하게 사용하다가 위와 같이 연결이 되면 프라이빗 호스팅 영역에서 test.example.com을 우선 조회하고 없으면 연결이 되지 않습니다! 이미 수많은 서브 도메인이 존재하기 때문에 선택적으로 프라이빗 도메인을 사용할 방법을 찾아야만 했습니다.

Route 53 Resolver 사용하기

Route 53에는 DNS 해석기 (Resolver)가 존재합니다. 이 기능은 VPC 내에서 특정 도메인을 호출할 때, 해당 도메인에 대한 DNS 요청을 VPC 내에서 처리하지 않고 원하는 다른 DNS 서버로 전달하는 것입니다. 느낌이 오시나요?

계정 A에서는 다른 계정에서 호출이 넘어올 수 있도록 인바운드 엔드포인트를 설정합니다. 보안 그룹은 반드시 53번 포트를 허용해야 합니다. 그리고 아이피는 기억하기 쉽게 수동으로 지정하는 것이 좋습니다. 저는 53번을 선호합니다.

안정성을 위해 최소 2개 이상의 아이피를 등록합니다.

계정 B에서는 다른 계정으로 호출을 할 수 있도록 아웃바운드 엔드포인트를 설정합니다. 보안 그룹은 아웃바운드만 허용하면 됩니다. 아이피는 특별히 기억할 필요가 없으므로 자동으로 선택해도 됩니다. 생성 화면은 인바운드와 유사하므로 생략하겠습니다. 아웃바운드 엔드포인트는 인바운드와 달리 규칙을 설정할 수 있습니다. 규칙을 추가하는 순간 계정 B의 아웃바운드 엔드포인트에 연결된 VPC에서는 해당 규칙에 따라서 도메인을 전달하게 됩니다.

규칙 생성을 눌러 아웃바운드 엔드포인트를 선택하고 example.com에 대해 전달 규칙 유형을 생성합니다. 시스템 규칙 유형은 전달하지 않고 내부에서 처리한다는 의미입니다. 전체 도메인과 서브 도메인의 전달, 시스템 규칙을 조합하면 원하는 도메인에 대해서만 전달을 할 수 있습니다. 규칙 생성시에 아이피는 계정 A의 인바운드 엔드포인트에서 설정한 아이피를 입력하면 됩니다.

전달은 DNS 쿼리를 “전달” 한다는 뜻입니다.

이제 모든 설정이 완료되었습니다. 규칙이 활성화 되기까지 잠시 기다립니다. 그리고 계정 B에서 nslookup을 해보면 계정 A의 인바운드 엔드포인트 아이피를 DNS 서버로 하여 쿼리가 되는 것을 확인할 수 있습니다.

nslookup -q=NS example.com
Server: 10.200.6.53
Address: 10.200.6.53#53

Non-authoritative answer:
example.com nameserver = ns-512.awsdns-00.net.
example.com nameserver = ns-0.awsdns-00.com.
example.com nameserver = ns-1024.awsdns-00.org.
example.com nameserver = ns-1536.awsdns-00.co.uk.
Code language: PHP (php)

이렇게 해서 계정 B에서는 계정 A의 프라이빗 도메인을 원하는 서브 도메인만 골라서 이용할 수 있게 되었습니다. 위 과정에서 문제가 있을 경우에는 다음 사항들을 살펴보시기 바랍니다.

  • 인바운드 엔드포인트 보안 그룹 53 포트 허용 여부
  • 아웃바운드 엔드포인트 보안 그룹 아웃바운드 허용 여부
  • Route 53 해석기 내에 규칙 활성화 여부
  • 계정 A와 B의 VPC 피어링 여부
  • 계정 A와 B의 각 VPC에 대한 라우팅 테이블 등록 여부

자, 이제 이렇게 해서 계정 A에 위치한 Bitbucket에는 앞단에 Internal CLB를 생성하고 Route 53에서 프라이빗 도메인을 생성 및 할당하였습니다. 물론, Internet-facing CLB에는 GoDaddy를 통해서 퍼블릭 도메인이 할당되어 있습니다.

OpsNow 인프라가 위치한 여러 계정들은 이제 VPC 피어링을 통해서 계정 B에 설정한 것과 같이 Route 53 해석기를 설정하였고, 프라이빗 도메인을 통해서만 Bitbucket에 안전하게 접근할 수 있게 되었으며, 이로 인해서 보안이 강화되었습니다. 보안 그룹을 정리한 끝에, 기존 91개의 보안 그룹 인바운드 룰을 현재 24개로 줄였으며, 앞으로 10여개를 더 줄일 수 있을 것으로 기대됩니다.