Jenkins에서 자바 웹 프로젝트 자동 배포

Jenkins logo

들어가기

개발자로서 커리어를 처음 시작하고 모 은행 SI프로젝트에 가서 처음 주어졌던 일이 허드슨(지금은 이름이 바껴 Jenkins) 배포 관리였다. 현업이나 PL/PM들이 주로 아침에 개발서버와 스테이징 서버를 통해서 변경된 기능에 대해서 확인하고 이슈를 확인하는 식이였다. 문제는 개발자들이 야근하면서 로컬에서 작성한 코드들을 커밋하고 퇴근하면서 다른 컴포넌트의 의존성 문제로 허드슨의 빌드가 깨져서 개발서버 인스턴스가 부팅이 되질 않아 아침에 확인을 못하는 문제가 생겼다. 그래서 나같은 신입 개발자들이 주로 밤 늦게까지 빌드가 정상적으로 동작하는 지 확인하고 문제가 생기는 경우 직접 수정하거나 개발자한테 연락해서 수정을 하는 식으로, 그야말로 몸빵(?)을 했던 추억이 있다. 그 이후로는 CI툴은 거의 써보질 못했고 대부분 형상관리 시스템이나 개발 프레임워크에서 지원하는 식으로 배포에 대한 이슈가 없었다.

최근에 부산에 몇몇 업체에 개발프로젝트 일을 하거나 주위의 이야기를 들어보면 자동 배포는 개나 줘버려라는 식으로 개발자 로컬 PC에서 파일 단위로 FTP를 통해서 개발서버에 배포하는 경우가 대부분이였다. 일단 개발자의 수가 작은 프로젝트라서 배포에 대한 고민이 크질 않거나 프로젝트마다 개발 환경이 달라져서 CI구성을 하기 힘든 경우가 많아지니 CI나 자동배포를 포기하게 되는 원인이질 않을까 추측해 본다.

내 생각에는 혼자 개발하지 않는 이상, 개발 프로젝트에서는 꼭 CI툴을 이용해서 자동배포하는 환경이 꼭 있어야 본다. 첫번째는 비용(개발자가 하나하나씩 배포할 경우에 시간과 노력)적인 문제이고 두번째는 서버에서 빌드된 파일들의 관리 문제이다. 자바 프로젝트의 경우 개발자 로컬 PC에서 컴파일한 클래스파일을 서버에 올리는 문제인데 개발서버가 리눅스고 자바 환경이 Open JDK를 사용하는 경우 크게 문제가 없을 거 같은데 유닉스 환경에서 벤더사의 JDK를 사용할 경우에는 문제가 달라 진다. 벤더 사의 JDK로 컴파일된 바이트 코드가 런타임 환경과 다를 경우 성능 이슈나 나중에 알 수 없는 오류를 접할 가능성이 있다. 그러므로 꼭 자바 웹 프로젝트일 경우 CI툴을 사용해서 자동배포를 꼭 하자.

준비물(환경)

얼마전에 회사 프로젝트에 배포를 수정한거라 기억이 가물가물하다.

자바 웹 프로젝트이고 프로젝트 루트에 SRC와 WEB(WAS의 도큐먼트 루트와 매칭)이라는 디렉토리가 있고, SRC에 있는 자바 파일을 컴파일되어 개발 서버의 WEB-INF/classes 디렉토리로 배포되어야 한다. 형상관리는 Git을 사용하고 저장소는 Github 유료 플랜으로 비공개 저장소를 사용한다. 개발서버는 데비안 리눅스 버전이고 Open JDK그리고 WAS는 Apache Tomcat이다. Jenkins는 물리적으로 다른 서버에 설치되어 있다. Jenkins 설치 정보는 구글링해 보면 많이 나오니 참고하면 된다.

배포 시나리오는 Git Branch애 개발자가 개발 커밋을 푸시하면 Github 트리거가 발생해서 Jenkins의 웹훅을 호출하고, Jenkins의 스케줄러잡에서 해당 브랜치가 설정되어 있으면 연결된 Git Branch를 체크아웃 받아서 Gradle로 빌드하고 자바 클래스 파일을 War로 묶어서 개발서버에 배포 한다. 그리고 Jenkins 작업 마지막에 원격으로 개발서버에 접속해서 셸스크립트를 실행해서 개발서버 work directory에서 해당 브랜치의 파일들을 pulling해서 WAS의 document ROOT로 복사하는 식이다.

사실 하고 나면 간단한 내용(실제로도 별 내용이 없다)인데 초보자일 경우 아마 환경적인 부분에서 삽질(X) 헤매는 경우가 있을거라 본다. 이런 부분들이나 지적사항은 댓글 남겨 주면 고맙겠다.

Jenkins 설정

1) 젠킨스 Github 플러그인 설치. “Jenkkins 관리”-“플러그인 관리” 화면서 Git/Github 연동 플러그인을 설치 한다. 아래는 실제 설치된 목록이다.

Delivery Pipeline Plugin
Visualisation of Delivery/Build Pipelines, renders pipelines based on upstream/downstream jobs. Full screen view for information radiators.
0.9.5 0.9.4
  
GIT client plugin
Shared library plugin for other Git related Jenkins plugins.
1.18.0  1.17.1
  
GIT plugin
This plugin allows use of Git as a build SCM. A recent Git runtime is required (1.7.9 minimum, 1.8.x recommended). Plugin is only tested on official git client. Use exotic installations at your own risks.
2.4.0 2.3.5
  
GitHub API Plugin
This plugin is a library plugin used by other GitHub related plugins to share the same libraries. This plugin does not have any user visible feature by itself. There's no need to install this plugin manually, although you want to keep it up to date.
1.69  1.68
  
GitHub plugin
This plugin integrates Jenkins with Github projects.
1.12.0  1.11.3
  
Groovy
This plugin adds the ability to directly execute Groovy code.
1.27  1.25
  
Maven Integration plugin
Jenkins plugin for building Maven 2/3 jobs via a special project type.
2.11  2.10
  
SSH Slaves plugin
This plugin allows you to manage slaves running on \*nix machines over SSH.
1.10  1.9

2) 새로운 스케줄러 잡 등록
젠킨스 첫 화면에서 “새로운 Item”을 클릭해서 새로운 스케줄 잡을 등록하자. 사용하는 Jenkins가 한글화되어 있어서 영어 단어와 다를 수 있으니 유의하길 바란다.
그리고 스케줄러 잡에는 왠만하면 공백을 넣지 않도록 한다. 잡 이름이 시스템상의 경로가 될 수 있기에 편의상 공백없는 이름을 추천 한다.

  • 기본 정보 “Github project”에서 Git Repository 주로 입력
  • 소스코드 관리 항목에서 Git을 선택하고 “Repository URL”과 젠킨스에서 사용할 GitHub 계정을 추가한다. “Branches to build”에서 실제 빌드한 브랜치를 적어 준다. GitFlow를 사용해서 Devel이나 Release브랜치를 기입 한다.
  • Build항목에서 Invoke Gradle 항목을 선택한다 실행 권한 문제로 Jenkins에서 제공하는 Gradle를 사용했다. “Tasks”는 컴파일 빼곤 다른 게 없어 “build”를 입력하고 “ROOT build script”에서 Gradle 스크립트과 위치한 경로를 기입 한다.

레드햇 리눅스에 yum으로 설치된 젠킨스일 경우 아래 경우에는 gradle스크립트를 위치해 놓는다.

/var/lib/jenkins/jobs/(Jenkins 스케줄러 이름)/workspace

아주 간단한 Gradle 스크립트다. 빌드에서 class파일을 build/bin으로 타겟디렉토리를 지정 한다. 자바 웹 프로젝트이기 때문에 서블릿 jar파일을 빌드 클래스패스에 추가 했다.

apply plugin: 'java'
apply plugin: 'war'
apply plugin: 'distribution'

sourceSets {
main {
output.classesDir = 'build/bin'
output.resourcesDir = 'build/bin'
java {
srcDir 'src'
}
resources {
srcDir 'src'
}
}

}
// In this section you declare where to find the dependencies of your project
repositories {
// Use 'maven central' for resolving your dependencies.
// You can declare any Maven/Ivy/file repository here.
mavenCentral()
}

// In this section you declare the dependencies for your production and test code
dependencies {
// The production code uses the SLF4J logging API at compile time
compile 'org.slf4j:slf4j-api:1.7.5'
providedCompile 'javax.servlet:servlet-api:2.5'
compile fileTree(dir: 'lib', include: '*.jar')

}
  • 다시 Jenkins 스케줄러 화면에서 “빌드 후 조치” 섹션에서 “Send build artifacts over SSH”항목에 개발 서버에 배포할 파일, 여기서는 빌드한 클래스 파일은 build/bin밑에 war파일을 sftp방식으로 배포할려고 한다. 배포한 파일을 ssh로 셸스크립트를 이용해서 archive 해제해서 웹루트에 복사 한다. 항복별로 보면 “Transfers”-“Transfer Set Source files”에서 "**/*.war",입력했고 exec command 에서 셸스크립트의 경로를 지정했다. 참 SSH Server는 미리 Jenkins 전역 설정 화면에서 미리 추가해 놓아야 한다. 나 같은 경우 Jenkins->개발서버로 SSH로 접속시에 공개키로 ssh접속이 가능토록 미리 설정해 놓아서 따로 인증 정보를 추가할 필요가 없다. 이런 방식으로 하길 권한다.
/home/tomcat/deploy/sh/runapp.sh;

나머지 정보는 환경에 맞도록 입력하고 저장 한다. Jenkins에서 “Build Now”를 선택해서 해당 잡의 Console 로그 화면에서 Git까지 체크아웃하고 Gradle까지 실행되었다면 거의 다 한셈이다.

이젠 빌드후에 개발서버에서 실행된 셸스크립트 파일을 간단하게 만들어 봤다. 위에서 설정한 runapp.sh의 내용이다. Git에서 프로젝트 소스를 체크아웃해서 워크디렉토리를 통해 pulling 받은 소스를 웹루트에 복사하는 스크립트이다.

#!/bin/sh
cd home/tomcat/webapps/app/ROOT;
jar -xf /home/tomcat/webapps/app/ROOT/build/libs/workspace.war
rm -rf /home/tomcat/webapps/app/ROOT/build/;
# 워킹 디렉토리에서 Git pulling
cd /home/tomcat/deploy/(프로젝트 체크아웃 디렉토리)/web;
git checkout develop;
git pull;
# 웹루트로 카피
cp -rf /home/tomcat/deploy/(프로젝트 체크아웃 디렉토리)/web/* /home/tomcat/webapps/app/ROOT/;
# tomcat 컨테이너 재기동
/home/tomcat/tomcat7/bin/shutdown.sh;
/home/tomcat/tomcat7/bin/startup.sh;
# 슬랙 웹훅으로 빌드 성공 메시지를 전송하면 더 좋다.
curl -X POST --data-urlencode 'payload={"channel": "#server_room", "username": "bot", "text": ":ok_hand::ok_hand:deploy complete.", "icon_emoji": ":ok_hand:"}' (웹훅 URL) > /dev/null 2>&1

다시 Jenkins로 돌아와서 “Build Now”로 빌드를 실행해서 결과를 확인 한다.

Github 설정

브랜치에 push이벤트가 발생하면 위 Jenkins가 빌드 잡을 실행하도록 서비스 훅 설정이 필요하다. 해당 리파지터리 “Settings”메뉴에서 “Webhooks & Services”메뉴를 선택하고 아래 “Service” 항목에서 Jenkins 정보를 추가 하자.

Github hook service

Github설정까지 끝났으면 브랜치에 소스를 push해서 Jenkins스케줄러 잡이 동작하는 지 확인 해본다. push시에 Jenkins가 동작하지 않는다면 웹 훅 설정이 잘 못된 것이므로 다시 확인해 본다.

사실 내용에 나온 Gradle도 잘 모르는 상태고 Jenkins 설정도 오랫만에 보는거라 삽질을 좀(사실 꽤) 했다. 정리한 내용이 다음 삽질자(?)에게 도움이 되었으면 좋겠다.