이번 시간에는 지난 글에 이어 CI/CD를 Jenkins 를 이용해서 구축해 보도록 하겠다.
https://tech-monster.tistory.com/336
[인프라] CI/CD 구축하기 1탄 : 기존의 미니PC에 젠킨스 세팅하기 (feat. CI/CD 의 의미와 젠킨스)
이번 글에서는 CI/CD에 대해서 이해가 좀 부족한 것 같아서 젠킨슨을 활용해 A-Z까지 정리할 것이다. 아마 1탄, 2탄으로 구성할 것이니 확인 바란다. 사실 이전의 흐름과 미니PC 설정은 이전 글에서
tech-monster.tistory.com
마지막에 말했듯이 사실 AI한테 흐름과 학습 과정을 알려달라 하고 나는 실행하고 확인하는게 전부이긴 한다.
하지만 이걸 매번 물어보며 안다고 착각하는것과 실습을 통해 이해도를 높이고 실제로 경험하는건 하늘과 땅 차이 즉 천지차이! 이니 그렇게 생각하지말자! (노노노노노 노노노~)

목표
1. Jenkins에 실제 내 private 저장소를 연결해 CI가 작동하는지 확인한다.
2. 학습의 의의를 생각해본다.
0. 시작하기 전에
필자가 mini PC의 잘못된 jenkins URL을 접속했더니 다음처럼 집사가 화가 잔뜩났다.

미... 미안해 ㅠㅠ 화내지마
1. 실습코드 세팅 : Spring Boot + Spring Web + Spring Actuator
현재의 목표는 코드를 올려서 dev, prod 에 테스트하고 배포까지 다 되도록 함에 있다.
restful 한 통신을 위해서 Spring Web의 의존성을, 운영 상태를 모니터링하고 관리할 수 있게 Spring Actuator를 추가했다.
build.gradle은 다음과 같다.
build.gradle
plugins {
id 'java'
id 'org.springframework.boot' version '4.1.1'
id 'io.spring.dependency-management' version '1.1.7'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
description = 'cicd-lab'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter-webmvc'
testImplementation 'org.springframework.boot:spring-boot-starter-actuator-test'
testImplementation 'org.springframework.boot:spring-boot-starter-webmvc-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
tasks.named('test') {
useJUnitPlatform()
}
그리고 알아서 코드는 Claude 한테 테스트용으로 작성해달라고 했다.
이걸 뭐... 일일히 다 복붙하기에는 번거롭고 내용도 너무 없어서 그냥 패스!

다음을 실행하자.
cd C:\dev\cicd-lab
.\gradlew build
gradle wrapper를 활용해 terminal 환경에서 build를 했다.


하다가 재미있는 점이 있는데 Actuator 의존성 추가시 이를 위한 별도의 port 를 설정할 수 있다고 한다. (굳이 왜?)

다음에는 이런 말이 있다.
Customizing the Management Endpoint Paths
Sometimes, it is useful to customize the prefix for the management endpoints. For example, your application might already use /actuator for another purpose. You can use the management.endpoints.web.base-path property to change the prefix for your management endpoint, as the following example shows:
출처: https://docs.spring.io/spring-boot/reference/actuator/monitoring.html#actuator.monitoring.customizing-management-server-port
다른 목적으로 path를 다르게 설정하고 사용하기 위해서라고 하니 port 도 대놓고 사용자들한테 공개하면 안되니
어떤 health check용 포트는 다르게 설정하게 도와주나보다. (반박시 님 말 맞음. 알려주셈 ㅠ)
하여간 빌드해보고 실제 실행해보고 봤는데 문제가 없었다.
개인적으로 생성한 git 저장소에 이것을 올렸다.

그럼 어떻게 해야 Jenkins가 코드의 변경을 인식하고 CI/CD를 해주는가?
2. Jenkins와 github 연결하기 1 : github에서 token 발급받기
다시 젠킨스 화면으로 돌아와서 Credentials를 클릭한다.

그리고 키를 등록하자.
현재 나의 환경에서는 Jenkins이 어디 Github을 봐야하는지 연결되어 있지 않으니 이를 먼저 진행하려고 한다.



여기서 비밀번호는 Github에서 생성한 token 을 넣으면 된다고 한다.

잘 보면 Settings > Developer Settings 에서 키를 만들면 되는데, 필자는 private 프로젝트만 읽을 수 있는 token을 90일 만료 기준으로 생성했다.
여기서 permission을 필자는 빼먹었다. 빼먹지 않고 access 권한도 추가해줘야 한다.

사실 필자는 이를 제외했다가 403에러가 발생했다. 이를 빼먹었기 때문이다.

3. Jenkins와 github 연결하기 2 : Jenkins에 등록 + Jenkinsfile추가
Spring Boot에 다음 Jenkinsfile을 추가하겠다.
Jenkinsfile
// 파이프라인 전체를 감싸는 블록. Declarative(선언형) 문법의 시작
pipeline {
// 이 파이프라인을 어디서 실행할지.
// any = 사용 가능한 아무 곳. 지금은 Jenkins 컨테이너 자신 하나뿐이라 거기서 실행된다
agent any
// 파이프라인을 자동으로 시작시키는 조건
triggers {
// Poll SCM = Jenkins가 주기적으로 GitHub에 "새 커밋 있어?"라고 물어본다.
// 새 커밋이 있을 때만 빌드한다. 없으면 아무것도 안 함.
// 'H/5 * * * *' = 5분마다 (cron 형식: 분 시 일 월 요일)
// H = 정각에 몰리지 않게 Jenkins가 분을 알아서 흩어준다는 뜻
pollSCM('H/5 * * * *')
}
// 파이프라인 전체에 적용할 옵션
options {
// 이전 빌드가 아직 돌고 있으면 다음 빌드는 기다린다.
// 2일차에 배포까지 붙으면, 두 배포가 동시에 서버를 건드리는 사고를 막아준다
disableConcurrentBuilds()
}
// 실제로 할 일들. stage 단위로 나뉘고, 위에서부터 순서대로 실행된다
stages {
// 단계 이름. Jenkins 화면에 이 이름으로 표시된다
stage('Build & Test') {
// 이 단계에서 실행할 명령들
steps {
// sh = 리눅스 셸 명령 실행.
// Windows에서 올린 gradlew에 실행 권한이 빠져 있을 수 있어서 한 번 더 부여한다 (안전장치)
sh 'chmod +x gradlew'
// Gradle Wrapper로 빌드한다.
// clean = 이전 빌드 결과(build 폴더)를 지우고 처음부터
// build = 컴파일 + 테스트 + jar 생성
// 테스트가 하나라도 실패하면 이 명령이 실패하고, 파이프라인도 여기서 멈춘다
sh './gradlew clean build'
}
}
}
// 모든 stage가 끝난 뒤 실행되는 블록
post {
// always = 성공하든 실패하든 항상 실행
always {
// Gradle이 남긴 테스트 결과 파일(XML)을 읽어서
// Jenkins 화면의 "Test Result"에 어떤 테스트가 통과, 실패했는지 보여준다
junit 'build/test-results/test/*.xml'
}
}
}
그리고 cicd-lab이라는 이름으로 item을 추가하고 pipeline 을 선택해서, SCM을 등록한다.
SCM이란 Source Code Management(소스 코드 관리)의 약자로 Jenkins에서 소스코드를 저장하고 관리하는 버전 관리 시스템으 연결하는 설정을 말한다.
이걸 해주면 Git같은 저장소에서 코드를 가져와 그 안의 Jenkinsfile을 실행한다.

우선 Jenkinsfile을 반영 안하고 그냥 해보니 다음과 같이 나왔다.

그리고 Jenkinsfile을 올렸다.
그러니까 In Progress 라고 벌렁벌렁(?) 한다.

이미 Jenkinsfile 에 5분마다 확인해서 읽어서 CI 작업을 하도록 해놨기 때문에 잠시후 보면 잘 실행된것을 확인할 수 있다.

코드를 변경해서 올려도 이제 자동으로 되나? 싶어서 이번에는 코드를 살짝 바꿔서 다시 commit, push를 진행했다.

그랫더니 잘 올라가서 Jenkinsfile설정대로 돌아간것을 확인했다.

이를 보면서 깨달은 점이 있다.
아! 여태 별도의 branch에서 다 확인하고 나서 그제서야 이제 main 에 병합을 하는 이유가 이렇게 특정 branch를 바라보고
CI/CD가 되도록 설정해서 그렇구나 하는 점을 말이다. 그리고 생각보다 툴이 친절하게 다 나와 잇어서 그렇게 어렵지 않았다.
정말 막상 해보니 어렵지 않았다. 하지만 현재는 학습용으로 좀 뭔가 미흡하다.
생각하기로는 dev, prod로 나눠서, 또한 docker를 여러개 띄워놓고 로드벨런싱 한 환경에서 다 배포가 한번에 되도록 하고 싶으니,
다음 글에서 이를 해볼 것이다. 어렵지는 않은데 은근 연결하고 확인하는게 오래걸렸다.
참고: