오늘의집 MSA Phase 1. 서비스 구축과 배포 with Mortar
MSA 구축 및 배포 관리 플랫폼의 설계부터 구현까지
2021년 12월 17일 로버

안녕하세요. 오늘의집 플랫폼 백엔드 개발자 로버입니다. 오늘의집에서 MSA로의 전환을 결정한 이후, 플랫폼팀에서는 서비스를 구축하는 PoC를 진행했습니다. 그 과정에서 Microservice로 서비스를 개발할 때 생기는 여러 문제를 도출하고 이를 해결하기 위해 'Mortar'라는 프로젝트를 시작했습니다.

📌 MSA 전환 첫 번째 이야기는 진식님 블로그 글 참고 (클릭)

참고로 ‘Mortar’은 건설현장에서 벽돌이나 블록 등을 붙이는데 사용하는 건설 재료를 뜻합니다. 오늘의집 각각의 Microservice를 오늘의집을 지을 블록으로 생각한다면, Mortar 그 사이를 잘 붙여서 튼튼한 집으로 만들기 위한 도구라는 뜻으로 이름을 지었습니다.

장미빛 미래로 향하는 가시밭길 (Why)

플랫폼팀에서는 Microservice로 서비스를 개발할 때 생기는 여러 문제를 아래 여섯 가지로 생각했습니다.

  1. 서비스 라우팅 및 인증 구축 필요
  2. 프로젝트 생성 및 배포 설정 어려움
  3. 서비스 유지 보수 및 관리 비용
  4. API 연동 비용 발생
  5. 호출 스택 증가로 인한 latency 증가
  6. 서비스 간 로그 분석 및 디버깅의 어려움

그리고 이러한 점들이 MSA의 많은 장점에도 불구하고 전환을 망설이게 한다는 점을 알게 되어 플랫폼팀은 MSA 서비스 개발 시에 나타나는 이러한 어려움을 하나씩 제거하기 위한 목표를 세우고, 이를 달성하기 위한 각각의 프로젝트를 수립하였습니다.

가장 먼저 API Gateway와 인증 서비스를 2021년 초부터 개발하여 1번 과제였던 ‘서비스 라우팅 및 인증 구축’을 달성하였습니다. 그리고 이번 글에서 설명할 Mortar 프로젝트는 서비스의 관리 및 배포 등을 포함하여 위에서 이야기한 2~5번 문제점을 해결하고 6번을 포함해 이후 예상되는 어려움을 풀기 위한 단초 마련을 목표로 시작하게 되었습니다. 추가로 Mortar 연계하여 현재의 Service Mesh와 Open Telemetry 시스템 도입, Log SDK 개발을 통해 MSA 전환에 따른 어려움을 해결하는 것을 목표로 하고 있습니다.

Mortar가 해결하려는 문제 (how)

공통 업무 자동화

개발자가 Microservice를 구축할 때, 개발 프로세스는 크게 7가지 단계로 진행된다고 가정하였습니다. [1]

Mortar Microservice 구축 단계마다 꼭 필요하지만 반복적인 공통 업무들을 자동화하여 비즈니스 로직 구현(그림에서 인터페이스 정의, 비즈니스 로직 개발, 테스트)에만 집중할 수 있도록 설계했습니다. 이때 각 비즈니스 로직의 요구사항과 개발팀이 선택하는 언어 및 프레임워크에 따라 형태가 많이 다를 수 있으므로, 이를 커버하기 위해 최대한 언어 및 프레임워크에 독립적인 형태로 사용할 수 있도록 만들 필요가 있었습니다. 따라서 특정 언어나 프레임워크에 종속되지 않는 새로운 설정 파일 및 Command Line Interface(CLI) 도구를 자체 개발하였습니다.

한 가지 더, 통합 서비스 관리

Mortar에서는 서비스 설계에 최대한 제한을 가하지 않으면서 각 서비스가 통합되기 좋은 방향을 제시하고자 했습니다. 아무런 제한이 없는 경우, 개별 서비스에서 서비스명이나 버전, API 정의 클라이언트 사용 방식 등을 너무 다르게 개발하면 서비스 간 연동이나 DevOps, 그리고 인프라 관리의 측면에서도 어려움이 발생할 수 있습니다. 이로 인해 실패하는 Microservice 구축 사례도 쉽게 찾아볼 수 있습니다. 따라서 Mortar는 관리에 꼭 필요한 것들을 통합하여 유지할 수 있도록 제한하는 역할도 하도록 했습니다. 또한 이러한 통합을 통해 이후 Service Discovery 및 Log Tracing 등에서 활용할 수 있는 정보의 단초를 마련하려는 목적도 있었습니다.

설계

프로젝트 초기에는 단계별로 Best Practice를 정의하고 각 단계에 필요한 툴과 서비스들을 사용할 수 있도록 가이드라인을 제공하는 정도로만 진행하려고 했으나 이는 곧 문제에 봉착하게 됩니다.

어떤 것이 문제인가

Microservice라는 개념이 나온지 이미 상당한 시간이 흘러 지금은 커뮤니티에서 상황에 따른 여러 해결책이 제시되고 있습니다. 그러나 각각의 해결책은 특별한 목적을 가지고 작은 부분을 잘 해결해주는데 집중하기 때문에 전체 프로세스를 구성하기 위해서는 다수의 해결책을 잘 배치하고 연결하는 작업이 필요했습니다. 하지만 서비스 개발자가 이 모든 것을 공부하고, 향후 변경에 대응해 나가는 것은 굉장히 많은 시간과 노력이 소요됩니다. 실제로 로직 구현보다 더 많은 시간이 걸리는 경우가 있을 정도로 MSA 전환에 큰 걸림돌이 되었습니다. Mortar 이러한 각각의 도구를 직접 사용하지 않고도 하나의 설정 파일로 대부분의 기능을 사용할 수 있을 뿐만 아니라,  커스터마이징을 위해 각각의 도구를 직접 사용할 수도 있게 설계되었습니다.

어떻게 해결하는가

Mortar은 mortar.yaml이라는 단일 설정 파일을 통해 위에서 필요한 여러가지 툴과 프레임워크들을 조합하여 프로세스를 자동화하였습니다. Mortar의 사용자는 gRPC나 Docker build, Init, Prepare, Build, Deploy라는 4가지 Lifecycle을 통해 각각의 개발 프로세스 단계에 맞는 구현체를 제공받아 개발을 더욱 빠르게 진행할 수 있습니다. 예를 들어, Prepare 단계에서 앞서 정의된 gRPC proto 파일을 통해 stub 코드를 미리 제공함으로써 비즈니스 로직 개발 시 해당 코드를 상속하기 쉽게 정의했습니다.

동작 방식 및 구현

Mortar을 구현하면서 가장 중요하게 생각했던 부분은 패키지 관리 및 프로세스는 단일화하되 실제 구현을 제약하는 부분은 최소화하는 것이었습니다. 현재 나와 있는 일부 솔루션은 특정 언어 혹은 프레임워크를 사용해야 한다는 제약이 있었고 (protobuf gradle plugin), 이는 결국 다양한 기술스택이 사용 가능하다는 Microservice의 장점 중 하나를 퇴색시켰습니다. 이에 Mortar에서는 특정 빌드 시스템의 plugin 형태가 아닌 독립된 CLI로 따로 분리 설계하고 새로운 설정 파일(mortar.yaml)을 통해 관리되도록 설계했습니다.

아래는 Mortar 설정 파일(mortar.yaml)의 예제입니다.

apiVersion: v1 name: sample-echo # 서비스 명 namespace: mortar # 서비스 네임 스페이스 group: platform # 소유 팀 정보 version: 0.0.3 # SemVer 2.0 <https://semver.org/lang/ko/> type: Service # 서비스 타입 ['Service', 'Library', 'External'] 중 하나 description: |- Echo service for mortar testing protos: # protobuf IDL 파일들이 있는 장소 includes: - "protos" imports: # Ohouse 내부에서 제공하는 패키지 의존성, 기본값 없음. - namespace: googleapis name: api-common-protos version: 1.50.0 gen: # 코드 생성 설정, 기본값 없음. grpc: server: kotlin clients: - java - kotlin - node doc: # 자동 생성되는 문서, html, asciidoc, json 지원 - html build: cmd: |- ./gradlew bootJar -x mortarPrepare image: # 도커 파일 이미지 생성 및 저장소로 배포 dockerfile: docker/Dockerfile # Dockerfile 위치, 기본값 docker/Dockerfile target: # 리포지토리에 push할 target 이미지, 기본값 app - app repo: mortar/sample-echo # Docker repository 리소스 명 metadata: gitrepo: <http://github.com/bucketplace/example> envs: - name: beta endpoint: <http://beta.example.com>

터미널에서 Mortar init을 실행하면 서버, 클라이언트 언어 선택 등 몇 가지 질문이 나오는데요. 답변을 통해 설정 파일과 함께 기본적인 서비스의 boilerplates를 생성해줍니다. (create-react-app이나 spring initializr과 유사)

이렇게 생성된 프로젝트 위에서 인터페이스 정의 파일을 수정하고 mortar prepare를 실행하면 서버 stub을 만들어주므로 인터페이스를 직접 구현할 필요 없이 비즈니스 로직을 바로 작업할 수 있습니다. 비즈니스 로직이 완성되면 설정 파일에 정의된 build command를 실행해 주는 mortar build 명령으로 실제 서비스를 빌드하고 실행해 볼 수 있으며, mortar test를 이용하면 정의된 Proto file을 기반으로 쉽게 테스트 해볼 수 있는 Web interface도 제공받을 수 있습니다.

▲ 테스트 web UI 화면

테스트가 완료되면, mortar deploy 명령을 통해 로컬에서 배포하거나 github workflow를 통해 mortar deploy action을 활용해 github action 형태로 배포할 수도 있습니다. 배포가 되면 서버코드는 docker image로 빌드되어 docker 저장소에 배포되고 클라이언트 코드는 사내 nexus로 배포되어 아래와 같이 준비됩니다.

  • @bucketplace/mortar-sample-echo-stubs : NodeJS
  • se.ohou.mortar:sample-echo-stubs-kotlin: Kotlin
  • se.ohou.mortar:sample-echo-stubs-java: Java
  • se.ohou.api.proto.mortar:sample-echo: proto file descriptor set 과 documents

배포 이후에는 Mortar 서비스 관리 페이지에서 배포된 서비스 정보와 각 버전별 연동 방법 등을 확인할 수 있으며, 현재 서비스의 traffic, latency 등의 기본 metrics 등을 볼 수 있게 개발을 진행 중에 있습니다.

구현

사내에서 Mortar CLI를 간단하게 설명할 때, 여러 도구들의 wrapper라고 언급하곤 합니다. 실제로도 Mortar 실행되면 내부적으로 아래 리스트의 툴들을 위한 설정 파일을 생성하여 명령어를 실행 혹은 API 호출을 통해 활용합니다.

  • Gradle, Npm: 사내 패키지 저장소에 stub 배포
  • protobuf compiler: protobuf IDL로 정의된 서버 및 클라이언트 stub 생성
  • Buf: gRPC의 품질 관리 (Breaking Change Detection)
  • Docker: 서버 이미지 빌드 및 배포
  • Git: IDL 의존성 관리
  • Nexus: 사내 저장소 상태 확인 및 호출

또 이러한 도구 및 라이브러리들은 docker image 형태로 packing 되어 실행되기 때문에 사용자는 위의 수많은 툴들을 로컬에 설치할 필요가 없도록 구현했습니다. (docker 형태로 구현된 것은 이후 Github Action을 만들 때도 큰 도움이 되었습니다.)

구현상의 가장 큰 난제는 서비스의 형태가 모두 제각각이어서 이를 모두 수렴할 수 있는 구현체가 필요하다는 점, 더불어 각 도구들이 요구하는 설정의 형태나 환경이 완전히 다르다는 점이었습니다. 따라서 내부적으로는 각각의 단계를 task 형태로 추상화 하고 실행 시 들어온 설정 파일들을 통해 각각의 task를 scheduling 하도록 구현하였습니다.

향후 나아갈 길

현재 Mortar Kotlin, client는 Java/Kotlin, NodeJS, Go만 지원하고 있으며, 앞으로 다양한 기술 스택을 지원하기 위해 언어 지원을 점차 늘려나갈 예정입니다. 또한 각각의 언어에서 더욱 사용하기 편리하도록 sdk 및 gradle plugin 등을 제작하고 있습니다. 이 외에도 서비스의 배포, 관리, 유지 측면에서 DevOps팀과 협업하여 좀 더 많은 인프라 연동을 지원하고, Log tracing 및 standard metrics 시스템 등과 Mortar와의 연계성을 더 높여 사용자가 서비스에 대해 더 많은 정보를 쉽게 확인할 수 있도록 만드는 작업을 진행하고 있습니다. 이 외에도 buildpacks 연계를 통한 이미지 빌드 자동화 등 추가로 자동화할 수 있는 부분들을 찾아 개선하려고 합니다. 그리고 이를 통해 개발자가 메인 로직을 개발하는데 집중할 수 있게 하여 효율성을 높이고 속도를 빠르게 하는 것이 최종적인 목표입니다.

오늘의집에서 당신을 찾고 있습니다!
Software Engineer, Backend (집중채용)Software Engineer, Android (집중채용)Software Engineer, iOS (집중채용)Senior Software Engineer, FrontendSoftware Engineer, FrontendSenior Software Engineer, DataSoftware Engineer, DataSenior Software Engineer, Machine LearningSoftware Engineer, Machine LearningSoftware Engineer, Extended RealitySenior DevOps EngineerSenior Cloud EngineerCloud EngineerEngineering Manager, CommerceTechnical Lead & Manager, BackendTechnical Lead & Manager, Backend, AdsTechnical Lead & Manager, FrontendTechnical Lead & Manager, DataSenior Software Engineer, Extended RealitySenior Technical Program ManagerSoftware Engineer (전문연구요원)
목록으로 돌아가기