CDN , Architecture ,

5시간 만에 CDN 개발하기

by Mimul FollowApril 12, 2021 · 10 min read · Last Updated:
Share this

CDN 도입이나 오픈소스로 구현하려고 할 때 고민해야할 부분들에 대해 잘 정리된 포스트 The 5-hour CDN이라는 글에 대해 번역해 보았습니다.

CDN(Content Delivery Network)이라는 용어는 Google과 같은 대기업이 여러대의 하드웨어를 관리하고 초당 수백 기가비트의 데이터를 처리하는 모습을 상기한다. 그러나 CDN은 단순한 웹 애플리케이션이다. 우리가 생각하는 이미지와는 다르지만 사실이다. 8년 전에 구입한 노트북을 가지고 커피숍에서 제대로 작동하는 CDN을 구축할 수 있다. 이 포스트에서는 앞으로 5시간 만에 CDN을 개발하려고 할 때 여러분이 생각할 수 있는 것들에 대해 말할 것이다.

우선 CDN의 정확한 기능을 정의할 필요가 있다. CDN은 중앙 저장소(통칭 origin)에서 파일을 찾아 사용자와 가까운 위치에 복사본을 저장한다. 초기의 오리진은 CDN의 FTP서버였다. 현재 오리진은 웹 앱일 뿐이며 CDN은 프록시 서버 역할을 한다. 즉, 우리가 구축하려는 CDN은 분산 캐싱 프록시이다.

캐싱 프록시

HTTP는 복잡하고, 까다로우며, 보안 위협이 되는 것들도 캐시 인프라에서 정의할 수 있다. 그래서 복잡하고 위협적이니깐 처음부터 구축하고 싶은 욕구는 자제하고, 다른 사람이 만들어 준 것을 이용하자.

CDN에는 몇가지 옵션이 있다. Varnish(스크립트, Edge Side Includes, 개발자 블로그 있음), Apache Traffic Server(도입하면 올해 새롭게 ATS를 채용하는 유일한 팀이 될 것), NGINX(우리도 이미 이용하고 있다)를 이용해도 괜찮다. 그러나 확실히 단언컨데, 어느 것을 선택해도 잘 다루기는 어렵다. 모두 시도해 보고 가장 사용하기 쉬운 것을 선택하자.

(Netlify는 ATS로 제작되었고, Cloudflare는 NGINX, Fastly는 Varnish를 사용한다.)

앞으로 구축하는 CDN의 수준은 기본도 안될 것이다. 그렇다고 전혀 사용할 수 없는 수준도 아니다. 우리가 해야 할 일은 구식 철도를 가지고 여러 도시에서 운영하는 것이다. 호주 사용자를 시드니 서버로, 칠레 사용자를 산티아고 서버로 안내할 수 있다면 CDN이라고 부를 수 있을 것이다.

트래픽 라우팅

사용자를 가장 가까운 서버로 라우팅하는 문제에 대해 기본적으로 세가지 옵션이 있다.

  • 애니캐스트: 라우팅 가능한 주소의 블록을 검색하고 BGP4를 통해 여러 위치로 전송한다. 그리고는 Twitter에서 커뮤니티나 루트 리플렉터에 대해 관심있는 척 해 보자. 대신 인터넷이 라우팅을 처리해준다. 이 방법의 단점은 어렵고, 인터넷이 먹통일 수 있다. 장점은 참지 못할 것이다.
  • DNS: 트릭 DNS 서버를 실행하고 IP의 지리적 위치를 기반으로 특정 서버의 주소를 반환한다. 단점은 인터넷에서 위치를 확인할 수 있는 DNS 소스 주소가 줄어들고 있다는 점이다. 이점은 도움없이도 어디에서나 도입할 수 있다.
  • 게임 서버와 유사한 방식 : 여러 서버에 핑을 보내고 최고의 서버를 활용한다. 단점은 자신의 클라이언트가 필요하다. 하지만, 당신은 자신의 클라이언트가 아니어도 되기 때문에 문제가 되진 않는다.

일반적으로 이 3가지 옵션 중 (1)과 (2)를 각각 조금씩 이용하게 된다. DNS의 로드 밸런싱은 매우 간단하다. 직접 구축할 필요는 없으며 DNSimple과 같은 기업 서비스를 이용하여 DNS를 호스팅하고 주소를 반환하는 규칙을 정의하기만 하면 된다.

애니 캐스트는 난이도가 높다. 설명해야 할 사항이 많이 있지만 이 포스트에서는 더 이상 말하지 않겠다. 대신 저희 서비스를 이용하면 애니캐스트 주소가 있는 앱을 2분 내에 구현할 수 있다. 당사를 광고하는것처럼 되었지만, 사실이기도 하다.

CDN 개발로 돌아가서 NGINX를 각 도시 그룹으로 설정하고 트래픽 라우팅을 위해 DNS 또는 애니캐스트를 실행하면 이제 90%가 완료된 것이다. 그러나 나머지 10%를 달성하는데에는 몇 달이 걸린다.

인터넷 장애

심해 바닥에 깔린 해저 케이블은 항상 가까이 항해하는 선박으로 절단되는 위험에 노출되어 있다. 하지만 육지가 바다에 비해 안전한 것은 아니다. 과거의 네트워크 기술자는, "굴삭기로 깊게 파면, 백본(회선)이 안 된다"라고 자주 화자되었다. 회선이 끊어지는 우려는 서버 1대를 1곳에서 가동시키는 것만으로는 그다지 신경이 쓰이지 않을 것이다. 2대 이상 가동하면 점차 신경이 쓰이게 된다. 전세계에서 서버를 실행하면 걱정으로 밤에도 잠을 잘 수 없게 된다.

그러나 다행히도 하나의 NGINX를 여러 도시에서 실행하면 큰 중복성을 즉시 확보할 수 있다. 서버중 하나가 어떤 이유로 다운되었더라도 많은 서버로 트래픽을 보낼 수 있다. 한 서버가 오프라인 상태인 경우에도 나머지 서버는 여전히 많은 사용자에게 서비스를 제공할 수 있다.

이것을 실현하는 작업은 지루하지만 간단하다. 먼저 헬스 체크를 수행한다(덧붙여서, CDN의 지역이 다운되면 일반적으로 통신 속도가 느려진다. 따라서 상태 확인으로 감지할 수 있어야 한다). 그러면 NGINX 서버가 다운된 타이밍을 알 수 있다. 이에 따라 DNS를 변경하는 스크립트를 실행하거나 BGP의 루트를 취소한다(경우에 따라 해당 지역의 BGP4 서비스를 중지).

이것은 서버 장애이므로 발견하기 쉽지만, 인터넷 장애는 발견하기가 어렵다. 여러 위치에서 외부 헬스 체크를 실시해야 하기 때문이다. 그러나 여러 관점을 가진 기본적인 모니터링은 어렵지 않다. 당사는 Datadogupdown.io를 이용하고 있으며, 독자적인 사내 서비스도 개발 중이다. 필요한 정보는 cURL에서 알 수 있는 정보와 큰 차이가 없어야 한다. 다시 말하지만 CDN에 대해 특별히 주의해야 할 점은 지역의 통신 속도가 느려져도 인터넷이 완전히 차단되는 것은 아니다.

덧붙여서, 이러한 모니터링 옵션은 모두 다른 데이터 센터에서 자신의 데이터 센터로의 통신을 모니터링하는 것이다. 데이터 센터 간의 트래픽은 출발점으로는 나쁘지 않으며 통신량도 엄청나다. 그러나 사용자는 데이터 센터에 (아마도) 살지 않기 때문에 대표적인 트래픽이라고 할 수 없다. 사이트가 매우 인기가 많다면 실제 고객의 상황을 잘 알 수 있는 모니터링이 바람직하다. 이러한 요구에 부응하여 수백개의 기업이 일반적으로 몰래 내장된 JavaScript 버그 형태로 RUM(Real User Monitoring) 서비스를 판매하고 있다. 내가 좋아하는 RUM은 술쪽의 럼이지만. 이것을 많이 마시면서 Honeycomb.io를 이용하여 스스로 모니터링용 코드를 추가한다.

인터넷의 짜증나는 문제에 정말로 최악이다. 그러나 다행히도 많은 사람들이 인터넷을 사용하는 동안 해결책을 짜내고 있기 때문에 이 문제에 대해 많이 말할 필요가 없다. 캐시의 작동 방식은 더욱 흥미롭다. 이하에서는 양파 모양의 레이어에 대해 이야기 하자.

황금 캐시 히트률

캐시 효율성의 지표를 "캐시 비율"이라고 한다. 캐시 비율은 오리진에서 데이터를 읽는 경우에 대해 캐시에서 데이터를 읽는 비율을 측정한 것이다.

캐시 비율 80%는 "요청을 받으면 80%는 캐시에서 데이터를 제공할 수 있지만 나머지 20%는 요청을 오리진으로 전달해야 한다"는 의미이다. CDN과 같은 서비스를 구축하는 경우 캐시 비율이 높은 것이 좋다.

앞서 언급한 GitHub 리포지토리의 링크에 액세스 해 당사의 나이브한 NGINX 설정 파일을 봤다면 독립된 단일 서버라는 것을 눈치챘을 것이다. 이것을 20곳에 도입하면 20대의 개별 서버를 제공할 수 있다. 정말 간단하다. 그러나 단순성에는 비용이 들고, 지역별 복제본이 없다. 20대의 서버는 모두 오리진에 요청을 보내야 한다. 이 구조는 취약하고 캐시 비율이 낮을 것이다. 더 좋은 방법이 있어야 한다.

단순한 방법은 각 리전에 두번째 서버를 추가하면 복제율 쉽게 높일 수 있다. 그러나 이것은 캐시 비율을 크게 저하시킬 수 있다. 서버를 1대만으로 하는 메리트는, 모든 유저에게 1개의 캐쉬를 호스팅하면 된다는 점이다. 서버가 2대라면 오리진 1대당 요청 건수는 2배가 되고 캐시가 없는 경우도 두배가 되게 된다.

이 문제를 해결하려면 서버가 서로 통신하고 다른 서버가 콘텐츠를 캐시하는 경우 요청할 수 있게 해야한다. 이를 위한 가장 쉬운 방법은 캐시 샤드를 만드는 것이다. 데이터를 분리하고 각 서버가 데이터 청크를 가지고 있고 사용자로부터 요청받은 서버는 캐시 샤드를 보유한 서버로 요청을 전달한다.

복잡해 보이지만, NGINX에 탑재된 로드 밸런서는 해시 기반 로드 밸런싱을 지원한다. 이 기능은 요청을 해시하고 서버를 사용할 수 있다고 가정하여 "동일한 요청"을 동일한 서버로 전달한다. 이 블로그의 홈 버전을 보는 사람은 여기에서 바로 사용할 수 있는 NGINX 클러스터의 예시를 확인할 수 있다. 이 클러스터는 피어를 발견하고 URL을 해시하며 사용 가능한 서버를 통해 요청을 보낸다.

NGINX-IMAGE

a.jpg요청이 NGINX 인스턴스에 도달하면 모든 인스턴스가 요청을 클러스터의 동일한 서버로 전달한다. b.jpg에 대해서도 마찬가지다. 이 설정을 통해 서버는 로드 밸런서의 프록시와 스토리지 샤드 기능도 일부 수행한다. 또한 고급 기능을 CDN에 탑재하려는 경우 이러한 레이어를 분리할 수도 있다.

PR을 위한 약간의 여담

클러스터링된 NGINX 예제에서는 Fly를 사용한다. 우리는 Fly의 기능이 정말 훌륭하다고 생각한다. 영구 볼륨은 NGINX 업그레이드 동안에도 캐시 비율을 높은 수준으로 유지하는 데 기여한다. 암호화된 프라이빗 네트워킹은 NGINX 간의 안전하고 간편한 커뮤니케이션을 제공하여 mTLS의 복잡한 설정에 대한 수고를 덜어준다. 기본 제공 DNS 검색 서비스는 서버를 추가 및 삭제할 때 클러스터를 최신 상태로 유지하는 데 도움이 된다. 우리가 이러한 기능을 CDN형 워크로드 전용으로 구축했기 때문에 CDN과 너무 완벽하게 일치하고 있지 않을까 생각될지도 모르겠다. 물론 위의 기능은 모두 Fly가 아니더라도 실현할 수 있다. 그러나 Fly라면 간단해진다.

양파 모양의 레이어

두가지 진실: 높은 캐시 히트율은 좋고 인터넷은 악이다. 일석이조를 좋아하는 사람이라면 캐시율 문제와 인터넷 라우팅이 잘 되지 않는 문제를 동시에 해결할 수 있다면 좋을 것이다. 두가지 문제를 모두 해결하려면 인터넷이 HTTP 요청을 복잡하게 처리하지 못하게 해야 한다. 캐시 비율을 높이려면 제어할 수 없는 인터넷은 우회하여 제대로 작동하고 신뢰할 수 있는 네트워크를 통해 오리진에 요청을 보내는 것이다.

CDN은 일반적으로 고객의 오리진에 가까운 지역에 서버를 설치한다. 우리의 NGINX 예제를 버지니아주로 설정하면 즉시 AWS의 가장 큰 리전 근처에 서버를 소유하게 된다. 그리고 당신은 확실히 AWS를 사용하는 고객이 있어야 한다. 이것이 대규모로 강력한 독점 기업에 다가가는 장점이다.

ORIGIN-IMAGE

NGINX와 프록시를 조금 조정하면 오리진 서버에 대한 모든 요청을 버지니아를 통해 보낼 수 있다. 이것은 기쁜 일이다. 이는 버지니아 서버와 고객의 서버 us-east-1(AWS 버지니아 북부 지역) 사이에 인터넷의 데이터 통신을 방해하는 것들이 적기 때문이다. 이제 특정 고객의 요청을 처리하는 단일 공식 서버 군을 설정할 수 있다.

다행이다. 위의 설정은 캐시 비율을 향상시킬 뿐만 아니라 나쁜 인터넷 환경으로의 라우팅을 피할 수 있다. 또한 추가 CDN 기능의 기반이 되기도 한다.

CDN을 선택하려고 하면, "Shielding(잠금)"이나 "Request Coalescing(요청 집계)"이라고 하는 용어를 접하게 될 것이다. 오리진의 쉴딩은 일반적으로 모든 트래픽을 알려진 데이터 센터를 통해 전송하는 것을 의미한다. 오리진 서버로의 트래픽을 최소화할 뿐만 아니라 자신의 CDN 리전에서 사용하는 IP를 알 수 있기 때문에 간단한 L4 방화벽 규칙을 통해 액세스를 제어할 수 있다.(그래서 오리진 서버를 보호할 수 있다.)

또한 요청 집계를 통해 오리진 트래픽을 최소화할 수 있다. 이 방법은 많은 사용자가 동일한 콘텐츠에 액세스하려고 하는 대규모 이벤트에서 특히 유용하다. 100,000명의 사용자가 한번에 작성한 최신 블로그 게시물에 액세스하고 아직 게시물이 캐시되지 않은 경우 오리진에 100,000건의 요청이 동시에 전송될 수 있다. 이것은 대부분의 오리진이 처리할 수 없는 트래픽 수준이다. 이 문제를 해결하려면 특정 URL을 잠금을 하고 하나의 NGINX 서버가 요청을 원본으로 보내는 동안 다른 클라이언트는 파일이 캐시될 때까지 일시 중지해야 한다. 당사 클러스터 NGINX의 예에서 이 설정은 단 2줄만으로 된다.

통신이 느리면

한 리전을 통해 요청을 보내 캐시 비율을 높이는 것은 약간의 속임수이다. CDN 전체의 목적은 사용자를 위해 속도를 높이는 것이다. 콘텐츠를 캐시한 NGINX 서버의 응답은 거의 항상 오리진 서버보다 빠르기 때문에 싱가포르에서 버지니아로 요청을 보내면 속도가 조금 빨라진다. 그러나 이것으로는 속도가 느리고 만족이라고 말할 수 없다.

이 문제는 양파 모양의 레이어를 늘려 해결할 수 있다.

ORIGIN-IMAGE2

호주로부터의 요청을 싱가포르를 통해 버지니아로 보내면 된다. 호주에서 버지니아까지 14,624km는 빛의 속도에도 시간이 걸린다. 따라서 호주에서 4,300km 거리의 ​​싱가포르에 요청을 보내고 싱가포르 캐시를 사용하여 눈에 띄는 지연을 피할 수 있다. 이 경우 캐시가 없으면 직접 버지니아에게 요청을 보내는 것보다 통신 속도는 약간 느려진다. 그러나 여기에서의 차이는 '짜증날 정도의 느림'과 '짜증날 정도의 느림보다 150밀리초 느린'의 차이 정도는 작은 것이다.

범용 CDN을 구축하는 경우 이것은 좋은 방법이다. 각 리전의 캐시 데이터를 집계하는 몇 개의 광역 리전을 설정하면 된다.

범용 CDN이 아니라 단순히 애플리케이션 속도를 높이고 싶다면 이 솔루션은 불안정할 수 있다. 애플리케이션의 일부를 여러 리전에 분산시키는 것이 더 좋다.

현재 우리는 어디까지 와있나?

CDN의 기본 아이디어는 새로운 것이 아니라 쉽게 이해할 수 있다. 그러나 CDN 구축은 전통적으로 야심찬 팀을 위한 사업이었고, 개인 개발자가 주말에 다루는 프로젝트는 아니다.

그러나 지금은 유용한 CDN을 구축하기 위한 기본 요소를 NGINX 등의 툴로 이용할 수 있게 되어 있다. 집에서 위에서 설명한 GitHub 저장소를 사용해 보면 여기에서 다루는 가장 복잡한 디자인 반복, 즉 리전별 중복성이 있으며 요청의 리전 간 라우팅을 기본적으로 제어 할 수 있는 디자인조차도 심지어 대부분의 경우 NGINX를 설정하기만하면 된다. 게다가 그 설정도 그리 복잡하지 않다. 당사가 추가한 '코드'는 주소를 플러그인하기에 충분하다.

이것으로 CDN의 완성되었다. 간단한 캐시라면 문제 없게 해낼 수 있다. 그러나 복잡한 앱에서 사용하기에는 부족한 부분이 있다.

특히 이 기사에서는 캐시의 만료에 대해 전혀 다루지 않았다. CDN을 이용할 때 반드시 알아야할 철칙은 점심 때 릴리스에서 부끄러운 오타가 있어, 그리고 알아채는 것이 늦었고, 모든 캐쉬 서버에 오타가 들어간 카피가 보존되는 것이다. 분산된 캐시의 삭제는 CDN에 어색하고 큰 문제이다. 이 문제만으로 전체 1개의 포스트를 쓸 수 있다.

CDN 레이어는 앱 기능을 추가하는 곳으로도 매우 좋다. 이미지 최적화, WAF, API 속도 제한, 봇 감지 등의 기능을 사용할 수 있다. 이러한 테마만으로 10개 기사를 쓸 수 있다.

마지막으로 또 한가지. 앞서 언급했듯이 이 기사는 전반적으로 편향되어 있다. 우리가 이 CDN 설계를 강조하고 있는 것은, 그것을 매우 간단하게 실현할 수 있는 플랫폼을 당사가 구축했기 때문이다(여러분도 꼭 테스트해 주세요). 이 플랫폼의 기능을 활용하면 Fly로 CDN을 쉽게 구축할 수 있을 뿐만 아니라 전체 애플리케이션을 쉽게 배포할 수 있다. 에지 전달을 위해 설계된 애플리케이션은 CDN이 전혀 필요하지 않을 수 있다.