Java Exception


1. 개요

자바 계통 언어에서는 오류를 표현하는 2가지 방법이 존재한다. 첫 번째는 Error 클래스인데 Error 클래스를 상속받는 하위 클래스는 시스템 오류를 표현하게 된다. 두 번째는 Exception 클래스로 어플리케이션 레벨에서 개발자가 예외사항을 표현하기 위해 사용한다.

이번 포스팅에서는 ErrorException에 대해서 작성한다.

2. Error

Error 클래스는 시스템 오류를 표현하는 것으로 주로 JVM에서 사용한다. 어플리케이션 레벨에서 사용하는 경우도 있기는 한데 거의 사용되지 않고, try~catch로 처리할 수 없다. 시스템 오류를 표현하는 것이므로 어플리케이션 개발자가 관여하게될 가능성이 매우 낮다. 흔히 볼 수 있는 Error 클래스의 구현체는 OutOfMemoryErrorStackOverflowError가 있다.

3. Exception

Exception 클래스는 어플리케이션을 개발하는 과정에서 로직을 처리하는 중에 예외사항이 발생할 경우 사용하게 된다. Exception은 크게 Checked ExceptionUnchecked Exception으로 구분되는데 이 둘의 차이는 아래 표와 같다.

Checked ExceptionUnchecked Exception

명시적 처리

명시적으로 처리해야 함

명시적인 처리를 강요하지 않음

처리 시점

Compile Time

Application Runtime

대표 Class

Exception 클래스와 하위 클래스 중 RuntimeException과 그 하위 클래스를 제외

RuntimeException

자바에서는 Exception을 처리하는 과정에서 RuntimeException과 그 하위 클래스들을 특별하게 취급하여 Compile Time에서 처리를 강제하지 않고, Runtime에서 처리한다.

주로 개발자에게 예외사항이 발생할 수 있음을 알려주기 위하거나, 비즈니스 요구사항을 표현하기 위해서 CheckedException을 사용하게 된다. 반대로 Unchecked Exception은 반드시 처리하지 않아도 되는 경우나, 굳이 개발자가 알 필요가 없을 경우 사용하게 된다.

Exception을 사용하게 되면 비즈니스 로직에서 true/false 혹은 object/null을 사용하는 것에 비해서 훨씬 다양하게 예외사항을 표현할 수 있고, 비즈니스 요구사항을 코드로 깔끔하게 담을 수 있다. 또한 의미와 용도에 맞는 Exception을 다양하게 사용하게 된다면, 디버깅이나 유지부수에도 큰 이점을 가질 수 있다. Exception은 주로 분석/설계 단계에서 정의를 하게 된다. 실제 비즈니스 로직을 구현하면서 메소드에 throws를 명시적으로 작성함으로써 해당 메소드를 사용하는 개발자에게 예외사항이 발생할 수 있음을 열려준다.

4. 예시

어플리케이션을 개발하는 과정에서 요구사항을 분석하는 과정이 있고, 이 과정에서 User StoryScenario를 정의할 수 있다.

예를 들어 TODO Item을 관리하는 어플리케이션에서 TODO의 상태를 변경하는 기능 요구사항이 있고, 요구사항 분석 결과 아래와 같은 User StoryScenario가 나왔다고 가정한다.

User Story:
TODO Item의 상태를 변경할 수 있다.
Todo/Doing/Done으로 상태를 변경할 수 있고, 상태변경은 Todo > Doing, Doing > Done, Done > Doing, Doing > Todo 로만 할 수 있다.


Scenario 1: Todo에서 Doing으로 상태를 변경한다.
Scenario 2: Doing에서 Done으로 상태를 변경한다.
Scenario 3: Done에서 Doing으로 상태를 변경한다.
Scenario 4: Doing에서 Todo로 상태를 변경한다.
Scenario 5: Todo에서 Done으로 상태를 변경한다.
Scenario 6: Done에서 Todo로 상태를 변경한다.

위와 같이 User StoryScenario를 정의한다고 할 때 Scenario 5Scenario 6은 예외사항이 발생하는 Scenario 이다. 이렇게 분석 과정에서 예외사항이 발생할 수 있음을 미리 파악하고, 이를 설계 및 구현에서 반영을 하게 된다.

위의 예시를 코드로 표현하면 아래와 같다.

public TodoItem changeState(@NonNull TodoItem todo, @NonNull TodoState state) throws IllegalStateChangeException {
  TodoItem savedItem = todoItemRepository.findById(todo.getIdx())
      .orElseThrow(ResourceNotFoundException::new);

  final boolean possibleToChangeState = TodoState.isPossibleToChangeState(savedItem.getState(), state);
  if (not(possibleToChangeState)) {
    throw new IllegalStateChangeException();
  }

  savedItem.setState(state);

  return todoItemRepository.save(savedItem);
}

위의 코드 예시와 같이 비즈니스 요구사항에 의한 예외사항은 Checked Exception으로 처리하여 throws를 통해 명시적으로 알려준다. 반면에 비즈니스 요구사항이 아닌, 해당 TODO Item이 없어서 처리할 수 없는 예외같은 경우는 Unchecked Exception을 통해 처리를 하면 된다.


Dockerfile


1. Dockerfile?

DockerfileImage 설정 파일로 Image를 생성할 내용을 작성한다.

2. Options

OptionDescription

FROM

어떤 이미지를 기반으로 생성할지 설정한다. <이미지 이름>:<태그> 형식으로 설정한다.

MAINTAINER

메인테이너 정보를 작성한다.

ENV

이미지의 환경변수를 설정한다.

RUN

쉘 스크립트 혹은 명령어를 실행한다.

VOLUME

호스트와 공유할 디렉토리를 지정한다.

CMD

컨테이너가 시작됐을 때 실행할 실행 파일 혹은 쉘 스크립트를 지정한다.

ADD

호스트에서 이미지로 복사할 파일을 지정한다.

EXPOSE

호스트와 연결할 포트 번호를 지정한다.

3. Example

FROM centos:centos6.8
MAINTAINER HyeonilJeong <dev.nuti0102@gmail.com>

ENV V_NGINX=1.12.1 \
  V_TOMCAT=8.5.23 \
  DIR_CONTENTS=/opt/project/contents \
  STATIC_DOMAIN=http://static.nuti.pe.kr \
  VM_XMS=256m \
  VM_XMX=1024m \
  VM_XX_NEW_SIZE=384m \
  VM_XX_MAX_PERM_SIZE=128m \
  DB_HOST=db.nuti.pe.kr \
  DB_NAME=blog \
  DB_USERNAME=nuti \
  DB_PASSWORD=db_pw

RUN rpm --import /etc/pki/rpm-gpg/RPM* \
  && yum update -y

ADD repos.d/nginx.repo /etc/yum.repos.d/nginx.repo

RUN yum install -y nginx $V_NGINX \
  && rm -f /etc/nginx/conf.d/*.conf \
  && yum install -y wget \
  && rpm -Uvh  http://dl.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm \
  && rpm -Uvh http://rpms.famillecollet.com/enterprise/remi-release-6.rpm

RUN yum install -y supervisor
RUN yum install -y java-1.8.0-openjdk-devel.x86_64
RUN mkdir -p $DIR_CONTENTS

ADD nginx/nginx.conf /etc/nginx/nginx.conf
ADD nginx/copy.conf /etc/nginx/conf.d/copy.conf
ADD supervisor/supervisord.conf /etc/supervisord.conf
ADD scripts/start.sh /scripts/start.sh
ADD tomcat /tomcat
ADD war/ROOT.war /tomcat/webapps/ROOT.war

RUN chmod +x /scripts/start.sh \
 && chmod +x /tomcat/bin/startup-tomcat.sh

EXPOSE 80 8080

VOLUME $DIR_CONTENTS
VOLUME /tomcat/logs

CMD ["/scripts/start.sh"]

docker-compose


1. docker-compose?

Docker Compose는 하나의 설정으로 여러 컨테이너를 정의하고 실행할 수 있도록 도와주는 툴이다. 하나의 명령어로 여러 컨테이너를 띄워서 어플리케이션을 실행할 수 있다.

  • 사용 환경

    • 개발

    • 테스트

    • 스테이징

2. Options

OptionDescription

image

사용할 이미지의 태그나 ID를 명시

build

Docker파일이 있는 디렉토리의 경로

dockerfile

기본 Dockerfile명이 아닐 경우 여기에 이름을 명시

command

기본 command를 지정

links

컨테이너를 다른 서비스와 연결. 연결하려는 서비스의 이름이나 별칭을 작성하면 컨테이너의 /etc/hosts 파일에 그 내용이 추기

external_links

links와 비슷하지만 compose외부의 서비스와 연결을 위해 사용

extra_hosts

/etc/hosts에 외부 호스트 정보를 추가

ports

호스트포트:컨테이너포트 식으로 호스트와 컨테이너의 포트를 포딩한다.

expose

호스트에는 노출하지 않고 연결된 서비스끼리만 접근 가능한 포트를 명

volumes

볼륨으로 마운트할 디렉토리를 명

volumes_from

다른 서비스나 컨테이너의 볼륨 전체를 마운트

environment

환경 변수를 명시

env_file

환경 변수를 파일에서 추

extends

다른 compose 설정을 현재 파일에 가져와 사용

labels

컨테이너docker label을 이용해서 메타데이터를 추가

container_name

컨테이너 이름을 지정

log driver

컨테이너에서 사용할 로깅 드라이버를 명시

net

네트워킹 모드를 지정. docker-net 옵션과 동일

pid

PID 모드를 호스트 PID모드로 설정. 호스트와 컨테이너 운영체제가 같은 PID 주소 공간을 공유

dns

컨테이너가 사용할 커스텀 DNS서버를 설정

dns_search

DNS검색 도메인을 지정

cap_add

컨테이너의 시스템에 관한 권한을 추가

cap_drop

컨테이너의 시스템에 관한 권한을 삭제

devices

디바이스 매핑 목록. docker--devices와 동일

3. Example

version: '3'
services:
  example:
    build:
      context: ./docker
      dockerfile: Dockerfile
    image: example:latest
    environment:
      STATIC_DOMAIN: "http://static.nuti.pe.kr"
      VM_XMS: "256m"
      VM_XMX: "1024m"
      VM_XX_NEW_SIZE: "384m"
      VM_XX_NEW_PERM_SIZE: "128m"
      DB_HOST: "db.nuti.pe.kr"
      DB_NAME: "blog"
      DB_USERNAME: "nuti"
      DB_PASSWORD: "db_pw"
    volumes:
      - ./logs:/tomcat/logs
    ports:
      - "80:80"
    network_mode: "bridge"

Maven


빌드 툴로 많이 알려진 메이븐은 빌드 툴이라기 보다는 프로젝트 관리 툴이라고 지칭해야 한다. 메이븐은 소스코드로부터 배포 가능한 산출물을 빌드할 뿐만 아니라, 의존성 관리, 패키징, 문서화, 자동화 테스트, 각종 리포팅, 배포 등 여러가지 기능을 지원한다. 메이븐에서 지원하는 기능을 사용하기 위해서는 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.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion> (1)

  <groupId>kr.pe.nuti</groupId> (2)
  <artifactId>build-deployment</artifactId> (3)
  <version>1.0.0-SNAPSHOT</version> (4)
  <packaging>jar</packaging> (5)
  <name>build deployment</name> (6)
  <description>build and deployment description.</description> (7)
  <url>https://blog.nuti.pe.kr</url> (8)

  <properties>
    <url.github>https://github.com/hyeonil</url.github>
    ...
  </properties> (9)

  <modules>...</modules> (10)
  <parent>...</parent> (11)
  <reporting>...</reporting> (12)
  <dependencies>...</dependencies> (13)
  <build>...</build> (14)
  <dependencyManagement>...</dependencyManagement> (15)
  <distributionManagement>...</distributionManagement> (16)
  <pluginManagement>...</pluginManagement> (17)
  <repositories>...</repositories> (18)
  <pluginRepositories>...</pluginRepositories> (19)
  <profiles>...</profiles> (20)
</project>
1POM 모델의 버전을 명시한다.
2프로젝트를 소유하는 그룹의 정보를 작성한다. 흔히 대표 도메인을 역순으로 작성한다.
3프로젝트를 식별하는 유일한 아이디를 명시한다. 이 아이디는 프로젝트 소유 그룹내에서 유일해야 된다.
4프로젝트 버전에 대해서 명시한다. 개발이 완료되지 않은 버전일 경우 -SNAPSHOT을 접미사로 사용해서 개발중임을 명시한다.
5프로젝트를 어떻게 패키징할지 명시한다. 기본값으로 jar이고, war, ear, pom을 사용할 수 있다.
6프로젝트의 이름을 작성한다.
7프로젝트에 대해 설명하는 설명문을 작성한다.
8프로젝트의 홈페이지를 작성한다.
9pom.xml내에서 사용할 프로퍼티(변수)에 대해서 명시한다.
10다중 모듈 프로젝트일 경우 하위 프로젝트에 대하여 명시한다.
11다중 모듈 프로젝트일 경우 상위 프로젝트에 대하여 명시한다.
12site lifecycle에서 실행할 plugin정보를 명시한다.
13프로젝트에서 사용할 의존성에 대해서 명시한다.
14프로젝트를 빌드하는 방법에 대하여 명시한다.
15다중 모듈 프로젝트일 경우 상위 프로젝트의 의존성을 하위 프로젝트에서 사용할 수 있도록 명시한다.
16프로젝트를 배포할 원격 서버에 대해서 명시한다.
17다중 모듈 프로젝트일 경우 플러그인 설정을 하위 모듈에서도 사용할 수 있도록 한다.
18프로젝트에서 의존성관계를 가지는 라이브러리를 명시한다.
19플러그인을 받아오는 저장소 정보를 명시한다.
20프로젝트 환경별로 달리 사용할 스크립트에 대해서 작성한다. properties, dependencies, build등을 재정의 할 수 있다.

1. Options

1.1. modules

<modules>
  <module>child-project</module> (1)
</modules>
1하위 모듈 프로젝트의 artifactId를 명시한다.

1.2. parent

<parent>
  <groupId>kr.pe.nuti</groupId> (1)
  <artifactId>parent-project</artifactId> (2)
  <version>1.0.0</version> (3)
</parent>
1상위 프로젝트의 groupId를 명시한다.
2상위 프로젝트의 artifactId를 명시한다.
3상위 프로젝트의 version을 명시한다.

1.3. reporting

<reporting>
  <plugins> (1)
    <plugin>
      <groupId>org.apache.maven.plugins</groupId> (2)
      <artifactId>maven-javadoc-plugin</artifactId> (3)
      <version>2.9.1</version> (4)
      <executions> (5)
        <execution>
          <id>attach-javadocs</id> (6)
          <configuration> (7)

          </configuration>
        </execution>
      </executions>
    </plugin>
  </plugins>
</reporting>
1site lifecycle 실행할 플러그인 목록
2플러그인 groupId
3플러그인 artifactId
4플러그인 version
5플러그인 실행 환경 설정
6플러그인 실행 환경 유일 id
7플러그인 실행 환경 옵션 설정

1.4. dependencies

<dependencies>
  <dependency>
    <groupId>log4j</groupId> (1)
    <artifactId>log4j</artifactId> (2)
    <version>1.2.17</version> (3)
    <exclusions> (4)
      <exclusion>
        <groupId>javax.mail</groupId> (5)
        <artifactId>mail</artifactId> (6)
      </exclusion>
      <exclusion>
        <groupId>javax.jms</groupId>
        <artifactId>jms</artifactId>
      </exclusion>
      <exclusion>
        <groupId>com.sun.jdmk</groupId>
        <artifactId>jmxtools</artifactId>
      </exclusion>
      <exclusion>
        <groupId>com.sun.jmx</groupId>
        <artifactId>jmxri</artifactId>
      </exclusion>
    </exclusions>
    <scope>provided</scope> (7)
  </dependency>
</dependencies>
1의존성 라이브러리의 groupId를 명시한다.
2의존성 라이브러리의 artifactId를 명시한다.
3의존성 라이브러리의 version을 명시한다.
4의존성 라이브러리에 포함되는 의존성 중 제외할 라이브러리를 명시한다.
5의존성 라이브러리에 포함되는 의존성 중 제외할 라이브러리의 groupId를 명시한다.
6의존성 라이브러리에 포함되는 의존성 중 제외할 라이브러리의 artifactId를 명시한다.

1.5. build

<build>
  <finalName>${artifactId}-${project.version}</finalName> (1)
  <resources> (2)
    <resource>
      <directory>src/main/resources/${environment}</directory> (3)
    </resource>
  </resources>
  <testResources> (4)
    <testResource>
      <directory>src/main/webapp</directory> (5)
    </testResource>
  </testResources>
  <plugins> (6)
    <plugin>
      <groupId>org.apache.maven.plugins</groupId> (7)
      <artifactId>maven-compiler-plugin</artifactId> (8)
      <version>3.6.0</version> (9)
      <configuration> (10)
        <source>${version.java}</source>
        <target>${version.java}</target>
        <encoding>UTF-8</encoding>
        <useIncrementalCompilation>false</useIncrementalCompilation>
        <compilerArgument>-Xlint:all</compilerArgument>
        <showWarnings>true</showWarnings>
        <showDeprecation>true</showDeprecation>
      </configuration>
    </plugin>
  </plugins>
</build>
1최종 빌드될 파일의 이름
2리소스 설정
3리소스로 사용할 디렉토리 지정
4테스트 환경 리소스 설정
5테스트 환경 리소스로 사용할 디렉토리 설정
6플러그인 목록 설정. 각각의 플러그인별로 executionconfiguration이 다르니 각 플러그인별로 확인해야 한다.
7플러그인 groupId
8플러그인 artifactId
9플러그인 version
10플러그인 실행 옵션. 각 플러그인별로 다르므로 플러그인 문서에서 확인해야 한다.

1.6. dependencyManagement

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.apache.commons</groupId> (1)
      <artifactId>commons-lang3</artifactId> (2)
      <version>3.4</version> (3)
    </dependency>
  </dependencies>
</dependencyManagement>
1의존성 라이브러리의 groupId
2의존성 라이브러리의 artifactId
3의존성 라이버리리의 version

1.7. distributionManagement

<distributionManagement>
  <repository> (1)
    <id>maven-releases</id> (2)
    <name>release repository</name> (3)
    <url>https://repo.maven/maven-release</url> (4)
  </repository>
  <snapshotRepository> (5)
    <id>maven-snapshots</id> (6)
    <name>snapshot repository</name> (7)
    <url>https://repo.maven/maven-snapshot</url> (8)
  </snapshotRepository>
</distributionManagement>
1Release 버전을 배포할 원격 저장소 정보
2레파지토리 id. settings.xml에 작성한 id와 매핑된다.
3레파지토리의 이름을 명시
4레파지토리의 url을 명시
5Snapshot 버전을 배포할 원격 저장소 정보. 프로젝트 버전에 -SNAPSHOT이라는 접미어를 사용하면 여기에 배포된다.
6레파지토리 id. settings.xml에 작성한 id와 매핑된다.
7레파지토리의 이름을 명시
8레파지토리의 url을 명시

1.8. pluginManagement

<pluginManagement>
  <plugins> (1)
  </plugins>
</pluginmanagement>
1다중 모듈 프로젝트에서 하위 모듈에서도 사용할 플러그인 정보를 설정한다. 설정방법은 plugin설정 방법과 동일하다.

1.9. repositories

<repositories>
  <repository>
    <id>maven-public</id> (1)
    <url>https://repo.maven/maven-public</url> (2)
    <releases> (3)
      <enabled>true</enabled> (4)
    </releases>
    <snapshots> (5)
      <enabled>true</enabled> (6)
      <updatePolicy>always</updatePolicy> (7)
    </snapshots>
  </repository>
  <repository>
    <id>in-project</id> (8)
    <name>custom jars</name>
    <url>file://${project.basedir}/lib</url> (9)
  </repository>
</repositories>
1레파지토리 id. settings.xml에 작성한 id와 매핑된다.
2레파지토리의 url을 명시
3Release 버전에 대한 옵션 설정
4Release 버전을 사용할 지 설정
5Snapshot 버전에 대한 옵션 설정
6Snapshot 버전을 사용할 지 설정
7Snapshot 버전의 업데이트 정책을 사용(같은 버전이 여러개가 있을 수 있으므로 업데이트를 어떻게할 지 지정한다.)
8레파지토리 id
9로컬 레파지토리의 경로 지정

1.10. pluginRepositories

<pluginRepositories>
    <pluginRepository>
        <id>central</id> (1)
        <name>plugin repository</name> (2)
        <url>https://repo.maven/maven-public</url> (3)
        <layout>default</layout> (4)
        <snapshots> (5)
            <enabled>false</enabled> (6)
        </snapshots>
        <releases> (7)
            <updatePolicy>never</updatePolicy> (8)
        </releases>
    </pluginRepository>
</pluginRepositories>
1plugin repository의 id 지정
2plugin repository의 이름 지정
3plugin repository의 url 지정
4저장소가 artifact를 찾고 저장하기 위해 사용하는 디렉토리 레이아웃. legacydefault를 사용 가능
5Snapshot버전에 대한 설정
6Snapshot버전을 사용할 지 지정
7Release버전에 대한 설정
8Release버전의 업데이트 정책 설정

1.11. profiles

<profiles>
  <profile> (1)
    <id>development</id> (2)
    <properties> (3)
      <environment>development</environment>
    </properties>
  </profile>
  <profile>
    <id>production</id>
    <properties>
      <environment>production</environment>
    </properties>
  </profile>
</profiles>
1빌드 옵션에 따라 별도로 사용할 설정 지정
2profile id. 메이븐 빌드 시 -P옵션을 사용해 지정한다.
3해당 profile에서 사용할 설정 지정 properties뿐만 아니라 build, dependencies등 다른 설정들도 재정의 할 수 있다.

2. settings.xml

각각의 사용자별로 별도의 설정을 지정하고자 할 때 사용한다. 경로는 ~/.m2/settings.xml에 위치하게 된다.

<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
                      http://maven.apache.org/xsd/settings-1.0.0.xsd"> (1)
  <servers> (2)
      <server>
          <id>maven-releases</id> (3)
          <username>username</username> (4)
          <password>password</password> (5)
      </server>
      <server>
          <id>maven-snapshots</id>
          <username>username</username>
          <password>password</password>
      </server>
      <server>
          <id>maven-public</id>
          <username>username</username>
          <password>password</password>
      </server>
  </servers>
</settings>
1settings.xml의 root
2maven repository server의 정보를 작성한다.
3서버의 id를 명시한다. 이 정보는 pom.xmlrepository의 id와 매핑된다.
4서버가 private repository일 경우 사용자 이름을 작성한다.
5서버가 private repository일 경우 사용자 비밀번호를 작성한다.

3. Lifecycle

maven lifecycle
Figure 1. Maven Lifecycle

Jenkins 사용 설명(번역)


1. Introduction

본 문서는 Continuous Integration(지속적 통합) & Continuous Deliver(지속적 전달)툴인 Jenkins에 관하여 설명하는 목적으로 작성한다.
본 문서는 공식 가이드 문서와 사용자 경험을 바탕으로 작성하고, 초안은 2.46.2 버전을 기준으로 작성한다.

1.1. Continuous Integration

In software engineering, continuous integration (CI) is the practice of merging all developer working copies to a shared mainline several times a day. Grady Booch first named and proposed CI in his 1991 method, although he did not advocate integrating several times a day. Extreme programming (XP) adopted the concept of CI and did advocate integrating more than once per day - perhaps as many as tens of times per day.

지속적 통합(이하 CI)이란 소프트웨어를 개발하는 과정에서 개발자들이 개발한 소스코드 내용을 정기적으로 통합을 하는 것을 의미한다. 소스코드를 지속적으로 통합하고 빌드함으로써 최종 통합 과정에서 발생하는 위험 줄이고, 지속적으로 퀄리티 컨트롤을 적용하는 프로세스를 실행하여 제품의 품질을 높이는 것이 목적이다. 또한, CI는 소프트웨어 개발 과정에서 반복적으로 하는 수동 작업을 자동화 함으로써, 빌드에 소요되는 시간을 줄이는 것을 목적으로 한다.

CI의 이점
  • 생산성 향상

  • 테스트 자동화에 따른 빠른 버그 검출 및 해결

  • 정적 코드 분석에 의한 소스코드 품질 향상

  • 빠른 업데이트 제공

1.2. Continuous Delivery

CI가 지속적으로 통합하고, 빌드하는 것을 의미한다면, 지속적 전달(이하 CD)이란 코드 변경에 따라 지속적으로 배포하는 것을 의미하는 것이다. 지속적으로 배포하는 대상은 테스트 환경이 될 수도 있고, 프로덕션 환경이 될 수도 있다. CD를 Continuous Deployment(지속적 배포)라고 말하는 이들도 있는데, 지속적 전달과 지속적 배포의 차이는 프로덕션 환경에 배포하는 과정이 수동 배포(지속적 전달)인지, 자동 배포(지속적 배포)인지에 대한 차이가 있다.

CD의 이점
  • 생산성 향상

  • 테스트 환경 자동 배포에 따른 빠른 버그 검출 및 해결

  • 빠른 업데이트 제공

  • 소프트웨어 릴리즈 자동화

1.3. Jenkins

Jenkins는 Sun Microsystem에서 Hudson이라는 이름으로 출시된 프로젝트이지만, Sun Microsystem이 Oracle에 인수되면서 Oracle과의 상표권문제로 인해서 이름을 변경하며 Jenkins로 분리되어 나왔다. Jenkins는 Java기반의 오픈소스로 CI와 CD를 가능하게 하는 툴이다. 초기에는 CI툴로 자리를 잡았으나, Blue Ocean이라는 서브 프로젝트를 구축하면서, CD툴로도 자리를 잡아가고 있다.

Jenkins가 제공하는 기능
  • 웹 인터페이스를 통한 간편한 설정

  • 강력하고 편리한 Reporting 기능

  • 지속적인 자동화 빌드

  • 지속적인 자동화 테스트

  • 커버리지 검사

  • 코드 품질 검사

  • 다양한 인증기반과 결합한 인증 및 권한 관리 기능

  • Groovy script를 이용한 고수준의 Job Scheduling 기능

  • 커맨드라인 인터페이스 제공

  • 자동화된 배포 관리

  • 분산빌드 기능

  • 윈도우 커맨드 스케쥴링 실행기능

다양한 테스트 및 빌드 환경을 만들어 놓으면 Jenkins가 작업을 처리해 주기 때문에 Jenkins를 집사라고 표현하기도 한다. 각종 환경을 만드는 것은 사람이 직접해야하고, Jenkins를 효율적으로 활용하기 위해서는 다음과 같은 작업이 함께 이루어져야 한다.

빌드 자동화

빌드 자동화 스크립트를 만들어 놓음으로써 Jenkins에게 빌드를 명령하여 지속적으로 빌드할 수 있도록 한다.

테스트 자동화

단위 테스트, UI 테스트 등 테스트를 자동화해놓음으로써 개인이 미처 발견하지 못한 오류를 검출하고 개인에게 알림이 갈 수 있도록 한다.

코드 표준 준수 여부 검사

정적 코드 분석을 통하여 코드 품질을 높일 수 있도록 한다.

Build Pipeline 구성

2개 이상의 모듈로 구성되는 프로젝트의 경우 각 모듈의 참조관계에 따라서 순차적으로 빌드할 수 있도록 한다.

2. Installing Jenkin

본 장에서는 Jenkins의 기본 설치 방법 및 초기 설정방법에 대하여 기술한다.

2.1. Pre-install

2.1.1. System Requirements

최소 요구사항:

  • Java 7

  • 256MB free memory

  • 1GB+ free disk space

소규모 팀 기준 권장 요구사항:

  • Java 8

  • 1GB+ free memory

  • 50GB+ free disk space

2.2. Installation

2.2.1. Unix/Linux

Ubuntu와 같은 Debian 계열에서는 apt를 통하여 설치할 수 있다. 기본 저장소에서 가장 최근의 버전을 설치할 수 있고, LTS버전을 설치하려면 별도의 저장소 설정을 해주어야 한다.

wget -q -O - https://pkg.jenkins.io/debian/jenkins.io.key | sudo apt-key add -
sudo sh -c 'echo deb http://pkg.jenkins.io/debian-stable binary/ > /etc/apt/sources.list.d/jenkins.list'
sudo apt-get update
sudo apt-get install jenkins
/etc/default/jenkins 파일을 통해 기본 설정을 수정할 수 있고, 포트 중복 방지를 위해서 기본 포트를 바꿔주는 것을 권장한다.

2.2.2. OS X

  • jenkins 공식 홈페이지에서 패키지를 다운받는다.

  • 패키지를 실행한다.

brew를 통해서도 설치를 할 수 있다.

  • 최신 버전 설치

brew install jenkins
  • LTS 버전 설치

brew install jenkins-lts

2.2.3. Windows

  • jenkins 공식 홈페이지에서 패키지를 다운받는다.

  • 패키지를 실행한다.

2.2.4. Docker

  • Docker 저장소에서 jenkins 이미지를 pull 한다.

docker pull jenkins
  • Docker 컨테이너를 실행한다.

docker run -d -p 9000:8080 -v $PWD/jenkins:/var/jenkins_home -t jenkins
  • 위와 같이 실행할 시 로컬호스트의 9000포트가 컨테이너의 8080포트와 매핑이 된다.

  • -d 옵션은 데몬 실행 옵션이니, 데몬으로 실행하기를 원하지 않을 경우 제외하면 된다.

2.2.5. Other

war로 패키지된 파일을 다운받아서 Tomcat이나 Jetty와 같은 컨테이너로 실행할 수 있다.

2.3. Post-installation(Setup Wizard)

2.3.1. Create Admin User and Password for Jenkins

최초로 Jenkins를 실행하고 브라우저를 통해 페이지 진입 시, 관리자 인증을 요구한다. 이 때 필요한 토큰은 Jenkins의 로그에 남아있으니, 확인후 입력해주면 된다.

*************************************************************

Jenkins initial setup is required. A security token is required to proceed.
Please use the following security token to proceed to installation:

41d2b60b0e4cb5bf2025d33b21cb

*************************************************************
administrator password
Figure 1. 관리자 비밀번호

2.3.2. Initial Plugin Installation

관리자 인증을 마치면 초기 Plugin 설치를 물어보는 페이지가 나타나는데, 처음 사용한다면 추천 플러그인을 설치할 것을 권장한다.

initial plugin installation
Figure 2. 초기 플러그인 설치
install suggested plugins
Figure 3. 추천 플러그인 설치

3. System Configuration

본 장에서는 Jenkins 시스템 설정하는 방법에 대하여 기술한다.

Jenkins를 설치하면 동시 실행 가능 executor의 수, 메일 서버, VCS 등 여러가지 정보를 설정할 수 있고, 플러그인 설치에 따라 더욱 다양한 정보를 설정할 수 있다. 각자 확인 후 필요한 정보들을 입력해주면 된다.

4. Managing Security

본 장에서는 Jenkins 보안 설정하는 방법에 대하여 기술한다.

Jenkins는 웹 환경에서 동작하기 때문에 보안과 관련된 다양한 설정을 제공한다. Jenkins를 안전하게 사용하기 위해서 보안 설정을 필수로 해줄 것을 권장한다.

4.1. Enabling Security

Enable Security설정에 따라 보안 사용 여부를 지정할 수 있다. 보안 설정은 반드시 활성화 할 것을 권장한다.

enable security
Figure 4. 보안 설정

4.1.1. JNLP TCP Port

Jenkins는 JNLP 프로토콜을 통해 Agent를 실행할 수 있도록 TCP 포트를 사용한다.

JNLP 기반 Agent를 위해 다음과 같은 두 가지 옵션을 제공한다.

Random

JNLP 포트를 랜덤으로 선택한다.

Fixed

JNLP 포트를 관리자가 임의로 지정한다.

4.1.2. Access Control

Access Control은 Jenkins의 주된 보안 메카니즘이다. Access Control설정을 위해 주된 두가지 설이 필요하다.

  1. Security Realm은 사용자 정보를 가져을 방법과 위치를 알려주는 보안영역이다. 일반적으로 "인증"이라고 한다.

  2. Authorization은 사용자 혹은 그룹이 어던 측면까지 액세스할 수 있는지 설정하는 영역이다. 일반적으로 "권한"이라고 한다.

Security Realm과 Authorization 설정에 따라 완화된 혹은 엄격한 인증 및 권한 부여를 구성할 수 있다.

또한, Role-based Authorization Strategy와 같은 플러그인을 통해 Access Control의 기능을 확장할 수 있다.

Security Realm

Jenkins는 기본적으로 몇가지 Security Realm을 제공한다.

Delegate to servlet container

인증 절차를 Jenkins를 실행하고있는 Servlet Container에 위임한다. 이 옵션을 사용한다면, Servlet Container의 인증 문서를 확인하길 바란다.

Jenkins’ own user database

인증 절차를 다른 외부 시스템에 위임하지 않고 Jenkins 소유의 사용자 데이터베이스를 사용한다. 이 설정은 2.0부터 지원을 하고, 소규모 환경에 적합하다.

LDAP

모든 인증 과정을 LDAP 서버에 위임한다. 이 옵션은 이미 LDAP 환경이 구축된 대규모 조직에 적합하다.

LDAP 옵션이 제공되지 않는다면, plugin:ldap[LDAP plugin] 플러그인을 설치해서 사용할 수 있다.
Unix user/group database

인증 절차를 Unix 운영체제에 위임한다. 이 옵션은 Unix 그룹과 사용자를 재사용할 수 있다.

다음과 같은 플러그인을 통해 추가적인 Security Realm을 사용할 수 있다
  • plugin:active-directory[Active Directory]

  • plugin:github-oauth[GitHub Authentication]

  • plugin:crowd2[Atlassian Crowd 2]

Authorization

Security Realm은 누가 Jenkins에 액세스 가능한지를 정한다. 그와 다르게 Authorization은 누가 무엇을 액세스할 수 있는지를 정한다. Jenkins는 기본적으로 몇가지 Authorization을 제공한다.

Anyone can do anything

모든 사용자가 Jenkins의 모든 기능을 활용할 수 있다. 로컬 테스트 용도가 아니라면 이 설정을 사용하지 않는 것을 권장한다.

Legacy mode

사용자가 "admin"일 경우 시스템 전체의 제어 권한을 갖고, 익명 사용자를 포함한 기타 사용자는 읽기 권한만 갖는다. 로컬 테스트 용도가 아니라면 이 설정을 사용하지 않는 것을 권장한다.

Logged in users can do anything

인증된 사용자가 시스템 전체 제어 권한을 갖는다. 추가 옵션으로 익명 사용자에게 읽기 권한을 부여할 지 여부를 정할 수 있다. 인증 과정을 강제로 지정할 때 유용하다.

Matrix-based security

특정 사용자에게 특정 제어 권한을 부여할 수 있다.

Project-based Matrix Authorization Strategy

Matrix-based security의 확장으로 각각의 프로젝트에서 특정 사용자에게 특정 제어 권한을 부여한다.

4.1.3. Markup Formatter

Jenkins는 사용자 입력으로 HTML이나 Javascript를 입력하는 것을 허용한다. 기본 설정으로는 <&와 같은 안전하지 않은 문자를 이스케이프처리하는 Plain Text 설정을 사용한다. Safe HTML 설정을 사용하면 사용자와 관리자가 프로젝트 설명과 같은 곳에 HTML을 사용할 수 있다.

4.2. Cross Site Request Forgery

CSRF 보호 설정 사용 여부를 지정할 수 있다.

4.3. Agent/Master Access Control

개념적으로, Jenkins master와 agent들은 여러 개별 프로세스와 시스템에서 실행되는 결합 시스템으로 생각할 수 있다. 이를통해 agent가 master 프로세스에 파일의 내용과 같은 사용 가능한 정보를 요청할 수 있다.

For larger or mature Jenkins environments where a Jenkins administrator might enable agents provided by other teams or organizations, a flat agent/master trust model is insufficient. The Agent/Master Access Control system was introduced [2: Starting with 1.587, and 1.580.1, releases] to allow Jenkins administrators to add more granular access control definitions between the Jenkins master and the connected agents. As of Jenkins 2.0, this subsystem has been turned on by default.

4.3.1. Customizing Access

For advanced users who may wish to allow certain access patterns from the agents to the Jenkins master, Jenkins allows administrators to create specific exemptions from the built-in access control rules. By following the link highlighted above, an administrator may edit Commands and File Access Agent/Master access control rules.

Commands

"Commands" in Jenkins and its plugins are identified by their fully-qualified class names. The majority of these commands are intended to be executed on agents by a request of a master, but some of them are intended to be executed on a master by a request of an agent. Plugins not yet updated for this subsystem may not classify which category each command falls into, such that when an agent requests that the master execute a command which is not explicitly allowed, Jenkins will err on the side of caution and refuse to execute the command. In such cases, Jenkins administrators may "whitelist" [3: en.wikipedia.org/wiki/Whitelist] certain commands as acceptable for execution on the master. 20

Advanced

Administrators may also whitelist classes by creating files with the .conf extension in the directory JENKINS_HOME/secrets/whitelisted-callables.d/. The contents of these .conf files should list command names on separate lines. The contents of all the .conf files in the directory will be read by Jenkins and combined to create a default.conf file in the directory which lists all known safe command. The default.conf file will be re-written each time Jenkins boots. Jenkins also manages a file named gui.conf, in the whitelisted-callables.d directory, where commands added via the web UI are written. In order to disable the ability of administrators to change whitelisted commands from the web UI, place an empty gui.conf file in the directory and change its permissions such that is not writeable by the operating system user Jenkins run as.

File Access Rules

The File Access Rules are used to validate file access requests made from agents to the master. Each File Access Rule is a triplet which must contain each of the following elements: 1. allow / deny: if the following two parameters match the current request being considered, an allow entry would allow the request to be carried out and a deny entry would deny the request to be rejected, regardless of what later rules might say. 2. operation: Type of the operation requested. The following 6 values exist. The operations can also be combined by comma-separating the values. The value of all indicates all the listed operations are allowed or denied. ◦ read: read file content or list directory entries ◦ write: write file content ◦ mkdirs: create a new directory ◦ create: create a file in an existing directory ◦ delete: delete a file or directory

◦ stat: read metadata of a file/directory, such as timestamp, length, file access modes. 3. file path: regular expression that specifies file paths that matches this rule. In addition to the base regexp syntax, it supports the following tokens: ◦ <JENKINS_HOME> can be used as a prefix to match the master’s JENKINS_HOME directory. ◦ <BUILDDIR> can be used as a prefix to match the build record directory, such as /var/lib/jenkins/job/foo/builds/2014-10-17_12-34-56. ◦ <BUILDID> matches the timestamp-formatted build IDs, like 2014-10-17_12-34-56. The rules are ordered, and applied in that order. The earliest match wins. For example, the following rules allow access to all files in JENKINS_HOME except the secrets folders: # To avoid hassle of escaping every '\' on Windows, you can use / even on Windows. deny all <JENKINS_HOME>/secrets/.* allow all <JENKINS_HOME>/.* Ordering is very important! The following rules are incorrectly written because the 2nd rule will never match, and allow all agents to access all files and folders under JENKINS_HOME: allow all <JENKINS_HOME>/.* deny all <JENKINS_HOME>/secrets/.*

Advanced

Administrators may also add File Access Rules by creating files with the .conf. extension in the directory JENKINS_HOME/secrets/filepath-filters.d/. Jenkins itself generates the 30-default.conf file on boot in this directory which contains defaults considered the best balance between compatibility and security by the Jenkins project. In order to disable these built-in defaults, replace 30- default.conf with an empty file which is not writable by the operating system user Jenkins run as. On each boot, Jenkins will read all .conf files in the filepath-filters.d directory in alphabetical order, therefore it is good practice to name files in a manner which indicates their load order. Jenkins also manages 50-gui.conf, in the filepath-filters/ directory, where File Access Rules added via the web UI are written. In order to disable the ability of administrators to change the File Access Rules from the web UI, place an empty 50-gui.conf file in the directory and change its permissions such that is not writeable by the operating system user Jenkins run as.

4.3.2. Disabling

While it is not recommended, if all agents in a Jenkins environment can be considered "trusted" to the same degree that the master is trusted, the Agent/Master Access Control feature may be disabled. Additionally, all the users in the Jenkins environment should have the same level of access to all configured projects. 22 An administrator can disable Agent/Master Access Control in the web UI by un-checking the box on the Configure Global Security page. Alternatively an administrator may create a file in JENKINS_HOME/secrets named slave-to-master-security-kill-switch with the contents of true and restart Jenkins. Most Jenkins environments grow over time requiring their trust models to CAUTION evolve as the environment grows. Please consider scheduling regular "check- ups" to review whether any disabled security settings should be re-enabled.


Pagination