로컬에서는 무사히 빌드가 되는데, 왜 서버로 올라가서 CI가 돌리면 컴파일 에러가 날까

이럴 때가 정말 황당하고 답답한데, 뭐 방법은 역시 에러 메시지를 잘~~ 살펴보면 됩니다. 아님, 사부님한테 물어보던지요.ㅋㅋ

문제 분석

일단 CI 서버에서 문제가 생기면, 로그 메시지를 보고 어느 Phase에서 에러가 난건지 확인 합니다. 컴파일 에러가 났으면 당연히 compile Phase에서 에러가 난거겠죠. 그럼 로컬에서는 어떤지 로컬에서 compile을 해봅니다. 즉 mvn compile 이라고 하면 되죠. 이 때 서버랑 똑같이 컴파일 문제가 생기면 아주 아주 좋은겁니다. 그런데 이 글의 제목처럼 로컬에서는 잘 돌아간다면, 약간 당황스러워 집니다. 이 때부턴 여태까지의 경험과 로그 분석으로 난국을 헤쳐나가야 합니다. mvn 로그를 보려면, -X를 추가해서 실행하면 됩니다. 서버로 가서 mvn -X compile 이라고 서버에서 실행하고 자세히 들여다 봅니다. 어떤 라이브러리들을 가져왔는지, 플러긴은 제대로 가져왔는지 등등..

문제 해결

0. 로컬에서 빌드가 제대로 돌았다는 사실을 의심해야 합니다.

“왜 서버에선 안 돌아갈까?” 전에.. “왜 로컬에서는 돌아가는거지?” 라는 생각을 먼저 해보는게 문제 해결의 지름길 입니다. ‘서버에서 돌린 스크립트를 그대로 돌렸나??’ 라고 생각해본다거나, 이클립스에서 실행한건 아닌지.. 의심해야 합니다.

1. 인코딩 문제

로그를 보다가 UTF-8 뭐시기.. 인코딩 뭐시기 하는 메시지가 쭉~~~~~~~~~뜨는 경우가 있습니다. 이 때는 소스 파일 인코딩이랑 서버의 인코딩이 맞지 않아서 그런데, 리눅스의 경우 export LANG=ko_KR.eucKR 이런식으로 인코딩을 변경할 수 있으니, 인코딩을 변경한 다음에 다시 빌드를 해봅니다. 되면 귿!

2. 라이브러리 참조 문제

사용자 삽입 이미지
필요한 라이브러리들을 제대로 참조 했는지, 내가 원하던 버전을 참조하고 있는지 확인합니다. -X 옵션을 주면 볼 수 있으니까 잘 보면서 확인합니다.

2-1. 원하던 라이브러리가 없다.

<dependency> 엘리먼트 안 에 정의한 <scope>를 의심해보시기 바랍니다. <scope>에 test라고 입력했다면, 소스 코드 compile 시점이 아니라 테스트 코드 compile 시점에 참조하게 됩니다. 따라서 mvn compile 할 떄는 해당 라이브러리가 없겠죠.

2-2. deps 들이 꼬였다.

deps가 꼬일 일이 없으면 좋겠지만, 간혹… 정말 간혹.. 꼬일 수도 있습니다. managed deps 들이랑, transient deps들이 잘못해서 꼬이면 정말 찾기가 힘들지만, 어떻게 하겠습니까? 로그를 보면서 찾아야 합니다. 찾으면 그 담엔 제외 시키던지, 아니면 새로 deps를 정의해서 nearest first 전략을 활용하면 됩니다.

저의 에피소스

문제는 금요일 저녁에 발생했는데, 해결은 방금 전에 했습니다. 똑같은 컴파일 에러.. 똑같은 상황… 10번도 넘게 빌드를 해봤지만, 문제 원인은 쉽게 떠오르질 않고, 왜 자꾸 spring에 있는 mock 패키지를 못찾는 다고 하는 건지.. 로컬에선 잘 돌아갔는데.. mvn compile을 해도 돌아갔는데.. 대체 왜 이러니…

조금 전 연결이 된 사부님에게 문제 상황을 말씀 드렸더니, 저번에도 겪은 문제(deps가 꼬인 경우)가 아니겠냐고, -X 옵션으로 확인해보라는 대답을 들었습니다. 속으로는 ‘아.. 안 되는데.. 그 문제면 정말 귀찮은데…’ 이러면서 확인해 봤더니, 컴파일에러에서 찾지 못한다는 그 클래스를 가진 라이브러리가 없었습니다. ‘뭐지.. 왜 spring-test.jar가 없지?’ 제가 돌린 프로젝트는 스프링 테스트를 확장한 클래스(테스트 클래스가 아니라 그냥 일반 소스 코드로써의 클래스)를 가지고 있었기 때문에 spring-test.jar가 필요했습니다. ‘올커니… scope 때문이구나.’ 아니나 다를까. pom.xml을 열어봤더니, 해당 라이브러리가 test 스콥으로 잡혀있었습니다. 해당 라인을 지워버리니까 빌드는 무사히 성공~

Continuous Integration의 장점 1

황당한 사태가 벌어져도 당황할 필요 없다.

불과 몇 초전에 발생했던 황당한 사태 입니다.

사용자 삽입 이미지오늘 하루도 정말 열심히 일했고 할 만큼 했다는 생각이 들어서 최종적으로 커밋을 깔끔하게 집에 갈 생각이었습니다. 그런데 왠 걸.. FAILED 메시지가 구글톡에 전해졌습니다. 아 놔~~ 집에 가고 싶은데.. ㅠ.ㅠ

대나무(Bamboo) 숲으로 들어가서 고장난 빌드 로그를 살펴봤습니다. 이런!! 테스트가 죄다(는 아니지만 상당히 많이) 깨졌군요.

사용자 삽입 이미지
헐.. 60개가 넘는 테스트가 한 방에… 그것도 바로 전까지 모두 Success 였는데.. 흠~ 예전 같으면 갑자기 어깨가 굳고 눈동자가 커지면서 조급해졌을텐데, 이젠 좀 CI랑 친해졌는지 더이상 두려워지지 않고 차근 차근 로그를 보고 깨진 테스트를 로컬(이클립스와 콘솔에서 메이븐으로)에서 실행해 봅니다.

그리고 문제의 원인을 발견합니다. 문제의 원인은 분명 이전에 커밋한 코드와 방금 커밋하기 전 코드 사이에서 생겨난 겁니다. 왜냐면 바로 전 단계의 빌드는 Success였기 때문이죠.

문제의 원인은 간단했습니다. 스프링 XML 설정파일에서 빈 이름을 잘못 설정했기 때문입니다. 그래서 모든 통합테스트들이 깨졌고, 단위테스트들은 살아남았던 겁니다. 이걸 계기로 통합테스트가 총 몇 갠지도 덤으로 알 수 있게 됐네요. ㅋㅋ

어쨋거나 중요한 건 CI가 주는 장점 한 가지. 바로 버그 발생 원인 탐사 범위를 좁혀준다는 거… 매우 귿입니다.

이젠 그만 집으로 ㄱㄱㄱ

Continuous Integration 요약 2

참조
http://martinfowler.com/articles/continuousIntegration.html
http://moai.tistory.com/224

오늘은 두 번째 파트인 Continuous Integration 실천하기를 살펴보겠습니다.

각각의 실천 요소에 대한 번역판을 살펴볼 수 있습니다.

1. 단일 소스 레파지터리 유지하기.
svn이나 cvs와 같은 것을 사용하여 프로젝트와 관련된 모든 파일을 관리하라. 특정 파일을 찾으러 돌아다닐 필요가 없어진다.
실험 또는 앞서 출시한 버전의 버그 수정을 할 때 브랜치를 사용하면 유용하다.
단 빌드 파일의 결과물 같은 것들은 오히려 레파지터리를 지저분하게 만든다.

2. 빌드 자동화 하기.
손수 명령어를 치고 빌드 하는 것은 시간 낭비이며 실수를 할 확률이 높다.
Java의 Ant, Ruby의 Rake, .Net의 MSBuild를 사용하라.

3. 셀프 테스팅 빌드 만들기.
버그를 더 빨리 효율적으로 발견하는 방법은 빌드에 자동화 테스트를 추가하는 것이다.
XUnit 프레임워크를 사용하면 편리하다.
테스트에 통과했다고해서 버그가 없는 것은 아니다.

4. 모든 사람이 매일 커밋하기.
커밋의 주기가 잦을 수록 버그가 발생한 반경은 좁아진다.
작업의 단위를 몇 시간 이 내에 할 수 있는 작은 단위로 쪼개면 유용하다.
여러 곳의 코드가 수정되었다면 diffdibugging 활용하기.

5. 모든 커밋은 통합 서버의 메인라인에서 빌드되어야 한다.
커밋이 이루어지면 통합 서버에서 빌드를 거쳐야 한다.
서버를 빌드하는 작업을 정기적으로 할 수 있는데, 방법은 수동과 자동이 있다.
수동으로 하면 단순하지만 로컬에서 빌드하는 방법과 비슷하다.
자동은 CI 툴을 사용하는 방법으로 서버를 모니터링하고 있다가 커밋이 이루어지면 서버에 빌드 작업을 하고 그 작업 결과를 개발자에게 이메일로 알려준다.
메인 빌드가 실패하면 고쳐질 때까지 아무도 집에가지 못한다.
CI 서버 사용에 대한 학습이 필요하다.

6. 빌드가 빨리 되도록 유지하기.
빌드 과정 중에서 가장 시간을 많이 잡아 먹는 것은 테스트다.
빌드를 여러 Stage로 쪼갠 Staged 빌드를 활용하라.
커밋 빌드를 거친 뒤에 테스트 빌드를 별도의 서버에서 수행하여 응답 시간를 줄일 수 있다.

7. 운영 환경과 동일한 환경에서 테스트하라.
실제 제품이 운영되는 같은 버전의 같은 데이터베이스 소프트웨어, 같은 버전의 운영체제를 이용하라.
현실적인 한계가 있을 수 있기 때문에 가상화 서버를 이용할 수도 있다.

8. 누구든 최신 실행본에 쉽게 접근할 수 있게 하라.
각 이터레이션 마다 빌드를 유지하라.
데모를 통해서 기능에 익숙해 지도록 할 수 있다.

9. 모든 사람이 무슨 일이 일어나고 있는지 알게 하라.
지속적인 통합은 의사소통을 위한 것이다.
특히 메인라인의 빌드 상태를 공유해야 한다.
CI 서버를 사용하면 그러한 것이 매우 유용하다.
매일 같이 빌드가 제대로 되었는지 체크하는 달력도 유용하다.

10. 배포 자동화
지속적인 통합을 하다 보면 여러 환경에 배포하는 활동이 필요하다. 이것을 자동화 하라.
자동화된 배포는 프로세스 진행 속도를 높여주며 에러를 줄이는데 도움이 된다.
RoR의 Capistrano와 같은 것이 유용하다.

Continuous Integration 요약 1

참조
http://martinfowler.com/articles/continuousIntegration.html
http://moai.tistory.com/224

맨 첫 번째 파트인 “Building a Feature with Continuous Integration“[footnote]번역본 링크 http://moai.tistory.com/226[/footnote]을 살표보겠습니다.

간단한 기능 하나를 지속적인 통합(CI) 환경에서 개발하는 과정을 설명하고 있습니다.

1. 현재 통합되어 있는 소스 코드를 로컬 개발 컴퓨터로 다운 받는다.
이 것은 소스 코드 관리 시스템을 사용하여 mainline을 체크아웃 받으면 된다.

2. mainline을 체크아웃 받은 working copy를 가지고 작업을 하기 시작한다.
기존의 코드와 테스트를 수정하기도 하고, 새로운 코드와 테스트를 추가하기도 한다.

3. 작업을 완료 한 뒤, 로컬에서 빌드를 수행한다. 물론 이 빌드 과정 중에는 자동화 된 테스트를 포함하고 있다.
빌드와 테스트가 무사히 완료 될 때까지 코드를 수정하거나, 계속해서 개발한다.

4. 이제 소스코드를 저장소에 커밋해야 한다.
이 때 커밋하기 전에 반드시 다른 사람이 코드를 변경했을 수도 있기 때문에 업데이트부터 해야 한다.
업데이트를 할 때 충돌이 발생할 수 있다. 이것은 Merge를 사용해서 해결한다.
업데이트를 받은 다음에 다시 로컬에서 빌드를 수행한다.
역시 에러가 발생하면 잡을 때 까지 수행한다.

5. 커밋을 수행한 다음에는 integration machine에서 빌드 작업을 수행한다.
커밋이 제대로 수행되지 않았을 수도 있기 때문에 반드시 이 작업을 무사히 완료해야 모든 일이 끝났다고 할 수 있다.
통합 빌드는 손수 할 수도 있고, CruiseControl 같은 시스템을 사용할 수도 있다.

이렇게 작업하는 것의 장점

1. 에러를 초기에 발견할 수 있다.
에러를 발견하면 가장 중요한 일은 그것을 해결하는 것이다.
자주 통합 할 수록 에러가 발생하는 반경은 좁기 때문에 에러를 잡는 것이 수월해진다.
2. 안정된 프로젝트 기반을 공유하고 있을 수 있다.
매우 잘 동작하며, 에러가 거의 없는 소프트웨어를 유지할 수 있다.

Buildix

영회형 블로그에서 Continuous Integration을 검색했더니 나온 툴 입니다. 일단 ThoughtWorks가 사용해 봤다고 하니 엄청나게 신뢰가 갑니다. 거기에 여러 오픈소스(Trac, CruiseControl, Subversion)을 사용해서 만들었기 때문에 이것도 무료로 사용할 수 있다고 합니다.

매우 호감가는 툴이라 꼭 사용해보고 싶어집니다. 일단 소개 페이지를 번역해 봤습니다.

원문 : http://buildix.thoughtworks.com/

그래서 이게 뭔가?

Buildix는 간단한 리눅스용 패키지로 Debian을 기반으로 한 KNOPPIX를 기반으로 하고 있다. Agile 개발자들이 프로젝트를 좀 더 빠르고 쉽게 올리고 실행하는 것을 돕는 것이 목적이다.

필요하지 않은 것들은 제거하고, 자바 기반의 Agile 소프트웨어 개발 프로젝트를 지원하기 위해 필요한 도구들을 추가했다.  이 도구들을 연동하기 위한 설정 작업들을 모두 해둠으로써, 가능한한 시스템을 관리하는데 부가적으로 필요로 하는 것들을 최소화했다. 가장 큰 장점은, 간단한 샘플 프로젝트를 추가해 두었기 때문에, 이 도구들을 사용하는 방법중에 우리가 선호하는 빌드 과정을 익힐 수 있을 것이다.

아래의 다이어그램이 간략한 아웃라인을 보여준다.

사용자 삽입 이미지