티스토리 뷰

이번 시간에는 CI(continous Integration) 환경을 구축해보겠습니다. 먼저 CI 파이프라인의 워크플로우부터 살펴보겠습니다.

 

CI 파이프라인 워크플로우

  1. 개발자가 GitHub 레포지토리에 Pull Request를 올립니다.
  2. GitHub는 Pull Request 생성 이벤트가 발생하게되면 Jenkins에 web-hook을 전송합니다.
  3. Jenkins가 프로젝트의 빌드 스크립트를 읽어들여 빌드를 진행합니다.
    • 테스트코드를 실행 결과가 실패일 경우, 빌드 fail을 일으킵니다.
    • 테스트 커버리지를 분석하여 80% 미만인 경우, 빌드 fail을 일으킵니다.
  4. Jenkins가 빌드 결과를 GitHub에 전송합니다.
  5. 빌드 결과에 따라 GitHub의 merge 버튼이 활성화/비활성화 됩니다.
그림1. CI/CD 파이프라인 구조. 빨간색 영역은 CI에 해당하는 영역

 

Jenkins 서버가 동작시킬 프로젝트 빌드 스크립트부터 작성해보겠습니다.

 

 

 

Gradle 스크립트 작성

 

스프링 부트 플러그인 및 기타 라이브러리 추가

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.1.0'
    id 'io.spring.dependency-management' version '1.1.0'
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-jdbc'
    implementation 'com.h2database:h2:2.1.214'
    compileOnly 'org.projectlombok:lombok:1.18.24'
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
}

test {
    useJUnitPlatform()
}

Java, Spring Boot 기반의 프로젝트를 만들 것이므로 관련 플러그인, 라이브러리를 build.gradle에 적어줍니다.

 

 

테스트 커비리지가 80% 이하일 경우, 테스트가 실패하게 하기

 

Jacoco는 Java 코드의 커버리지를 체크하는 라이브러리입니다.

Jacoco 플러그인을 사용하여 테스트 커비리지가 80% 이하일 경우, 테스트에 실패하도록 build.gradle를 작성하겠습니다.

본 글에 사용된 build.gradle은 Groovy DSL로 작성하였습니다.

전체 코드는 CI/CD example/build.gradle에서 확인할 수 있습니다.

 

1. jacoco 플러그인 추가

plugins {
    id 'java'
    id 'jacoco' // Jacoco 플러그인 추가
    id 'org.springframework.boot' version '3.1.0'
    id 'io.spring.dependency-management' version '1.1.0'
}

// Jacoco 플러그인 버전 명시
jacoco {
    toolVersion '0.8.8'
}

plugins {} 블록에 jacoco 플러그인을 추가하고, jacoco {} 블록에 버전을 명시합니다.

 

2. 테스트 커버리지 설정

jacocoTestCoverageVerification {
    violationRules {
        rule {
            element 'CLASS'
            limit {
                counter = 'BRANCH'
                value = 'COVEREDRATIO'
                minimum = 0.8 // 테스트 커버리지 최소 80%
            }
        }
    }
}

Jacoco 플러그인에는 jacocoTestReport 와 jacocoTestCoverageVerification task가 있습니다. jacocoTestCoverageVerification는 원하는 커버리지 기준을 만족하는지 확인해주는 task입니다.

위 예시처럼 minimum 값을 0.8로 설정하면, 커비리지를 최소한 80% 이상으로 유지할 수 있게 해줍니다.

 

3. 테스트에서 제외하기

jacocoTestCoverageVerification {
    violationRules {
        rule {
            element 'CLASS'
            limit {
                counter = 'BRANCH'
                value = 'COVEREDRATIO'
                minimum = 0.8
            }
            
            // 테스트 커버리지 체크 대상에서 제외
            excludes = ['*.*Application', '*.*Exception', '*.dto.*']
        }
    }
}

이름이 Application, Exception, dto로 끝나는 클래스는 비즈니스 로직과 관련이 없으니 커비리지 체크 대상에서 제외하도록 하겠습니다. excludes = [] 블록 안에 제외 대상의 클래스를 포함시켜줍니다. 여기서 조심할 점은 패키지 + 클래스명을 적어줘야 합니다.

 

4. task 의존성 설정하기

test {
    useJUnitPlatform()
    finalizedBy jacocoTestCoverageVerification
}

 test {} 블록의 finalizedBy 키워드는 test task가 종료된 후에 실행할 task를 정할 수 있게 해줍니다. test task가 끝나고  jacocoTestCoverageVerification task가 실행될 수 있도록 해주겠습니다.

useJUnitPlatform()은 Gradle에게 JUnit을 사용한다고 알려주는 것입니다. 이 설정을 빼먹으면 jacocoTestCoverageVerification task가 스킵되므로 꼭 적어줍시다.

 

최종 코드

plugins {
    id 'java'
    id 'jacoco'
    id 'org.springframework.boot' version '3.1.0'
    id 'io.spring.dependency-management' version '1.1.0'
}

group 'edu.flab'
version '1.0-SNAPSHOT'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-jdbc'
    implementation 'com.h2database:h2:2.1.214'
    compileOnly 'org.projectlombok:lombok:1.18.24'
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
}

jacoco {
    toolVersion '0.8.8'
}

jacocoTestCoverageVerification {
    violationRules {
        rule {
            element 'CLASS'
            limit {
                counter = 'BRANCH'
                value = 'COVEREDRATIO'
                minimum = 0.8
            }
            excludes = ['*.*Application', '*.*Exception', '*.dto.*']
        }
    }
}

test {
    useJUnitPlatform()
    finalizedBy jacocoTestCoverageVerification
}

 

 

 

네이버클라우드 CI/CD(Jenkins) 서버 구축

가장 먼저 CI/CD 자동화 작업을 해줄 서버를 생성해야합니다.

 

1. 네이버클라우드 홈페이지 접속 → 메뉴 상단 [서비스] - [Compute] - [Server]

 

2. 서버 생성 버튼 클릭

 

3. Jenkins 서버 생성 요청

 

4. 서버 리소스 설정

Jenkins는 JVM 애플리케이션입니다. 사양에 따라 성능이 달라질 수 있습니다.

저는 개인 프로젝트에 붑일 CI/CD 서버를 구축하고 있기 때문에 고사양을 필요로 하지 않으며, 비용을 최대한 아끼기 위해 최소한의 사양을 선택하였습니다.

 

5. 서버 인증키 생성

서버 인증키를 생성합니다.

나중에 서버의 최초 비밀번호를 얻을 때 사용하므로 별도로 보관하셔야 합니다.

 

6. 네트워크 접근 설정

ACG란 네트워크 접근에 대한 규칙을 정의하는 객체입니다.

네이버 클라우드에서는 기본으로 제공하는 ACG를 제공하고 있으며, 사용자가 언제든지 커스텀할 수 있습니다.

학습 목적이므로 ACG를 복잡하게 구성하지 않을 것입니다.

기본으로 제공하는 ACG를 선택해 줍니다.

 

7. 최종 확인 및 생성

 

8. 공인 IP 할당

좌측 메뉴 Server → Public IP → 공인 IP 신청

네이버 클라우드 플랫폼 외의 네트워크에서 접근할 수 있도록 공인 IP를 발급받아줍니다.

추후 GitHub 에서 해당 IP로 web-hook을 전송할 때 사용됩니다.

 

"적용 서버 선택"란에 1~7번 과정을 통해 생성한 서버를 선택해줍니다.

 

9. ACG 편집

좌측 메뉴 Server → ACG

 

네이버 클라우드가 제공하는 Jenkins 서버는 18080 포트를 통해 접속할 수 있습니다. 하지만 기본으로 제공하는 ACG에는 18080을 허용하고 있지 않습니다. CI/CD 서버가 18080 포트를 오픈할 수 있도록 이를 설정해줍니다.

 

1. ncloud-default-acg 선택

2. ACG 설정 버튼 클릭

  • 9-3. ACG 규칙 추가
  • 프로토콜: TCP
  • 접근 소스: 0.0.0.0/0 (전체)
  • 허용 포트: 18080

 

 

 

Jenkins 시작

Jenkins 서버를 생성하고 처음 Jenkins 웹 화면세 접속하면 초기 설정을 진행하게 됩니다. Jenkins 초기 설정은 네이버 클라우드 Jenkins 시작 가이드에서 자세히 설명하고 있습니다.

 

 

Jenkins ↔︎ GitHub Webhook 설정

 

지금까지의 과정을 통해 Jenkins 서버를 생성하였습니다. 이제 GitHub 에서 Pull Request 이벤트가 발생할 때 마다 Jenkins 서버에서 프로젝트 코드를 받아오고, gradle.build 스크립트가 자동 실행될 수 있도록 해보겠습니다.

 

1. GitHub Token 발급

GitHub 홈페이지 프로필 사진 → Settings → Developer Settings → Personal access tokens → Tokens(classic) 순서로 클릭하여 GitHub Personal Access Tokens(Classic) 페이지로 이동합니다. Generate new token 버튼을 눌러 Classic Token 생성을 요청합니다. 발급된 토큰은 미리 저장해둡니다. 미리 저장해두지 않으면 일정 시간이 흐른 뒤 확인이 불가능합니다.

 

2. Jenkins - Pull Request Builder 플러그인 설치

Jenkins 웹 페이지에 접속합니다. 대쉬보드 페이지에서 Jenkins 관리 → Plugins → Available plugins 를 순서대로 클릭하여 플러그인 설치를 위한 페이지로 이동합니다. GitHub Pull Request Builder를 검색하여 설치합니다. 설치가 완료되면 Jenkins 서버를 재시작해줍니다.

 

3. Jenkins Item 생성

"새로운 Item" 버튼을 클릭하고 Item 이름을 입력한 뒤, Freestyle project를 선택해줍니다.

 

3-1. General 카테고리에서 GitHub project 체크박스를 선택하고, 프로젝트 URL을 입력합니다.

 

3-2. 소스 코드 관리를  None → Git 으로 변경해주고, 세부 내용을 입력해줍니다.

 

3-3. Credentials 발급

만약 Credentials란에 채워넣을 게 없다면 Add 버튼을 눌러 Jenkins Credentials Provider 팝업창을 띄워줍니다. Secrets 칸에는 GitHub에서 발급받은 토큰의 값을 입력해줍니다. 이렇게 등록된 토큰은 언제든지 다시 재사용할 수 있게되는데, ID 값을 통해 Jenkins 시스템 내에서 구분됩니다.

 

3-4. 빌드 유발 작성

아래 그림을 참고하여 빌드 유발 내용을 작성합니다.

 

 

3-5. 빌드 스텝 정의

Gradle Build를 이용하여 빌드하도록 설정합니다.

 

4. Webhooks 확인하기

GitHub Repository → Settings → Webhooks 페이지로 이동하면 해당 레포지토리와 연동된 webhooks 목록을 확인할 수 있습니다.

 

Pull Request 생성 및 Jenkins 빌드 테스트하기

프로젝트에 참여하는 개발자가 되어 기능을 구현한 뒤 Pull Request 를 올리는 상황을 가정해보겠습니다.

여러분이 구현한 코드는 JavaFoo 클래스입니다.

package org.example;

public class JavaFoo {
    public String hello(String name) {
        return switch (name) {
            case "펭" -> "하";
            case "hello" -> "world";
            default -> "none";
        };
    }

    public void callMe() {
        System.out.println("Please, call me");
    }
}

 

해당 클래스의 테스트코드도 작성해줍니다.

package org.example;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

class JavaFooTest {
    private final JavaFoo javaFoo = new JavaFoo();

    @Test
    public void partiallyCoveredMethodTest() {
        String actual = javaFoo.hello("펭");
        assertEquals(actual, "하");
    }
}

 

 

GitHub에서 Pull Request를 생성합니다.

 

잠깐 기다리면 Jenkins 서버에서 빌드가 수행되고 빌드 결과가 표시됩니다. 아래 그림에서는 빌드가 실패했습니다. Details를 눌러 빌드 상세 내용을 살펴보겠습니다.

 

Console Output 버튼을 누르면 빌드 과정에서 출력된 로그를 확인할 수 있습니다. 테스트 커버리지가 80% 이상 넘기지 못해 빌드가 실패했음을 확인할 수 있습니다.

 

 

 

참고 자료

 

다른 글을 보고 싶다면

댓글