LINE Corporation이 2023년 10월 1일부로 LY Corporation이 되었습니다. LY Corporation의 새로운 기술 블로그를 소개합니다. LY Corporation Tech Blog

Blog


Multi CDN 트래픽 모니터링을 위한 클러스터 구축기

시작하며

글로벌 서비스를 제공하는 회사에서는 CDN(Content Delivery Network)을 이용하여 이미지나 동영상, JavaScript, CSS 파일과 같은 리소스나 콘텐츠 전송에 소요되는 시간을 줄이는 작업을 많이 수행합니다. API 호출처럼 콘텐츠를 캐시하기 어려운 경우에는 네트워크에서 발생하는 지연 시간(latency)이 최소화되도록 튜닝한 네트워크를 이용해 불확실한 구간을 줄이고 가속 효과를 얻을 수 있는 CDN 제품을 사용하기도 합니다. LINE에서도 메신저뿐만 아니라 여러 웹 기반의 서비스에서 미디어 파일을 빠르게 송, 수신하고 API 호출 작업 시 지연 시간을 줄이기 위해 CDN을 활용하고 있습니다. 

기술의 발전과 시장의 변화에 맞추어 CDN을 사용하는 방식에도 변화가 일어나고 있습니다. 근래 인터넷 서비스 기업에선 특정한 회사의 단일 CDN 플랫폼을 사용하는 것을 지양하고, 서비스의 성격과 사업을 전개하고 있는 지역에 가장 적합한 CDN 사업자를 발굴하여 전송 품질을 높이면서도 비용을 적절히 관리할 수 있는 방법을 찾아나가고 있습니다. 여기에 직접 개발한 CDN 엔진과 플랫폼을 활용하여 품질과 비용의 황금 접점을 찾아내는 데 많은 노력을 기울이고 있습니다. LINE 역시 비슷한 전략으로 Akamai, AWS CloudFront와 같은 외부 CDN 플랫폼과 함께 자체 개발한 LISA(LINE Site Accelerator)를 통해 사용자에게 훌륭한 경험을 전달하기 위한 환경을 만들고 있습니다.

여러 CDN 플랫폼을 함께 사용하는 것은 품질과 비용 관점에서 얻는 이점이 확실합니다. 하지만 그에 따른 불편함도 불가피하게 발생하는데요. 그런 불편함 중 가장 대표적인 것이 트래픽 모니터링입니다. 하나의 플랫폼이나 제품만을 사용하는 경우 이미 제공되고 있는 모니터링 도구나 API를 이용하여 쉽게 모니터링할 수 있습니다. 하지만, 여러 플랫폼을 같이 쓰게 되면 서로 다른 방식으로 모니터링을 해야 하는 경우가 많아 모니터링하는 게 복잡해집니다. 따라서 모든 것을 한 곳에서 모니터링할 수 있는 방법을 준비해야 합니다. 

모니터링 체계를 구성하기 위한 준비하기

무엇을 어떻게 모니터링해야 할까?

서비스를 운영할 때 필요한 모니터링은 크게 두 가지로 나눌 수 있습니다. 안정적으로 서비스가 운영되고 있는지 확인하기 위한 실시간 트렌드 모니터링이 한 가지이고, 다른 한 가지는 문제 상황이 발생했을 때 상세한 원인을 확인하거나 트렌드 모니터링에서 확인되지 않는 지표를 추출하는데 사용하는 로그 기반의 모니터링입니다. 두 가지 모니터링을 위해서는 필요한 것들이 무척 많습니다. 그런 모든 걸 처음부터 만들어야 했다면 아마 지금도 플랫폼을 만드느라 많은 시간과 노력을 쏟아붓고 있었을 겁니다. 다행히도 LINE에는 훌륭한 팀들이 만들어 낸 여러 가지 공통 플랫폼이 있어서 많은 시간을 단축할 수 있었는데요. 우선 공통 플랫폼으로 제공되는 Datachain이나 Promgen 등을 쉽게 이용할 수 있어서 로그를 적재, 분석, 시각화하는 시스템을 별도로 만들 필요가 없었습니다. 또한 LINE 데이터 센터 내부에서 운영되면서 로그를 생성하는 주체가 일정하게 정해져 있는 자체 개발 시스템인 경우, 공통 플랫폼으로 큰 어려움 없이 로그를 전달하여 모니터링 체계를 만드는 작업을 해낼 수 있습니다.

CDN 트래픽 트렌드를 모니터링하는 그라파나 대시보드

하지만, 외부 플랫폼의 로그를 수집하는 것은 이보다 조금 복잡한 이야기입니다. 우선 Akamai와 Amazon은 서로 다른 회사이기 때문에 제공되는 로그의 포맷이나 내용이 다를 수밖에 없습니다. Akamai만 보더라도 로그를 제공하는 방식이 CloudMonitor, Download Receipt, DataStream으로 나뉘어져 있고 각각 다른 포맷과 내용으로 로그가 제공되고 있는데요. LINE에서는 필요에 따라 커스텀 필드를 추가해서 수집하는 항목들이 있어서 일반적으로 Akamai 로그를 사용하는 것보다 복잡도가 조금 더 높은 편입니다. AWS CloudFront는 단일 로그 포맷을 제공하고 있어서 조금 나은 편이긴 하지만, Akamai의 로그와 비교해 봤을 때 내용이나 포맷이 달라 또 하나의 로그 포맷으로 볼 수밖에 없습니다. 상황이 이렇다 보니 자연스럽게 내부 공통 플랫폼에 데이터를 적재하기 위해 가장 먼저 해야 할 일로 로그 표준화가 떠오를 수밖에 없었습니다.

로그 표준화하기

로그를 표준화하기 위해 우선 현재 제공되고 있는 각 플랫폼의 로그 포맷 분석에 착수했습니다. CDN은 쉽게 생각하면 대규모의 프락시(proxy) 서버를 제공하는 플랫폼이기 때문에 포맷은 다르지만 Apache나 nginx에서 제공하는 액세스 로그의 항목들이 각 사가 제공하는 로그의 기본적인 내용이 됩니다. 사용자 요청에 담긴 호스트 헤더의 값이라던가, 서버가 내려준 HTTP 응답 코드와 같은 것들이 필드의 이름은 조금씩 다르지만 모든 사업자들의 로그 형식에 포함되어 있습니다. 하지만 공통적인 값 이외에 특정 플랫폼에서만 제공하는 값이라던가, 의미는 같더라도 세부 규격이 다른 경우가 있어서 무엇을 살리고 무엇을 버릴지에 관한 문제를 해결해야 했습니다. 모든 것을 일단 다 쌓아두면 언젠가 쓰이지 않을까라는 생각도 잠시 했었지만, 쓰이지도 않을 것을 쌓아두기만 하는 것은 데이터 부채가 될 여지가 다분했습니다.

그동안 발생했던 CDN 플랫폼들의 장애와 트러블슈팅 사례, 그리고 유용하게 활용했던 이력을 살펴보면서 각 로그 형식의 공통분모를 찾고 새로운 필드 이름을 정의하여 추후 새로운 CDN 플랫폼이 추가될 수 있다는 가정하에 Multi CDN을 위한 로그 포맷을 정의했습니다. 인덱싱의 효율을 높이기 위해 유사한 항목을 최대한 합쳐보려는 노력을 했지만 한계가 있었습니다. 가령 AWS CloudFront는 사용자 요청에 대하여 에지(edge) 서버의 처리가 성공했는지 실패했는지를 최초(initial)의 상태 값과 최종(final) 상태 값으로 나누어서 기록하고 있습니다. 반면 Akamai는 사용자의 파일 전송이 성공했는지 실패했는지를 나타내는 값이 존재하고, 이는 이슈가 발생하여 문제를 해결할 때 중요한 단서로 많이 활용되고 있었습니다. 언뜻 보면 파일 전송의 성공을 의미하는, 성격이 비슷한 두 개의 필드지만 각 플랫폼에서 활용하는 방법과 규격이 달랐기 때문에 값을 없애지 않고 유지해야만 했습니다. 이런 항목들은 `cf_` 와 `ak_` 라는 접두어를 붙여 표준 로그 규격에 넣었습니다.

이런 과정을 거쳐 꼭 담아야 하는 로그 항목들을 표준 로그 포맷에 정의하고 나니 "제3의 플랫폼을 또 쓰게 되면 어떻게 할까?"라는 고민이 들었습니다. 그래서, 새로운 CDN 플랫폼이 서비스에 새롭게 활용되는 상황과, 지금은 중요하지 않은 필드라고 생각하여 표준 로그 포맷에서 제외했지만 미래(혹은 가끔)에 수집이 필요한 항목들을 담을 수 있도록 5개의 예약(reserved) 컬럼을 통합 로그 포맷에 추가했습니다. 완벽하지는 않지만 어느 정도 쓰임새가 있는 포맷이 만들어졌다는 판단이 섰습니다. 이렇게 준비된 로그 포맷을 로그 데이터 적재를 담당하는 사내 플랫폼인 Datachain의 규격에 맞추어 필요한 설정을 정의하고 코드를 머지(merge)하여 로그 수집을 위한 기본적인 준비를 마쳤습니다. 이제 실제 CDN 플랫폼에서 로그를 전송받아 필요한 로그 가공 작업을 수행하고 Datachain으로 전달하기만 하면, 문제 해결의 후반전으로 넘어갈 수 있었습니다. 하지만, 본 게임은 여기서부터였습니다.

정의한 표준 로그 포맷은 ProtoBuf 규격으로 기술되어 Datachain 과제에 머지됨

시스템 만들기

실시간 트렌드 모니터링 체계 구축하기

저 수준(raw)의 로그를 이용하여 트렌드 데이터를 매번 추출하는 것은 합리적인 방법이 아닙니다. 로그를 다시 가공하여 트렌드를 확인할 수 있는 데이터로 재가공하는 것은 많은 컴퓨팅 자원을 소모할 뿐 아니라 응답성에 있어서도 그리 좋은 수치를 보여주기 어렵습니다. 그래서 Prometheus를 통해 실시간으로 트렌드 데이터를 취합하여 시각화하는 방식을 이용하기로 했습니다. Prometheus는 워낙 유명한 오픈소스 TSDB(time series database) 플랫폼이기 때문에 여러 애플리케이션을 위한 데이터 추출기, 소위 익스포터(exporter)가 준비되어 있습니다. 그뿐만 아니라 익스포터의 제작에 관한 가이드도 있기 때문에 훌륭한 개발자들이 만든 다양한 익스포터를 어렵지 않게 찾아볼 수 있습니다. 다행히(?) Akamai의 CloudMonitor를 위한 익스포터도 발견할 수 있었습니다.

하지만 이를 그대로 사용할 수는 없었습니다. 커스텀 로그 필드가 많이 추가되어 있었고 필요한 규격에 맞지 않는 문제 등이 있어 수정할 수밖에 없었습니다. 같은 선상에서 트렌드 데이터를 보여주기 위해서는 Download Receipt와 같은 다른 규격도 익스포터가 이해하고 로그를 가공하여 트렌드 데이터를 추출해야 했기 때문에 상당 부분의 코드를 수정한 별도의 익스포터를 준비해야만 했습니다. 이전까지 많이 사용해보지 못했던 Go 언어 기반의 익스포터를 입맛에 맞도록 수정하는 작업은 생각보다 어려웠고 긴 시간이 소요됐습니다. 로그를 표준화하는 것과 마찬가지로, 부족한 필드를 처리하는 것도 신경 쓰이는 부분이었습니다. 그렇지만 결국 무사히 잘 동작하는 바이너리를 만들어 낼 수 있었고, 트렌드 데이터를 Prometheus에 적재할 수 있었습니다.

Prometheus는 손쉽게 구성할 수 있고 다양한 익스포터의 도움을 받을 수 있어 많은 엔지니어가 애용하는 제품입니다. 하지만, 클러스터링이 제공되지 않고 인스턴스가 많아지는 경우 통합 관리가 어려운 부분이 있어 규모가 커질수록 사용하기가 다소 까다로워진다는 단점을 가지고 있습니다. 이를 해결할 방법으로 LINE이 공개했던 Promgen을 이용하기로 했습니다. 이미 내부 용도로 큰 규모의 클러스터가 구성되어 있어서 간단하게 이용할 수 있었습니다. Promgen을 이용하면 익스포터의 등록과 삭제, 알람의 설정과 Prometheus 인스턴스의 운영까지 크게 신경 쓸 부분이 없기 때문에 모니터링 체계의 운영 부담을 크게 줄일 수 있었습니다. 

수많은 시행착오의 연속이었던 Go 기반의 익스포터 개발

로그 적재 시스템 구성하기

개발 과정에서 약간의 어려움을 겪긴 했지만, 트렌드 모니터링 체계의 각 컴포넌트는 큰 부하가 없었기 때문에 구축 이후 운영 과정에서는 특별한 문제가 발생하지 않았습니다. 하지만, 저 수준(raw)의 로그 적재 시스템을 구성하는 건 트렌드 모니터링 체계 구축에 비해 손이 많이 가는 작업이었습니다. 서로 다른 플랫폼에서 전달되는 로그를 적재하기 위해서 표준을 정의하긴 했지만, 호수 위에 떠 있는 백조의 바쁜 발놀림처럼 표준에 맞도록 로그를 가공하는 작업이 필요했습니다. LINE의 데이터 플랫폼은 로그 수신을 위해서 Kafka 기반의 데이터 파이프라인을 제공하고 있습니다. Kafka 자체는 전달되는 데이터의 포맷을 가리지 않기 때문에 지정된 토픽(topic)으로 적재할 로그를 전달하기만 하면 되었습니다. 문제는 토픽을 구독하고 해석하는 Datachain의 컴포넌트에서는 정보를 효율적으로 인덱싱하고 저장해야 하기 때문에 전달되는 로그가 사전에 코드를 통해 약속하고 병합한 ProtoBuf 규격에 맞아야만 했습니다.

규격을 맞추기 위해 널리 사용되는 솔루션 중 레퍼런스가 풍부하고 검증된 Elastic Stack을 이용하기로 했습니다. 스택의 구성 요소 중 Logstash는 다양한 소스로부터 로그를 입력받아 적절히 가공한 후 다음 처리자에게 출력해 주는 역할을 합니다. 입력 로그의 종류별로 형식과 가공해야 하는 대상 필드가 달랐기 때문에 파이프라인과 개별 설정을 만들었습니다. 데이터 플랫폼에서 입력되는 데이터가 적절히 파싱되고 있는지를 실시간으로 보기는 어려웠기 때문에 사내 클라우드 플랫폼 Verda의 Kafka와 VES(Verda Elastic Search)를 이용하여 시험용 플랫폼을 구성하고 사전에 데이터 가공 및 전달 흐름에 문제가 없는지를 조사해 보기로 했습니다.

시험용 플랫폼을 통해 로그를 수집해 보면서 많은 문제점을 확인할 수 있었고, 확인된 이슈들을 수정했습니다. 가령, CSV 형태로 로그를 전달하는 Download Receipt의 경우 Akamai 기술 문서에 기록되어 있는 것처럼 전달되지 않거나 값이 누락되는 경우가 많았고, 예상치 못한 문자열 때문에 Logstash 필터에서 많은 예외 처리를 해야 했습니다. 중첩 JSON 형태로 들어오는 CloudMonitor 로그는, 다시 플랫(flat)한 구조의 JSON 형태로 바꾸고 타입 캐스팅에 문제가 없도록 대소문자와 숫자를 처리하는 데 애를 많이 먹었습니다. 많은 개발에서 그러하듯 시간 정보를 처리하는 것은 특히나 까다로운 작업이었습니다. 유닉스 타임스탬프 값을 어느 정도의 정밀도로 처리해야 하는가도 입력되는 로그 형태에 따라 예외가 많았습니다. Logstash에 전용 입력 플러그인이 존재하는 S3에 적재된 CloudFront의 로그를 처리하는 것이 가장 쉬운 편이었습니다.

시험적으로 몇 가지 도메인의 로그 정보를 수집해 실시간으로 조회해 보면서 정합성을 맞추는 과정의 어려움을 잊을 수 있었습니다. 이제 구성의 마지막 작업으로 Logstash의 출력을 데이터 플랫폼으로 이전하기만 하면 모든 작업이 끝날 것이라고 생각했습니다. 하지만, 복병은 아직도 남아 있었습니다.

초기에 사용했던 로그 수집 체계의 시스템 흐름도

부하를 이겨내라!

LINE은 수억 명의 사용자가 이용하는 플랫폼입니다. 패밀리 서비스라 불리는 앱과 웹 기반의 독립 서비스까지 포함하면 사용자는 더 늘어나기도 합니다. CDN 플랫폼의 도움이 없었다면 이 모든 서비스에서 발생하는 리소스 요청을 직접 감당해 내는 것은 어려웠을 겁니다. 로그 기반의 모니터링 클러스터를 구축하면서 부하에 대한 예상치를 계산해 보긴 했지만, 이 수치가 의미하는 것이 실제로 얼마나 큰 숫자인지에 대해서 다소 순진하게 바라보았던 것이 과제의 최종 보스, '부하(load)'에게 한 방 얻어맞는 상황을 만들고 말았습니다. CDN 플랫폼에서 전달되는 로그는 개수도 많았지만 이를 가공하는 데 들어가는 지연이 생각보다 컸습니다.

Logstash는 메모리나 디스크를 이용한 버퍼를 제공하고 있습니다. 하지만, 데이터의 가공 작업이 지연되거나 출력 작업이 지연되는 경우 크래시를 막기 위해 추가로 인입되는 로그 입력을 429 Too Many Request로 거부하는 동작을 취합니다. 이는 재전송 시도를 하지 않는 이상 수집하려 했던 로그가 소실된다는 것을 의미합니다. Logstash의 처리량은 로그의 가공을 얼마나 많이, 복잡한 연산을 이용해서 하느냐에 따라 달라지고 이는 CPU 코어 개수와 직접적인 연관을 갖고 있습니다. 사내 클라우드에서 제공하는 가장 작은 규모의 VM은 차오르는 로그를 감당하기 어려워했고, 수시로 429 에러를 맞이하며 데이터 처리의 어려움을 겪어야만 했습니다.

Logstash의 부하를 경감시키는 가장 좋은 방법은 입력 로그를 순수(?)하게 사용하는 것입니다. 입력받는 로그를 특별히 복잡한 연산 없이 그대로 출력 대상으로 보내면 처리량을 늘릴 수 있습니다. 하지만, 각 플랫폼의 포맷이 서로 달라 로그 가공이 불가피했기 때문에 Logstash가 제공하는 여러 가지 설정 옵션과 스케일링 가이드에 따라 최적의 구성을 찾는 데 많은 시간을 투자했습니다. 가능한 한 최선의 설정을 찾아 적용했지만, 처리해 낼 수 있는 절대적인 수치의 한계가 있었습니다. 그래서 접근 방향을 바꾸어서, 클러스터의 수평 전개를 손쉽게 할 수 있도록 구성하는 방법을 찾아보기로 했습니다.

클러스터의 에러 응답과의 싸움은 현재 진행형

서버 스케일링 자동화하기

먼저 클러스터의 수평 전개를 위해서 신규 서버 투입 시 필요한 작업 절차를 정리했습니다. 인스턴스의 생성부터 필요한 패키지를 설치하고 설정 파일을 업데이트하는 과정까지 다양한 명령어를 사용해야 했기 때문에 터미널에서 이런 작업을 진행하는 것은 효율적이지 않다는 판단을 내렸습니다. 패키지 정도의 설치까지라면 인스턴스 생성 시에 초기화 스크립트를 활용해 볼 수 있겠지만, 내부 소스 저장소에 접근하여 필요한 설정 파일을 그룹에 따라 구분해서 가져오는 작업을 자동화하는 것까진 무리가 있었습니다. 그래서 같은 팀의 권용찬 님이 구축하신 Ansible 타워의 오픈소스 버전인 Ansible AWX를 이용하여 이 작업을 자동화해보기로 했습니다(권용찬 님의 Ansible 타워 구축기는 이곳(프로비저닝 자동화를 위한 Ansible AWX, 설치부터 엔터프라이즈 환경 적용까지 - 1, 2)을 참고하세요).

Ansible을 어떻게 사용하는지 책과 자료를 통해 가볍게 이해하고는 있었지만 실전에 적용해 보는 것은 처음이었기에 역시 시행착오를 겪을 수밖에 없었습니다. 하지만 한 번 완성된 템플릿은 언제든 멱등성을 기대하며 구동시킬 수 있게 되었고, 지속적으로 증가하는 로그 처리량에 맞추어 두 차례 Logstash 서버 증설 작업에 활용할 수 있었습니다. Ansible은 널리 사용되는 많은 제품과 각 운영체제 환경에 맞는 명령어 셋을 모듈 형태로 제공하고 있습니다. 이를 통해 셸(shell) 스크립트가 가지고 있는 한계와 불편함을 넘어설 수 있고 YAML 형태로 작업을 정의할 수 있어 운영 자원 투입을 극단적으로 줄일 수 있게 해주었습니다. 앞서 이야기한 것처럼 Ansible은 CLI보다도 타워나 AWX를 통해 사용할 때 더 큰 효용성과 편리함을 느낄 수 있습니다.

사내 Ansible AWX의 GUI 콘솔

남은 과제들

우여곡절이 참 많았습니다. 많이 부딪히고 배우면서 그동안 써보지 않았던 많은 기술과 제품을 사용해 볼 수 있었고, Go 기반의 코드까지 만들어 보았습니다. 완전 자동화를 위해서는 여전히 가야 할 길이 많이 남아 있지만, 단기간에 끝낼 수 있는 것은 아니기 때문에 긴 호흡으로 조금씩 보완해 나가야 할 것입니다. 완전한 플랫폼을 갖추는 것보다 더 중요한 것은 준비된 체계를 활용하여 보다 적극적으로 서비스 품질을 모니터링하고 의미 있는 후속 조치를 할 수 있게 만드는 것이라 생각합니다. 직접 손으로 해가면서 소모적으로 시간을 투입해야 했던 일들을 줄이고, 그렇게 확보한 시간을 새로운 기술 연마에 투자하여, 이를 통해 LINE이 새로운 길을 걸어갈 수 있도록 애쓰는 사람들과 나란히 걸어갈 수 있을 때 고생한 것에 대한 진정한 보상을 받게 되지 않을까라고 생각해 봅니다. 시행착오를 통해 만든 클러스터가 사용자에게 유용한 도구가 될 수 있도록 시즌 2에서 할 일들을 정리하며 한 해를 마무리해야겠습니다.