마이크로 서비스에서의 테스트 환경 구축

아주 작은 독립적인 기능을 수행하는 여러개의 마이크로 서비스 묶어서 하나의 비즈니스 기능을 구현하는 방식을 마이크로 서비스 아키텍처라고 합니다. 시스템을 이렇게 구성하는 이유는 작은 단위로 구성함으로써

  • 각 서비스별로 독립적인 개발, 배포를 진행할 수 있어
  • 병렬 개발, 각 서비스의 특징에 맞는 개발 언어, DB 사용 등을 할 수 있으며
  • 운영 중에도 각 서비스별로 배포, 확장, 축소 등을 자유롭게 할 수 있는

등의 장점이 있기 때문입니다.

장점이 있으면 당연히 단점도 있겠죠. 주요 단점으로는

  • 단위 서비스의 복잡도는 줄어들지만 전체 시스템의 복잡도는 더 증가할 수 있고
  • 서비스 운영 중 장애 발생 시 장애 지점을 찾기 어려우며
  • 각 서비스를 하나의 트렌젝션을 묶기 어렵고
  • 연결된 서비스들이 많기 때문에 자동화된 테스트 코드 작성 시 고려 사항이 많다

라는 것입니다.

이번 글에서는 마이크로 서비스 환경에서 Docker Compose를 이용하여 어떻게 자동화된 테스트 환경 만들어서 사용하고 있는지에 대해서 공유하려고 합니다.

마이크로 서비스의 테스트에 대한 글은 다음 글을 보시면 아주 자세하고 상세하게 나와 있습니다. 이번 글에서는 아래 Link에 있는 내용 중에 Unit Test 부분이아닌end-to-end 테스트 환경에 대한 글입니다.

테스트 관련 현실

현재 서비스 전체 서비스가 20 ~ 30개 정도의 서비스가 연동되면서 운영되고 있습니다. 예를 들어 PoS에서 제품을 판매를 등록하기 위해서 먼저 고객 서비스에서 고객의 회원 등급을 조회하고, 쿠폰 서비스에서 해당 고객의 쿠폰이 유효한지 검증, 재고 서비스에서 재고 처리, 택배 서비스에서 택배 처리 등을 수행하고 있습니다.

이전의 글에서도 밝혔지만 아직까지 개발팀의 역량이 Mock 등을 통해 Unit Test를 만들어 나가기 어려운 상황입니다. 이런 상황에서 하나의 서비스가 배포되면 변경된 API 기능에 의해 다른 서비스의 기능에도 영향을 미치는 상황이 자주 발생하게 되었습니다. 물론 이런 경우 어디서 문제가 발생했는지 찾기도 점점 어려워지고 있습니다. Mock 기반으로 테스트를 만들었다 하더라도 각 서비스에서의 문제 발생 시 다른 서비스에서 어떤 영향을 미치는지를 확인하기 어려운 상황은 여전할 것입니다.

이런 상황에서 Unit Test를 지속적으로 추가해 나가는 것을 시도하는 동시에 중요 기능에 대해서는 각 기능에 맞는 실제 운영 환경과 비슷한 환경을 구성하여 테스트를 수행하기로 하였습니다. 다행히 모든 서비스는 Docker 기반으로 운영되기 때문에 별도 인프라 환경을 구성하기 보다는 Docker Compose를 이용하여 개발자의 각자 PC 환경에서도 테스트가 가능하도록 할 수 있다고 판단하여 진행해보기로 했습니다.

End-to-end 테스트 환경 구성의 문제점

End-to-end 테스트 환경을 구성하기 위해서는 실제 환경에서와 같은 구성이 필요한데 DNS, L4 등과 같은 네트워크 구성이외에 실제와 비슷한 운영되는 서비스 환경 구성에 다음과 같은 문제점이 있습니다.

  • API Gateway, MySQL, Redis Kafka 등 다양한 기본 서비스가 테스트 환경에 구성되어야 합니다.
  • 테스트에 필요한 기본 데이터도 필요한데 하나의 서비스의 데이터가 아닌 테스트에 사용되는 모든 서비스의 데이터가 준비되어야 합니다.
  • 테스트 환경에 모든 서비스를 실행하면 좋겠지만 테스트 실행 시 테스트 속도가 느려질 수 있으며 PC 장비 사양의 제약 때문에 모든 서비스를 실행하는 것 보다는 테스트하려고 하는 기능에 참여하는 서비스만 실행하는 것이 좋습니다. 하지만 개발자도 본인이 사용하는 다른 서비스가 어떤 것인지 알 수가 없습니다. 직접 호출하는 API 는 알 수 있지만 그 API 이후에 다시 어떤 API가 호출되는지 알 수 없습니다. 하나의 단말 API에 대한 전체 Call Trace를 알수 없습니다.

테스트 환경 구성

이런 현실적인 상황과 테스트 환경의 문제점을 극복하기 위해 다음과 같은 구상을 해 보았습니다.

msa_test_process

서비스 개발자는 테스트 코드를 작성할 때 테스트 기초 데이터를 xml 이나 프로그램에서 생성하지 않고 미리 정의된 디렉토리에 sql 파일로 만듭니다. 이 부분은 개발자마다 취향이 달라 조금 논쟁의 이슈가 있는데 전체 테스트를 원할하게 수행하기 위해 강제한 조건입니다.

이렇게 만들어진 서비스는 운영 환경에서의 Docker Repository로 배포되는데 이때 자동으로 사무실에 구성된 Local Docker Repository로 동기화 구성을 해 놓았습니다. 이렇게 한 이유는 여러 개발자가 동일한 Container를 계속 원격(클라우드)에 있는 레포지토리로 부터 다운 받는 것에 대한 부담과 사무실의 네트워크 환경이 좋지 않은 이유도 있습니다.

테스트에 참여하는 서비스의 정보는 기존에 운영중인 Mingbai(明白)(1)라는 모니터링 서비스에서 가져 왔습니다. 이 모니터링 서비스는 zipkin 과 비슷한 형태로 구성되어 각 서비스들 간의 호출 관계를 트랙킹할 수 있는 기능을 가지고 있습니다. 아래 화면은 Mingbai에서 제공하는 서비스간 호출 관계를 표시한 화면입니다.

mingbai_call_trace

이렇게 테스트 환경을 구성하기 위한 모든 정보가 준비되면 테스트를 수행하는 프로그램에서 테스트를 수행합니다. 일반적으로 go 언어에서 테스트는 go test <test module> 형태로 수행하지만 직접 만든 간단한 스크립트를 통해 앞에서 구성한 정보를 이용하여 Docker Compose 파일을 만든 다음에 Docker Compose를 실행한 후 실제 테스트 코드를 수행하게 됩니다.

테스트 환경 구성 시 발생했던 문제점

대부분의 문제는 Docker Compose 구성 시 발생했던 문제입니다.

Container의 실행 순서 문제

현재 운영 중인 대부분의 API 서버 컨테이너들은 실행 시 사용하는 DB 서버, Redis, Kafka와 같은 서버에 접속이 안되는 경우 자동 종료되는 로직이 추가 되어있습니다. DB 서버나 Kafka 서버 등과 같이 단순히 프로세스가 실행되었다고 해서 해당 컨테이너가 사용 준비가 된 상태가 아니라, 데이터 로딩, 서버 환경 구성 등이 완료되어야 사용 가능 상태가 됩니다. 테스트 환경이라면 보통은 수초내로 사용 가능하지만 이 시간 이내에 응응 프로그램 컨테이너가 실행되면 문제가 발생하게 됩니다.

Docker Compose로 DB 서버 등을 실행하는 경우 Compose의 "depends_on" 설정을 이용하여 해당 컨테이너가 사용하는 연관된 다른 컨테이너를 지정할 수 있습니다. Docker Compose 버전 2에서는 "condition", "healthcheck" 설정을 이용하여 depends_on에 설정된 컨테이너가 단순 시작이 아닌 healthcheck에서 확인이 된 경우에만 준비가 되었다고 판단하는 기능이 있었는데 버전 3에서는 이런 기능이 사라졌습니다. 버전 3에서는 wait 스크립트 등을 사용하면 우회할 수 있다고 하는데 이런 상황은 특정 컨테이너의 초기 실행 명령 등을 조작할 수 있어야 가능합니다. 많은 마이크로 서비스 컨테이너를 다루어야 하는 상황에서 각 컨테이너의 실행 명령을 조작한다는 것은 무척 어려운 방법이라 다음과 같은 방법으로 해결하였습니다.

  • 테스트 시 Docker Compose 파일을 생성하고 테스트를 실행하는 직접 제작한 스크립트에서 DB, Kafka 등과 같은 서비스는 하나씩 실행하고 상태를 확인한다.
    • docker-compose up --detach mysql-server 와 같이 각각 실행
    • 확인 방법은 직접 mysql client 명령으로 mysql이 정상 동작하는지, 데이터 로딩은 다 되었는지 확인

Compose 외부에서의 접근

실제 테스트하고자 하는 기능을 가지고 있는 프로젝트(서비스)도 Docker Compose에 포함하여 실행할 수 있지만 위 구성에는 컨테이너 외부에서 실행하고 있습니다. 따라서 테스트 프로그램에서 데이터베이스, Kafka 등에 접근이 되어야 하는데 이 경우 Docker의 Port 매핑 기능을 이용하여 접근할 수 있습니다. 하지만 Kafka의 Docker를 사용하게 되면 IP 기반으로 호출하는 것이 어려워 hostname을 export하는 데 이 경우 다음과 같이 internal, external hostname을 지정해야 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
  kafka-server:
    container_name: test-kafka
    environment:
      KAFKA_ADVERTISED_LISTENERS: INSIDE://test-kafka:9092,OUTSIDE://localhost:29092
      KAFKA_ADVERTISED_PORT: 9092
      KAFKA_INTER_BROKER_LISTENER_NAME: INSIDE
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INSIDE:PLAINTEXT,OUTSIDE:PLAINTEXT
      KAFKA_LISTENERS: INSIDE://:9092,OUTSIDE://:29092
      KAFKA_ZOOKEEPER_CONNECT: test-zookeeper:2181
    hostname: test-kafka
    ports:
    - 9092:9092

결론

마이크로 서비스는 장점이 있는 반면 서비스간의 의존 관계로 인해 실제 상황과 비슷한 환경에서 테스트를 수행하기 어려운 문제가 있습니다. 이번 글에서는 중요 기능에 대해서는 실제 운영 환경과 비슷한 환경에서 end-to-end 테스트를 수행하시 위해 어떻게 테스트 환경을 구성했는지에 대한 경험을 공유했습니다. 현재 환경은 시범적으로 운영해보고 있으며 문제점을 보완해나가며 서비스 하나씩 적용해 나가고 있습니다. 언제나 그렇듯이 정답은 없고, 부딪히면서 개선해나가는 것이 정답이라 할 수 있겠죠.

주석

(1) Mingbai: 제 글중에 Mingbai에 대한 언급이 조금씩 나오는데 아직 공개할 수준은 아니고 조금씩 만들어 나가고 있습니다. 올 하반기 공개를 목표로 열심히 개발하고 있으니 조금만 기다려주세요.


Popit은 페이스북 댓글만 사용하고 있습니다. 페이스북 로그인 후 글을 보시면 댓글이 나타납니다.