이번엔 지속적 통합 사례를 하나 정리해보겠다. (지속적 통합의 개념 설명은 이곳에..)
Components and Basic Workflows
이번에 구성해본 지속적 통합(CI) 환경의 구성 요소는 다음과 같다.
비록 RTC라는 상용 툴을 사용하고는 있지만, 이 글에서 다루는 대부분의 내용은 개념적인 것이라, 다른 툴(예: Hudson, TeamCity, CruiseControl)을 사용할 때도 그대로 적용/응용할 수 있다.
전체 시스템을 그림으로 나타내면 대략 다음과 같다.
RTC 서버의 다양한 기능 중, 여기에서는 소스 관리와 빌드 관리, 그리고 웹 UI 정도이다. 거시적인 작업 흐름은 다음과 같다.
- 개발자가 변경 내용(change-set)을 소스 저장소에 전달한다.
- RTC 서버의 빌드 모듈이 이를 인식해 적당한 빌드 엔진에 할당한다.
- 빌드 엔진은 소스 저장소로부터 빌드에 필요한 데이터를 내려 받아 Ant 빌드 스크립트를 수행한다.
- Ant 빌드 스크립트는 빌드를 수행하고 산출물을 개발 서버 및 API 서버에 배포한다.
- 빌드 엔진은 빌드 결과 및 과정은 빌드 서버에 알리고, 서버는 개발자 PC에 푸시한다.
개발자나 프로젝트 관련자들은 이런 모든 과정/결과를 언제든 전용 Eclipse UI나 Web UI를 통해 확인할 수 있다.
또한, 어떤 빌드 엔진을 사용할 지, 어떤 스크립트를 사용한 지 등은 모두 configuration 가능하다.
Project Directory Layout
여러 팀, 다양한 과제에 걸쳐 일을 효율적으로 진행하려면 프로젝트 구성부터 일관적으로 유지하는 것이 좋다.
Ant는 비록 산업 표준 빌드 툴이지만, 프로젝트 구성에 대한 표준 규약은 제공하지 않는다. 때문에 담당자 취향만큼이나 다양한 구성이 존재하며, 그 구성을 정하고 관리하는데에만 상당한 고뇌와 노력이 소요된다. 그리고 재활용도 쉽지 않다. 바로 이 문제를 타파하고자 나온 오픈소스 프로젝트로 Maven이란 것이 존재한다. Maven 개발자들은 프로젝트의 구성에 관련된 모범 사례(best practice)들을 집대성하고자 하였다. 무분별한 컨피규레이션 허용보다는 잘 정의된 모범 사례를 따른는 것을 원칙으로 삼은 것이다(Convention over Configuration). 물론 그 결과가 이상적으로 완벽하진 않지만, 상당수의 프로젝트에 적용하는데 큰 무리가 없을 것이다.
어쨌든, 본 예제에서는 Ant를 사용하지만 Maven의 이상과 결과를 상당부분 따르고 참조할 것이다. 물론 Ant이기 때문에 언제든 어렵지 않게 수정 가능하다.
그래서 내가 구성한 기본 구성은 다음과 같다.
- src/main/java – 제품 소스 코드
- src/main/resources – 제품에 포함될 리소스
- src/main/config – 제품 설정 정보
- src/main/webapp – 웹 애플리케이션 소스
- src/test/java – 테스트 소스 코드
- src/test/resources – 테스트에 필요한 리소스
- lib/main – 제품 수행에 필요한 라이브러리
- lib/test – 테스트에 필요한 라이브러리 (junit, mokito 등)
- tools – 팀내 공용 툴 (예: FindBugs, CheckStyle, Code Pro Analytix 등)
- build.xml – Ant 빌드 스크립트
- build.properties – Ant 빌드 스크립트용 커스텀 프로퍼티 파일
- build-jazz.xml – RTC/Jazz용 빌드 스크립트(build.xml을 확장함)
- LICENSE.txt – 제품 라이선스 정보
- README.txt – readme 파일
참조함 Maven의 표준 디렉터리 구성과 크게 다르지 않다. 간소화를 위해, 크게 필요 없다고 생각되는 filters, assembly, site, NOTICE.txt 를 제거하였고, lib과 tools가 추가되었다.
lib이 추가된 이유는 Maven이 종속성 자동 관리 기능이 포함된데 비해 Ant는 직접 필요할 라이브러리를 관리해야 하기 때문이며, tools 는 개발팀 내 함께 쓰는 유용한 도구와 그 설정 정보를 공유하기 위함이다.
빌드 스크립트는 총 3개의 파일로 구성된다. build.xml 은 메인 빌드 스크립트이며, build.properties에는 그 중 사용자 정의 속성을 담아, 상황에 맞게 설정하여 빌드할 수 있게 하였다. 마지막의 build-jazz.xml 은 build.xml을 확장(import)하여 RTC/Jazz에 종속된 기능을 추가로 수행하기 위해 추가하였다. 즉, build-jazz.xml를 제외한 두 파일은 RTC/Jazz와 완전히 독립적이어서 어떤 환경에서건 그대로 재활용할 수 있다.
스크립트의 속 내용은 조금 후에 살펴보기로 하고, 빌드 과정에서 생성되는 디렉터리 레이아웃에 대해서 먼저 살펴보자.
- target/classes – 제품 소스를 컴파일한 클래스 파일들 & 리소스
- target/test-classes – 테스트 소스를 컴파일한 클래스 파일들 & 리소스
- target/reports/unit-test – 단위 테스트 결과 리포트 (XML 포맷)
- target/reports/unit-test/html – 단위 테스트 결과 리포트 (HTML 포맷)
- target/reports/integration-test – 통합 테스트 결과 리포트 (XML 포맷)
- target/reports/integration-test/html – 통합 테스트 결과 리포트 (HTML 포맷)
- target/reports/findbugs – FindBugs 수행 결과 보고서
- target/reports/checkstyle – CheckStyle 수행 결과 보고서
- target – 빌드 산출물 루트 겸, package 된 제품 바이너리 등 최종 산출물
특별한 설명은 필요 없으리라 본다. 그렇다면 이제 Ant 빌드 스크립트의 내용과 빌드 단계에 대해 알아보기로 하자.
Ant Build Script and Build Lifecycle
Ant 빌드 스크립트는 빌드 타깃(target)과 타깃간 종속성(선행 타깃 정의)과 타깃에서 실행해야할 실제 작업을 정의한다. 빌드 라이프사이클 역시 Maven의 그것을 기반으로 간소화한 후 약간 보강하였다. 다음은 build.xml에 정의된 타깃들을 라이프사이클에 따라 설명한 것이다.
- compile – 제품 소스 코드를 컴파일한다.
- test-compile – 테스트 코드를 컴파일한다.
- unit-test – 단위 테스트를 수행한다.
- package – 제품 컴파일 결과를 배포 형태로 패키징한다.
- integration-test – 통합 테스트를 수행한다.
- code-analysis – 정적 코드 분석을 수행한다. (FindBugs, CheckStyle)
- deploy – 패키징한 결과를 개발 서버 및 API 서버로 배포한다.
몇 가지만 살펴보겠다.
먼저, code-analysis가 Maven에 없는 새로 추가된 단계이다. 이 단계에서는 FindBugs, CheckStyle 등의 정적 코드 분석 툴을 이용하여 제품 소스 코드의 잠재적 결함과 코딩 규약 부합 여부를 검사한다. code-analysis 단계 외에는, 실패 시 다음 단계를 계속 진행할 수 없다.
unit-test 단계에서는 수행시간이 짧은 단위 테스트들을 실행한다. 통합 테스트 케이스와 시간이 오래 걸리는 테스트 등은 뒷쪽의 integration-test 단계에서 수행시킨다.
마지막 deploy 단계에서는 완성된 바이너리를 개발 서버로, 최신 API 문서를 API 서버로 배포한다.
변경 가능한 사용자 속성으로는 다음과 같은 것들이 있다.
- product.name - 제품명
- product.version – 제품 버전
- main.class – 실행 클래스명: jar 파일의 Manifest 파일에 추가됨
- compile.deprecation – javac 컴파일 옵션
- compile.debug – javac 컴파일 옵션
- compile.optimize – javac 컴파일 옵션
- compile.source – javac 컴파일 옵션
- compile.target – javac 컴파일 옵션
- proxy.host – 프락시 주소 (프락시 안에 갖힌 네트워크에서 수행될 때)
- proxy.port – 프락시 포트 (프락시 안에 갖힌 네트워크에서 수행될 때)
- deploy.binary.host – 패키징된 바이너리를 배포할 호스트 주소
- deploy.binary.user – 호스트 로그인 계정
- deploy.binary.passwd – 호스트 로그인 패스워드
- deploy.binary.keyfile – 호스트 로그인에 필요한 key 파일 위치 (xxx.pem)
- deploy.binary.dir – 바이너리를 복사해 넣을 호스트 내의 디렉터리 경로
- deploy.api.host – 최신 API를 배포할 호스트 주소
- deploy.api.user – 호스트 로그인 계정
- deploy.api.passwd – 호스트 로그인 패스워드
- deploy.api.keyfile – 호스트 로그인에 필요한 key 파일 위치 (xxx.pem)
- deploy.api.dir – 바이너리를 복사해 넣을 호스트 내의 디렉터리 경로 (웹 서버 혹은 파일 서버)
보는 바와 같이 소스 코드 디렉터리 구조, 산출물 파일 이름과 같은 정보는 따로 설정할 수 없도록 제안하고 있다. 이유는 초반에 언급한 Maven의 설계 원칙(Convention over Configuration)을 좀 더 강요하기 위함이다.
물론 build.xml의 내용을 보면 관련 정보를 속성으로 제공하여, 꼭 필요한 경우 쉽게 변경할 수 있다.
마지막으로 build-jazz.xml 파일을 살펴보자.
이 파일은 build.xml 을 확장하여 RTC/Jazz 빌드 서버에서 사용할 특화 타깃을 정의하고 있다. RTC/Jazz 빌드 기능에 특화된 만큼, 이 스크립트를 개발자 IDE에서 실행하려 하면 필요한 파일이 없다면서 에러를 발생시킬 것이다. 물론 몇 가지 설치/설정으로 가능케할 수 있지만, 별다른 이점은 없으니 Jazz 빌드 서버에 맡기기로 하자.
기본적으로는 다음의 네 가지 타깃이 제공된다.
- package-jazz – build.xml의 package 단계까지 수행한 후, 산출물과 보고서를 RTC/Jazz에 등록한다. 단위 테스트 보고서와 패키징된 바이너리가 이에 포함된다.
- integration-test-jazz – build.xml의 integration-test 단계까지 수행한 후, 산출물과 보고서를 RTC/Jazz에 등록한다. 위 결과에 통합 테스트 결과가 추가된다.
- code-analysis-jazz – build.xml의 code-analysis 단계까지 수행한 후, 산출물과 보고서를 RTC/Jazz에 등록한다. 위 결과에 정적 코드 분석 보고서가 차가된다.
- deploy-jazz – 최종 단계인 deploy까지 수행한 후, 산출물과 보고서를 RTC/Jazz에 등록한다.
특별히 위와 같은 네 단계만 정의한 이유는 잠시 후 Build Definitions 절에서 확인할 수 있다. 그 전에 정적 코드 분석(static code analysis) 단계에서 무엇을 하는지 살짝 알아보고 가기로 하자.
Static Code Analysis
앞서 살펴본바와 같이, unit-test와 package 단계 사이에 code-analysis 라는 단계가 추가되었다. 이는 Maven에도 정의되어 있지 않은 단계이다. 이 단계에서는 제품의 소스 코드를 분석하여 잠재적인 결함과, 코딩 규약 준수 여부를 확인한다.
쉽게 활용할 수 있는 오픈 소스 툴들로는 FindBugs와 CheckStyle, PMD 등이 있다. (기능면에서 CodePro Analytix가 가장 마음에 드나, 아쉽게도 Ant용 task를 제공하지 않아 여기서는 제외하였다.)
FindBugs는 버그 패턴 위주로 거의 100% 적중률로 문제를 분석해주며, CheckStyle과 PMD는 그 외에도 코딩 규약, 유사 코드 검색 등 다양한 기능을 제공한다. CheckStyle과 PMD 는 기능면에서 상당히 겹치기 때무에 둘 다 사용할 필요는 없다. 나는 PMD를 더 선호하였지만, 최근에 업그레이드가 이루어지지 않고 있어 CheckStyle로 선회하였다.
참고로, 이들 툴을 지속적 통합 프로세스의 일부로 등록해놓는 것은 좋은 생각이긴 하지만 IDE에 통합하여 개발자들이 수시로 확인해보는 것에 비할 바가 못된다. 다행히 CodePro Analytix를 포함하여 위의 모든 툴들은 Eclipse 플러그인을 제공하고 있다.
관련하여 Java 코딩 규약 관리 방법 역시 참고가 될 것이다.
Build Definitions
빌드 정의(Build Definition)는 빌드에 필요한 각종 정보와 수행 조건 등을 담는다. 예를 들어, 빌드에 사용할 파일(build-jazz.xml)명, 타깃, 빌드 스케줄 등이 그것으로, 프로젝트 특성에 맞는 다양한 전략을 수립할 수 있다. 상세 내용은 본 글의 주제와 크게 관련 없으니 지나치도록 하겠다.
어쨌든 Jazz의 빌드 서버는 이 정의를 바탕으로 빌드 엔진에 빌드를 요청한다. 아래의 그림은 내가 구축하기 원하는 빌드 전략이다.
그리고 위의 전략을 구현하기 위해 다음과 같은 네 가지의 빌드 정의를 작성하였다.
- Continuous: 변경 사항에 대한 빠른 피드백을 목적으로, 컴파일/단위 테스트/패키징 성공 여부까지 확인한다.
- 빌드 주기: 매 5분
- 빌드 타깃: package-jazz
- Integration: 더 많은 시간이 소요되며 다양한 테스트를 수행하는 통합 테스트까지 수행한다.
- 빌드 주기: 매 1시간
- 빌드 타깃: integration-test-jazz
- Nightly: 일별 snapshot을 만들어, 매일 아침 baseline을 생성한다.
- 빌드 주기: 월~토요일 3:00 AM
- 빌드 타깃: code-analysis-jazz
- Weekly: 주간 변경 내용을 종합 검증하여 baseline을 생성하고, 개발 서버에 배포한다.
- 빌드 주기: 매주 일요일 3:00 AM
- 빌드 타깃: deploy-jazz
Jazz 빌드 서버는 위의 네 가지 빌드 정의에 기반해, 자동으로 빌드/테스트/배포를 수행하며, 그 결과를 개발자에게 알려주게 된다.
Summary
이상으로 빌드 시스템의 거시적인 구성부터 빌드 단계 정의, 빌드 정의를 통한 지속적 빌드 전략 수립까지 구성해 보았다.
다소 이론적인 면에 집중하여 설명하였지만, 상세 내용으로 들어가면 내용이 너무 길고 장황해지니 양해 바란다.
(관련 Ant 빌드 스크립트는 약간 다듬어서 추후 업데이트하겠음)