추천 서비스가 컨테이너를 만났을 때-Part 1

왓챠
WATCHA
Published in
15 min readAug 18, 2021

--

안녕하세요. 왓챠에서 ML 소프트웨어 엔지니어로 일하고 있는 찰스입니다.

여러분이 생각하는 좋은 추천 서비스는 어떤 서비스일까요? 사용자의 숨겨진 의도를 파악하여 추천하는 서비스? 추천을 통해 사용자의 재방문율을 높이는 서비스? 아마 많은 분들이 추천의 정확도와 관련된 기준을 먼저 생각하실 겁니다. 저는 추천의 정확도를 높이는 것도 중요하지만 그에 못지 않게 빠르고 안정적으로 추천 결과를 제공하는 것도 중요하다고 생각합니다. 아무리 정확도가 높은 추천 서비스도 잦은 장애나 높은 지연시간과 같은 문제가 있다면 좋은 추천 서비스라고 말하기 어려울 겁니다.

최근 왓챠와 왓챠피디아 서비스는 겉으로 드러나지는 않지만 내부적으로 엄청난 변화가 있었습니다. 왓챠를 구성하고 있는 대부분의 서비스를 컨테이너 환경으로 이전한 것인데요. 왓챠 ML팀에서도 이러한 변화에 발 맞추어 추천 서비스를 성공적으로 이전했고 더욱 기민하고 견고한 추천 서비스를 구성할 수 있게 되었습니다. 이 글에서는 컨테이너로 이전하는 과정에 있었던 여러 고민과 선택, 그리고 개선점을 공유합니다.

들어가며

  1. 왓챠 추천 서비스는 Public Cloud 서비스인 Amazon Web Service(이하 AWS)의 다양한 서비스를 활용하여 만들어졌습니다. 이 글에서는 여러 AWS 서비스 이름이 직접적으로 언급될 예정이고, 독자도 AWS 서비스에 대해 어느 정도 지식이 있다고 가정합니다.
  2. 이 글에서는 컨테이너 환경으로 이전하면서 사용한 여러 가지 기술이나 도구(Docker, K8s, Helm chart, ArgoCD 등)의 설정 과정을 상세하게 다루지 않습니다. 다만, 적용한 기술이 무엇을 위해 사용되었고 그로 인해 기존보다 나아진 점을 중점적으로 기술합니다.
  3. 편의를 위해 작성될 내용은 2개의 글로 나뉘어 작성될 예정입니다. 이 글에서는 주로 컨테이너 환경으로 이전하면서 고려해야 할 점과 개선점에 대해 이야기하고, 다음 글에서는 컨테이너 환경으로 이전하면서 부가적으로 변경된 내용에 대해 다룰 예정입니다.

가상머신에서 컨테이너 환경으로

컨테이너는 어플리케이션 실행에 필요한 모든 환경(운영 체제, 실행 파일, 환경 변수, 라이브러리, 컴파일러 버젼 등)을 패키징하여 호스트와 분리된 환경을 구축하는 기술입니다. 컨테이너는 운영 체제(OS)를 가상화하여 호스트와 커널을 공유하기 때문에 호스트 OS 위에 여러 게스트 OS를 구동하는 가상머신(VM)에 비해 가볍고 빠르게 실행할 수 있습니다. 또한 어플리케이션 실행에 필요한 최소한의 환경을 구성할 수 있어서 리소스 낭비 없이 효율적인 운영이 가능합니다.

컨테이너와 가상머신의 구조(출처: https://aws.amazon.com/ko/docker/)

Docker는 어플리케이션을 빠르고 쉽게 구축 및 테스트, 배포할 수 있는 컨테이너 소프트웨어 엔진으로서 지난 수 년간 많은 DevOps 개발자의 사랑을 받아온 컨테이너 구현체입니다. 왓챠 ML팀에서는 기존의 왓챠 추천 서비스를 Docker를 이용한 컨테이너 환경으로 이전하여 아래와 같은 장점을 가질 수 있게 되었습니다.

동일한 실행 환경 제공

첫 번째 장점은 통일된 실행 환경을 보장함으로써 실행 환경의 차이에서 오는 비효율을 줄일 수 있다는 점입니다. Docker를 적용하기 전에는 아래와 같이 실행 환경 차이에서 오는 비효율을 종종 경험했었는데요.

  • 개발 환경에서 문제 없던 코드가 실 서비스에서는 문제를 발생시켜 배포된 서비스 롤백
  • OSX 환경에서 열심히 만들어놓은 쉘 스크립트를 Linux 환경에서도 잘 동작할 수 있도록 재작성
  • 로컬과 배포 서버의 Git 버젼이 상이하여 로컬 테스트를 통과한 배포 파이프라인 스크립트가 배포 서버에서 오작동

Docker를 적용한 이후에는 Docker 이미지를 통해 실행환경을 제공함으로써 실행 환경의 차이에서 오는 비효율을 많이 줄일 수 있었습니다. 특히 단순히 개발 환경과 실 서비스 환경의 차이 뿐 아니라 작업 환경을 자주 공유해야 하는 개발자, 연구원끼리 동일한 작업 환경을 보장하고 반복되는 설치 과정을 최소화하여 각자 연구와 개발에만 집중할 수 있는 여건을 만들 수 있게 되었습니다.

소스코드와 실행 환경의 통합

기존 왓챠 추천 서비스는 AWS Elastic Compute Cloud(이하 EC2) 위에 어플리케이션을 직접 올려 제공되었습니다. EC2는 AWS에서 제공하는 클라우드 가상 컴퓨팅 환경 서비스로써, 물리 서버 위에 가상화된 가상 머신입니다. 만약 가상 머신의 개념이 익숙치 않다면 하나의 물리적인 서버라고 생각해도 좋습니다. EC2 위에서 어플리케이션을 실행하기 위해서는 정상적으로 어플리케이션을 실행할 수 있는 실행 환경이 필요했습니다. 이러한 실행 환경을 관리하기 위해 Amazon Machine Image(이하 AMI)에 프로젝트마다 이미지를 만들어 버젼 관리를 했고, 버젼 업데이트가 일어날 때마다 어떤 라이브러리를 설치했는지 어떤 Python 버젼을 사용했는지 등을 따로 문서화해야 했습니다.

이제 프로젝트에 Dockerfile을 포함시켜 실행환경을 명세할 수 있습니다.

이제는 Docker를 이용하면서 별도의 서버 이미지를 구성할 필요 없이 소스코드만 있어도 실행환경을 관리할 수 있게 되었습니다. 즉, 소스코드에 실행 환경을 명세한 Dockerfile을 추가하고 CI/CD 파이프라인에서 Docker이미지를 만들어 배포함으로써 실행 환경을 소스코드와 동기화하는 부담을 줄일 수 있게 되었습니다. 뿐만 아니라 개발 중인 Git 브랜치를 전달하는 것만으로도 다른 팀원에게 실행 환경을 공유할 수 있어 불필요한 설치 과정을 줄일 수 있었습니다.

환경 구성에 필요한 비용 감소

새로운 실행환경을 구성할 때 소비되는 시간과 이를 실행시키는 시간이 상당히 줄었다는 점은 Docker 적용 후 얻을 수 있는 또 하나의 장점입니다. 추천 서비스에서는 추천에 필요한 ML 모델을 학습하기 위해 GPU를 활용해야 할 경우가 많습니다. 기존에는 GPU 연산이나 분산 처리를 위해 필요한 프레임워크나 라이브러리를 일일히 설치하는 수고로움이 있었는데 이제는 필요한 Docker 이미지를 Docker Hub와 같은 저장소에서 받아 쉽게 환경을 구축할 수 있게 되었습니다. 또한, Docker 이미지의 각 레이어는 캐싱되므로 변경 사항에 대해서만 빌드하여 새로운 Docker이미지를 빠르게 만들 수 있어 효율적이었습니다. 이렇게 빌드된 Docker 이미지는 AWS Elastic Container Registry(이하 ECR)에 저장되어 필요할 때마다 가져와서 사용하였고, 가져온 Docker 이미지는 Docker 엔진 위에서 동작하여 프로비져닝된 노드만 있다면 실행 시간을 상당부분 절약할 수 있었습니다.

지금까지 Docker를 적용해서 얻게된 장점들에 대해 살펴보았습니다. 물론 Docker는 위에서 언급한 장점 때문에 많은 DevOps 개발자들의 사랑을 받았습니다. 하지만 이렇게 많은 사랑을 받게된 가장 큰 이유는 컨테이너를 배포, 관리, 확장하는 일련의 과정을 자동화시켜주는 컨테이너 오케스트레이션 도구가 있었기 때문입니다. 지금부터 왓챠 추천 서비스에 컨테이너 오케스트레이션 도구인 쿠버네티스(이하 K8s)를 적용하고 어떤 변화들이 생겼는지 살펴보겠습니다.

컨테이너 오케스트레이션

여러분이 운영하고 있는 서비스는 얼마나 많은 사용자가 이용하고 있나요? 만약 사용자가 많지 않은 서비스를 운영한다면 하나의 서버에 하나의 컨테이너 만으로도 기본적인 서비스 운영이 가능할 겁니다. 하지만 어느 정도 규모가 있는 서비스라면 대용량 트래픽에 대응하기 위해 많은 수의 컨테이너가 필요합니다. 왓챠 추천 서비스도 많은 사용자가 이용하고 있기 때문에 수 많은 컨테이너를 어떻게 제어해야 할지 고민이 필요했습니다. 이러한 고민을 해결하기 위해 컨테이너를 자동으로 관리해줄 수 있는 컨테이너 오케스트레이션 도구를 알아보았고, 그 중 가장 대중적인 도구인 K8s를 사용하게 되었습니다.

K8s는 구글에서 만든 오픈소스 컨테이너 오케스트레이션 도구로서, 각 오브젝트에 대한 명세를 통해 전반적인 시스템을 구성할 수 있습니다. K8s를 활용하면 배포 스케쥴링은 어떻게 할지, 배포 버젼 관리는 어떻게 할지, 장애가 발생했을 때 어떻게 복구할지, 트래픽 분산을 위한 오토 스케일링은 어떻게 처리할지와 같은 다양한 문제를 쉽게 해결할 수 있습니다. 그 중에서 몇 가지 구체적인 적용 사례를 들어 K8s를 통해 왓챠 추천 서비스가 어떻게 개선되었는지 살펴보도록 하겠습니다.

서비스 오토 스케일링 간소화

왓챠 추천 서비스는 많은 사용자가 이용하는 왓챠의 트래픽에 직접적인 영향을 받기 때문에 대용량 트래픽에 유연하게 대응할 수 있는 확장성이 필요합니다. 즉, 왓챠의 트래픽이 급증하면 지연 시간이 발생하지 않도록 적절한 로드 밸런싱으로 트래픽을 분산시킬 수 있어야 하고 분산된 트래픽을 잘 처리할 수 있도록 충분한 파드(Pod)가 필요합니다. 물론 트래픽이 감소하면 불필요한 파드를 제거하여 비용을 최소화할 수 있어야 합니다.

<기존 왓챠 추천 서비스는 오토 스케일링을 위해 여러 AWS 서비스를 조합해서 사용해야 했습니다.>

기존 왓챠 추천 서비스는 위 그림과 같이 유동적인 트래픽 상황을 감지하고 적절하게 스케일 인/아웃을 하기 위해 여러 AWS 서비스를 활용했습니다. AWS Auto Scaling Group(이하 ASG)에서는 미리 정의해 둔 조건에 부합하면 정의된 정책대로 EC2 인스턴스 개수를 조절했고, 만약 스케일 아웃이 발생했다면 AMI에 저장된 서버 이미지와 미리 컴파일되어 AWS S3에 저장된 바이너리 코드, 그리고 AWS CodeDeploy에 미리 정의된 배포 스크립트를 유기적으로 결합하여 전반적인 오토 스케일링을 처리했습니다. 이러한 방식은 AWS의 다양한 서비스를 조합해서 구성해야 하므로 각 서비스에 대해 명확히 인지하고 있어야하고 K8s에 비해 복잡한 설정 과정을 거쳐야 한다는 단점이 있었습니다.

반면에 K8s를 활용하면서 Horizontal Pod Autoscaler(이하 HPA)로 오토 스케일링을 쉽게 제어할 수 있게 되었습니다.

K8s의 오토스케일링 방식 (출처: https://abhisheksharmaa.com/setting-up-autoscaling-in-elastic-kubernetes-service.html)

위 그림을 살펴보면 K8s HPA가 어떻게 동작하는지 이해할 수 있는데요. HPA가 동작하는 과정을 순서대로 적어보면 다음과 같습니다.

  1. 메트릭 서버에서 주기적으로 메트릭 수집.
  2. 수집된 내용을 토대로 오토 스케일링 여부 판단.
  3. 변경이 필요한 경우 kube-scheduler에 의해 Scale-in/out 요청
  4. 실제 파드를 조정

이렇게 왓챠 추천 서비스에 HPA를 이용함으로써 AMI를 통해 서버 이미지를 가져오거나 S3에서 컴파일된 바이너리 코드를 가져오는 비효율 없이 ECR에 저장된 도커 이미지로 원하는 어플리케이션을 실행할 수 있게 되었습니다. 또한 기존에 새로 추가된 어플리케이션이 헬스 체크 통과 후 트래픽 라우팅의 대상이 되는 과정을 K8s의 Liveness Probe와 Readiness Probe으로 쉽게 적용할 수 있을 뿐 아니라 메트릭 서버의 수집 주기, 다양한 스케일링 정책에 대한 제어를 세밀하게 할 수 있어 유연하게 오토 스케일링 제어를 할 수 있게 되었습니다.

퍼시스턴트 볼륨을 활용한 데이터 서빙 메커니즘 개선

일반적으로 추천 서비스는 추천의 성격에 따라 다양한 추천 데이터를 필요로 합니다. 추천 데이터는 다양한 기계학습 모델을 통해 학습된 모델 뿐 아니라 사용자가 추천 받을 수 있는 후보 콘텐츠, 콘텐츠의 메타 데이터, 사용자 정보 등 추천을 위해 필요한 모든 데이터를 말합니다. 이러한 데이터는 추천의 범위나 실시간성 등 추천 서비스에 요구하는 요구사항이 많을수록 용도에 맞게 다양한 종류의 데이터가 준비되어야 합니다. 가령 실시간 추천을 위해 사용자의 최근 정보가 필요한 경우에는 데이터베이스나 캐시 스토리지에 매번 조회할 필요가 있고 잘 변하지 않는 콘텐츠 메타 데이터나 오랜 시간 동안 학습한 딥러닝 모델은 정해진 경로에 저장해두고 필요할 때마다 다운로드받아 메모리에 올려 사용하거나 Kubeflow의 KFServing과 같은 서빙 도구를 이용하는 것이 효과적일 겁니다.

기존에는 위 그림과 같이 S3로부터 추천 데이터를 다운로드 받아 메모리에 로드한 후 추천에 활용했습니다. 추천 데이터는 서비스를 새로 시작하거나 워커에서 주기적으로 갱신될 때마다 다운로드했는데 이러한 방식은 추천 데이터의 크기가 커질수록 파일 입출력에 대한 부담이 생겼고, 특히 서비스가 새로 시작될 때 추천 데이터를 다운로드 받는 시간이 존재하여 급작스러운 Scale-out 상황에 예민하게 반응하지 못하는 문제가 있었습니다.

왓챠 ML 팀에서는 이러한 문제점을 해결하기 위하여 K8s Persistent Volume(이하 PV)과 네트워크 스토리지인 AWS Elastic File System(이하 EFS)를 활용하기로 했습니다. PV는 클러스터 내 각 컨테이너의 볼륨 관리를 추상적인 레벨에서 손쉽게 관리할 수 있도록 K8s에서 제공하는 오브젝트입니다. PV에 EFS를 등록함으로써, 쉽게 클러스터 내 여러 파드들에 EFS를 볼륨 마운트할 수 있게 되었습니다.

결국 모든 파드는 위 그림과 같이 동일한 EFS를 볼륨 마운트하여 추천 데이터를 공유할 수 있게 되었고, 이로 인해 불필요한 파일 입출력을 하지 않는 것 뿐 아니라 서비스 부트스트랩 시간이 줄어들어 급작스런 트래픽 증가에도 대응할 수 있게 되었습니다.

이 외에도 노드의 유휴 리소스를 관리하여 새로운 컨테이너가 적절한 노드에 배치할 수 있도록 파드와 노드를 스케쥴링하거나 컨테이너 장애 발생 시 이를 감지하고 빠르게 복구할 수 있도록 도와주는 등 K8s를 활용함으로써 서비스 운영에 많은 부분을 자동화 할 수 있게 되었습니다.

EKS NodeGroup? Fargate?

왓챠 ML팀에서는 AWS를 주로 사용하고 있기 때문에 K8s에 관련된 모든 내용을 직접 구성 및 관리하지 않고 관리형 K8s 서비스인 Amazon Elastic Kubernetes Service(이하 EKS)를 활용했습니다. EKS를 통해 K8s 어플리케이션을 실행하고 클러스터 생성, 노드 프로비져닝, 버젼 업데이트 등 K8s 운영에 필요한 주요 작업을 쉽게 관리할 수 있게 되었고, 최신 보안 패치를 클러스터 제어 플레인(Control Plane)에 자동으로 적용하여 EKS 클러스터의 안정성을 보장할 수 있었습니다.

EKS를 적용할 때 가장 고민이 되었던 부분은 K8s 클러스터 내에 어떤 종류의 워커 노드를 사용해야 하는가였습니다. EKS 클러스터의 노드는 아래와 같이 3가지 노드 타입을 지원합니다.

  • 직접 노드의 프로비져닝 및 수명 주기를 관리하는 자체 관리형 노드 그룹
  • 노드의 프로비져닝 및 수명 주기를 자동화하여 관리해주는 관리형 노드 그룹
  • 서버리스 컨테이너 컴퓨팅 엔진인 Fargate

각 방법은 장단점이 존재하기 때문에 이를 잘 파악하여 서비스에 특성에 맞는 노드 그룹을 구성하는 것이 중요합니다. (다만, 자체 관리형 노드 그룹은 관리를 직접해야 한다는 점 때문에 고려대상에서 제외했습니다.)

EKS 관리형 노드 그룹과 Fargate의 장단점

위의 도표는 관리형 노드 그룹과 Fargate의 장단점을 정리해 본 도표입니다. 우선 Fargate는 미리 웜업해둔 서버를 프로비져닝하여 제공하므로 관리형 노드 그룹에 비해 속도가 빠르다는 장점을 가지고 있습니다. 그렇기 때문에 스케일 아웃 속도에 민감한 서비스는 Fargate를 활용하는 것이 좋습니다. 그러나 Fargate는 GPU 인스턴스를 지원하지 않고, 최대 4 vCPU, 30GB 메모리까지만 지원하므로 고사양의 워커 노드나 GPU가 필요한 작업이 있다면 관리형 노드 그룹을 이용하는 것이 바람직합니다.

비용적인 측면을 살펴보면, Fargate가 관리형 노드 그룹에 비해 비싼 과금 방식을 가지고 있어 관리형 노드 그룹이 매력적으로 느껴질 수 있습니다. 하지만 Fargate는 하나의 노드에 하나의 파드만 할당하여 동작하도록 설계되어있기 때문에 파드가 노드의 모든 리소스를 사용할 수 있는 반면(물론, Fargate 특성상 데몬셋을 띄울 수 없기 때문에 사이드카 패턴을 이용해야 하므로 제약이 있습니다.), 관리형 노드 그룹은 인스턴스 타입을 미리 지정해야 하고, 그에 따른 유후 비용이 발생할 수 있기 때문에 컨테이너 사용 방식에 따라 적절한 선택을 해야 합니다.

마지막으로 관리 리소스를 살펴보면, 관리형 노드 그룹은 AMI 변경이나 노드 업데이트를 직접 해주어야 하지만, Fargate는 이러한 노드 업데이트를 고려하지 않아도 되기 때문에 관리 리소스를 줄일 수 있다는 장점이 있습니다.

왓챠 ML 팀에서는 각 노드 그룹의 장단점들을 비교해보고, 추천 결과를 제공하는 API 서비스와 추천 데이터를 생성하는 워커에 모두 관리형 노드 그룹을 이용하기로 했습니다. 왓챠 추천 서비스가 Fargate에서 지원하는 하드웨어 스펙보다 더 좋은 하드웨어 스펙을 필요로 했고, 프로비져닝 속도도 생각보다 크게 차이가 나지 않아 Fargate를 사용해야하는 직접적인 장점을 찾기 어려웠기 때문입니다.

마치며

지금까지 왓챠 추천 서비스에 Docker와 K8s를 어떻게 활용하였고 그로 인해 어떤 장점이 생겼는지 살펴보았습니다.

다음 글에서는 Docker와 K8s를 적용한 후 서비스를 더 효율적으로 운영할 수 있도록 개선한 부분에 대해 설명할 예정입니다. 감사합니다.

  • P.S) 현재 왓챠 ML팀에서는 콘텐츠 추천 뿐 아니라 다양한 문제를 함께 고민하고 해결할 멋진 동료를 구하고 있습니다. ML 엔지니어, 머신러닝 연구원 등 여러 직군에 대해 모집하고 있으니 주저하지 말고 신청해주세요.

--

--

왓챠
WATCHA

왓챠와 왓챠피디아를 가꾸고 있어요.