젠킨스( Jenkins )는 빌드, 테스트, 배포 등의 지속적인 통합을 자동화 해주는 툴입니다.

지속적으로 코드를 통합해서 배포하기 때문에 고객은 개발의 진행상황을 쉽게 파악할 수 있다는 장점이 있습니다.

( 지속적인 통합( CI, Continuous Integration )에 대한 자세한 정보는 여기를 참고해주세요. )




준비 작업

이 글에서는 젠킨스를 사용해서 애플리케이션을 배포하는 방법에 대해 알아볼 것입니다.

먼저 준비해야 할 것은 다음과 같습니다.

1) 배포 할 자바 웹 애플리케이션

( 여기서는 Guestbook Mybatis 버전으로 진행하겠습니다. )

2) Jenkins 서버 준비

( CentOS 7 서버에 JDK, Tomcat, Git, Maven이 설치되어 있어야 합니다. Jenkins 서버가 준비되어 있지 않다면 여기를 참고해주세요. )

3) Github의 repository를 생성해서 애플리케이션을 올립니다.

( Guestbook Mybatis 버전 깃헙 - 링크


준비가 다 됐다면 서버에서 톰캣을 실행한 후에, 젠킨스 페이지에 접속합니다.





Jenkins 관리 - JDK , Git, Maven 환경변수 설정

Jenkins가 Git, Maven 등을 실행시키기 위해서는 파일의 경로를 설정해줘야 합니다.


1. 

우선 Jenkins 페이지에서 좌측의 " Jenkins 관리 "를 클릭한 후, 이어서 " Global Tool Configuration "을 클릭합니다.


그러면 Maven Configuration, JDK, Git, Gradle, Ant, Maven, Docker 를 추가 할 수 있는데, 여기서는 JDK, Git, Maven만 추가할 것입니다.




2. JDK 설정

" Add JDK " 버튼을 클릭합니다.

그러면 아래와 같이 " JDK를 자동으로 설치 한다 "고 되어 있는데, 이미 서버에 JDK를 설치했으니 체크를 해제합니다.


이어서 이름과 JDK가 설치된 경로를 작성합니다.


3. Git 설정

Git은 다음과 같이 작성하면 됩니다.


4. Maven 설정

" Add Maven " 버튼을 클릭한 후 JDK 설정 때와 같이 " 자동으로 설치 버튼의 체크 "를 해제합니다.

그리고 아래와 같이 이름과 Maven이 설치된 경로를 작성합니다.


필요한 것은 다 설정했으므로 " apply "와 " save "버튼을 눌러서 설정을 적용합니다.





새 작업 생성

다음으로 지속적인 통합을 위한 작업을 생성하도록 하겠습니다.


1.

dashboard 첫 화면에서 좌측의 " 새로운 Item "을 클릭하고,

Item의 이름을 작성한 후에 " Freestyle project "를 선택한 다음에 " OK "버튼을 클릭합니다.




2. 소스 코드 관리

다음으로 이 작업이 소스 코드를 어떻게 관리 할 것인지 결정하기 위해, 상단의 " 소스 코드 관리 " 탭을 클릭합니다.

Git을 사용할 것이므로 Git에 체크를 한 후에, 준비했던 배포 할 애플리케이션의 깃헙 주소를 작성합니다.

( https 프로토콜을 작성하면 에러가 발생하므로, git 프로토콜로 작성했습니다. )




3. Build 

다음은 Build를 어떻게 할 것인지에 대한 목표를 작성하기 위해, 상단의 " Build " 탭을 클릭합니다.

그리고 " Add build step "을 클릭해서 " Invoke top-level Maven target " 을 클릭하여 보이는 창에서 Maven Version을 설정하고, Goals를 작성합니다.

clean package tomcat:redeploy -P production -D maven.test.skip=true



tomcat:redeploy

톰캣 매니저( Tomcat manager )와 통신해서 war파일을 deploy 하겠다는 의미입니다.


-P production

뒤에서 pom.xml 파일에 <profile>을 작성할 것인데, 이 설정은 <id>가 production인 <profile>설정 그대로를 컴파일 하라는 의미입니다.

윈도우 환경의 이클립스에서 톰캣의 Servlet은 런타임 환경에서 자동으로 classpath에 잡히지만, 리눅스에서는 이클립스를 사용하지 않기 때문에 <profile>설정을 해줘야 합니다.

( 그동안 이클립스에서 maven으로 build를 하지 않기 때문에, pom.xml 파일에서 <profile>을 설정하지 않았습니다. )


-D maven.test.skip=true

테스트를 생략하겠다는 설정입니다.

Junit을 사용하지 않을 것이므로 생략했습니다.



CI를 위한 소스 코드 관리와 Build 설정을 마쳤으므로 " apply " , " save " 버튼을 클릭합니다.





빌드

이제 배포를 하기 전에 빌드를 해보도록 하겠습니다.

CI에서 가장 중요한 것은 빌드가 깨지지 않도록 항상 유지하는 것입니다.


1.

dashboard에서 좌측의 " build now "버튼을 클릭합니다.

그러면 Build History에 빌드 내역이 추가 됩니다.
빌드 번호를 클릭하면 빌드에 대한 정보를 볼 수 있는데, 젠킨스가 빌드를 잘했는지 확인하기 위해 " Console Ouput "을 클릭하겠습니다.



2.

콘솔을 확인해보면, 아마 아래와 같은 에러가 발생했을 것입니다.

Caused: java.io.IOException: Cannot run program "mvn" (in directory "/root/.jenkins/workspace/guestbook"): error=2, 그런 파일이나 디렉터리가 없습니다


이 에러는 " 메이븐 명령어를 실행할 수 없다 "는 에러입니다.

서버에서 Maven을 설치 할 때, Maven의 환경 변수를 추가했기 때문에 쉘에서 maven 명령어를 수행할 수 있지만,

젠킨스를 실행하는 환경인 톰캣은 데몬으로 실행되고 있기 때문에 maven 명령어를 알지 못합니다.

( 데몬은 쉘과 연결이 끊어집니다. )

그래서 같은 이유로 톰캣 데몬을 돌리기 위해서 tomcat.service안에 JDK에 대한 환경 변수를 추가했었습니다.



따라서 이와 마찬가지로 톰캣이 메이븐을 알 수 있도록 maven 경로를 잡아줘야 합니다.


우선 echo $PATH 명령어로 PATH경로를 출력 한 후 복사하여,

vi 편집기로 /usr/lib/systemd/system/tomcat.service 파일을 열어서 아래와 같이 붙여넣기 합니다.


톰캣을 수정했으니 톰캣 서비스를 재시작 합니다.

# systemctl daemon-reload

# systemctl stop tomcat

# systemctl start tomcat





3. 

다시 빌드를 해봅니다.

콘솔 내역을 보시면, 뭔가 엄청나게 많은 것들을 실행했습니다.

이 과정은 여러가지 플러그인을 다운로드하고 있는 것입니다.

이클립스에서 pom.xml 파일에 <dependency>를 추가하면 m2 폴더( 저 같은 경우는, C:\Users\samsung\.m2\repository )에 파일이 저장되는 것을 기억하실 겁니다.


즉 Jenkins는 이와 같은 환경을 구축하고 있는 것입니다.

실제로 ls -l /root/.m2/repository 명령어로 디렉터리를 확인하면 여러 파일이 저장된 것을 확인할 수 있습니다.




그런데 빌드의 최종 결과를 보면 에러가 발생합니다.


경고와 에러 내용을 보면 다음과 같습니다.

1) UTF-8 인코딩이 되어 있지 않다.

2) profile " production "이 없다.

3) javax 패키지를 찾을 수 없어서 GlobalExceptionHandler 클래스에서 에러가 발생


이 에러를 해결 하기 위해서는 pom.xml을 수정한 후에, 깃헙에 push하여 빌드를 다시 해야 합니다.




4. 

이클립스에서 애플리케이션을 열고, pom.xml 파일을 열어서 아래와 같이 수정합니다.

<project xmlns="http://maven.apache.org/POM/4.0.0"          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0http://maven.apache.org/xsd/maven-4.0.0.xsd">


      <modelVersion>4.0.0</modelVersion>

      <groupId>com.victolee</groupId>

      <artifactId>guestbook</artifactId>

      <version>0.0.1-SNAPSHOT</version>

      <packaging>war</packaging>


      <!-- 변수 -->

      <properties>

            <org.springframework-version> 4.2.1.RELEASE </org.springframework-version>


                <!-- Jenkins에서 사용하는 부분 -->

            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

            <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

            <!--  -->

      </properties>


      <!-- 라이브러리를 추가 -->

      <dependencies>

            // ... 생략

      </dependencies>

      <!-- Jenkins에서 사용하는 부분 -->

      <profiles>

            <profile>

                  <id>production</id>

                  <build>

                        <resources>

                             <resource>

                                   <directory>${project.basedir}/src/main/resources</directory>

                                   <excludes>

                                         <exclude>**/*.java</exclude>

                                   </excludes>

                             </resource>

                        </resources>


                        <plugins>

                             <plugin>

                                   <groupId>org.apache.maven.plugins</groupId>

                                   <artifactId>maven-resources-plugin</artifactId>

                                   <configuration>

                                         <encoding>UTF-8</encoding>

                                   </configuration>

                             </plugin>

                        </plugins>

                  </build>


                  <dependencies>

                        <!-- Servlet -->

                        <dependency>

                             <groupId>javax.servlet</groupId>

                             <artifactId>javax.servlet-api</artifactId>

                             <version>3.0.1</version>

                             <scope>provided</scope>

                        </dependency>

                        <dependency>

                             <groupId>javax.servlet.jsp</groupId>

                             <artifactId>jsp-api</artifactId>

                             <version>2.0</version>

                             <scope>provided</scope>

                        </dependency>

                  </dependencies>

            </profile>

      </profiles>


      <build>

            <plugins>

                  <plugin>

                        <artifactId>maven-compiler-plugin</artifactId>

                        <version>3.7.0</version>

                        <configuration>

                             <source>1.8</source>

                             <target>1.8</target>

                        </configuration>

                  </plugin>

                  <plugin>

                        <artifactId>maven-war-plugin</artifactId>

                        <version>3.0.0</version>

                        <configuration>

                             <warSourceDirectory>webapp</warSourceDirectory>

                        </configuration>

                  </plugin>

                  <!-- Jenkins에서 사용하는 부분 -->

                  <plugin>

                        <groupId>org.codehaus.mojo</groupId>

                        <artifactId>tomcat-maven-plugin</artifactId>

                        <configuration>

                             <url>http://localhost:8080/manager/text</url>

                             <path>/guestbook</path>

                             <username>admin</username>

                             <password>manager</password>

                        </configuration>

                  </plugin>

                  <!--  -->

            </plugins>

      </build>

</project>

<properties>에서 UTF-8를 추가함으로서 에러 1)를 해결했습니다.


id가 production인 <profile>을 추가함으로서 에러 2)를 해결했습니다.


<profiles>의 <dependencies>에서 javax.servlet-api , jsp-api 의존성을 추가함으로서 에러 3)을 해결했습니다.

의존성을 추가한 이유는, 메이븐에서는 톰캣 라이브러리를 찾지 못하기 때문입니다.

이클립스에서는 Build Path를 통해 javax.servlet.http 클래스 같은 톰캣 라이브러리가 등록되어 있지만, 메이븐은 이 클래스를 알 방법이 없습니다.

( 이클립스에서는 maven을 이용해서 빌드를 하지 않습니다. )

그래서 의존성을 추가했고, 이 의존성은 꼭 <profile> 내부가 아니라, 일반 <dependencies>에 있어도 상관은 없습니다.


그리고 빌드를 마친 후에, tomcat manager를 통해 deploy를 할 수 있도록 <plugin>을 추가했습니다.

( tomcat manager에 대해서는 여기를 참고해주세요 )



pom.xml 파일을 위와 같이 수정을 한 후에, 깃헙에 push를 합니다.

그리고 jenkins에서 다시 build를 하면 성공할 것입니다.




5.

dashboard에서 확인해보면 build가 성공했으므로 빨간 불이 아니라 파란 불이 들어온 것을 확인할 수 있습니다.



build를 여러번 해주면 햇빛이 쨍쨍한 상태로 만들 수 있습니다.



이제 테스트를 위해 브라우저에서 서버IP주소/프로젝트폴더이름 으로 접속해보겠습니다. ( 저같은 경우는, http://192.168.245.132:8080/guestbook/main/ )

만약 애플리케이션이 DB와 연결되어 있지 않다면 성공적으로 배포된 것을 확인할 수 있지만,

DB와 연결되어 있는 JDBC Connection 에러가 발생할 것입니다.






JDBC Connection 에러 해결

JDBC Connection 에러가 발생한 이유는 애플리케이션에서 사용된 DB Connection 정보가 서버에 없기 때문이다.

윈도우에서 개발할 때는 윈도우에 DB가 설치되어 있기 때문에 아무런 문제 없이 사용을 했지만, 서버에서는 이에 대한 정보가 없습니다.

따라서 서버에 DB를 설치하고 사용하는 database와 계정을 생성해야 합니다.

( CentOS 7에서 MySQL 5.6.15 설치는 여기를 참고해주세요 )


저는 MySQL 5.6.15 버전을 사용하고 있고, database 명은 'webdb' , 계정명과 비밀번호도 'webdb' 이므로 다음과 같이 명령어를 작성해서 MySQL 계정을 생성했습니다.

# mysql -u root -p
# create database webdb;
# create user 'webdb'@'localhost' identified by 'webdb';
# grant all privileges on webdb.* to 'webdb'@'localhost';



계정을 생성한 후에 다시 접속해보면 연결에는 문제가 없지만, DB 테이블이 정의되어 있지 않다는 에러가 발생할 것입니다.

DDL 스크립트가 있다면 실행하면 될 것입니다.


DB 모델링 툴 프로그램을 사용해서 DDL을 정의했다면 해당 툴이 서버의 Mysql에 접속할 수 있도록 계정을 추가합니다.

# create user 'webdb'@'192.168.219.118' identified by 'webdb'; # grant all privileges on webdb.* to 'webdb'@'192.168.219.118';

Host에서 접속하는 webdb 계정을 생성했습니다. ( 저의 Host( 윈도우 ) IP 주소는 192.168.219.118 입니다. )





이상으로 젠킨스를 이용하여 지속적인 통합 및 배포를 구현해보았습니다.

많이 복잡하고, 또 저의 개발환경과 다른 부분이 많을 것이기 때문에 이 글이 완벽하지 않을 것입니다.

그래도 큰 틀에 대해서는 참고할 수 있을 것이라 생각합니다.