시나리오
가비아로 구매한 도메인을 버셀로 배포된 프론트와 EC2에 배포된 백과 연결하려고 한다. 이때 프론트는 www.도메인.com
의 서브 도메인일 때, 백은 api.도메인.com
의 서브 도메인일 때 전달되어야 한다. 또한, 버셀에서는 http로 API 연결하는 게 불가능하므로 백은 https로 배포되어야 한다. 그림으로 따지자면 아래와 같은 상황이 된다.
작업 0: 도메인 발급 및 배포
가비아에서 도메인 구매, 버셀에서 프론트 배포, 그리고 EC2에 백 배포는 이미 되어 있다고 가정한다.
작업 1: Route53 생성
먼저 Route53을 생성해야 한다. Route53은 클라우드 도메인 이름 시스템(DNS) 서비스로, 도메인 이름을 AWS 내부/외부 서비스로 매핑하기 위해서 사용된다. Route53에서 호스팅 영역 생성을 클릭한다.
가비아에서 구입했던 도메인 이름을 도메인.com
과 같이 적어준다(서브 도메인X 루트 도메인만O)
그러면 기본적으로 다음과 같은 레코드가 생성된 것을 볼 수 있다.
NS (Name Server) 레코드는 해당 호스팅 영역에 대한 네임 서버 정보를 나타낸다. 네임 서버는 도메인에 대한 DNS(Domain Name System) 조회 요청을 처리하는 서버이다. 이 레코드를 통해 도메인의 DNS 조회가 Route 53으로 라우팅되며, Route 53 네임 서버가 해당 도메인에 대한 DNS 쿼리를 처리한다.
SOA (Start of Authority) 레코드는 호스팅 영역의 기본 설정과 호스팅 영역을 관리하는 주체에 대한 정보를 포함한다. 호스트마다 하나의 SOA 레코드가 존재하며, 주로 호스팅 영역의 소유자 정보와 직렬 번호, 캐시 속성 등을 포함한다.
즉 NS 레코드는 어떤 네임 서버를 봐야 하는지 알려주는 레코드이고, SOA는 일종의 호스팅 설정이나 호스팅 관리주체에 대한 정보를 가진 레코드이다. 둘 다 변경 불가능한 값이기 때문에 너무 신경쓰지 않아도 된다.
작업 2: ACM 생성 및 Route53에 적용
이제 ACM으로 이동해서 인증서를 발급받는다.
퍼블릭 인증서를 요청하고, 도메인 이름은 루트 도메인인 도메인.com
과 서브 도메인을 나타내는 *.도메인.com
에 대해서 둘 다 발급받는다. 는 임의의 문자열을 나타내는 와일드 카드이기 때문에 .도메인.com
에 대해서 발급받으면 www.도메인.com
이나 api.도메인.com
등 모든 서브 도메인에 대해서 발급된 것이다.
처음 생성하면 아직 검증이 되지 않은 상태이다. Route53에서 레코드 생성을 클릭한다.
클릭하면 AWS가 레코드를 생성할 도메인을 보여준다. 루트와 서브 도메인 모두 레코드를 생성해주면 된다.
5~10분 정도 기다리면 검증되었다고 뜬다. 참고로 이렇게 Route53과 연결해줘야 하는 이유는, ACM 자체는 그냥 암호화를 가능하게 해주는 인증서일 뿐이고, 어떤 인증서를 적용해야 하는 건 지를 알기 위해서는 Route53에 등록해줘야 하기 때문이다. ACM 인증서는 암호화 프로그램, Route53은 이 암호화 프로그램으로 향하는 길을 알려주는 표지판이라고 생각하면 될 것 같다.
인증이 성공하면 아래와 같이 CNAME 레코드가 하나 생긴 것을 볼 수 있다. 밑에도 언급하겠지만 CNAME 레코드는 하나의 도메인 네임을 별칭 이름으로 매핑시키는 레코드이다. AWS 내부적으로 ACM은 _98d2646601fa951d53639ac514e785e8.acm-validation.aws.
와 같은 값을 가지고 있기 때문에 이 값으로 연결하기 위해서 CNAME을 사용한다. 마찬가지로 우리가 변경할 필요가 없는 값이다.
작업 3: ALB 생성 및 ACM 적용
이제 EC2의 앞단에 ALB를 생성하려고 한다. 먼저 ALB가 트래픽을 보내주고 싶은 대상 그룹을 먼저 생성한다. 당연히 springboot 서버가 돌아가고 있는 EC2로 보내줘야 하므로, 대상 유형은 인스턴스이고 프로토콜:포트는 http:80이면 된다.
대상 그룹이 만들어졌으면 이제 ALB를 생성한다.
반드시 인터넷 경계로 만들어주고, 네트워크 매핑은 퍼블릭 네트워크에 두어야 한다.
프라이빗 네트워크로 매핑하면 ALB를 접근할 수가 없다(당연함). 꼭 퍼블릿 네트워크로 매핑한다. 참고로 이 글을 읽는 사람들이라면 다른 서비스를 사용하기 위해서 이미 만들어 놓은 퍼블릭 네트워크가 있을 것 같은데, 그냥 ALB를 위해 새롭게 2개의 가용 영역에 퍼블릭 네트워크를 하나씩 만들어주는 것을 추천한다.
그 이유는 각 서브넷의 역할을 명확하게 분리하기 위해서도 있고, ALB는 서브넷 하나당 무려 8개의 IP를 소모하기 때문에 다른 서비스가 존재하는 서브넷에 ALB를 같이 만들어놓으면 나중에 서비스를 확장할 때 ALB 때문에 IP 주소가 부족할 수도 있다.
참고로 ALB도 보안 그룹이 필요하다. HTTP와 HTTPS에 대해서만 Anywhere로 열어놓는다.
앞서 만든 보안 그룹을 ALB에 적용한다.
HTTPS:443 리스너를 하나 만들어주고, 앞서 만들었던 대상 그룹으로 forward하도록 만들어준다. 이제 HTTPS:443로 들어오는 프로토콜은 springboot EC2 서버로 이동한다.
HTTPS는 보안 프로토콜이므로 이 프로토콜을 위한 ACM 인증서가 필요하다. 앞서 만든 인증서를 적용하면 된다. 참고로 아까 Route53에 인증서를 등록해준 것 같은데 ALB에 또 적용해야 하는 이유는, Route53은 ACM으로 향하는 길을 알려주는 표지판이라면 실제로 길을 따라가는 주체는 ALB이기 때문이다.
ALB는 사용자의 HTTPS 연결 요청을 들으면 Route53에 등록된 CNAME 레코드를 따라서 ACM을 찾아가 이 인증서를 클라이언트에게 보내준다. 클라이언트와 서버는 인증서를 바탕으로 Secret Key를 생성하고, 이 키를 바탕으로 HTTPS 프로토콜, 즉 데이터 암호화가 가능해진다.
여기까지 했으면 나머지 설정은 전부 기본으로 두고 ALB를 생성하면 된다.
작업 4: ALB 리스너 수정으로 HTTP→HTTPS
만약 클라이언트가 HTTP로 접근하려고 하는 경우는 어떻게 할까? 그냥 리스너를 안 만들 수도 있지만 이렇게 되면 서버와의 연결이 전혀 불가능하다. HTTP 프로토콜의 접근을 아예 막는 것보다는, HTTP로 접근해도 자동으로 HTTPS를 적용해 암호화해주는 경우가 좋다.
이때 ALB의 redirect를 사용해주면 HTTP to HTTPS 리다이렉트가 가능하다. 로드 밸런서의 리스너 및 규칙에서 리스너 추가를 클릭한다.
HTTP:80 용 리스너를 만들고, “URL로 리디렉션”을 클릭해서 HTTPS:443으로 리다이렉트되게 만든다.
수정하고 나면 로드 밸런서의 리스너 및 기본 작업은 다음과 같이 만들어진다. 이제 HTTP:80→HTTPS:443→대상 그룹(springboot EC2) 전달이 가능해진다.
작업 5: Route 53에 레코드 생성
여기까지 했다면 route53은 지금 이런 상황이다.
여기에 우리는 2개의 레코드를 만들어주어야 한다. 하나는 프론트엔드용 CNAME 레코드이고, 다른 하나는 벡엔드용 A 레코드이다.
A 레코드, 즉 “주소(addresss)” 레코드는 말 그대로 도메인 이름을 IP 주소로 매핑하는 레코드이다. 우리는 api.도메인.com으로 들어오는 트래픽은 ALB로 매핑해야 한다. ALB의 IP 주소를 우리는 모르지만 당연히 AWS 내부적으로 사용하는 IP가 있다. 이 IP에 매핑해야 하므로 백엔드를 위해선 A 레코드를 사용한다.
캐노니컬 네임 레코드, 줄여서 CNAME 레코드는 하나의 도메인 네임을 별칭 이름으로 매핑시키는 도메인 네임 시스템의 리소스 레코드의 일종이다. 버셀에서는 프론트를 배포하면 도메인 이름이 생성되는데, www.도메인.com으로 들어오는 트래픽을 이 버셀의 도메인으로 매핑해야 하므로 프론트를 위해서는 CNAME을 사용해야 한다.
백엔드 매핑을 위해서 레코드 이름으로는 api.도메인.com을, 유형으로는 A 레코드를, 그리고 별칭으로는 앞서 만들었던 ALB를 선택한다.
프론트 매핑을 위해서 레코드 이름으로는 www.도메인.com
을, 유형으로는 CNAME 레코드를, 그리고 값으로는 버셀에서 배포된 도메인을 입력해준다.
작업 6: 가비아에 네임 서버 등록
앞서 Route53으로 생성된 네임 서버를 사용하기 위해서는 가비아에서 등록해주어야 한다. Route53이 만들어준 네임 서버는 “도메인이름-값” 쌍을 가지고 있는 DB라고 생각하면 된다. 이 DB에 DNS 쿼리를 던지기 위해서는 일단 어떤 DB를 사용해야 하는지 알아야 한다. 세상에는 구글 네임 서버 등등의 다양한 NS가 있는데, 이 중 어느 NS를 봐야지 내가 알고 싶은 도메인 이름에 대한 값이 있는 지를 알기 위해서는 가비아에 “이 네임 서버를 찾아가세요”라고 네임 서버를 등록해주어야 한다.
가비아의 마이페이지에서 내 도메인을 찾은 후, 네임 서버의 설정을 클릭한다.
앞서 Route53에서 만들어진 AWS 네임 서버 4개를 등록해주면 된다.
작업 7: Docker 포트 포워딩으로 HTTP:80→TCP:8080
이제 도메인 이름으로 들어오는 트래픽은 EC2에 http:80 트래픽으로 들어온다. 그런데 문제는 우리의 springboot 서버는 tcp:8080을 사용 중이다. http:80 트래픽을 tcp:8080으로 전달하기 nginx를 사용하는 방법도 있지만, 지금은 중간 발표를 위해서 급하게 https를 배포해야 하는 상황이라 nginx를 테스트하기보다는 기존에 이미 사용 중이던 docker의 포트 포워딩만 8080:8080에서 80:8080으로 바꾸어주려고 한다. 원래는 아래와 같이 docker로 springboot 서버를 운영 중이었다:
docker run -d --name backend-server -p 8080:8080 --restart unless-stopped [이미지_이름]
단 한 단어만 수정하는 것으로 간단하게 포트 포워딩이 가능하다.
docker run -d --name backend-server -p 80:8080 --restart unless-stopped [이미지_이름]
여기까지 했으면 이제 www.도메인.com
으로는 프론트로, api.도메인.com
은 백으로 이동하며, http://api.도메인.com
과 같이 접근하면 자동으로 https://api.도메인.com
으로 리다이렉트된다🥳
🎯 Trouble shooting
버셀에서도 호스팅이 가능하고, Route53에서도 호스팅이 가능하다. 그런데 버셀에서는 프론트용 호스팅일 뿐이고 다양한 레코드 생성 등은 불가능하다. HTTPS 트래픽 적용이나, 서브 도메인 분기 등을 사용하려면 Route53을 사용해야 한다. 만약 둘 다 호스팅해주고, 네임 서버를 둘 다 등록해주면 사용하는 네임 서버가 달라질 때마다 API 호출이 되었다가 안되었다가 하는 혼란스러운 상황이 발생한다(내가 경험한 이야기).