토스의 백엔드는 어떻게 자동화되는가?

이 글에서는 토스 서비스의 백엔드 구성이 어디까지 동적으로 자동화 되어 있는지, 서버 개발자들이 서비스 구현만 집중할 수 있는 환경을 어떻게 구성하고 있는지에 대한 개념을 소개하려고 한다.

자세한 기술적 내용은 향후에 따로 정리해 보겠다. 이유는 현재 토스에 적용된 내용은 핀테크의 특성상 실 서비스는 AWS 등을 사용하지 못하고 IDC에서만 구성된다는 제약 사항과 토스 서비스의 레거시 정책을 함께 지탱해야 하는 특수성이 포함되어 있기 때문이다. 조만간 AWS에서 순수한 상태로 대규모 클러스터로 확장 가능한 백엔드 구축에 대해 기술적인 정리를 해보려고 한다(그럴 여유가 있을지는 모르겠지만).

위 그림은 격리된 내부망에 구성한 내용을 간략하게 손으로 그린 것이다. 각기 하나의 박스 또는 원으로 표현해 놓았지만, 모든 것이 클러스터링 또는 이중화를 통해 장애 상황에 최대한 안전하게 구성되어 있다.

개인적인 경험으로 백엔드 서비스 배포에서 가장 필수적이면서도 다루기 까다로운 부분들은 다음과 같다.

  1. 리소스 배분
    하나의 서비스가 사용하는 리소스의 양은 일반적으로 운용하는 서버 한 대의 가용 리소스 대비 매우 적다. 그렇다고 하나의 서버에 이런저런 서비스를 있는대로 올리면 어느 순간 서버가 갑자기 죽어 나가면서 장애의 순간을 맞게 된다. 때문에 서버가 죽지 않도록 서비스들을 각 서버에 배치하는 것은 매우 조심스럽고 어려운 일이다.
  2. Metric & Log 수집
    내가 개발한 서비스가 정확하게 동작하고 있는지, 얼마만큼의 리소스를 요구하고 있는지, 어떤 부분에서 무슨 이유로 장애가 발생했는지 알기 위해서는 로그와 메트릭 수집이 중요하다. 일반적으로 Agent들을 설치해서 수집한다. 다만, 시스템 정보를 통해 확인할 수 있는 메트릭에 비해 각 서비스별로 수집해야 하는 로그는 보통 파일로 쓰고 Agent가 그 파일의 변경을 추적해서 중앙에 집중시키는데, 로그파일이 디스크를 꽉 채워서 서버를 통째로 죽이는 일도 빈번하고, 서버에 새로운 서비스를 배치할 때마다 로그 수집 환경을 추가해야 하는 것도 번거롭다.
  3. 무정지 배포
    REST API를 중심으로 서비스하는 환경에서는 새로운 버전을 배포할 때 기존의 배포된 버전이 새로운 버전으로 교체되기 전에 기존의 사용자 요청에 대해 안전하게 응답을 마치고 종료되기를 희망한다. 그러나 여러가지 시나리오 때문에 쉽게 가능한 일은 아니다.
  4. 서비스 변화와 확장에 따른 Routing 설정 변경
    가장 짜증날 수 있는 부분이다. 앞서 이야기한 적절한 리소스의 배분과 무정지 배포를 신경 쓰면서 추가와 삭제, 스케일링 되는 서비스들을 유기적으로 로드밸런서에 연결하기란 쉽지 않다. 게다가 마이크로 아키텍처에서 REST API 서비스를 위해서는 Host Domain과 URI Path에 따라 다양하고 복잡한 라우팅 변경이 빈번하게 요구된다.

그렇다면 토스 서비스의 백엔드는 위의 어려움들이 어떻게 관리되고 있을까?

  1. 리소스 배분
    DC/OS는 Mesos cluster를 기반으로 Marathon을 결합한 가장 오래되고 가장 성숙한 Container Orchestration System이다. 여기서 Docker Orchestration이라고 지칭하지 않은 이유는 Docker Swarm이나 Kubernetes가 Docker의 관리와 제어만을 위한 Orchestration System이라면, Mesos는 None Docker Container도 노드에 올릴 수 있기 때문이다. 어쨌거나, DC/OS는 서비스가 요구한 리소스(CPU와 Memory, Disk 등)를 충분히 제공할 수 있는 적절한 HW 노드를 찾아 자동으로 실행해 주기 때문에, 서비스를 어느 서버에 설치해야 하는지에 대해 고민하거나 서비스의 과도한 리소스 사용으로 서버가 죽는 상황을 미연에 방지해 준다.
  2. Metric & Log 수집
    메트릭은 Telegraf를 통하여 시스템과 Docker 메트릭을 동시에 수집한다. 이 때, Docker Container의 메트릭은 docker name 또는 DC/OS에 배포한 서비스 ID를 통해 Grafana에서 손쉽게 검색할 수 있다. 전자는 DC/OS를 통하지 않은 단독 실행시 검색 방법이며, 후자는 DC/OS에 의해 실행된 서비스 Container들을 검색하는 방법이다. 따라서 후자의 경우 서비스 ID를 통해 새로운 서비스의 메트릭을 즉각적으로 추적할 수 있다.
    또한, 로그에서는 stdout, stderr에 로그를 출력하고 Filebeat을 통해 수집하는 방법과 직접 Kafka로 로그를 Producing하는 방법이 있는데, 어느 쪽을 선택하던지 개별 서버에 접근하여 추가적인 조작을 가할 필요 없이 배포만으로 Elasticsearch에서 즉각적인 로그의 실시간 추적이 가능하다.
  3. 무정지 배포
    DC/OS에서는 기본적으로 Canary Deployment와 Blue-Green Deployment를 할 수 있는 기능이 있다. 그러나 이 방식이 REST API가 안전하게 응답을 완료할 수 있도록 기본적인 보호를 제공하지는 않는다. 그러나 토스 서비스의 백엔드에서는 Marathon-LB의 특성을 이용하여 완벽한 무정지 배포 방식을 완성하였다.
  4. 서비스 변화와 확장에 따른 Routing 설정 변경
    Marathon-LB는 Marathon과 연계하여 서비스 배포 옵션에 맞추어 동적인 로드밸런싱을 제공한다. 토스 서비스에서는 Host와 URI Path 정보를 통해 동적으로 Routing을 확장하고 변경하고 있다. 따라서 REST API의 변화에 따라 로드밸러서를 조작하다가 서비스에 장애를 일으키는 상황을 최대한 제거할 수 있게 되어있다.

요약하자면, 토스 서비스의 백엔드에서는 개발한 서비스를 DC/OS에 배포하는 즉시 가장 안전한 노드에 자동으로 배치되고, 서비스가 시작됨과 동시에 메트릭과 로그 수집이 시작된다. 또한, 내가 지정한 Host Domain과 URI Path에 맞춰 로드밸런서에 연결되어 바로 원하는 형태의 REST API를 서빙할 수 있다. 여기에 Failover는 덤이고 이 모든 것이 처음부터 끝까지 자동이다.

DC/OS를 중심으로한 Micro Service Architecture와 DevOps 환경 구성의 목적은 절대로 서버에 SSH Terminal로 로그인하지 않는 것이었다. 실제로 위와 같은 구성은 매우 광범위한 시스템들을 다루고 있기 때문에 아직 최적화와 좀 더 스마트한 응용이라는 도전 과제가 남아있기는 하지만, 지난 한달간 레거시 서비스 일부와 새로운 서비스들을 DC/OS로 이동하면서 소기의 목적 대부분을 달성한 것 같다. (그러니까 잘하는 서버 개발자 있으시면 와서 같이 개발해요~ 코드만 짜게 해드릴께요~ 꽃길만 걷게 해드릴께요~ DevOps에서 Ops는 대부분 자동화 되었응께~)

마지막으로 Infra Structure를 지원해 주시고 계신 김병륜님과 배포 시나리오의 구멍을 계속 지적해 주신 이형석님, 엉성하게 연결 해놓은 Kafka와 ELK를 최적화 및 고도화 해주고 계신 정민규님, Grafana 사용법을 여쭤볼때마다 친절하게 설명해주시는 유승민님, 첫타자로 DC/OS에 서비스를 적용하면서 고생하신 김건우님께 매우 감사드립니다.