ECS를 이용한 Serverless Batch 서버

travis ci + ECS를 이용한 Batch서버 효율화

--

대용량 데이터를 다루기 위한 데이터 아키텍쳐

데이터를 뽑는 방법 중 가장 많이 쓰이는 방법은 데이터베이스에 저장하고 이를 SQL을 이용하여 추출하는 방법입니다. 예를 들어, 오늘 샌프란시스코에서 예약은 몇 건인지는 SQL 등으로 간단히 뽑을 수 있습니다. 그러나 이를 미국의 모든 도시별 예약 수를 지난 1년간으로 확장하면 할 수록 예약 건수 * 도시 수 * 365로 생성하는 시간이 오래 걸리거나 때로는 불가능할 때도 있습니다. 팀장님이 인자한 얼굴로 오늘 언제까지 뽑을 수 있어 물어보시면서 등 뒤에 병풍을 전개한다면 식은땀이 줄줄 흐르는 때이죠.

데이터 천재가 되고 싶은 홍대리는 이럴 때 전통적인 방법인 Batch를 사용하여 미리 데이터를 생성하고 있습니다. 그날그날 도시별 예약 건수를 계산하여 미리 저장한다면 데이터를 추출하는 데 필요한 시간을 대폭 줄일 수 있습니다. 즉, 필요한 데이터를 수집하고 미리 계산하여 사용자에게 보여주는 방식입니다. 미리 생성된 데이터가 있다면 계산해야 될 데이터 수가 줄어들고 결과도 빠르게 볼 수 있습니다. 덤으로 팀장님 병풍타임도 기하급수적으로 줄일 수 있는 장점이 있습니다. 또한 어떤 종류의 데이터는 시간성이 매우 중요한 데이터도 있습니다. 팀장님이 말하자마자 데이터를 뽑을 수 있는 실시간성까지 확보한다면 정말 데이터 천재 홍대리가 될 수 있겠지요.

데이터 처리의 흐름은 아래와 같이 Batch로 처리되는 레이어와 실시간으로 처리되는 레이어를 조합하여 사용하는 흐름이 되고 있습니다.

Lambda Architecture(http://lambda-architecture.net/)

Batch 레이어는 종전에 해왔던 데이터 처리를 위한 Batch 프로세스입니다. 어떤 단위시간 내에(일반적으로 하루 단위 또는 시간 단위)들어 온 데이터를 모아서 대량으로 처리하는 것이 보통입니다. 어제까지의 예약 수는 구할 수 있지만 Batch가 돌지 않은 어제~현재까지의 예약 수는 구하지 못합니다. Batch 레이어가 처리하지 못하는 영역인 실시간 데이터 처리를 할 필요성이 있습니다. Speed 레이어는 버퍼나 큐 등을 이용하여 스트리밍으로 효율적인 증분 처리 방식을 담당하게 됩니다.

람다 아키텍쳐가 꼭 정답은 아니지만 마이리얼트립도 데이터가 증가함에 따라 점점 모습이 닮아가고 있습니다. 물론 되어 있는 부분도 있지만 없거나 매우 부족한 상태인 부분도 아직 많이 남아 있는 상태입니다. 두 레이어 모두 안정적인 운영과 확장성이 필요합니다. 여러가지 이유로 인해 문제도 발생되고 때때로 심각한 장애가 발생하기도 합니다. 장애 등을 해결하면서 조금씩 개선하였던 사례 중 Batch 레이어에서 자주 겪게 되는 Batch 서버의 효율화를 위해 컨테이너 기반 서비스인 AWS Fargate를 이용한 ETL 처리방법을 공유합니다.

Batch 인스턴스의 비효율성

상품데이터를 모아서 검색 서버에 색인을 하는 일, 매출 데이터를 집계하는 일, 사용자 행동 로그를 수집하여 변경된 유저군을 업데이트하는 일등 다양한 일들을 Batch로 처리하게 됩니다. 이런 Batch 작업은 여러개의 작업을 거치면서 생성되는 중간 결과들을 조합한 데이터를 주기적으로 생성하는 일, 즉 ETL(Extract, Transform, Load)이 대부분입니다. 제이 크랩스님의 표현대로 고대 그리스의 ETL 작업의 모습이지만 지금와 모습과는 크게 다르지 않습니다.

Sisyphus (1548–49) by Titian, Prado Museum, Madrid, Spain

무거운 돌을 지고 나르듯이, Batch는 순간적으로 서버의 리소스가 많이 필요합니다. 또한 이런 일들을 주기적으로 반복하여 처리하여야 합니다. 그러므로, 얼마만큼의 일을 처리할 수 있는지 서버의 부하를 늘 모니터링하고, Batch 잡들이 서로에게 영향을 주어 느려지지 않도록 적절히 잡을 분산하여 처리하려고 합니다. 그러나 이런 노력을 아무리 해도 Batch 잡은 늘어나게 마련이고, 어느 순간이면 임계점에 도달하면서 장애가 발생하는 영원히 고통받는 루프에 빠지면 닭집을 차릴까 현타가 오기도 합니다.

위 그래프는 마리트에서 얼마전까지 현역으로 일하던 Batch 서버의 CPU 모니터링입니다. 주기적으로 스파이크를 치고는 있지만 아직은 서버의 자원이 놀고 있는 시간이 대부분입니다. 개발자가 평안을 가질 수 있는 상태입니다.(물론 이외에도 바쁘게 일하고 있는 다른 서버들도 존재하지만요)

그러나 경험상 준비하지 않는 자에게 이런 평안의 시간은 짧습니다. Batch 잡이 점점 무거워져서 느려지던지, 다른 잡들이 추가가 되면서 결국 장애 요정이 나타납니다. 서버의 추가나 변경이 쉽지 않은 On-premise 환경에서는 이런 서버들을 하나의 서버로 묶고, Batch 스케쥴러를 이용하고, 모니터링을 붙히는 등의 작업등으로 서버 운영에도 상당히 많은 노력을 기울여야 합니다. 그럼에도 불구하고 문제는 여전히 발생할 수 있고, 아래 그림같이 낭비되는 시간이 발생할 수 밖에 없습니다.

AWS Batch를 통한 손쉬운 일괄 처리 작업 관리하기 — 윤석찬

개발자가 겪는 문제의 원인은 여러가지가 있습니다만, 대부분 서버를 운영함으로써 발생하는 문제입니다.

On-premise와 달리 클라우드 환경에서는 여러가지 방법으로 서버를 운영하는 노력을 줄일 수 있습니다. 극단적인 경우로는 람다나 ECS같이 Serverless 서버를 아예 운영하지 않는 방법도 있습니다. 서버를 운영하지 않음으로 프로비저닝, 구성 및 운영에 대한 부담을 줄일 수 있습니다. 마리트에서도 기존의 스프링 기반으로 만들어진 Batch 잡들이 다수 있고, 이를 ECS 기반으로 옮기기로 결정했습니다.

Travis CI -> ECR -> ECS Fargate

컨테이너는 애플리케이션과 그 의존성을 독립된 단위로 묶어 격리하고 어디서든 실행이 가능하게 도와줍니다. 필요한 순간에만 서버를 프로비저닝 하려는 우리의 목적과 매우 잘 어울립니다.

그럼 컨테이너만 있으면 될까요? 컨테이너를 운영하기 위해서도, 인스턴스를 생성하고, Docker를 설치하고 , docker swarm 또는 kubernetis등으로 클러스터 구성을 하는 작업이 여전히 필요합니다. AWS Fargate는 서버 또는 클러스터를 관리할 필요 없이 컨테이너를 실행할 수 있도록 해줍니다. 그러면 방향은 결정되었습니다.

각 Batch 잡을 컨테이너로 구성하고, Fargate를 통해 필요한 순간에만 컨테이너를 생성하여 Batch를 돌리고 작업이 마무리되면 종료합니다. 이러면 동시에 수많은 서버 자원을 이용할 수 있으니, 백만 Batch 잡이 몰려와도 겁날 일이 없을 것같습니다.

Github -> Travis CI -> AWS ECR -> AWS Fargate

기존의 코드는 Github를 통해 관리되고 있었으므로,

  1. 기존 프로젝트 Docker로 변경하기
  2. Travis CI와 깃헙 연동
  3. Travis CI로부터 생성된 Docker 이미지를 ECR에 등록
  4. 주기적으로 Fargate를 통해 task만 실행

을 하는 순으로 개발을 진행하였습니다.

  1. 기존 프로젝트를 Docker로 변경하기

전환 대상으로 선정한 프로젝트는 Spring Boot와 Maven으로 조합된 프로젝트입니다. 스프링 공식문서(https://spring.io/guides/gs/spring-boot-docker/)를 보면 Maven 또는 Gradle 프로젝트에서 Docker 빌드를 추가하는 것을 잘 설명 해주고 있습니다.

Maven Docker Plugin의 설치와 Dockerfile 작성을 해주는 것이 핵심입니다. plugin 설치는 빌드 작업 시 여러가지 소소한 이점을 제공 하고 있습니다. 여러 플러그인중 저는 spotify의 플러그인을 선택하였습니다. 추가를 위해 pom.xml을 변경합니다.

Dockerfile은 별다른 것 없이 alpine기반 이미지를 사용하고 빌드된 jar만 도커이미지로 옮겨 용량을 가볍게 만들도록 합니다.

maven의 dockerfile:build 를 수행해봅니다. 로컬에서는 정상적으로 이미지가 생성된 것을 확인할 수 있습니다. 해당 이미지를 실행해보시고 문제가 없다면 이제 다음 단계로 가보시죠.

2. Travis CI로부터 생성된 도커 이미지를 ECR에 등록

Travis CI는 오픈소스 커뮤니티를 위한 지속적인 통합(continuous integration) 서비스입니다. 2011년 초 Ruby 커뮤니티를 위해 시작되었지만 지금은 수많은 언어를 지원하는 형태로 발전하였습니다. 마이리얼트립은 Ruby로 이루어진 부분이 있어, 오래 전부터 Travis CI를 사용하고 있습니다. Travis CI 는 다양한 언어와 쉬운 플러그인 지원으로 여러가지 장점이 있으나 유료 사용이 기본입니다. 만약 본인이 무료로 시작하겠다고 생각하시면 Circle CI도 추천드릴만한 서비스 입니다.

Travis와 깃헙 연동은 공식문서를 참고하여 쉽게 진행이 가능합니다.

Travis CI를 통해 Docker 이미지를 만드는 것은 놀라울 정도로 간단합니다.

단 2줄만 .travis.yml에 추가하고 github에 push하면, Travis가 자동으로 빌드를 시작하게 됩니다.

다음은 빌드 된 Docker 이미지를 AWS의 ECR에 등록하기 위한 스크립트를 추가합니다. AWS에 푸쉬하기 위해서는 AWS_ACCESS_KEY, AWS_SECRET_KEY등의 정보를 알고있어야 합니다. 전환 프로젝트는 GCP를 사용하고 있으므로, Google credential file을 포함하고 있는 상태입니다. 이런 정보들이 평문으로 깃헙 등 공개된 리포지토리에 올라갈 경우 문제가 발생할 수 있습니다. 다행히 Travis에서는 키 또는 파일에 대한 암호화 기능을 제공하고 있습니다.

참조: 트라비스 공식 문서 , Travis 키 암호화하기

특정 밸류가 .travis.yml에 암호화된 텍스트로 추가됨을 알 수 있습니다.파일의 경우에도 비슷한 방식으로 생성할 수 있습니다.

암호화된 파일(*.enc)이 프로젝트 루트 디렉토리에 생성되게 됩니다. 주의하실 점은 평문파일은 .gitignore에 등록하시고, .enc파일을 git에 등록해 주셔야 합니다. 이런 방식은 민감한 정보를 안전하게 저장하여 사용할 수 있습니다.

ECR에 푸시하기 위한 스크립트를 작성합니다.

최종적으로 모두 반영이 된 .travis.yml을 살펴보죠

저는 master 브랜치만 빌드되기를 원했기 때문에, 브랜치 제약 조건을 추가하였습니다. deploy(이 단계는 기본적으로 master브랜치로 제약이 걸려있습니다)에서는 앞에 작성한 push_ecr.sh을 이용하여 ECR에 이미지를 푸쉬하고 있습니다. env에 정의된 AWS키들은 빌드가 시작되기 전 환경 변수로 셋팅이 됩니다. 또한 GCP 관련 크레디셔널 파일은 복호화되어 프로젝트의 원 위치로 파일이 복호화 됩니다.

자! 마스터 브랜치 변경 사항을 머지하면, 아름답게 ECR에 최신 이미지가 등록된것을 확인할 수 있습니다.

이제 마지막 단계입니다. ECR에 등록된 이미지를 바탕으로 주기적인 잡 실행을 해보도록 하시죠

3. 주기적으로 Fargate를 통해 task만 실행

ECR 생성은 단순하니 생략하도록 하겠습니다. 다만, 빌드된 이미지가 너무 많이 쌓이지 않도록 ECR의 생명주기 정책을 꼭 설정하여 주시기 바랍니다.

Fargate를 돌리기 위해서는 Cluster를 먼저 생성해야 합니다. Cluster는 Docker Instance들의 집합을 뜻합니다. 우리는 task만 수행할 예정이니 간단하게 네트워킹 전용으로 만들도록 하겠습니다.

이제 실제로 ECR에 푸쉬된 이미지를 기반으로 한 Docker를 수행할 작업(task)를 정의합니다.

Task에서는 컨테이너에 할당할 메모리, CPU 리소스를 결정할 수 있습니다. 필요한 만큼의 리소스를 할당합니다. 그리고, 컨테이너 정의 영역에서 ECR에 푸쉬된 이미지를 컨테이너 추가를 통해 추가합니다.

참조: Fargate 시작 유형을 사용하여 작업 실행

Fargate는 예약 작업도 지원이 되나 아쉽게도 오늘 기준(2019/07/03) 서울리젼을 사용을 할 수 없습니다. (예약된 작업 cron 처리)

대안으로, Cloud Watch Event로 람다를 호출하는 방식으로 cron 잡을 수행할 수 있습니다.

기존 프로젝트가 정상적으로 Task 기반으로 도는 것을 확인하였다면, 사용하고 있던 Batch 인스턴스를 조용히 터미네이트 시키면 됩니다. 이제 우리의 Batch 잡은 필요한 순간에 필요한 만큼의 리소스를 이용하여 작업을 하고 있을 테니까요.

결론

이번 작업을 통해, 일부 Batch를 컨테이너 기반으로 전환할 수 있었습니다. 더욱 싼 비용으로 동일한 작업을 수행할 수 있게 되었고, 홍대리는 더 이상 그라파나를 보고 있지 않아도 되게 되었습니다.(물론, 다른 그라파나를 여전히 보아야 하지만요) 또한, 백만 Batch 잡이 들어온다고 해도 걱정이 없을것 같은 안정감을 얻게 되었습니다.

Batch 잡을 컨테이너 서비스로 하는 것은 아주 새로운 아이디어는 아닙니다. 이미 훌륭한 도구들과 방법들이 인터넷 상에 많이 올라와 있고, 저 또한 그런 도구와 먼저 좋은 글들을 작성해준 분들의 도움을 받아 작업을 진행할 수 있었습니다. 특히 awskrug의 변규현님의 동영상이 많은 도움이 되었습니다. 이번 기회에 Travis, Docker 그리고 ECS 같이 여러 서비스들을 배울 수 있어 한발 더 데이터 천재로 다가선 듯하여 뿌듯한 홍대리가 되었습니다.

마이리얼트립은 모든 팀이 데이터를 기반으로 의사결정을 하고 있습니다. 점점 늘어나는 각 팀의 요구를 맞추기 위해 데이터플랫폼팀은 데이터 처리를 최대한 안정적으로 빠르게 처리할 수 있는 데이터 플랫폼을 만들기 위하여 노력중에 있습니다. 때로는 장애로 슬프기도 하지만, 이렇게 하나하나의 문제를 같이 해결하는 순간이 개발자로써 가장 행복한 때가 아닌가 합니다.

함께 고민하고 문제를 해결할 데이터플랫폼팀의 동료를 기다립니다.

채용공고 : https://career.myrealtrip.com/

--

--