들어가며
회사에서 웹 프로젝트를 개발하면서 협업 단계에서 서로 다른 개발환경과 로컬/테스트/배포 환경의 구성으로 인해 많은 문제점을 마주쳤었다. 이번 포스트에는 이와 관련된 문제들가 이를 어떻게 해결했었는지를 기록해보고자 합니다.
문제점
꽤 오래전의 일이지만 설명의 흐름을 위해서 처음부터 시작해보겠습니다.
1. 서로 다른 개발환경
팀 프로젝트를 진행하면서 다른 개발자가 커밋한 코드를 받아서 내 로컬에서 실행하면 한 번쯤은 오가는 말들이 있습니다.
A : "커밋 해주신거 설치해봤는데 안 돌아가는데요?"
B : "어 저는 돌아가는데요? 왜 그러지..."
확인해보면 .gitignore로인해 SSH/Oauth2 key, Database Profile 등 민감한 데이터가 공유되지 않은 것 부터 환경 변수 차이 등 다양한 문제가 있습니다. 하지만 대부분 버전이 조금씩 다르거나 같은 버전이어도 JVM 설정이 다른 경우 등 자잘한 문제를 찾고 해결하는데 진땀을 흘린적이 많았습니다.
2. 배포과정
1번 문제를 해결해서 이제 서로 공유된 코드를 실행할 수 있다고 해도 배포 과정에서 다시 문제가 생깁니다. 이전에 정리했던 설정을 일일이 업데이트하고 설정 변경사항이 생기면 이 또한 업데이트를 해야 원활히 배포코드를 실행할 수 있습니다.
1차 해결방법
짐작했겠지만, 컨테이너를 도입해서 개발환경을 일치시키도록 하였습니다. Chef를 이용하는 방법도 있지만, 단순한 Configuration 세팅 뿐만 아니라 OS의 차이 등 Chef만으로는 통일시키기 어려운 환경구성도 존재하기에 우리는 컨테이너를 도입했습니다.
컨테이너 중에서도 생태계가 가장 발달된 Docker를 선택하였습니다. Docker는 clinet-server 구조로 인해서 보안과 관련된 이슈가 존재했기는 하지만 이익에 비해 큰 문제는 되지 않을 것이라 생각했습니다. Docker-compose를 이용해서 Proxy(Nginx with Client), Middleware(Spring), Database 3개의 컨테이너 개발환경을 한번에 구성할 수 있었습니다. 하지만 이것도 원활한 개발에는 문제가 있었죠.
새로운 문제
처음에는 Docker Network를 이용하여 3개의 컨테이너가 내부적으로 통신하도록 구현했습니다. 그러다 보니 local에서 개발할 때마다 디버그를 위해서는 빌드를 하고 빌드파일을 이용해 이미지를 생성한 다음 컨테이너를 실행해야 했었는데, 핫픽스 같은 경우에도 매번 이런 과정을 거쳐야 하기에 개발시간이 늦어지고 팀원들의 노트북이 점차 뜨거워지며 이륙을 하기 시작했습니다.
그리고 개발을 할 때 IDE환경에서 출력되는 로그를 보고 에러나 프로파일을 즉각적으로 확인하고 고쳐야 하는데, 컨테이너로 실행하면 일일이 컨테이너 터미널을 실행해서 로그를 찾아봐야한다는 점 또한 매우 번거로웠습니다.
2차 해결방법
여기서 local,dev,prod가 등장합니다. 저는 다른 분들의 환경 구성을 참고하여 우리 개발 환경에 필요사항을 추가하였습니다. 아래의 설명은 일반적으로 알려진 local,dev,prod의 역할보다 추가사항을 중점으로 서술하겠습니다.
1. local
이 환경의 목표는 서로 다른 로컬에서 동일하게 동작하도록 하는 것과 자신이 사용하고 있는 IDE와 원할히 호환하는 것이었습니다. 이전에 말한 문제를 해결하기 위해 local 환경에서는 컨테이너가 서로 Docker Network를 이용해서 내부적으로 통신하지 않고 호스트를 통해서 통신하도록 하였습니다.
여기서 만약 자신이 벡엔드 개발자라 미들웨어 코드를 핫픽스하고 이 코드가 데이터베이스와 프록시 사이에서 잘 동작하는지 테스트 하고 싶다면, Middleware 컨테이너를 멈추고 Intellij에서 빌드를 실행하면 됩니다. 그러면 Intellij의 프로파일링 등 유용한 기능을 그대로 사용하면서 데이터베이스, 클라이언트와의 통신과정 등 통합 테스트를 빠르게 진행할 수 있습니다. 프론트도 마찬가지입니다, Webstorm에서 빌드를 실행하고 클라이언트 컨테이너는 멈추고 localhosat로 api를 전송하면, Middleware, Database와 통합테스트가 가능합니다. (Mocking 테스트 코드를 이용하면 이런과정이 필요없지 않을까 생각도 했지만, CORS같이 실제로 연결해봐야 알 수 있는 에러도 빠르게 파악하고자 local에 이 과정을 도입하였습니다.)
2. Dev
Dev는 각 소규모 Feture들이 dev 브랜치에 합쳐저 한 번의 스프린트에서 구현할 기능들을 통합하여 테스트하기 위한 환경입니다. Local 과정에서 각 컨테이너별 기능 버그를 모두 해결했다는 가정하에 통합테스트를 진행합니다. 배포환경과 매우 유사하게 만들고 end-to-end 테스트를 진행하는데 이 과정에서는 종합적으로 오류가 발생하지 않는지, 컨테이너간 통신 병목현상 등이 발생하지 않는지 확인하고 개선 계획을 세운다. 이를 위해선 각 컨테이너에 대한 모니터링이 필요하기 때문에, Docker Network와 컨테이너 외부 포트를 모두 사용합니다. 외부 포트는 모니터링을 위해서만 사용하도록 합니다. (그라파나, 프로메테우스 같은 모니터링 툴을 사용하지 않는 이유는 제한적인 모니터링만 가능하기 때문입니다. 가령 미들웨어 파일시스템이 이미지가 잘 업로드 되는지 확인하려면 직접 터미널을 열어서 확인하는 경우가 더 빠르고 편리하기 때문)
3. Prod
Prod는 실제 서비스를 제공하는 환경입니다. Dev에서 테스트가 완료되면 CD를 통해서 자동으로 배포가 진행되며, 직접적인 코드수정을 금지합니다. Dev환경과 상당히 유사하지만 Middleware, Database 컨테이너가 외부에 포트를 공개하지 않는다는 점이 차이점입니다. Docker Network를 이용해서 오직 Proxy를 통해서만 접근이 가능합니다. 원천적으로 외부와의 직접적인 연결을 차단하기에 보안을 향상시킬 수 있습니다. 하지만 배포 환경도 잘 동작하는지 확인이 필요합니다. 외부 포트를 공개하지 않기에 간접적으로 개발자가 미들웨어나 데이터베이스에 접근하기 위한 중개자가 필요한데 이 때 그라파나, 프로메테우스를 사용합니다.
마치며
중간까지는 개발환경 구성을 다른 포스트나 문서를 보고 그대로 구현하는 경우가 많았는데, 우리 팀의 상황을 고려해서 개발환경을 개선하는 작업은 즐겁고 공부가 많이 되었습니다. 아직 미숙해서 문제점이 많겠지만, 문제점이 나오는데로 새로운 개선방법을 모색할 예정입니다.