서버 사이드 테스트 자동화 여정 – 3. Docker를 활용한 통합 테스트 환경 개선

2편에서 소개한 Docker 기반의 통합 테스트를 이용하여 PR 단위 회귀 테스트를 수행할 수 있게 되면서 적은 테스트 비용으로도 안정적으로 서비스를 개발할 수 있게 되었습니다. 하지만 Docker의 기술적 특성 때문에 발생하는 문제들이 있었습니다. 이번 글에서는 Docker의 기술적 특성 때문에 발생했던 여러 문제를 해결하는 과정을 소개하고, 문제를 해결하기 위해 구축했던 인프라 환경을 활용하여 시스템을 추가적으로 개선했던 사례를 소개하고자 합니다.

 

Docker 적용 후 발생한 문제점과 해결 방법

 

자동 삭제되는 로그를 보관하고 쉽게 찾아볼 수 있게 만들기

Docker를 활용하니 이전에는 생각하지 못했던 형태의 테스트(PR 단위로 격리되어 구성되는 가상 테스트 환경에서 통합 테스트 수행)가 가능해졌습니다. 그런데 Docker 컨테이너는 종료될 때 컨테이너 내부 데이터가 사라져 버리는 특성이 있었는데요. 이 특성 때문에 로그를 확인할 수 없어서 테스트가 실패했을 때 원인을 찾기가 어려웠습니다. Docker는 이런 문제를 해결하기 위해 호스트 머신에 데이터를 저장할 수 있는 방법을 제공하고 있습니다. 이를 활용하면 로그를 지속적으로 보관할 수 있지만, 로그가 물리적으로 어느 서버에 저장되어 있는지 확인할 방법이 필요했습니다.

이 문제를 해결하기 위해서 보통 중앙 집중형(centralized) 로깅을 많이 사용합니다. 중앙 집중형 로깅이 가능한 환경은 다양한 방법으로 구성할 수 있는데요. 가능한 한 공수가 적게 드는 방법으로 환경을 구성하고 싶었습니다. 이미 팀 내 많은 개발자들이 사용하고 운영한 경험이 있는 ELK Stack을 고려해 봤지만, 접근(access) 로그와 같이 한 줄로 표현되는 로그가 아니라 여러 줄로 표현되는 디버깅 로그는 Kibana로 보기에 적합하지 않았습니다. 그래서 중앙화된 저장소에서 디버깅 로그를 찾을 수 있는 도구를 따로 만들까 고민하던 차에, Kibana 6.7 버전에서 Logs라는 기능이 정식으로 릴리스되었다는 것을 알게 되었습니다.1 이 기능은 요청 식별 ID를 이용해 여러 줄로 구성된 디버그 로그를 보기에 적합한 기능이었습니다. 그래서 이를 바탕으로 Docker 컨테이너에서 발생하는 서버 모듈의 로그를 모두 Elasticsearch 저장소에 저장할 수 있도록 아래 그림과 같이 구성했습니다. 

통합 테스트 환경에서 수행된 테스트 결과로 생성되는 모듈의 디버깅 로그를 Elasticsearch에 저장하고 Kibana로 조회하는 구조입니다. 적용 결과, 테스트가 어느 Docker 노드에서 실행됐는지 확인할 필요 없이 요청 식별 ID를 이용하여 디버깅에 필요한 로그를 쉽게 찾을 수 있게 되었습니다.

 

로그 검색 인프라를 활용하여 디버깅 시간 단축하기

Elasticsearch와 Kibana를 이용하여 통합 테스트 환경에서 발생하는 로그를 찾을 수 있게 되었지만, 로그를 찾기 위해 필요한 요청 ID를 찾는 게 어렵다는 문제가 발생했습니다. 테스트에 사용된 요청 ID가 모든 테스트 시나리오의 실행 결과 로그에 포함되도록 개발했지만, 통합 테스트 구조를 모르는 사용자 입장에서는 디버깅 대상이 되는 요청 ID를 찾는 게 어려운 것 같았습니다.

이 문제를 어떻게 해결할지 고민하다가 pytest-html 플러그인이 제공하는 보고서 커스터마이징 기능을 활용했습니다. 커스터마이징을 통해 테스트 케이스에서 발생된 모든 트랜잭션 기록을 아래 그림과 같이 보고서에 첨부할 수 있었습니다. 또한 각 트랜잭션 기록을 클릭하면 디버그 로그를 바로 확인할 수 있도록 바로가기 링크도 연결했습니다. 

커스터마이징된 pytest HTML 보고서

그 결과, 리포트의 Trace 칼럼에 표시되는 트랜잭션 기록 중 문제가 되는 것으로 추정되는 요청 (예: 위 그림의 경우 응답 코드가 503인 두 번째 요청이 디버깅 대상)을 클릭하면 아래 그림과 같이 디버그 로그를 확인할 수 있어서 문제 상황을 좀 더 빨리 파악할 수 있게 되었습니다. 

 

테스트 병렬화로 테스트 수행 시간 단축하기

자동화된 테스트 및 결과 확인 환경이 갖춰지고, 어느 정도 동작이 안정화된 이후, 통합 테스트 활용 효과를 더 높이기 위해서 테스트 케이스를 열심히 추가했습니다. 그러다 보니 다시 피드백 전달이 늦어지는 문제가 발생했습니다. 테스트 항목이 많아지니 테스트 보고서가 생성되기까지 걸리는 시간이 늘어나는 것은 당연한 일이었지만, 향후 수천 개의 테스트 항목을 추가할 목표를 가지고 있었는데 100여 개 밖에 안되는 테스트 항목을 수행한 보고서가 생성되는 데 20분이 넘게 걸린다는 것은 큰 문제라고 생각했습니다.

테스트를 수행하는 데 오랜 시간이 걸리는 원인은 테스트 케이스가 서버로 요청을 보내고 응답이 올 때까지 기다린 후 검증하는 구조 때문이었는데요. 통합 테스트 수행 목적상 이 문제를 근본적으로 해결할 수는 없었습니다. 다만 병렬화하면 어느 정도는 해결될 것 같았습니다. pytest에 동시에 여러 개의 테스트 케이스를 실행할 수 있는 플러그인을 찾아서 적용했고, 그 결과 테스트 수행 시간을 기존 대비 1/10 수준으로 단축시킬 수 있었습니다 

 

통합 테스트 실행 환경 준비 시간 단축하기

테스트 케이스 실행을 병렬화하여 이전보다 훨씬 빠르게 보고서를 받아볼 수 있게 만들었는데도 종종 피드백 제공에 오랜 시간이 걸리는 문제가 발생했습니다. 문제의 원인은 새로 생성되는 PR과 이미 열려 있는 PR에 추가되는 커밋이 비슷한 시간대에 몰리기 때문이었는데요. Jenkins 자원의 한계가 있기 때문에 모든 검증 작업을 동시에 실행시킬 수는 없었습니다. Jenkins 작업이 밀리는 문제를 이미 경험해 보았기에 처음에는 단순히 노드를 추가하면 되지 않을까 생각했는데요. 조금 더 고민해 보니 평균 작업 수행 시간이 긴 작업과 짧은 작업이 섞여서 동작하기 때문에 각 PR에서 수행해야 하는 테스트 케이스가 모두 완료되기까지 기다려야 하는 시간이 증가한다는 것을 깨닫게 되었습니다. 

이 문제는 여러 테스트 작업을 각 작업 특성에 맞는 전용 Jenkins 풀(pool)에서 실행하면 해결할 수 있겠다고 생각했는데요. 막상 문제를 해결하기 위해 새로운 Jenkins 풀을 구성하려니 발생하게 될 운영 비용이 부담으로 다가왔습니다. 그런데 참 고맙게도 Jenkins에선 이런 경우에 활용할 수 있는 라벨(label) 기능을 제공하고 있었습니다. 라벨을 이용하면 Jenkins에서 잡을 실행할 때 어느 노드에서 실행할지 결정할 수 있어서 각 작업별 특성에 맞게 서로 분리된 전용 노드에서 작업을 실행할 수 있도록 구성을 변경했습니다. 그 결과 통합 테스트를 제외한 모든 검증 작업은 약 3분 이내, 통합 테스트 보고서 생성은 8분 이내에 완료될 수 있도록 개선할 수 있었습니다. 아래 그림과 같이 Jenkins 라벨을 사용하면 잡이 실행될 노드를 지정할 수 있습니다.

 

개발자 로컬 환경에서 통합 테스트를 실행할 수 있게 만들기

테스트 코드를 작성할 때 ROI(Return On Investment)를 높이는 방법은 최대한 테스트 코드를 많이 실행하는 것이라고 생각합니다. 어떻게 하면 최대한 테스트를 많이 실행할 수 있을까, 고민하다 보니 통합 테스트를 PR 생성 시에만 실행하는 것이 아니라 개발자의 개발 업무용 PC에서도 실행할 수 있도록 만들어야겠다고 생각하게 되었습니다. 최근 모듈의 중요한 역할을 담당하고 있는 파일 90개 정도를 변경해야 하는 큰 작업이 있었는데요. 이 작업을 진행하면서 개발 업무용 PC에서 지속적으로 통합 테스트를 수행해 보니 생각지도 못한 버그도 발견할 수 있었고, 작업도 보다 빠른 속도로 진행되었습니다.

만약 이러한 문제들을 단위 테스트를 통해 발견할 수 있었다면 문제가 되는 지점을 좀 더 빠르고 정확하게 찾을 수 있었겠지만, 통합 테스트 덕분에 단위 테스트가 작성되어 있지 않거나 혹은 작성하기 어려운 형태의 레거시 로직에 대해서도 간접적으로 테스트해 볼 수 있었습니다. 다른 팀원들도 이런 장점을 누릴 수 있도록 아래 그림과 같이 PR마다 변경되는 실행 인자를 포함한 실행 방법을 댓글로 함께 첨부하여 누구나 쉽게 실행할 수 있게 만들었습니다.

 

자동화된 테스트 시스템의 최종 구조

총 3편에 걸쳐 LINE 미디어 플랫폼 조직이 안정적인 서비스를 개발하기 위해 준비하고 업무 환경에 적용한 다양한 테스트 자동화 활동을 함께 살펴보았는데요. 소개한 내용이 모두 포함된 자동화된 테스트 시스템의 최종 구조를 간략히 표현하면 아래 그림과 같습니다.

 

테스트 자동화 여정을 돌아보며

처음에는 단순히 ‘업무 생산성 저하를 막아보자’라는 생각으로 시작했던 테스트 자동화 작업이었는데요. 점진적으로 개선해 나가다 보니 어느새 최종 목표였던 ‘오랜 기간에 걸쳐 정립한 일관된 기준에 따른 시스템 평가’, ‘정량화된 테스트 지표 수집’ 그리고 ‘자동화된 평가’를 할 수 있는 시스템이 만들어졌습니다. PR을 생성하는 것만으로 기본적인 단위 테스트, 설정 검증 작업뿐 아니라 서비스 팀 및 인프라 현황 검증이 자동으로 이루어져 업무 효율이 대폭 개선되었고, PR마다 수행되는 통합 테스트가 회귀 테스트의 효과를 발휘, 개발자가 코드 리뷰를 시작하기도 전에 문제 발생 여부를 확인할 수 있게 되었습니다. 또 이전부터 LINE 메신저, Slack, 이메일 등으로 GitHub에서 발생한 이벤트에 대한 알림을 받고 있다보니 매번 GitHub에 접속하지 않아도 PR에 대한 기본적인 검증 작업이 완료되었는지를 확인할 수 있어 개발 업무 시간을 더욱 효율적으로 사용할 수 있게 되었고, 대규모로 코드를 변경해도 두렵지 않게 되었습니다.

앞으로는 현재의 테스트 자동화 시스템을 더욱 개선하여 모듈의 성능 측면에서 발생하는 병목 현상을 개선하고, 한계 상황을 자동으로 평가하는 작업과 카오스 엔지니어링을 가능하게 하여 시스템의 안정성을 더욱 높이고자 합니다. 미디어 플랫폼은 메신저로서의 기능을 넘어 사용자에게 즐거움을 주는 서비스, 그리고 생활 밀접형 서비스로 확장해 나가는 LINE 서비스 생태계의 기반입니다. 서비스 안정성을 향상시켜 기반을 더욱 튼튼하게 만들기 위한 노력은 앞으로도 계속될 것입니다.

전 세계 사용자가 발생시키는 막대한 규모의 트래픽을 안정적으로 처리하는 경험을 함께 해보고 싶지 않으신가요? 대규모 트래픽의 안정적인 처리에 관심이 있으신 분은 적극적으로 지원 부탁드립니다. We are hiring!

 


 

  1. Kibana Logs는 X-Pack 기능 중 일부로 Basic License 이상의 라이선스가 필요합니다. Basic License는 테스트 목적으로 사용하는 것은 허용되지만 상용 서비스에서 사용하는 것은 금지하고 있기 때문에 사용시 주의가 필요합니다. 테스트 자동화 시스템은 업무 환경에 적용한 지도 꽤 오래되었고, 그 기능과 사용 범위 또한 확대되었기 때문에 더 이상 Elasticsearch를 테스트 목적으로 사용하고 있다고 말하기 어려워졌습니다. 또한 테스트 대상이 되는 애플리케이션의 로그를 보다 빠르고 효율적으로 분석할 필요도 생겼습니다. 이에 따라 현재는 Basic License가 아닌 오픈 소스 버전의 Elasticsearch를 기반으로 자체 로그 뷰어를 개발하여 운영하고 있습니다(2020.06 내용 추가됨). 

Related Post