자바에서 웹어플리케이션은 JEE 스펙에서 정의한 디렉토리 구조를 갖는 war 파일의 형태로 컨테이너에 배포됩니다. 메이븐에서는 웹어플리케이션을 package 단계에서 기본적으로 war 파일로 포장합니다.

그런데 꼭 war라는 아카이브 파일로만 배포할 수 있는 건 아닙니다. maven-war-plugin 플러그인에는 war:war 골뿐만 아니라 war:exploded나 war:in-place도 있습니다.

웹어플리케이션을 배포하기 위한 패키징 유형을 3가지로 나눌 수 있습니다.

1. package(archive) : 아카이브(war,ear) 파일로 배포
- 아카이브는 결국 WAS에 의해 압축이 풀림
- 파일이 많을 경우 압축해제 시간 오래걸릴 수 있음
- 리모트 서버에 배포시 한개의 파일만 전송하면 됨
- WAS에서 제공하는 업로드를 통한 배포기능 활용가능

2. exploded(expanded) : 아카이브를 압축해제한 디렉토리 형태 구조
- 압축 및 해제 과정이 불필요
- 별도의 디렉토리에 원본 소스를 복사하여 만듬
- 파일이 많은 경우 복사 시간 오래걸릴 수 있음
- 원본 소스를 건드리지 않고 배포를 원하는 경우 적합
- 리모트 서버에 배포시 파일이 많은 경우 전송 시간이 오래걸릴 수 있음.(rsync는 빠르다?)

3. in-place : 소스 디렉토리(전체 또는 일부)를 그대로 배포
- 추가적인 복사 과정 불필요
- 로컬 서버에 배포하는 경우에 적합
- WAS가 런타임시 생성하는 파일이 소스와 섞일 수 있음

Posted by 에코지오
,

배포용 어플리케이션(deployable application)이 생성된 위치와 설치될 위치가 다를 경우 어플리케이션을 어떤 방식으로 전송할까?

1. 로컬 전송
-생성위치와 설치위치가 같은 서버에 있는 경우
-copy, sync 등

2. 리모트 전송
-생성위치와 설치위치가 다른 서버에 존재하는 경우
-ftp, sftp, scp, rsync 등

3. 업로드 전송
-어플리케이션이 배치되는 WAS가 업로드 방식의 배치를 지원하는 경우
-WAS 어드민화면 또는 WAS 제공 API를 통해 업로드

Posted by 에코지오
,

리소스설정치환에 관한 글에서 얘기했듯이 개발소스와 빌드결과물은 여러 서버를 거치면서 생성,이동,배치됩니다.

개발자 로컬PC -> (소스저장소) -> 빌드서버(CI서버) -> 개발서버 -> 테스트서버 -> 스테이징서버 -> 운영서버

1차원적으로 일렬로 나열하니까 간단해보이지만 실상 어플리케이션의 이동경로는 2차원적으로 설계됩니다. 마치 수학자 오일러가 최단코스를 찾기 위해 고민한 것처럼 우리는 각각의 서버를 거점으로 하는 배포 코스를 그려가야 합니다.(배포의 위상수학? 배포 아키텍처? 음.. 배포 아키텍처가 맘에 드는군요.)

하지만 너무나 다양하고 특이한 개발환경이 많이 존재하기 때문에 배포코스에 관한 뚜렷한 공식은 없는것 같습니다. 아래에 저의 경험과 동료들의 의견 그리고 온라인상의 자료 등을 종합해서 크게 3가지의 대표적인 코스를 그려보았습니다. 어떤게 베스트 프랙티스다라고 딱부러지게 말씀은 못드리겠군요.
(코스에서 테스트서버는 포함하지 않았습니다. 솔직히 제가 겪었던 모든 프로젝트에서는 개발서버가 테스트서버,스테이징서버의 역할을 모두 겸하고 있었습니다.)

1.빌드서버 중심 코스
-개발서버용, 스테이징서버용, 운영서버용 어플리케이션(빌드산출물)을 모두 빌드서버에서 생성
-어플리케이션은 빌드서버에서 각각의 서버로 전송되어 배포됨


-또는 스테이징 서버에 전송된 어플리케이션을 다시 운영서버로 전송하기도 함



 

2.소스저장소 중심 코스
-개발서버, 스테이징서버, 운영서버가 개별적으로 소스장소로부터 전체 개발소스를 취합하여 각각의 환경에 맞는 어플리케이션을 빌드하여 배포

 

3.혼합된 코스
-개발서버용 어플리케이션은 빌드서버에서 생성하여 개발서버로 전송되어 배포
-스테이징서버용은 스테이징 서버가 소스저장소로부터 전체 개발소스를 취합하여 빌드하고 배포
-스테이징서버에서 빌드된 어플리케이션을 운영서버로 전송

자, 어떤 코스를 타시렵니까?

Posted by 에코지오
,

이전 글에서 리소스 설정치환 방법에 대해 얘기했습니다. 그럼 메이븐 환경에서 리소스 설정치환 작업은 어떤 phase에서, 어떤 리소스를 대상으로 이루어져야 할까요?

1. 클래스패스에 포함되는 리소스

(1) 리소스 필터링 방식
메이븐이 process-resource 단계에서 타겟 디렉토리로 복사된 리소스를 대상으로 필터링을 해줍니다. 이때 원본 리소스는 변경이 없습니다. 그러니까 /src/jdbc.properties 파일에 password=${jdbc.password} 처럼값을 설정해놓으면 /target/classes/jdbc.properties 파일에는 그 부분이 password=my_password 식으로 바뀌어 있다는 말입니다.

(2) 리소스 교체 방식
타겟 디렉토리에 복사된 리소스 파일에 대해서 교체 처리해줍니다. 어차피 process-resource 단계에서는 resources:resources 골이 가장 먼저 실행되기 때문에 교체 작업(ant의 copy 타스크 이용)은 그냥 process-resources 단계에 바인딩하면 됩니다. 역시 원본 리소스 파일은 변경이 없습니다. 예를 들어 /src 밑의 jdbc.properties와 jdbc.properties.dev가 /target/classes 밑에 복사된 후 /target/classes/jdbc.properties.dev 파일이 /target/classes/jdbc.properties 파일을 덮어쓰게 됩니다.

 

2. WEB-INF 밑에 있는 리소스(web.xml, struts-config.xml, applicationContext.xml 등등)

WEB-INF 밑의 리소스에 대한 설정치환은 좀 지저분합니다. 
메이븐은 package 단계에서 war:war를 실행하는데, war:war는 무조건 war:exploded 를 먼저 실행하고 나서 exploded된 웹어플리케이션을 war 파일로 묶어냅니다. 우리가 원하는 것은 exploded 된 웹어플리케이션의 리소스 파일에 대해 리소스설정치환 작업을 하고나서 war 파일로 패키징하는 것인데, 중간에 끼어들 여지가 원천 봉쇄된 것입니다.

뭐 package 단계나 war:war를 실행하지 않으면 되긴합니다. war:exploded를 명시적으로 실행한 뒤에 설정치환을 하면 됩니다. 그런데 설정치환하는 execution을 어느 phase에 바인딩하는게 좋을지... 하여간 원본 리소스 파일을 건드리지 않은 상태로 리소스 치환을 하려니 지저분해지는군요.

만약 원본 리소스를 건드려도 무방하다면 package 전 단계(prepare-package 단계는 maven 2.1부터 지원하기 때문에 2.1 이전 버전이라면 process-resources 단계가 괜찮을까요)에서 원본 리소스를 대상으로 설정치환 작업을 처리하면 됩니다. 
Posted by 에코지오
,

우리가 개발하는 어플리케이션은 최종 사용자에게 서비스되기 전에 여러 장소를 거칩니다.

개발자 로컬PC -> (소스저장소) -> 빌드서버(CI서버) -> 개발서버 -> 테스트서버 -> 스테이징서버 -> 운영서버

불행히도 이렇게 거쳐가는 서버에 따라서 환경/정책이 조금씩 다르기 때문에 우리는 개발소스에 포함된 설정값을 다르게 가져가야 합니다. 예전에는 소스가 서버 사이를 옮겨갈 때마다 수작업으로(vi 에디터로) 설정값을 매번 편집을 해주거나, 설정값을 포함한 파일을 빼고(!) 소스를 옮기기도 했습니다. 여러분은 어떤 방법으로 설정값을 다르게 세팅하셨습나요?

여기, 좀더 우아한 방법이 있습니다.

아, 그보다 먼저 설정값을 포함하는 파일을 보통 뭐라고 부르나요? 설정파일 ? 리소스 파일?  뭐라고 부르든 리소스는 주로 xml, properties 로 작성을 하죠. 레일스에서는 yml인가 이런것도 있더군요. 암튼.

그리고 로컬,개발,운영 등 서버의 환경차이에 따라 리소스 파일을 변환하여 개발소스에 포함된 설정을 달리하는 일을 뭐라고 부르나요? 환경맞춤작업? 리소스변환리소스 설정치환? 이런 거에 대한 공식적인 용어를 들은 바가 없어서 일단은 제목에 리소스 설정치환이라고 임의대로 용어를 지었습니다. 혹시 널리 사용되는 용어를 아시는 분은 알려주세요. 언뜻 profile, portable이란 단어가 떠오르기는 합니다만....

아래는 제가 알고 있는 리소스 설정치환 방법 2가지입니다.(리소스 필터링, 리소스 교체 역시 모두 제가 임의로 만든 용어입니다. 더 나은 용어가 있으면 알려주세요.)

1. 리소스 필터링(filtering)
-빌드시에 설정 파일의 내용을 수정하는 방법
-설정값을 하드코딩하지 않고 정해진 형식의 변수로써 설정. 예를 들어 ${jdbc.password}.
-빌드 실행시 설정 파일 내의 변수는 환경에 적합한 설정값으로 치환됨
-환경별 설정값은 빌드스크립트에 보관됨(메이븐의 경우 프로파일 엘리먼트에)

2. 리소스 교체(replacing)
-빌드시에 기준(로컬) 설정 파일을 다른 파일로 교체하는 방법
-설정 파일을 환경별로 분리하여 관리
  (1)환경별로 별도 디렉토리를 만들어 기준 파일과 동일한 파일명으로 관리.
      예를 들어 /web/WEB-INF/web.xml 이 로컬PC용 설정파일이면,
      개발서버용 web.xml은 /conf/dev/web.xml에 두고 관리.
  (2)기준 설정파일과 동일한 경로에, 다른 파일명으로 관리.
      예를 들어 개발서버용 web.xml을 /web/WEB-INF/web.xml.dev 라는 이름으로 관리.
-빌드실행시 환경에 맞는 파일을 골라서 (1)빌드디렉토리로 이동하거나 (2)파일명을 변경하여 기준 설정 파일을 덮어씀

1번 리소스 필터링의 경우 메이븐에서는 Profile 기능과 Resource Filtering 기능을 이용하면 되고, 2번 리소스 교체의 경우 메이븐에서는 Profile 기능과 Ant의 copy 타스크 조합으로 처리할 수 있습니다. 아래는 메이븐에서 2번을 처리한 예제입니다.

... ...
<plugin>
 <artifactId>maven-antrun-plugin</artifactId>
 <executions>
  <execution>
   <id>resource-changing</id>
   <phase>process-resources</phase>
   <goals><goal>run</goal></goals>
   <configuration> <tasks>
          <copy todir="${project.build.outputDirectory}"
                    overwrite="true" preservelastmodified="true" verbose="true">
               <fileset dir="${project.build.outputDirectory}"
                          includes="**/${conf.replace.pattern}" />
                   <mapper type="glob" from="${conf.replace.pattern}" to="*" />
          </copy>
    </tasks></configuration>
  </execution>
...... .......
..... ........
 <profiles>
  <profile>
   <id>env-dev</id>
   <activation>
    <property><name>env</name><value>dev</value></property>
   </activation>
   <properties>
    <conf.replace.pattern>*.dev</conf.replace.pattern>
   </properties>
  </profile>
  <profile>
   <id>env-prod</id>
   <activation>
    <property><name>env</name><value>prod</value></property>
   </activation>
   <properties>
    <conf.replace.pattern>*.prod</conf.replace.pattern>
   </properties>
  </profile>
 </profiles>
Posted by 에코지오
,

통합빌드를 수행하기 위해 전체 소스를 SCM(소스저장소)에서 취합하는 방식은 2가지로 나뉜다.

1. 체크아웃 방식
-매번 새로 전체 소스를 SCM으로부터 체크아웃 받음
-소스의 양이 많을 경우 내려받는 시간이 오래걸림
-항상 깨끗한 상태의 소스를 이용하므로 스테이징/운영 서버에 배포하기 위한 용도에 적합

2. 업데이트 방식
-처음 한번만 전체 소스를 체크아웃 받고 그 이후로는 변경된 소스만 SCM으로부터 업데이트 받음
-변경된 소스만 받아오므로 상대적으로 시간이 덜 걸림
-순전히 빌드오류를 잡아내어 피드백을 주기 위한 빌드에 적합(?)


* 위 2가지는 다시 무조건 최신 소스를 가져오느냐 아니면 특정 버전(태그)의 소스만 가져오느냐로 나뉠 수 있다.
* Hudson에서는 빌드Job 선택 > Configure > Source Code Management > Advanced… > ‘Use Update’ 옵션 체크시 2번의 업데이트 방식이 적용된다.

* 조대협님 블로그 참조함

Posted by 에코지오
,

우리는 빌드,테스트,배포를 자동화하기 위해 Ant나 Maven 같은 걸로 작업을 정의한 스크립트를 만듭니다.
스크립트가 완성되면 이제 스크립트를 실행할 일만 남았습니다. 그럼 이 스크립트를 언제, 어떻게 실행할까요?
자동화 프로세스를 런치시키는 3가지 유형이 있습니다

1. 예약 자동화(scheduled)
-일정 주기마다 자동으로 작업 실행
-개발자들의 스케줄관리에 유리하며 대규모 빌드에 적당
-오랜 시간 소스에 변경이 없을 경우 불필요한 빌드 발생

2. 유발 자동화(triggered)
-이벤트 발생시 자동으로 실행
-이벤트 감지를 위한 폴링 간격이 짧고 커밋이 자주 발생하는 경우 빌드적체 유발 가능성 있음

3. 지시 자동화(commanded)
-커맨드라인에서 빌드스크립트를 직접 실행하거나 빌드서버에서 빌드버튼을 클릭하는 등의 방법으로 사용자가 수동으로 작업을 실행
-비정기적인 빌드/배포를 수행해야 할 경우 사용


* 조대협님 블로그와 실용주의 자동화 책 참고

Posted by 에코지오
,
최근에 찾은 배포 자동화에 대한 글 2개.

조대협님 : 9/20 devmento에서 발표한 빌드 배포 및 테스트 자동화에 대한 자료
빌드,배포,테스트,이슈관리 등을 묶어서 ALM이라는 큰 개념으로 다듬고 계시다. 좋은 자동화 솔루션 조합을 추천해주시고 내가 허드슨을 선택한 이유를 제공해주셨다.

강문식님 : 스프링노트 배포 환경 Before & After: Capistrano, God, HAProxy, Seesaw!
실전 루비환경에서 어떤식으로 배포하는지 알 수 있다. 배포작업도 솔루션을 쓴다는게 흥미롭다. 자바진영에도 이런 솔루션들이 다양하게 나와주었으면 좋겠는데...
Posted by 에코지오
,

- 메이븐에 내가 만든 phase를 추가할 수 있는가?

- skip 옵션을 지원하지 않는 goal이 어떤 phase에 이미 바인딩된 경우에, 과연 런타임시에 그 goal의 실행을 막을 수는 없는 것인가? 예를 들어 package phase에서 war:war가 실행되지 않길 바란다면?

- 어떤 플러그인에 대해서 execution을 여러개 정의한 경우에 (바인딩된 phase를 통하지 않고) 특정 execution만 골라서 실행 가능한가? 반대로 특정 execution만 실행에서 제외할 수 있는가? execution의 id를 어떻게 활용할 방법은 없나?

Posted by 에코지오
,

보안을 위해 서버 패스워드 같은 민감한 데이터를 pom.xml에서 감추는 방법.

1. 사용되는 부분을 프로퍼티로 처리하고 

<sshexec host="myserver" username="admin" password="${wasserver.password}"  command="xxxx" />

 mvn 실행시 -D옵션으로 값을 넘겨준다.

mvn deploy -Dwasserver.password=xxx

(그러나 허드슨 사용 환경에서 허드슨 빌드 console 화면에 데이터가 노출되는 문제가 있다)

2. 감추려는 데이터가 서버 접속 정보라면 settings.xml에 서버정보를 설정하여 pom.xml에서 데이터를 감출수 있다.

<settings> 
  <servers>
    <server>
      <id>server001</id>
      <username>my_login</username>
      <password>my_password</password>
      <privateKey>${user.home}/.ssh/id_dsa</privateKey>
      <passphrase>some_passphrase</passphrase>
      <filePermissions>664</filePermissions>
      <directoryPermissions>775</directoryPermissions>
      <configuration></configuration>
    </server>
  </servers>
  ...
</settings>
Posted by 에코지오
,