이 연재글은 SpringBoot2로 Rest api 만들기의 13번째 글입니다.

Jenkins를 이용하면 배포에 필요한 여러 가지 절차를 통합하여 편리하게 배포할 수 있는 시스템을 만들 수 있습니다. 이번장에서는 Jenkins로 배포시스템을 구축하고 배포 후에 문제가 생겼을 경우 Git Tag를 이용하여 직전 배포 내용으로 롤백할 수 있는 기능에 대하여 실습해 보겠습니다.

Jenkins 설치

jenkins는 여러가지 설치방법을 제공합니다. Mac이나 Windows에는 자체 설치 파일을 제공하며 Linux에서는 yum이나 apt-get으로 설치할 수 있습니다. 최신 버전이 필요하다면 jar파일로도 제공하기 때문에 jar파일을 다운로드 받아 직접 실행해도 되고, 별도로 설치한 tomcat의 webapps 하위에 jar파일을 옮겨놓고 실행해도 됩니다. OS별 Jenkins 설치에 대한 자세한 내용은 아래 링크를 참고하면 됩니다.

https://jenkins.io/doc/book/installing/

Jenkins 최초 설치후 http://서버IP:8080 접속시 모습입니다.

Jenkins를 활성화 하기 위해 아래 빨간 내용의 서버 경로로 가서 password를 복사해서 붙여넣습니다.

$ sudo cat /Users/Shared/Jenkins/Home/secrets/initialAdminPassword
Password:
c02e5269b6f54e0f9ec21eab5171a513

Jenkins에서 사용하는 플러그인을 다운로드하는 페이지 입니다. 특별히 플러그인을 입맛에 맞게 설치할 경우는 Select plugins to install을 선택합니다.

Jenkins 관리자 생성화면 입니다.

Jenkins Base url를 설정하는 부분입니다.

설치 완료 후 첫 화면 입니다. 이전에 만든 관리자계정으로 자동 로그인됩니다. 생성된 프로젝트가 없어 빈 화면이 노출됩니다.

Jenkins에서 github 연동을 위한 rsa key pair 생성

Jenkins에서 github 소스를 받아올려면 인증이 필요한데, 여러가지 인증방법중 rsa key pair를 통한 방법을 사용해 보겠습니다. 아래와 같이 서버 콘솔에서 ssh-keygen을 입력하면 rsa key pair를 생성할 수 있습니다.

$ ssh-keygen
 Generating public/private rsa key pair.
 Enter file in which to save the key (/home/happydaddy/.ssh/id_rsa):
 Created directory '/home/happydaddy/.ssh'.
 Enter passphrase (empty for no passphrase):
 Enter same passphrase again:
 Your identification has been saved in /home/happydaddy/.ssh/id_rsa.
 Your public key has been saved in /home/happydaddy/.ssh/id_rsa.pub.
 The key fingerprint is:
 SHA256:v9EGi7il7FGbWPmC1UxagjzDVoaZDJ34weY5QVUvHCU happydaddy@kyh-notebook
 The key's randomart image is:
 +---[RSA 2048]----+
 |    .B.*+.E..    |
 |    .o%+ . +     |
 |     +B+. = .    |
 |     .=o O .     |
 |       .S +      |
 |       B * +     |
 |      = * = o    |
 |     . = . +     |
 |     .=   .      |
 +----[SHA256]-----+
$ cd .ssh
$ ls
id_rsa  id_rsa.pub

생성 완료된 rsa key pair는 루트의 .ssh 디렉터리에 생성됩니다. id_rsa 는 비공개키, id_rsa.pub는 공개키를 저장하고 있습니다.

happydaddy@kyh-notebook:~/deploy$ cd ~/.ssh
happydaddy@kyh-notebook:~/.ssh$ ls
id_rsa  id_rsa.pub

github에 공개키 등록

배포 서버에서 github에 정보를 요청할려면 서버의 공개키를 등록해 줘야 합니다.

github로그인 – Settings – SSH and GPG Keys – New SSH Key

Jenkins 프로젝트 생성

github와 연동할 준비가 되었으므로 배포를 위한 Jenkins 프로젝트를 생성합니다.

홈 – 새로운 Item – Freestyle project

설명 – 적당한 내용을 입력합니다.

고급 – 사용자 빌드 경로 사용 체크를 하고 빌드할 디렉터리를 입력합니다. 디렉터리는 입맛에 맞게 설정하시면 되고, 여기서는 계정 홈 deploy 디렉터리가 배포의 base 디렉터리이며 하위의 SpringRestApi 디렉터리에 Git 프로젝트가 clone되도록 하겠습니다.

소스 코드 관리 – Git 체크

Repository URL은 sshkey를 등록하였으므로 프로젝트의 ssh주소를 적습니다.
credentials 항목은 등록된 credentials가 없어서 none으로만 나오고 선택이 불가능합니다. add – Jenkins 선택하여 credentials를 추가합니다.

위에서 생성한 서버의 비밀키로 Credentials를 생성 합니다.

생성된 jenkins credential을 선택합니다. 기본 배포 브랜치는 master로 설정되어 있습니다. 다른 브랜치를 배포 브랜치로 하려면 해당 브랜치로 Branch Specifier 내용을 수정합니다. 이후 항목은 세팅하지 않고 저장을 누르고 나옵니다.

build 테스트

설정한 git 프로젝트로 build가 되는지 테스트합니다. 먼저 Jenkins에서 설정한 사용자 빌드 디렉터리를 서버에 생성합니다.

$ cd /home/happydaddy
$ mkdir deploy
$ ls
deploy

프로젝트 메인 화면으로 돌아와 Build Now 수행합니다. git 주소 등록외에 별다른 작업을 하지 않았으므로 해당 git 프로젝트가 배포서버로 clone됩니다.

빌드 내역을 보면 SpringRestApi를 clone 하는것을 확인할 수 있습니다.

Started by user happydaddy
Building in workspace /home/happydaddy/deploy/SpringRestApi
No credentials specified
Cloning the remote Git repository
Cloning repository https://github.com/codej99/SpringRestApi.git
 > git init /home/happydaddy/deploy/SpringRestApi # timeout=10
Fetching upstream changes from https://github.com/codej99/SpringRestApi.git
 > git --version # timeout=10
 > git fetch --tags --progress https://github.com/codej99/SpringRestApi.git +refs/heads/*:refs/remotes/origin/*
 > git config remote.origin.url https://github.com/codej99/SpringRestApi.git # timeout=10
 > git config --add remote.origin.fetch +refs/heads/*:refs/remotes/origin/* # timeout=10
 > git config remote.origin.url https://github.com/codej99/SpringRestApi.git # timeout=10
Fetching upstream changes from https://github.com/codej99/SpringRestApi.git
 > git fetch --tags --progress https://github.com/codej99/SpringRestApi.git +refs/heads/*:refs/remotes/origin/*
 > git rev-parse refs/remotes/origin/master^{commit} # timeout=10
 > git rev-parse refs/remotes/origin/origin/master^{commit} # timeout=10
Checking out Revision 8ad0baad33040ca1b2c3656143cb2b7ee6781003 (refs/remotes/origin/master)
 > git config core.sparsecheckout # timeout=10
 > git checkout -f 8ad0baad33040ca1b2c3656143cb2b7ee6781003
Commit message: "Merge pull request #15 from codej99/feature/gracefullyshutdown"
First time build. Skipping changelog.
Finished: SUCCESS

아래와 같이 배포서버에 SpringRestApi가 clone되었음을 확인할 수 있습니다.

$ cd /home/happydaddy/deploy
$ ls
SpringRestApi
$ cd SpringRestApi
$ ls
README.md  build.gradle  deploy.sh  gradle  gradlew  gradlew.bat  settings.gradle  src

서버 배포 기능 추가

이전 장에서 생성한 deploy.sh, server_alpha.list를 /home/happydaddy/deploy 디렉터리에 복사합니다. aws서버 접속 인증서인 pem파일도 복사합니다. deploy.sh에는 실행 권한을 부여합니다. pem파일은 jenkins 계정에서 사용할 것이므로 소유자를 jenkins로 변경합니다.

$ pwd
/home/happydaddy/deploy
$ ls
AwsFreetierKeyPair.pem  SpringRestApi  deploy.sh  server_alpha.list
$ chmod +x deploy.sh
$ sudo chown jenkins:jenkins AwsFreetierKeyPair.pem

배포 쉘 스크립트(deploy.sh) 수정

PROJECT_HOME의 경로를 다음과 같이 변경합니다.

PROJECT_HOME=/home/happydaddy/deploy/${PROJECT}

ssh로 타겟 서버 최초 접속시 서버접속 동의없이 진행되도록 옵션을 세팅합니다.

# Target Server에 배포 디렉터리 생성
ssh -i $PEM -o "StrictHostKeyChecking no" $AWS_ID@$server "mkdir -p $DEPLOY_PATH/dist"

Jenkins 프로젝트 설정 추가

Invoke Gradle script 작업 추가

Add Build Step – Invoke Gradle script – Use Gradle Wrapper를 선택하고 다음 내용을 입력합니다. 스프링 소스를 빌드하여 executable Jar를 만드는 작업을 수행합니다.

Shell Script 작업 추가 – deploy.sh 실행

Add Build Step – Execute Shell을 선택하고 다음 내용을 입력합니다. 실제 배포를 진행하는 스크립트를 수행하는 명령입니다.

배포 테스트

첫 실행시엔 Gradle이 필요한 라이브러리를 다운로드를 하기때문에 오래걸립니다. 이후에 Git Pull -> Boot Jar생성 -> Jar파일 업로드 및 배포 순으로 작업이 진행됩니다.

Started by user happydaddy
Building in workspace /home/happydaddy/deploy/SpringRestApi
using credential github
 > git rev-parse --is-inside-work-tree # timeout=10
Fetching changes from the remote Git repository
 > git config remote.origin.url https://github.com/codej99/SpringRestApi.git # timeout=10
Fetching upstream changes from https://github.com/codej99/SpringRestApi.git
 > git --version # timeout=10
using GIT_SSH to set credentials 
 > git fetch --tags --progress https://github.com/codej99/SpringRestApi.git +refs/heads/*:refs/remotes/origin/*
 > git rev-parse refs/remotes/origin/master^{commit} # timeout=10
 > git rev-parse refs/remotes/origin/origin/master^{commit} # timeout=10
Checking out Revision 8ad0baad33040ca1b2c3656143cb2b7ee6781003 (refs/remotes/origin/master)
 > git config core.sparsecheckout # timeout=10
 > git checkout -f 8ad0baad33040ca1b2c3656143cb2b7ee6781003
Commit message: "Merge pull request #15 from codej99/feature/gracefullyshutdown"
 > git rev-list --no-walk 8ad0baad33040ca1b2c3656143cb2b7ee6781003 # timeout=10
[Gradle] - Launching build.
[SpringRestApi] $ /home/happydaddy/deploy/SpringRestApi/gradlew build
Downloading https://services.gradle.org/distributions/gradle-5.2.1-all.zip
..............................................................................................................................
Welcome to Gradle 5.2.1!

Here are the highlights of this release:
 - Define sets of dependencies that work together with Java Platform plugin
 - New C++ plugins with dependency management built-in
 - New C++ project types for gradle init
 - Service injection into plugins and project extensions

For more details see https://docs.gradle.org/5.2.1/release-notes.html

Starting a Gradle Daemon (subsequent builds will be faster)
> Task :compileJava
> Task :processResources
> Task :classes
> Task :bootJar
> Task :jar SKIPPED
> Task :assemble
> Task :compileTestJava
> Task :processTestResources NO-SOURCE
> Task :testClasses
> Task :test
BUILD SUCCESSFUL in 11s
3 actionable tasks: 3 up-to-date
Build step 'Invoke Gradle script' changed build result to SUCCESS
[SpringRestApi] $ /bin/sh -xe /tmp/jenkins7417178554894981921.sh
+ cd ..
+ ./deploy.sh alpha
Deploy Start
Target server - 15.164.98.87
Executable Jar Copying...
Server 8084 Starting...
Health check 8084
Check - false
Check - false
Check - false
Check - false
Server 8084 Started Normaly
Server 15537 Stop
Nginx Port Change
set $service_addr http://127.0.0.1:8084;
Nginx reload
Redirecting to /bin/systemctl reload nginx.service
Deploy End
Finished: SUCCESS

롤백 기능 추가

개발 결과물을 배포 한 후에 버그등의 이슈가 발생하여 이전 소스로 원복(Rollback)해야 하는 경우가 있습니다. git tag를 이용하면 Rollback을 쉽게 구현 할 수 있습니다. tag는 특정 시점의 스냅샷이라고 보면 됩니다. 개발 브랜치를 배포한 후 tag를 남겨 놓으면 해당 시점 기준 개발 내용이 tag로 저장됩니다.

이후부터는 배포를 통해 문제가 생기면 직전에 저장된 tag로 재 배포를 하여 이전 내용으로 원복할 수 있습니다.

프로젝트 설정 수정

이 빌드는 매개변수가 있습니다 체크 – 매개변수 추가

Git Parameter를 체크합니다. 선택에 없을경우엔 Jenkins관리 – 플러그인 관리 – Git Parameter를 검색 후 설치 한다음 Jenkins 서버를 restart합니다.

Git Parameter 추가

배포시 저장된 Git Tag로 배포할수 있도록 파라미터를 세팅합니다.

String parameter 추가

배포 완료 후 Tag 이름 생성시 추가 정보를 남기기 위해 세팅합니다.

Branch Specifire 수정

Tag 브랜치로도 배포 가능하도록 위에서 생성한 Git Parameter 변수로 내용을 변경합니다.

빌드 후 조치 추가 – Git Publisher

Git Publisher는 명칭 그대로 Git에 특정 작업을 수행할 수 있게 도와주는 플러그인입니다. 여기서는 배포 완료후에 해당 Git 프로젝트에 Tag를 만들어 Remote로 Push하기 위해 사용합니다.

Tags – Add Tag

저장하고 나와서 다시 배포 테스트를 합니다.
왼쪽 메뉴의 Build Now가 Build with Parameter로 변경된것을 볼수 있습니다.

이전에는 Build Now를 누르면 바로 배포가 실행되었지만, 이제는 롤백할 TAG를 선택하거나 TAG_ID를 입력해서 빌드할 수 있도록 보여집니다. TAG_ID에 First라고 입력 후 배포를 수행해 봅니다.

배포 완료 후 Console을 확인하면 다음과 같이 Tag가 생성되는것을 확인할 수 있습니다.

Deploy End
using credential b2e3085b-182d-4286-82bc-1ce1ec3ecc7d
 > git tag -l Release-21-First # timeout=10
 > git tag -a -f -m TAG INFO
BuildNo - 21
BuildTag - jenkins-api-deploy-21 Release-21-First # timeout=10
Pushing tag Release-21-First to repo origin
 > git --version # timeout=10
using GIT_SSH to set credentials 
 > git push git@github.com:codej99/SpringRestApi.git Release-21-First
Finished: SUCCESS

다시 Build with Parameters를 선택합니다. ROLLBACK_TAGS에서 이전에 배포한 내용의 TAG를 선택 할 수 있습니다.

Github에서는 Release 탭에서 다음과 같이 확인 할 수 있습니다.

롤백 테스트

파일을 하나 수정하고 배포합니다.
단순히 helloworld 문구를 helloworld-nice to meet you로 변경하였습니다.

public class HelloController {
    private static final String HELLO = "helloworld-nice to meet you";
    //이하 동일
}

배포후 서버 내용이 변경됨을 확인합니다.

롤백을 위해 First Tag를 선택후 다시 배포합니다

배포 완료 후에 다시 확인해 보면 배포 내용이 롤백 된것을 확인할 수 있습니다.

여기까지 Jenkins를 이용하여 배포 시스템 구축(롤백 처리 포함)을 실습해보았습니다. 나름 쓸만한 배포 시스템이 구축되었으며 배포 후 버그가 발생하거나 장애 발생 시 신속하게 대처할 수 있게 되었습니다.

이상 실습을 마치겠습니다!

GitHub Repository를 import하여 Intellij 프로젝트를 구성하는 방법은 다음 포스팅을 참고해주세요.

Docker로 개발 환경을 빠르게 구축하는 것도 가능합니다. 다음 블로그 내용을 읽어보세요!

스프링 api 서버를 이용하여 웹사이트를 만들어보고 싶으시면 아래 포스팅을 참고해 주세요.

연재글 이동[이전글] SpringBoot2로 Rest api 만들기(12) – Deploy & Nginx 연동 & 무중단 배포 하기
[다음글] SpringBoot2로 Rest api 만들기(14) – 간단한 JPA 게시판(board) 만들기