CI와 Execute Shell을 이용한 자동배포 부터 서버 실행까지…..
프로젝트 초반에 CI를 도입하기 전, 배포를 했었을 때 매우 불편한 점이 많았다.
로컬에서 개발한 소스코드를 개발계 서버에서 테스트, 검증을 하기위해 나는
- 매번 빌드를 해야 했으며,
- 개발용 서버에 접속하여 파일을 옮겨야 했으며,
- WAS를 재실행 해야 하는 불편을 느꼈다.
기존에는 CI툴은 지속적인 빌드, unit 테스트를 해주는 툴로 알고 있었지만,
CI의 다양한 옵션을 통하여 사용자가 원하는 모든것을 자동화 할 수 있다는 것을 알았다.
현재 프로젝트에서 개발계 테스트를 하기위한 배포 작업은 크게 2개로 나눌수 있었다.
이 두개의 영역의 연결 고리가 자동화가 된다면, CI 작업 한번으로 배포부터 서버 실행까지 되는 것이었다.
이 두개의 영역을 이어줄수 있는 고리를 찾아 보았는데, CI 옵션 중 빌드가 끝난 후 후속작업을 할 수 있는 옵션이 있었다.
그 중 Post Steps 의 "Execute shell" 이라는 옵션이 있었는데, 이 옵션을 통하여 shell 스크립트로 remote 서버에서 작업을 할 수 있었다.
배포부터 서버 실행 까지의 작업을 하기위해 먼저 CI의 JOB을 등록을 해야 했다.
여기서 나는 한가지 고민을 했었는데,
- 작업 한번으로 배포부터 서버실행 까지 한다.
- 배포 작업 , 서버실행 작업 2개로 나눈다.
1번 같은 경우에는 주기 적으로 최신소스가 반영되어 서버가 실행되지만, WAS 오류에 있어서 유연하게 대처하기 어렵다.
WAS의 오류 때문에 작업을 다시 시작 한다면, 수 분의 불필요한 빌드과정을 거쳐야 하기 때문이다.
그래서 서버 배포와 서버 실행 작업을 나눠, 2번으로 CI작업을 생성하기로 했다.
첫째, 반복적인 빌드 및 배포 작업 등록
Repository URL에 svn 주소를 설정하고, Check-out Strategy의 옵션을 Always check out a fresh copy로 한다.
빌드할 때마다 CI서버의 workspace를 삭제하고 다시 svn에서 check out 받는 옵션이다.
주기적인 반복 작업을 등록하기 위해서는 빌드 트리거의 옵션을 설정해야 한다.
Build periodically에서 시간을 설정 할 수 있는데, crontab 문법을 적용해야 한다.
1 : minute (0-59),
2 : hour (0-23),
3 : day of the month (1-31),
4 : month of the year (1-12),
5 : day of the week (0-6 with 0=Sunday).
공백으로 구분지어 시간을 설정하는데, 위 설정은 매년, 매월, 매일, 오전 3시 10분 마다 해당 작업을 실행한다는 의미이다.
마지막으로 빌드 옵션을 설정해야 하는데, deploy옵션을 지정하여 외부저장소(Nexus)에 배포를 한다.
이로써, 외부저장소에 주기적 배포가 되어 다수의 개발자가 최신소스의 상태로 개발을 할 수 있게 되었다.
둘째, 배포된 최신소스를 WAS로 실행시키기
CI와 WAS 작업을 연결 시켜주는 고리의 설정을 하는 중요한 작업이다.
이 작업에서 Execute Shell을 생성하여 WAS를 실행하기위한 작업을 스크립트로 만들어 놔야했다.
여기서 두번째 고민을 했다.
- Execute Shell로 빌드가 완료된 war 파일을 전송하는 스크립트를 만들어 WAS 실행을 해야 하는가?
- 프로파일을 설정하여 WAS에 전송하여 실행해야 하는가?
이 고민은 사실 그리 오래 하지 않았다. 왜냐 하면, shell로 실행을 하는것은 제대로 war 파일이 제대로 전송이 되었는지 확인을 할 수 없기 때문이다.
빌드하는 과정에 오류가 발생했다면, CI에서 에러 메세지를 확인하여 대체할 수 있으나, 빌드가 완료후 shell이 실행되는데 shell에서 발생한 오류는 CI에서 확인을 할 수 없기 때문에 난 2번의 방법을 생각을 굳혔다.
두번째 CI 작업등록은 첫번째 등록하는 것과 크게 다르지는 않으나, Build 하는 옵션이 살짝 다르다.
바로 Goals and options의 프로파일을 실행하는 " -P [프로파일 명]" 옵션이다. POM에 개발서버로 배포하는 설정을 프로파일로 명시해 두어야 한다.
<profile>
<id>examProfile</id>
<build>
<plugins>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<id>ftp</id>
<phase>install</phase>
<configuration>
<tasks>
<ftp action="send" server="111.111.111.1111"
remotedir="/tomcat/app-path" userid="exampleId"
password="examplePassWord" depends="yes" verbose="yes">
<fileset dir="${project.build.directory}">
<include name="${project.build.finalName}.war" />
</ileset>
</ftp>
</tasks>
</confiuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>2.0</version>
</dependency>
<dependency>
<groupId>ant</groupId>
<artifactId>ant-commons-net</artifactId>
<version>1.6.5</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
<profile>
maven antrun plugin을 이용하여 build가 완료된 최종 버전의 war 파일을 지정한 FTP 서버로 전송을 하는 설정이다.
이로써 CI서버와 WAS의 연결고리가 생성이 되었다.
다음 작업으로는 서버에 전송된 war파일을 WAS로 실행 시키는 작업을 해야 한다.
여기서 Execute Shell 옵션을 사용하게 되었다. 처음 shell 스크립트를 작성하는데 애를 먹었다.
처음 작성하는 것도 있었고, 리눅스에 익숙치 못한 이유도 있었다.
위에 보이는 설정이 바로 Execute shell의 설정 화면이다. CI 서버에 미리 WAS 실행에 대한 shell 스크립트를 준비를 해두어야 한다.
WAS 실행에대한 순서는
- 이미 실행되어있는 WAS의 서비스를 중지 시켜야 하고, (stop.sh)
- 서버에 있는 WAR파일의 압축을 풀어놔야 하고, 백업과 해당 서버 환경설정 해야 한다. (install.sh)
- WAS를 실행시켜야한다. (start.sh)
위에 설정된 순서로 shell이 실행이 된다.
모든 shell들은 모든 동작과 소요시간을 고려하여 작성을 해야 한다. 따라서 해당 서버에서의 소요시간을 미리 파악 해야 한다.
stop.sh
#! /usr/bin/expect
#서버접속
spawn ssh testUser@111.111.111.111
expect -re "password:"
sleep 1
#패스워드 입력
send "examplePassWord\r"
expect -re "Last login:"
#echo Login Success
sleep 1
#echo Tomcat Kill Call
# 톰캣 서비스 중지
send "sh /tomcat/bin/shutdown.sh\r"
sleep 5
#접속 종료
send "exit\r"
interact
해당 서버에 접속하여 톰캣(WAS)의 서비스를 중지 하고, 접속을 종료한다.
install.sh
#! /usr/bin/expect
#서버접속
spawn ssh testUser@111.111.111.111
expect -re "password:"
sleep 1
#패스워드 입력
send "examplePassWord\r"
expect -re "Last login:"
sleep 1
#사전 준비 작업 실행
send "sh /tomcat/app-path/war-install.sh\r"
sleep 5
#접속 종료
send "exit\r"
interact
해당 서버에 접속하여 war-install.sh(서버를 실행시기키 이전의 필요 과정)을 실행을 하고, 접속을 종료한다.
여기서 현 프로젝트는 배포 전,후 예기치 못한 상황에 대처하기 위해 기존의 파일을 백업하여 보관을 한다.
그리고, 서버마다 설정이 각각 다르기 때문에 서버를 실행시키기 전에 해당서버에 맞게 설정을 변경해 주어야 한다.
이런 작업들을 war-install.sh에 기술하고 있다.
start.sh
#! /usr/bin/expect
#서버접속
spawn ssh testUser@111.111.111.111
expect -re "password:"
sleep 1
#패스워드 입력
send "examplePassWord\r"
expect -re "Last login:"
#echo Login Success
sleep 1
#톰캣 서비스 중지
send "sh /tomcat/bin/shutdown.sh\r"
sleep 5
#톰캣 서비스 실행
send "sh /tomcat/bin/startup.sh\r"
sleep 3
send "exit\r"
interact
서버 실행전 모든 작업(install.sh) 이 완료되고, 기존의 톰캣이 실행되어있을지도 모르기 때문에 , 다시 톰캣 서비스를 중지하는 스크립트를 실행후, 톰캣을 다시 실행한 뒤, 접속을 종료한다.
이런 과정을 거치면, 배포부터 서버실행까지 한번의 작업으로 완료가 된다.
이런 프로세스를 만들고 난뒤 몇가지 알 수 없는 오류로 애를 먹은 적이 있다.
소스도 정상적으로 빌드가 되었는데도, 서버에서 보여지는 내용은 최신소스가 아닌경우도 있고, 빌드가 제대로 되었는데, 서버가 실행이 되지 않은 경우도 있다.
이런 예외 상황에 대처하는 나만의 방법을 얘기하고자 한다. 이 방법이 절대적이지 않지만, 알아두면 좋지 않을까 하는 마음에 적어본다.
Q : 빌드가 제대로 되었지만, 최신소스가 반영이 안되었을 때.
A : 소스코드관리 - Check-out-Strategy 옵션을 확인해 보자.
default 설정은 Use 'svn update' as much as possible 설정으로 되어있다. 이 옵션은 가능한 CI 서버의 workspace 소스를 업데이트를 받아서 빌드를 하라는 뜻인데, 가끔씩 업데이트가 제대로 되지 않고, 빌드를 하는 경우가 있다. 그래서 최신소스로 반영이 되지 않는 경우가 있다.
이럴때는 옵션을 Always check out a fresh copy 옵션을 설정하고 작업을 실행해 보자. 이 옵션은 항상 workspace를 지운뒤 새로 checkout받아 빌드를 실행한다. 그러므로써 이전의 소스가 반영되는 경우는 없을 것이다.
Q : 빌드도 제대로 되었지만, 서버가 제대로 실행되지 않았을 때.
A : Execute Shell을 확인해 보자.
shell들은 Run Only if build succeeds 옵션이 설정되어 있기 때문에, 빌드가 성공한 뒤 실행이 된다.
하지만 shell들은 remote 서버에서 실행이 되기 때문에, CI에서는 피드백, 관리 대상에서 제외가 된다.
그렇기 때문에 CI에서는 빌드가 성공하여 제대로 실행이 되었다 하더라도, shell에서 문제가 발생한 경우를 의심해 봐야 한다.