JD의 블로그

넷플릭스 마이크로 서비스 가이드 본문

클라우드/Devops

넷플릭스 마이크로 서비스 가이드

GDong 2020. 5. 13. 01:34

이 글은 넷플릭스 마이크로 서비스 가이드 발표 자료를 기반으로 정리한 내용입니다.

넷플릭스가 마이크로 서비스 아키텍쳐로 서비스를 함에 따라 겪었던 도전과 그에 따라 어떻게 이를 해결했는지에 대한 내용을 담고 있습니다.

 

 

1. 배경

넷플릭스는 8600만 사용자를 지니고 있으며 전 세계 190여 개 나라에서 서비스 중이며 모든 마이크로 서비스들은 AWS 위에서 동작하고 있습니다.

 

2. 마이크로 서비스란?

먼저 "무엇이 마이크로 서비스가 아닌 가"에 대해서 살펴봄으로써 마이크로 서비스에 대해서 알아볼 수 있습니다. 

초기에 넷플릭스의 아키텍쳐는 하드웨어 기반의 로드 밸런서가 있고 고가의 하드웨어를 사용한 리눅스 서버들을 연결하고 이 서버에는 아파치 톰캣이나 리버스 프록시 같은 것들을 사용한 일반적인 구성을 가지고 있었습니다. 그리고 거기에 자바 웹 애플리케이션을 서비스하였습니다. 이 자바 애플리케이션이 JDBC로 오라클 데이터베이스에 직접 연결되어 있었습니다. 그리고 데이터베이스 링크로 다른 오라클 데이터베이스와 연결되어 있었습니다.

 

초기 넷플릭스의 아키텍쳐

이 구조의 첫 번째 문제는 자바 애플리케이션 코드 베이스가 "모놀리틱" 하다는 것이었습니다.

따라서, 매주 반복적으로 배포되는 애플리케이션의 코드 베이스에 모든 사람들이 한꺼번에 붙어서 작업을 해야 했습니다. 이런 구조는 변경이 발생 할 때마다 문제를 일으켰으며, 문제가 발생하면 분석이 엄청 어려웠습니다. 

천천히 증가하는 메모리 누수를 찾는데 일주일이 걸린 적도 있었습니다. 

문제를 찾기 위해 코드를 변경하고, 다시 구동하고 분석을 시도하고, 다시 변경하는 과정에서 하나의 애플리케이션에 너무 많은 변화가 생겨 분석에 필요한 시간은 점점 더 늘어났습니다. 

 

데이터베이스의 경우 더 심각했는데, 거대한 하드웨어에서 동작하는 하나의 오라클 데이터베이스였으며 이를 "스토어 데이터베이스"라고 불렀습니다. 그런데 데이터베이스가 다운되면 모든 시스템이 다운되기 일쑤였습니다. 

그래서 트래픽이 몰리는 시점이 다가오면 부하로 인한 문제를 방지하기 위해 항상 더 좋은 하드웨어를 찾아 다녔습니다.

 

엔지니어링 측면에서 가장 큰 문제는 모든게 서로 너무 깊게 연결되어 있어 변경 사항을 적용하는데 너무 오래 걸린다는 점이었습니다.

 

데이터베이스에 직접 연결하고, 테이블 스키마에 깊은 의존성을 가지는 애플리케이션들은 테이블에 컬럼을 추가하는 간단한 변경 요구 조차 여러 개의 팀이 협업해야하는 프로젝트 규모가 되었습니다. 

 

이러한 모델이 바로 오늘날 만들면 안되는 서비스의 전형적인 모습입니다. 

 

 

그러면 마이크로 서비스 아키텍처란 무엇일까를 얘기해보면

먼저 마틴 파울러가 정의한 것을 인용해보겠습니다.

 

마이크로 서비스 아키텍처 스타일은 작은 서비스의 집합으로 하나의 애플리케이션을 구현 하는 것이며 각각의 작은 서비스들은 자신만의 독립적인 프로세스를 가지고 있고 가벼운 구조를 가지는데, HTTP 기반의 API를 사용해 서로 연동한다. 

 

이런 정의가 틀린 것은 아니지만, 실제로 마이크로 서비스를 구현 할 때 도움이 되는 이야기는 아닙니다.

 

실제로 마이크로 서비스 아키텍쳐가 가지는 특징을 얘기해보자면,

 

  • 위험을 잘게 쪼개어 내는 것(Separation of concerns) : 모듈화, 서비스에 필요한 데이터를 캡슐화하기
  • 수평적 확장(Horizontally scaling)
  • 분산(workload partitioning) : 거대한 워크로드를 거대한 시스템이 아니라 여러 개의 작은 부분으로 처리

이러한 것을 하기 위해 필요한 것이 탄력성을 가진 가상화 기반 환경입니다.

운영에 필요한 것들은 가능하면 최대한 자동화를 해야 하며 필요에 따라 리소스를 만들고 제거가 가능하다는 점은 마이크로 서비스를 구현할 때 매우 큰 장점이 됩니다.

 

넷플릭스의 아키텍쳐를 살펴보면

 

ELB 뒤에는 동적인 라우팅을 수행하는 Zuul로 구성된 프록시 계층이 있습니다.

넷플릭스 아키텍쳐 (1) : Zuul

그리고 NCCP라 불리는 레거시 계층이 있는데, 이는 오래된 옛날 장치를 지원하고, 기본적인 영화 재생 기능을 제공합니다.

넷플릭스 아키텍쳐 (2) : NCCP

그리고 넷플릭스 API가 API 게이트웨어를 통해 제공되며 넷플릭스 아키텍처의 핵심 중 하나로 고객이 발생시키는 요청에 대한 응답을 처리하기 위해 다른 많은 서비스들과 통신을 하게 됩니다.

 

넷플릭스 아키텍쳐 (3) : API

 

Zuul과 NCCP 그리고 API까지의 영역을 Edge라 부르며, 각종 기능을 하는 서비스들이 미들 티어와 플랫폼이라 불리는 영역에 합쳐진 형태로 구성되어 있습니다.

넷플릭스 아키텍쳐(4): Edge & Middle Tier & Platform

Middle Tier & Platform 에는 다음과 같은 서비스가 동작합니다.

A/B 테스트 인프라가 있고, 유입된 고객이 할당될 테스트용 A,B 서비스가 동작합니다.

구독(Subscriber) 서비스는 넷플릭스 고객에 대한 거의 모든 정보를 가지고 있는 서비스입니다.

추천(Recommendations) 서비스는 각 고객에게 맞는 적절한 영화를 제안하기 위한 기능을 수행하는 서비스입니다.

 

플랫폼 서비스들은 보다 기초적인 기능을 제공하기 위해 존재합니다.

라우팅(Routing) 서비스는 마이크로 서비스들이 서로를 찾아 연결하는데 도움을 주는 서비스이고

동적으로 설정 변경을 지원하는(Configuration) 서비스와 암호화 관련Crypto) 서비스, 그리고 데이터를 영구 보존하기 위한 서비스로 구성됩니다.

 

이런 서비스들은 애플리케이션의 오브젝트들처럼 전체 시스템의 일부로 동작합니다.

 

마이크로 서비스 역시 일종의 추상화로 볼 수 있습니다.

일반적으로 많은 개발자들이 마이크로 서비스를 매우 단순하게 이해하려는 경향이 있습니다.

예를 들어 " 잘 확장되는 마이크로 서비스 아키텍쳐가 있으니 다른 서비스들이 별 문제 없이 호출해서 접근할 수 있겠지" 하는 생각이다. 이는 간단한 것 같지만 실제로는 그렇게 간단하지 않습니다.

 

서비스를 하기 위해서는 다양한 데이터가 필요하고 이를 저장해야하는데 이러한 데이터의 대부분은 보통 "영구 보존 계층"에 위치하게 됩니다. 그리고 서버와 데이터베이스만으로는 성능이 충분하지 않기 때문에 캐시를 추가하게 됩니다. 그리고 캐시에 접근하기 위한 클라이언트도 필요하게 됩니다. 그러면 클라이언트 라이브러리간의 "조율"에 대해 고려해야 합니다. 캐시에 데이터가 없다면, 서비스는 다시 데이터베이스를 호출해야합니다. 요청에 대한 응답을 처리하고 나면 다음번에 동일한 요청을 더 빨리 처리하기 위해 캐시에 저장하게 됩니다. 이렇게 구성된 클라이언트 라이브러리는 각각의 마이크로 서비스에 포함됩니다. 이렇게 설명된 복잡한 기술과 설정의 조합이 바로 마이크로 서비스라 할 수 있습니다.

 

추상화된 마이크로 서비스

 

3. 도전과 해답

4가지 관점(의존성[Dependency], 확장성[Scale], 다양성[Variance], 변경가능성[Change])에서 사례들에 대해서 살펴보고자 합니다.

 

의존성(Dependency)

1) 외부 서비스 요청(Intra-service Requests)

마이크로 서비스 A가 마이크로 서비스 B를 호출하는 등 이런 과정이 클라이언트 요청을 처리하기 위해 필요합니다.

서비스가 다른 서비스를 호출할 때 프로세스와 서버를 벗어나서 다른 서버의 프로세스까지 가는 것은 큰 모험입니다. 

네트워크의 지연시간, 혼잡성, 하드웨어 오류, 트래픽으로 인한 라우팅 문제가 발생할 수 있으며 또는 호출한 서비스가 정상적인 상태가 아닐 수도 있습니다. 그러면 요청에 대한 응답이 느려지거나, 타임 아웃이 발생하거나 에러가 발생하게 됩니다.

 

굉장히 많이 발생하는 장애 시나리오 중 하나는 하나의 서비스에서 장애가 발생하면, 이 하나의 장애가 의존성이 있는 다른 서비스의 장애를 야기하는 연속 장애가 발생하고, 결과적으로 전체 서비스에 문제가 생기는 경우입니다.

 

넷플릭스에서는 이런 문제를 막기 위해 Hystrix라는 도구를 만들었습니다. 이 도구는 타임아웃이나 재시도 같은 동작을 구조적으로 다룰 수 있으며, fallback이라고 불리는 컨셉을 가지고 있는데 이는 만약 서비스 B를 호출 할 수 없다면, 

사전에 지정된 static한 응답을 주는 등의 동작을 하는 것이다. 이렇게 함으로써 고객이 에러를 보는 대신, 서비스가 계속 동작하는 것처럼 보이게 할 수 있습니다.

 

Hystrix의 또 다른 장점은 "고립된 스레드 풀"과 "서킷"에 대한 컨셉입니다. 

만약 서비스 B가 장애인 상황에서 계속 호출하는 것보다는, 호출을 중지하는게 더 좋은 방법일 것입니다. 

장애가 감지되면, 지정된 fallback으로 응답하고, 서비스 B가 복구되길 기다립니다. 

 

그러면 이런 마이크로서비스 아키텍처가 제대로 동작하는지 어떻게 판단할까요?

그것은 바로 실패를 프로덕션에 주입하는 방법(Fault Injection Testing, FIT)을 통해 장애에 대한 내성을 가졌는지 판단할 수 있습니다. FIT는 장치나 어카운트에서 발생된 요청을 실패를 유발하는 설정과 오버라이드해서 만들어진 '합성 트랜젝션'을 프로덕션에 주입할 수 있습니다. FIT에서는 어떤 형태의 테스트건 모두 수행할 수 있어야 합니다.

 

문제는 어떻게 서비스의 테스트와 범위를 제한해야 수백개의 서비스가 서로 통신하는걸 전부 시뮬레이션하지 않고 테스트를 진행할 수 있을 것인가 하는 것입니다. 

 

넷플릭스에서는 테스트의 대상이되는 기능의 동작을 위해 필수적으로 필요한 서비스들을 가려냅니다. 그리고 이러한 일련의 과정에 치명적인 의존성을 가지고 있는 서비스들을 분류해 냅니다. 그리고 그룹화하고, FIT 레시피를 만들 때 이 그룹 말고 나머지는 모두 블랙리스트로 배제하게 됩니다. 이렇게 만들어진 레시피로 원하는 대상 기능의 서비스만을 프로덕션에서 테스트 합니다. 따라서 의존성 있는 서비스들이 동작하지 않는 상태도 만들어 장애상황에서 원하는대로 동작하는지 확인하게 됩니다.

 

 

확장성(Scale)

1) Stateless service란

캐시나 데이터베이스가 아닌 것, 엄청나게 많은 데이터를 저장할 필요가 없습니다.

매우 자주 요청되는 메모리에 캐시된 메타데이터라고 할 수 있다.

고객이 동일한 서버에 접근하도록 해야 할 필요가 없는 경우

중요한 컨셉은 서버의 문제가 서비스의 문제가 아니라는 점이다.

 

문제가 생겼을 때 해결하는 법은 그저 동일한 역할을 하는 서버를 새로 켜기만 하면 됩니다.

이런 것을 Auto Scaling을 통해 할 수 있습니다. 그리고 이는 클라우드 기반 서비스에서 굉장히 중요합니다.

새로운 서버가 시작될 때 S3로부터 원본 이미지를 가져와서 구동되는 형태입니다.

이런 방식은 다양한 장점이 있는데, 먼저 트래픽에 따라 서버를 켜고 끄니 효율이 좋습니다.

또한 고장난 서버를 쉽게 대체할 수 있습니다.

가장 좋은 점은 DDoS나 급격한 요청의 증가로 인해 트래픽이 폭증하는 경우 또는 성능상의 버그가 있는 경우에도 무슨 일이 발생한건지 부넉하는 동안, 서비스가 정상적으로 동작할 수 있도록 해줍니다. 

 

2)Stateful Services

데이터베이스와 캐시입니다.

넷플릭스의 경우 멀티 리전 전략을 선택하면서 리전간 데이털르 복제할 필요가 있었는데 이 부분은 큰 골치거리였습니다. 따라서 가능하다면 비즈니스 로직과 상태 데이터를 하나의 애플리케이션에 모두 저장하는 방법을 피하는 것을 추천 드립니다. Stateless 서비스와 다르게 서버의 장애가 매우 신경써야 하는 이벤트라는 것입니다. 이런 서버들은 기존의 서버를 준비하고 대체하는데 몇 시간이 걸릴 수도 있습니다. 

 

넷플릭스는 EVCache라는 기술을 도입했습니다. EVCache는 기본적으로 memcached를 랩핑한 것이고 Squid 캐시처럼 샤딩으로 구성되어 있습니다. 다만 다른 것은 데이터가 여러 개의 노드에 중복해서 저장된다는 것입니다. 따라서 데이터가 저장될 때마다 여러 개의 노드에 데이터가 저장되고 이 저장되는 데이터는 여러개의 AZ로 복제되어 결과적으로 다수의 네트워크에 데이터가 존재하게 됩니다. 읽기도 효율을 위해 기본적으로 로컬 AZ에서 수행되지만 접근해야 할 노드가 로컬 AZ에 없는 경우엔 다른 AZ의 노드에서 데이터를 가져올 수 있습니다. 

 

3) Hybrid services

EVCache는 좋은 도구인 만큼, 잘못 사용하기 매우 쉽습니다. EVCache는 글로벌에서 초당 3천만 요청을 처리합니다. 

이는 하루에 2조개의 요청에 해당하는 분량입니다. 그리고 수 만개의 memcached 인스턴스를 사용하게 됩니다. 

일반적으로 fallback 방법은 한 개만 문제가 되었을 때는 잘 동작하지만 EVCache 계층 전체가 완전히 다운되면 다른 이야기가 됩니다. 이럴 경우 데이터베이스로 처리가 넘어가게 되는데 기본적으로 데이터베이스 서비스는 EVCache가 처리하던 막대한 요청을 처리할 능력이 되지 않습니다. 따라서 전체 서비스는 빠르게 다운되게 됩니다.

 

이에 대한 몇 가지 해답이 있었는데, 첫 번째는 배치 작업을 위한 캐시 요청과 실시간 캐시 요청을 분리하는 것입니다. 그리고 두 번째는 하나의 요청 안에서 발생하는 캐시 참조는 한 번만 발생하도록 함으로써 보다 효율적으로 EVCache 리소스를 사용할 수 있었습니다. 

 

다양성(Variance)

다양성이란 것은 다양한 아키텍처가 서비스 내에 존재하는 것을 의미합니다.

서비스에 다양한 아키텍처가 존재한다는 것은 복잡성이 증가한다는 것이며, 이건 서비스의 관리를 힘들게 하는 요인입니다. 

 

1) 운영의 변화(Operational Drift) 

 운영의 변화는 강제로 발생하는 것이 아니라 어떤 목적에 의해 자연스럽게 변화하는 것입니다. 시간이 흐르면서 발생하는 것의 예는 알람의 임계점 설정이 변화하는 것입니다. 배치 작업의 시간이 점점 길어지거나 하는 등의 이유로 타임 아웃과 같은 설정을 변경해야 할 필요가 있을 것입니다.  

 처리 성능 역시 지속적으로 테스트를 통해 확인하지 않으면 새로운 기능을 추가하는 등의 영향으로 약간씩 느려질 수도 있습니다. 또한 서비스를 운영하고 유지하는 방법에 굉장히 좋은 패턴을 발견했어도 팀의 절반의 엔지니어만이 이 패턴을 구현 가능한 경우도 있습니다. 

 따라서, 운영을 위해서 넷플릭스는 운영 하면서 지속적으로 학습하는 내용을 자동화해서 반영하는 방식을 도입하였습니다. 

 보통 우리는 어떤 사고를 통해 무언가를 학습 합니다. 어떤 장애가 발생하면, 엔지니어들은 고객의 문제를 해결 하고자 합니다. 사건에 대한 리뷰를 통해 서비스에 무슨 장애가 발생한건지 파악합니다. 즉시 효과를 볼 수 있는 해결책을 강구해서 적용합니다. 그 이후에는 분석을 시작합니다. 이것이 새로운 형태의 장애인지, 이를 해결할 수 있는 최적의 방법이 있는지 찾습니다. 또는 서비스에 큰 영향을 미칠 수도 있는 반복적으로 발생하는 문제인지 판단합니다. 그런 것들에 대해 알고나면, 가능한 모든 부분에 자동화를 시도합니다. 그리고 만들어진 자동화를 서비스에 반영합니다. 이게 바로 학습이 자동화 된 코드로 만들어지고, 마이크로 서비스에 반영되는 과정입니다.

 

운영을 위한 자동화 과정

 

2) 폴리 글럿(Polyglot) & 컨테이너(Containers)

처음엔 자바 기반 애플리케이션 뿐이였지만 점점 다양한 도구와 기술을 사용하게 되었습니다. 다양한 도구와 기술을 사용하는 것은 합리적이며 올바른 선택이었지만 이런 기술들을 서비스의 핵심 부분에 적용하는 것이 현실화하는데 어려움이 있었습니다. 다양성을 유지하기 위한 비용이 공짜가 아니라는 점 때문입니다. 

 컨테이너에 어떤 일이 발생하는지, 그리고 문제가 된 컨테이너를 서비스에서 제외하는 등의 작업을 위해서 CPU와 메모리가 얼마나 사용되는지를 알기 위해서는 기존과는 다른 도구와 다른 지표들을 필요로 했습니다. 

 이전의 넷플릭스는 거의 대부분의 애플리케이션에 아주 기본적인 AMI(Amazon Machine Image)를 사용했는데, 지금은 특화된 더 많은 종류의 AMI가 존재 합니다. 

 또한 컨테이너 관리는 엄청난 비용이 들어갑니다. 오늘날 클라우드 환경에서 원하는 방향으로 사용할 수 있는 아키텍처나 기술을 제공하고 있지 않기 때문입니다. (2017년 기준) 그래서 넷플릭세으세너는 Titus라고 불리는 새로운 계층을 만들었습니다. 이건 컨테이너의 오토스케일링을 비롯한 장애, 컨테이너의 교체 등의 작업 관리를 처리하는 계층이며 이걸만들기 위해 넷플릭스는 엄청난 비용을 투자해야 했습니다. 

 

변화(Change)

지금 하는 것은 변화를 위해 무언가 바꾸고 부수고 새로 만드는 일을 "업무 시간"에 하는 겁니다. 어떻게 가능한 빠르게, , 그리고 뭔가 잘못되지 않을거라는 자신감을 가지고 변경을 적용할 수 있는가?

 

그래서 넷플릭스는 "Spinnaker"라는 새로운 글로벌 클라우드 관리 플랫폼을 만들게 되었습니다. 자동화 된 배포를 수행하는 시스템입니다. Spinnaker는 그간의 다양한 경험을 바탕으로 발생하는 변경사항을 프로덕션에 적절히 배포할 수 있도록 디자인된 도구입니다. 

 

 

 

 

 

 

'클라우드 > Devops' 카테고리의 다른 글

Kubernetes 개념  (0) 2021.10.05
Jenkins란 무엇인가?  (0) 2020.01.26