Monorepo 환경에서의 CI/CD 최적화 방안
– 변경 기반 빌드 및 배포 전략 구축 사례 –
1. 서론
최근 프로젝트에서는 다수의 프론트엔드 애플리케이션과 공통 라이브러리를 하나의 저장소에서 관리하는 Monorepo 구조를 도입하였습니다. Monorepo는 코드 재사용성과 일관성 유지 측면에서 매우 효과적인 구조이지만, CI/CD 환경에서는 새로운 문제를 야기할 수 있습니다.
특히 모든 변경에 대해 전체 프로젝트를 빌드하고 배포하는 기존 방식은 Monorepo 환경에서 비효율을 초래합니다. 실제로 변경되지 않은 패키지까지 반복적으로 빌드 및 배포되면서 빌드 시간이 증가하고, 운영 비용이 불필요하게 상승하는 문제가 발생하였습니다.
본 글에서는 이러한 문제를 해결하기 위하여 “변경된 대상만 빌드 및 배포하는 CI/CD 구조”를 설계하고 적용한 사례를 소개해 드립니다.
2. 문제 정의
기존 CI/CD 파이프라인은 모든 커밋에 대해 전체 패키지를 대상으로 빌드와 배포를 수행하는 구조였습니다. 이로 인해 다음과 같은 문제가 발생하였습니다.
첫째, 빌드 시간이 과도하게 증가하였습니다. 변경되지 않은 패키지까지 포함하여 전체 빌드를 수행함으로써 CI 수행 시간이 길어지고, 개발 피드백 사이클이 지연되는 문제가 있었습니다.
둘째, 불필요한 배포가 빈번하게 발생하였습니다. 실제 변경이 없는 라이브러리까지 반복적으로 배포되면서 레지스트리 버전이 불필요하게 증가하였고, 배포 이력 관리 또한 복잡해졌습니다.
셋째, 변경 영향 범위를 명확히 파악하기 어려웠습니다. 특정 모듈의 변경이 어떤 애플리케이션에 영향을 미치는지 추적하기 어려워, 안정적인 배포 전략 수립에 어려움이 있었습니다.
이러한 문제를 해결하기 위해서는 단순 자동화를 넘어 “변경을 기준으로 한 선택적 실행 전략”이 필요하였습니다.
3. 해결 방안
3.1 변경 기반 빌드 전략
문제 해결의 핵심은 변경된 패키지만 정확하게 식별하는 것입니다. 이를 위해 Turborepo의 캐시 기반 빌드 구조를 활용하였습니다.
Turborepo는 이전 빌드 결과를 기반으로 변경 여부를 판단하며, 캐시 미스가 발생한 패키지를 변경 대상으로 간주합니다. 특히 dry run 기능을 활용하면 실제 빌드를 수행하지 않고도 변경된 패키지를 사전에 식별할 수 있습니다.
이 결과를 기반으로 CI 파이프라인에서는 변경된 패키지와 그 영향을 받는 대상만을 선별하여 빌드 및 배포하도록 구성하였습니다.
3.2 기준 시점 최적화
변경 여부를 판단하기 위한 기준 시점으로 단순히 이전 커밋이 아닌 “마지막 성공 빌드”를 사용하였습니다.
브랜치별로 마지막 성공 시점에 태그를 생성하고, 이후 빌드에서는 해당 태그를 기준으로 변경 사항을 비교하도록 하였습니다. 이 방식은 실패한 빌드의 영향을 배제하고, 보다 안정적인 기준에서 변경을 판단할 수 있도록 합니다.
3.3 내부 의존성 관리
Monorepo 환경에서는 내부 패키지 간 의존성 관리가 매우 중요합니다. 기존의 명시적 버전 관리 방식은 버전 불일치와 관리 복잡도를 증가시키는 문제가 있었습니다.
이를 해결하기 위해 내부 의존성을 workspace:* 형태로 통일하였습니다. 이 방식은 동일 저장소 내 패키지를 항상 최신 상태로 참조하도록 하여, 특정 라이브러리 변경 시 이를 사용하는 모든 패키지가 자동으로 변경 대상으로 포함되도록 합니다.
또한 CI 과정에서는 실제 배포 시점에 해당 의존성을 실제 버전으로 치환하여 배포 안정성을 확보하였습니다.
3.4 자동 버전 관리
버전 관리 또한 자동화 대상으로 설정하였습니다. 레지스트리에서 최신 버전을 조회한 후, 브랜치 정책에 따라 다음 버전을 자동으로 계산하도록 구성하였습니다.
release 브랜치에서는 patch 버전을 증가시키고, 그 외 브랜치에서는 pre-release 버전을 증가시키는 방식을 적용하였습니다. 이를 통해 일관된 버전 정책을 유지하면서도 수동 관리 없이 안정적인 배포가 가능해졌습니다.
3.5 CI/CD 파이프라인 구성
본 프로젝트의 CI/CD 파이프라인은 변경 기반 처리를 효율적으로 수행하기 위하여 단계별로 구성하였습니다.
먼저 prepare 단계에서는 변경된 패키지를 탐지하는 작업을 수행합니다. Turborepo의 dry-run 결과를 기반으로 캐시 미스가 발생한 패키지를 식별하고, 이후 단계에서 사용할 대상 목록을 생성합니다.
다음으로 publish-libraries 단계에서는 변경된 라이브러리를 빌드하고 레지스트리에 배포합니다. 이 과정에서 내부 의존성을 실제 버전으로 치환하며, 최신 버전을 기준으로 자동 버전 증가를 수행합니다.
이후 publish-apps 단계에서는 애플리케이션을 Docker 이미지로 빌드하고 컨테이너 레지스트리에 업로드합니다. 생성된 이미지 태그는 이후 배포 단계에서 활용됩니다.
gitops-deploy 단계에서는 GitOps 방식을 활용하여 Kubernetes 배포 정보를 업데이트합니다. 배포 YAML에 새로운 이미지 태그를 반영하고, 이를 저장소에 커밋함으로써 배포 이력을 관리합니다.
마지막으로 tag 단계에서는 현재 빌드의 성공 여부를 기록하기 위해 last-success-{branch} 태그를 갱신합니다. 해당 태그는 이후 CI 실행 시 변경 사항을 비교하는 기준으로 활용됩니다.
이와 같은 단계적 구조를 통해 각 단계의 역할을 명확히 분리하고, 안정적인 CI/CD 흐름을 구축하였습니다.
4. 적용 결과
해당 전략을 적용한 결과, CI 수행 시간은 기존 대비 크게 단축되었습니다. 전체 빌드를 수행하던 구조에서는 평균 20분 이상의 시간이 소요되었으나, 변경 기반 빌드 도입 이후에는 약 10분 내외로 감소하였습니다.
또한 불필요한 배포가 제거되면서 레지스트리 버전 관리가 단순해졌으며, 실제 변경 사항 중심의 배포가 이루어짐에 따라 운영 안정성이 향상되었습니다.
무엇보다 개발자는 CI/CD 과정에 대한 부담 없이 변경 사항에만 집중할 수 있게 되었으며, 이는 전체 개발 생산성 향상으로 이어졌습니다.
5. 결론
Monorepo 환경에서의 CI/CD는 단순한 자동화 적용을 넘어, 변경을 정확히 인식하고 제어하는 구조 설계가 핵심입니다.
본 사례에서는 변경 기반 빌드, 내부 의존성 관리, 자동 버전 관리, GitOps 기반 배포를 결합하여 효율적인 CI/CD 파이프라인을 구축하였습니다. 이를 통해 빌드 시간 단축, 배포 효율 개선, 개발 생산성 향상이라는 성과를 달성할 수 있었습니다.
향후에는 변경 영향 분석의 정밀도를 더욱 높이고, 테스트 자동화를 연계하여 보다 안정적인 배포 환경으로 발전시킬 계획입니다.
David